helmsnap 0.4.2 → 0.6.1

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: 8a5cb05f018581cbbccdfd9114f0ce1426457d63bd4fcd69864d558a6c790be5
4
- data.tar.gz: c6ff86c42987cbebe2fb71889d05dd0d11e33d0ca164a89419c75b58a6b671ab
3
+ metadata.gz: 8fbee3f7ea1da8f3400fa518d3f2a083b62274d9e11f11d472da24e74904de2a
4
+ data.tar.gz: ee79318dba8a65d214e6d64e27f4f91ee56d3c6e3ba125825ec8b627daf31b9f
5
5
  SHA512:
6
- metadata.gz: 60bf1bbab69d1990b05d93bcc3b22b67318f665d431ea2b770a6eddf894a6df800d139b32cdd35d61905067fd7e52ae21d91b069d94a99736ea57d25f9968ee6
7
- data.tar.gz: 804d6d566f1e6c546f1f94a9235b43ba69fb451f298987855c40a281033144fd1ee44ea53e0fdf764f1d6f2bfe29e6902fd1bb065d75f6b4eba3212feaca367a
6
+ metadata.gz: 80a280fe7d8f000f0706e0087cbc7607acdb1962b04ccb4154c64263dfba0ebf9fc50c086786fd2c06251fc3918762a3b83b7058344261ad615c603d1d011dde
7
+ data.tar.gz: 7534803fc0f920d817905a4cbdcdea81fd603daa06ce614a10cdfeee2a28a13cd82691d6c6d2217b5c987e99e2a87a7999d35362fa7304718378b63300cea2b6
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.4.2)
4
+ helmsnap (0.6.1)
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
@@ -4,16 +4,16 @@
4
4
 
5
5
  Helmsnap is a tool for generating and checking helm chart 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
17
  ```
18
18
 
19
19
  Get the full description of possible arguments:
@@ -30,6 +30,23 @@ The typical usage flow:
30
30
 
31
31
  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
32
 
33
+ ## Configuration
34
+
35
+ 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:
36
+
37
+ ```yaml
38
+ envs: [staging, production] # `[default]` by default
39
+ snapshotsPath: somedir/snapshots # `helm/snapshots` by default
40
+ ```
41
+
42
+ You can also override configuration file location using `--config` option.
43
+
44
+ ## Dependencies
45
+
46
+ - Ruby 2.7+.
47
+ - [Helmfile](https://github.com/roboll/helmfile), which in turn relies on [Helm](https://github.com/helm/helm).
48
+ - Colordiff or diff utility.
49
+
33
50
  ## Features
34
51
 
35
52
  ### Helm dependency management
@@ -58,7 +75,7 @@ Example job for Gitlab CI:
58
75
  check-snapshots:
59
76
  stage: test
60
77
  image: ghcr.io/tycooon/helmsnap:latest
61
- script: helmsnap check -c helm/mychart -s helm/snapshots -v helm/values/production.yaml
78
+ script: helmsnap check
62
79
  ```
63
80
 
64
81
  ## 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,22 +32,20 @@ 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
37
  opts.separator("Supported commands: `generate` and `check`.")
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
51
  opts.on("--version", "Show version") do
@@ -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,30 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class Helmsnap::Check
4
- def self.call(...)
5
- new(...).call
6
- end
7
-
8
- def initialize(chart_path:, snapshots_path:, values_path:)
9
- self.chart_path = chart_path
10
- self.snapshots_path = snapshots_path
11
- self.values_path = values_path
3
+ class Helmsnap::Check < Helmsnap::Service
4
+ def initialize(config)
5
+ super()
6
+ self.config = config
12
7
  end
13
8
 
14
9
  def call
15
10
  temp_dir_path = Pathname.new(Dir.mktmpdir)
16
11
 
17
- Helmsnap::Generate.call(
18
- chart_path: chart_path,
19
- snapshots_path: temp_dir_path,
20
- values_path: values_path,
21
- )
12
+ Helmsnap::Generate.call(config, snapshots_path: temp_dir_path)
22
13
 
23
- result = Helmsnap.run_cmd("which", "colordiff", allow_failure: true)
14
+ result = run_cmd("which", "colordiff", allow_failure: true)
24
15
  util = result.success ? "colordiff" : "diff"
25
16
 
26
- cmd_parts = [util, "--unified", "--recursive", snapshots_path, temp_dir_path]
27
- diff = Helmsnap.run_cmd(*cmd_parts, allow_failure: true).output
17
+ cmd_parts = [util, "--unified", "--recursive", config.snapshots_path, temp_dir_path]
18
+ diff = run_cmd(*cmd_parts, allow_failure: true).output
28
19
 
29
20
  diff.strip.empty?
30
21
  ensure
@@ -33,5 +24,5 @@ class Helmsnap::Check
33
24
 
34
25
  private
35
26
 
36
- attr_accessor :chart_path, :snapshots_path, :values_path
27
+ attr_accessor :config
37
28
  end
@@ -1,13 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class Helmsnap::Command
3
+ class Helmsnap::Command < Helmsnap::Service
4
4
  Result = Struct.new(:success, :output)
5
5
 
6
- def self.call(...)
7
- new(...).call
8
- end
9
-
10
6
  def initialize(cmd, stdout: $stdout, stderr: $stderr, allow_failure: false)
7
+ super()
11
8
  self.cmd = cmd
12
9
  self.output = +""
13
10
  self.stdout = stdout
@@ -38,6 +35,9 @@ class Helmsnap::Command
38
35
  Helmsnap::Console.print(stdout, "\n")
39
36
  Result.new(success, output)
40
37
  end
38
+ rescue SystemCallError => error
39
+ handle_error!(error.errno, error.message)
40
+ Result.new(false, error.message)
41
41
  end
42
42
 
43
43
  def handle_error!(exit_status, err_output)
@@ -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,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Helmsnap::Env
4
+ attr_reader :name, :release_paths
5
+
6
+ def initialize(name)
7
+ self.name = name
8
+ self.release_paths = get_release_paths
9
+ end
10
+
11
+ private
12
+
13
+ attr_writer :name, :release_paths
14
+
15
+ def get_release_paths
16
+ json = Helmsnap.run_cmd("helmfile", "--environment", name, "list", "--output", "json").output
17
+ YAML.load(json).map { |x| x.fetch("chart") }
18
+ end
19
+ end
@@ -1,35 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class Helmsnap::Generate
4
- def self.call(...)
5
- new(...).call
6
- end
7
-
8
- def initialize(chart_path:, snapshots_path:, values_path:)
9
- self.chart_path = chart_path
10
- self.snapshots_path = snapshots_path
11
- self.values_path = values_path
3
+ class Helmsnap::Generate < Helmsnap::Service
4
+ def initialize(config, snapshots_path: nil)
5
+ super()
6
+ self.config = config
7
+ self.snapshots_path = snapshots_path || config.snapshots_path
12
8
  end
13
9
 
14
10
  def call
15
- dep_list = run_cmd("helm", "dependency", "list", "--max-col-width", 0, chart_path).output
11
+ FileUtils.rmtree(snapshots_path)
16
12
 
17
- dep_list.scan(%r{file://(.+?)\t}) do |dep_path|
18
- run_cmd("helm", "dependency", "update", "--skip-refresh", chart_path.join(dep_path.first))
13
+ config.envs.flat_map(&:release_paths).uniq.each do |release_path|
14
+ Helmsnap::SetupDependencies.call(release_path)
19
15
  end
20
16
 
21
- dep_list.scan(%r{(https?://.+?)\t}) do |dep_path|
22
- run_cmd("helm", "repo", "add", Digest::MD5.hexdigest(dep_path.first), dep_path.first)
17
+ config.envs.each do |env|
18
+ run_cmd(
19
+ "helmfile",
20
+ "--environment",
21
+ env.name,
22
+ "template",
23
+ "--output-dir-template",
24
+ snapshots_path.join(env.name).join("{{ .Release.Name }}"),
25
+ "--skip-deps",
26
+ )
23
27
  end
24
28
 
25
- run_cmd("helm", "dependency", "update", "--skip-refresh", chart_path)
26
-
27
- FileUtils.rmtree(snapshots_path)
28
-
29
- run_cmd(
30
- "helm", "template", chart_path, "--values", values_path, "--output-dir", snapshots_path
31
- )
32
-
33
29
  snapshots_path.glob(["**/*yaml", "**/*.yml"]).each do |path|
34
30
  content = path.read
35
31
  content.gsub!(/\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d+/, "2022-01-01 00:00:00.000") or next
@@ -39,9 +35,5 @@ class Helmsnap::Generate
39
35
 
40
36
  private
41
37
 
42
- attr_accessor :chart_path, :snapshots_path, :values_path
43
-
44
- def run_cmd(...)
45
- Helmsnap.run_cmd(...)
46
- end
38
+ attr_accessor :config, :snapshots_path
47
39
  end
@@ -1,17 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class Helmsnap::Runner
4
- def self.call(...)
5
- new(...).call
6
- end
7
-
3
+ class Helmsnap::Runner < Helmsnap::Service
8
4
  def initialize(args)
5
+ super()
9
6
  self.args = args.dup
10
7
  end
11
8
 
12
9
  def call
13
10
  parser = Helmsnap::ArgsParser.new(args)
14
11
  self.options = parser.get_options!
12
+ self.config = Helmsnap::Config.new(options.config_path)
15
13
 
16
14
  cmd, *rest = args
17
15
 
@@ -35,25 +33,18 @@ class Helmsnap::Runner
35
33
 
36
34
  private
37
35
 
38
- attr_accessor :args, :options
36
+ attr_accessor :args, :options, :config
39
37
 
40
38
  def generate!
41
- Helmsnap::Generate.call(**options.to_h)
39
+ Helmsnap::Generate.call(config)
42
40
  Helmsnap::Console.info($stdout, "Snapshots generated successfully.")
43
41
  end
44
42
 
45
43
  def check!
46
- if Helmsnap::Check.call(**options.to_h)
44
+ if Helmsnap::Check.call(config)
47
45
  Helmsnap::Console.info($stdout, "Snapshots are up-to-date.")
48
46
  else
49
- example_cmd = Shellwords.join(
50
- [
51
- "helmsnap", "generate",
52
- "--chart-dir", options.chart_path,
53
- "--snapshots-dir", options.snapshots_path,
54
- "--values", options.values_path
55
- ],
56
- )
47
+ example_cmd = Shellwords.join(["helmsnap", "generate", "--config", options.config_path])
57
48
 
58
49
  Helmsnap::Console.error(
59
50
  $stdout,
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Helmsnap::Service
4
+ def self.call(...)
5
+ new(...).call
6
+ end
7
+
8
+ private
9
+
10
+ def run_cmd(...)
11
+ Helmsnap.run_cmd(...)
12
+ end
13
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Helmsnap::SetupDependencies < Helmsnap::Service
4
+ def initialize(chart_path)
5
+ super()
6
+ self.chart_path = Pathname.new(chart_path)
7
+ end
8
+
9
+ def call
10
+ dep_list = run_cmd("helm", "dependency", "list", "--max-col-width", 0, chart_path).output
11
+
12
+ dep_list.scan(%r{file://(.+?)\t}) do |dep_path|
13
+ run_cmd("helm", "dependency", "update", "--skip-refresh", chart_path.join(dep_path.first))
14
+ end
15
+
16
+ dep_list.scan(%r{(https?://.+?)\t}) do |dep_path|
17
+ run_cmd("helm", "repo", "add", Digest::MD5.hexdigest(dep_path.first), dep_path.first)
18
+ end
19
+
20
+ run_cmd("helm", "dependency", "update", "--skip-refresh", chart_path)
21
+ end
22
+
23
+ private
24
+
25
+ attr_accessor :chart_path
26
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Helmsnap
4
- VERSION = "0.4.2"
4
+ VERSION = "0.6.1"
5
5
  end
data/lib/helmsnap.rb CHANGED
@@ -7,17 +7,23 @@ 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"
17
+ require_relative "helmsnap/service"
18
+ require_relative "helmsnap/version"
19
+
14
20
  require_relative "helmsnap/args_parser"
15
21
  require_relative "helmsnap/check"
16
- require_relative "helmsnap/console"
17
22
  require_relative "helmsnap/command"
23
+ require_relative "helmsnap/console"
18
24
  require_relative "helmsnap/generate"
19
25
  require_relative "helmsnap/runner"
20
- require_relative "helmsnap/version"
26
+ require_relative "helmsnap/setup_dependencies"
21
27
 
22
28
  class Error < StandardError; end
23
29
 
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.4.2
4
+ version: 0.6.1
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-01 00:00:00.000000000 Z
11
+ date: 2021-12-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: colorize
@@ -63,9 +63,13 @@ 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
71
+ - lib/helmsnap/service.rb
72
+ - lib/helmsnap/setup_dependencies.rb
69
73
  - lib/helmsnap/version.rb
70
74
  homepage: https://github.com/tycooon/helmsnap
71
75
  licenses: