helmsnap 0.5.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5f9aee0530e4f99cd1d96fbc07be33606ed5462ebbc08540c568a30848274b85
4
- data.tar.gz: e2937f100b5e2008d3db5d720a3e4d79393cc760da004bb1453ddab3485a5d50
3
+ metadata.gz: 82cc0b42f25f49c54cb4ec921c092f7a3e6cc9de565bc3e39e357e4129b5c3ff
4
+ data.tar.gz: 0fc97b6105f8a9ab5fb8360bf390be3cfaea21bc24b21edf42960b88658c54a8
5
5
  SHA512:
6
- metadata.gz: 872efd529c6367ca4dfe4468eae9a9d2164e844273a14e9cacea55e07c5c5a795509c7ece536c83e0be75e319651f44ef55d05816e967c4e841b4007d74d67c2
7
- data.tar.gz: fdb9c9d45daea1554f0d03083e582b6f57be0f78da265b39e4eb11f62e54a0b02fdb5e5abc91f6881c041aad3c47e5501736bd97bc244e0341342d6a4c33dce5
6
+ metadata.gz: f514cd9633fab8f88efac3ae96dda8579c9b8dbd2a90da8822979958c334681e0b882c498e1b4acfeda279814beeaf181fd942366b036da04100b62cbe1d9dc8
7
+ data.tar.gz: 5b9046c477241a66acbe1a2bc8b7f982205b49bbcf3f53edbeb1f4f516b4008f0ab4c52aeb5f829538796a1f717931588bea7abce9d33ecd9726031fe43c24dd
data/Dockerfile CHANGED
@@ -2,8 +2,9 @@ FROM alpine/helm
2
2
 
3
3
  RUN apk add --update --no-cache ruby git colordiff
4
4
 
5
- WORKDIR /app
5
+ WORKDIR /wd
6
6
 
7
+ COPY --from=quay.io/roboll/helmfile:v0.142.0 /usr/local/bin/helmfile /usr/local/bin/helmfile
7
8
  COPY . .
8
9
 
9
10
  RUN gem install colorize && gem build && gem install helmsnap --local
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- helmsnap (0.5.1)
4
+ helmsnap (0.7.0)
5
5
  colorize
6
6
 
7
7
  GEM
@@ -23,7 +23,7 @@ GEM
23
23
  method_source (1.0.0)
24
24
  minitest (5.14.4)
25
25
  parallel (1.21.0)
26
- parser (3.0.3.0)
26
+ parser (3.0.3.1)
27
27
  ast (~> 2.4.1)
28
28
  pry (0.14.1)
29
29
  coderay (~> 1.1)
@@ -31,7 +31,7 @@ GEM
31
31
  rack (2.2.3)
32
32
  rainbow (3.0.0)
33
33
  rake (13.0.6)
34
- regexp_parser (2.1.1)
34
+ regexp_parser (2.2.0)
35
35
  rexml (3.2.5)
36
36
  rspec (3.10.0)
37
37
  rspec-core (~> 3.10.0)
@@ -55,7 +55,7 @@ GEM
55
55
  rubocop-ast (>= 1.7.0, < 2.0)
56
56
  ruby-progressbar (~> 1.7)
57
57
  unicode-display_width (>= 1.4.0, < 3.0)
58
- rubocop-ast (1.13.0)
58
+ rubocop-ast (1.14.0)
59
59
  parser (>= 3.0.1.1)
60
60
  rubocop-config-umbrellio (1.17.0.53)
61
61
  rubocop (= 1.17.0)
data/README.md CHANGED
@@ -2,18 +2,24 @@
2
2
 
3
3
  ## About
4
4
 
5
- Helmsnap is a tool for generating and checking helm chart snapshots. Example:
5
+ Helmsnap is a tool for generating and checking helmfile snapshots. Example:
6
6
 
7
- Generate snapshots (uses `helm template` under the hood):
7
+ Generate snapshots (uses `helmfile template` under the hood):
8
8
 
9
9
  ```sh
10
- helmsnap generate -c helm/mychart -s helm/snapshots -v helm/values/production.yaml
10
+ helmsnap generate
11
11
  ```
12
12
 
13
- Generate snapshots in some temp directory and check (diff) them against existing snapshots in `helm/snapshots` directory:
13
+ Generate snapshots in a temporary directory and check (diff) them against existing snapshots in `helm/snapshots` directory:
14
14
 
15
15
  ```sh
16
- helmsnap check -c helm/mychart -s helm/snapshots -v helm/values/production.yaml
16
+ helmsnap check
17
+ ```
18
+
19
+ Just build dependencies for each release in a helmfile:
20
+
21
+ ```sh
22
+ helmsnap dependencies # or `helmsnap deps`
17
23
  ```
18
24
 
19
25
  Get the full description of possible arguments:
@@ -30,6 +36,23 @@ The typical usage flow:
30
36
 
31
37
  This tool can also be useful when you are developing a new chart or updating an existing one: you can generate snapshots and see what is rendered without need to deploy the chart in your cluster.
32
38
 
39
+ ## Configuration
40
+
41
+ By default, helmsnap will render your helmfile using `default` environment and will place snapshots in `helm/snapshots` directory. If you want to configure that, create a `.helmsnap.yaml` file and put there configuration that looks like this:
42
+
43
+ ```yaml
44
+ envs: [staging, production] # `[default]` by default
45
+ snapshotsPath: somedir/snapshots # `helm/snapshots` by default
46
+ ```
47
+
48
+ You can also override configuration file location using `--config` option.
49
+
50
+ ## Dependencies
51
+
52
+ - Ruby 2.7+.
53
+ - [Helmfile](https://github.com/roboll/helmfile), which in turn relies on [Helm](https://github.com/helm/helm).
54
+ - Colordiff or diff utility.
55
+
33
56
  ## Features
34
57
 
35
58
  ### Helm dependency management
@@ -42,13 +65,13 @@ Helmsnap will automically replace all occurencies of patterns that look like tim
42
65
 
43
66
  ## Installation
44
67
 
45
- Just install a gem and use the provided `helmsnap` binary.
68
+ Just install the gem and use the provided `helmsnap` binary.
46
69
 
47
70
  ```sh
48
71
  gem install helmsnap
49
72
  ```
50
73
 
51
- Alaternatively, you can use a [Docker image](https://github.com/tycooon/helmsnap/pkgs/container/helmsnap) with Ruby, helm and helmsnap gem preinstalled. This is useful for CIs or if you don't want to install Ruby locally.
74
+ Alaternatively, you can use the [Docker image](https://github.com/tycooon/helmsnap/pkgs/container/helmsnap) with Ruby, helm and helmsnap gem preinstalled. This is useful for CIs or if you don't want to install Ruby and Helmfile on your machine.
52
75
 
53
76
  ## CI example
54
77
 
@@ -58,7 +81,7 @@ Example job for Gitlab CI:
58
81
  check-snapshots:
59
82
  stage: test
60
83
  image: ghcr.io/tycooon/helmsnap:latest
61
- script: helmsnap check -c helm/mychart -s helm/snapshots -v helm/values/production.yaml
84
+ script: helmsnap check
62
85
  ```
63
86
 
64
87
  ## Contributing
@@ -1,25 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Helmsnap::ArgsParser
4
- Args = Struct.new(:chart_path, :snapshots_path, :values_path, keyword_init: true)
5
- MissingOption = Class.new(OptionParser::ParseError)
4
+ InvalidConfigPath = Class.new(RuntimeError)
6
5
 
6
+ Args = Struct.new(:config_path)
7
+
8
+ DEFAULT_CONFIG_PATH = Pathname.new(".helmsnap.yaml")
9
+ CONFIG_PATH_HELP = %{Path to config (default: "#{DEFAULT_CONFIG_PATH}")}
7
10
  BANNER = "Usage: helmsnap CMD [options]"
8
11
 
9
12
  def initialize(options)
10
13
  self.options = options
11
- self.args = Args.new
14
+ self.args = Args.new(DEFAULT_CONFIG_PATH)
12
15
  self.parser = build_parser
13
16
  end
14
17
 
15
18
  def get_options!
16
19
  parser.parse!(options)
17
- raise MissingOption, "Missing option: CHARTDIR" unless args.chart_path
18
- raise MissingOption, "Missing option: SNAPDIR" unless args.snapshots_path
19
- raise MissingOption, "Missing option: VALUES" unless args.values_path
20
20
  args
21
- rescue OptionParser::ParseError => error
22
- print_help!(error)
21
+ rescue OptionParser::ParseError, InvalidConfigPath => error
22
+ print_help!(error.message)
23
23
  end
24
24
 
25
25
  def print_help!(msg)
@@ -32,25 +32,23 @@ class Helmsnap::ArgsParser
32
32
 
33
33
  attr_accessor :options, :parser, :args
34
34
 
35
- def build_parser # rubocop:disable Metrics/MethodLength
35
+ def build_parser
36
36
  OptionParser.new(BANNER, 50) do |opts|
37
- opts.separator("Supported commands: `generate` and `check`.")
37
+ opts.separator("Supported commands: `generate`, `check` and `dependencies`.")
38
38
  opts.separator("")
39
39
  opts.separator("Specific options:")
40
40
 
41
- opts.on("-c", "--chart-dir CHARTDIR", "Chart directory") do |option|
42
- args.chart_path = pn(option)
43
- end
41
+ opts.on("-c", "--config CONFIG_PATH", CONFIG_PATH_HELP) do |val|
42
+ path = Pathname.new(val)
44
43
 
45
- opts.on("-s", "--snapshots-dir SNAPDIR", "Snapshots directory") do |option|
46
- args.snapshots_path = pn(option)
47
- end
44
+ unless path.file? && path.readable?
45
+ raise InvalidConfigPath, "Not a readable file: #{val}"
46
+ end
48
47
 
49
- opts.on("-v", "--values VALUES", "Values file") do |option|
50
- args.values_path = pn(option)
48
+ args.config_path = path
51
49
  end
52
50
 
53
- opts.on("--version", "Show version") do
51
+ opts.on("-v", "--version", "Show version") do
54
52
  Helmsnap::Console.print($stdout, "#{Helmsnap::VERSION}\n")
55
53
  exit
56
54
  end
@@ -61,8 +59,4 @@ class Helmsnap::ArgsParser
61
59
  end
62
60
  end
63
61
  end
64
-
65
- def pn(...)
66
- Pathname.new(...)
67
- end
68
62
  end
@@ -1,26 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Helmsnap::Check < Helmsnap::Service
4
- def initialize(chart_path:, snapshots_path:, values_path:)
4
+ def initialize(config)
5
5
  super()
6
- self.chart_path = chart_path
7
- self.snapshots_path = snapshots_path
8
- self.values_path = values_path
6
+ self.config = config
9
7
  end
10
8
 
11
9
  def call
12
10
  temp_dir_path = Pathname.new(Dir.mktmpdir)
13
11
 
14
- Helmsnap::Generate.call(
15
- chart_path: chart_path,
16
- snapshots_path: temp_dir_path,
17
- values_path: values_path,
18
- )
12
+ Helmsnap::Generate.call(config, snapshots_path: temp_dir_path)
19
13
 
20
14
  result = run_cmd("which", "colordiff", allow_failure: true)
21
15
  util = result.success ? "colordiff" : "diff"
22
16
 
23
- cmd_parts = [util, "--unified", "--recursive", snapshots_path, temp_dir_path]
17
+ cmd_parts = [util, "--unified", "--recursive", config.snapshots_path, temp_dir_path]
24
18
  diff = run_cmd(*cmd_parts, allow_failure: true).output
25
19
 
26
20
  diff.strip.empty?
@@ -30,5 +24,5 @@ class Helmsnap::Check < Helmsnap::Service
30
24
 
31
25
  private
32
26
 
33
- attr_accessor :chart_path, :snapshots_path, :values_path
27
+ attr_accessor :config
34
28
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Helmsnap::Config
4
+ attr_reader :envs, :snapshots_path
5
+
6
+ DEFAULT_ENV = "default"
7
+ DEFAULT_SNAPSHOTS_PATH = "helm/snapshots"
8
+
9
+ def initialize(config_path)
10
+ if config_path.exist?
11
+ yaml = YAML.load_file(config_path.to_s)
12
+ else
13
+ yaml = {}
14
+ end
15
+
16
+ self.envs = parse_envs(yaml)
17
+ self.snapshots_path = parse_snaphots_path(yaml)
18
+ end
19
+
20
+ private
21
+
22
+ attr_writer :envs, :snapshots_path
23
+
24
+ def parse_envs(yaml)
25
+ value = yaml.fetch("envs", [DEFAULT_ENV])
26
+ value.map { |x| Helmsnap::Env.new(x) }
27
+ end
28
+
29
+ def parse_snaphots_path(yaml)
30
+ Pathname.new(yaml.fetch("snapshotsPath", DEFAULT_SNAPSHOTS_PATH))
31
+ end
32
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Helmsnap::Env
4
+ attr_reader :name
5
+
6
+ def initialize(name)
7
+ self.name = name
8
+ end
9
+
10
+ def release_paths
11
+ @release_paths ||= begin
12
+ json = Helmsnap.run_cmd("helmfile", "--environment", name, "list", "--output", "json").output
13
+ YAML.load(json).map { |x| x.fetch("chart") }
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ attr_writer :name
20
+ end
@@ -1,21 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Helmsnap::Generate < Helmsnap::Service
4
- def initialize(chart_path:, snapshots_path:, values_path:)
4
+ def initialize(config, snapshots_path: nil)
5
5
  super()
6
- self.chart_path = chart_path
7
- self.snapshots_path = snapshots_path
8
- self.values_path = values_path
6
+ self.config = config
7
+ self.snapshots_path = snapshots_path || config.snapshots_path
9
8
  end
10
9
 
11
10
  def call
12
- Helmsnap::SetupDependencies.call(chart_path)
13
-
14
11
  FileUtils.rmtree(snapshots_path)
15
12
 
16
- run_cmd(
17
- "helm", "template", chart_path, "--values", values_path, "--output-dir", snapshots_path
18
- )
13
+ Helmsnap::SetupDependencies.call(config)
14
+
15
+ config.envs.each do |env|
16
+ run_cmd(
17
+ "helmfile",
18
+ "--environment",
19
+ env.name,
20
+ "template",
21
+ "--output-dir-template",
22
+ snapshots_path.join(env.name).join("{{ .Release.Name }}"),
23
+ "--skip-deps",
24
+ )
25
+ end
19
26
 
20
27
  snapshots_path.glob(["**/*yaml", "**/*.yml"]).each do |path|
21
28
  content = path.read
@@ -26,5 +33,5 @@ class Helmsnap::Generate < Helmsnap::Service
26
33
 
27
34
  private
28
35
 
29
- attr_accessor :chart_path, :snapshots_path, :values_path
36
+ attr_accessor :config, :snapshots_path
30
37
  end
@@ -9,6 +9,7 @@ class Helmsnap::Runner < Helmsnap::Service
9
9
  def call
10
10
  parser = Helmsnap::ArgsParser.new(args)
11
11
  self.options = parser.get_options!
12
+ self.config = Helmsnap::Config.new(options.config_path)
12
13
 
13
14
  cmd, *rest = args
14
15
 
@@ -25,6 +26,8 @@ class Helmsnap::Runner < Helmsnap::Service
25
26
  generate!
26
27
  when "check"
27
28
  check!
29
+ when "dependencies", "deps"
30
+ setup_deps!
28
31
  else
29
32
  parser.print_help!("Unknown command: #{cmd}.")
30
33
  end
@@ -32,25 +35,18 @@ class Helmsnap::Runner < Helmsnap::Service
32
35
 
33
36
  private
34
37
 
35
- attr_accessor :args, :options
38
+ attr_accessor :args, :options, :config
36
39
 
37
40
  def generate!
38
- Helmsnap::Generate.call(**options.to_h)
41
+ Helmsnap::Generate.call(config)
39
42
  Helmsnap::Console.info($stdout, "Snapshots generated successfully.")
40
43
  end
41
44
 
42
45
  def check!
43
- if Helmsnap::Check.call(**options.to_h)
46
+ if Helmsnap::Check.call(config)
44
47
  Helmsnap::Console.info($stdout, "Snapshots are up-to-date.")
45
48
  else
46
- example_cmd = Shellwords.join(
47
- [
48
- "helmsnap", "generate",
49
- "--chart-dir", options.chart_path,
50
- "--snapshots-dir", options.snapshots_path,
51
- "--values", options.values_path
52
- ],
53
- )
49
+ example_cmd = Shellwords.join(["helmsnap", "generate", "--config", options.config_path])
54
50
 
55
51
  Helmsnap::Console.error(
56
52
  $stdout,
@@ -61,4 +57,8 @@ class Helmsnap::Runner < Helmsnap::Service
61
57
  exit 1
62
58
  end
63
59
  end
60
+
61
+ def setup_deps!
62
+ Helmsnap::SetupDependencies.call(config)
63
+ end
64
64
  end
@@ -1,12 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Helmsnap::SetupDependencies < Helmsnap::Service
4
- def initialize(chart_path)
4
+ def initialize(config)
5
5
  super()
6
- self.chart_path = Pathname.new(chart_path)
6
+ self.config = config
7
7
  end
8
8
 
9
9
  def call
10
+ config.envs.flat_map(&:release_paths).uniq.each do |chart_path|
11
+ setup!(chart_path)
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ attr_accessor :config
18
+
19
+ def setup!(chart_path)
10
20
  dep_list = run_cmd("helm", "dependency", "list", "--max-col-width", 0, chart_path).output
11
21
 
12
22
  dep_list.scan(%r{file://(.+?)\t}) do |dep_path|
@@ -19,8 +29,4 @@ class Helmsnap::SetupDependencies < Helmsnap::Service
19
29
 
20
30
  run_cmd("helm", "dependency", "update", "--skip-refresh", chart_path)
21
31
  end
22
-
23
- private
24
-
25
- attr_accessor :chart_path
26
32
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Helmsnap
4
- VERSION = "0.5.1"
4
+ VERSION = "0.7.0"
5
5
  end
data/lib/helmsnap.rb CHANGED
@@ -7,10 +7,13 @@ require "optparse"
7
7
  require "pathname"
8
8
  require "shellwords"
9
9
  require "tmpdir"
10
+ require "yaml"
10
11
 
11
12
  require "colorized_string"
12
13
 
13
14
  module Helmsnap
15
+ require_relative "helmsnap/config"
16
+ require_relative "helmsnap/env"
14
17
  require_relative "helmsnap/service"
15
18
  require_relative "helmsnap/version"
16
19
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: helmsnap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yuri Smirnov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-12-04 00:00:00.000000000 Z
11
+ date: 2021-12-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: colorize
@@ -63,7 +63,9 @@ files:
63
63
  - lib/helmsnap/args_parser.rb
64
64
  - lib/helmsnap/check.rb
65
65
  - lib/helmsnap/command.rb
66
+ - lib/helmsnap/config.rb
66
67
  - lib/helmsnap/console.rb
68
+ - lib/helmsnap/env.rb
67
69
  - lib/helmsnap/generate.rb
68
70
  - lib/helmsnap/runner.rb
69
71
  - lib/helmsnap/service.rb