helmsnap 0.5.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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