helmsnap 0.4.1 → 0.6.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: bd784fcd78ea76eeb09743cf411b599c9cd6ab2299a1a647a037d0dcc667cdb0
4
- data.tar.gz: a53928f8100827b1cd738840abff6ab43d875c0d44f5c7400023aa185d5ffeb3
3
+ metadata.gz: 00d1a53c6e09960c8a7b6d21e9998aba27e0765959b4450f2e433754440fe65b
4
+ data.tar.gz: 0f9fc47f9897eb76745e817528646ac340e43e39fad563a8370560bde810274f
5
5
  SHA512:
6
- metadata.gz: 9e5f7c1f7516cf65c9bcb3dde36977e739a5973ec7eaf7147380b797c9acc271b889a637fe6b9d3a6764839ed8362270e77c8160cc0fbcab1aee9775421c7c07
7
- data.tar.gz: 12d305a44dfc9c07956aa84ad5c221a7bf2b404888be7750de6bdcd06617c69d13e9c0a1846e2f07157b029a55b23b867a541014e550f6d94e62989a569494a7
6
+ metadata.gz: d014a938da2ea497e3e751f5cb600940ccb77a1d7bd297399a7b3380ac74c2d5a47bb9b4c31f6a540992c72d3e1012540d19b264e49d3498ffc282cb4f63b68a
7
+ data.tar.gz: 17614166a76919b0353d7bf322062047c546c3256826758ef5d0991bf66e30ee7ad0552c117ebad489ecba205e7c4771340dde7c947975cd1101895954eb8fe9
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.1)
4
+ helmsnap (0.6.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
@@ -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,22 +1,20 @@
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
+ Args = Struct.new(:config_path)
6
5
 
6
+ DEFAULT_CONFIG_PATH = ".helmsnap.yaml"
7
+ CONFIG_PATH_HELP = %{Path to config (default: "#{DEFAULT_CONFIG_PATH}")}
7
8
  BANNER = "Usage: helmsnap CMD [options]"
8
9
 
9
10
  def initialize(options)
10
11
  self.options = options
11
- self.args = Args.new
12
+ self.args = Args.new(DEFAULT_CONFIG_PATH)
12
13
  self.parser = build_parser
13
14
  end
14
15
 
15
16
  def get_options!
16
17
  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
18
  args
21
19
  rescue OptionParser::ParseError => error
22
20
  print_help!(error)
@@ -32,22 +30,14 @@ class Helmsnap::ArgsParser
32
30
 
33
31
  attr_accessor :options, :parser, :args
34
32
 
35
- def build_parser # rubocop:disable Metrics/MethodLength
33
+ def build_parser
36
34
  OptionParser.new(BANNER, 50) do |opts|
37
35
  opts.separator("Supported commands: `generate` and `check`.")
38
36
  opts.separator("")
39
37
  opts.separator("Specific options:")
40
38
 
41
- opts.on("-c", "--chart-dir CHARTDIR", "Chart directory") do |option|
42
- args.chart_path = pn(option)
43
- end
44
-
45
- opts.on("-s", "--snapshots-dir SNAPDIR", "Snapshots directory") do |option|
46
- args.snapshots_path = pn(option)
47
- end
48
-
49
- opts.on("-v", "--values VALUES", "Values file") do |option|
50
- args.values_path = pn(option)
39
+ opts.on("-c", "--config CONFIG_PATH", CONFIG_PATH_HELP) do |val|
40
+ args.config_path = Pathname.new(val)
51
41
  end
52
42
 
53
43
  opts.on("--version", "Show version") do
@@ -61,8 +51,4 @@ class Helmsnap::ArgsParser
61
51
  end
62
52
  end
63
53
  end
64
-
65
- def pn(...)
66
- Pathname.new(...)
67
- end
68
54
  end
@@ -1,31 +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
- diff = Helmsnap.run_cmd(
27
- util, "--unified", "--recursive", snapshots_path, temp_dir_path, allow_failure: true
28
- ).output
17
+ cmd_parts = [util, "--unified", "--recursive", config.snapshots_path, temp_dir_path]
18
+ diff = run_cmd(*cmd_parts, allow_failure: true).output
29
19
 
30
20
  diff.strip.empty?
31
21
  ensure
@@ -34,5 +24,5 @@ class Helmsnap::Check
34
24
 
35
25
  private
36
26
 
37
- attr_accessor :chart_path, :snapshots_path, :values_path
27
+ attr_accessor :config
38
28
  end
@@ -1,14 +1,12 @@
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
9
+ self.output = +""
12
10
  self.stdout = stdout
13
11
  self.stderr = stderr
14
12
  self.allow_failure = allow_failure
@@ -21,12 +19,10 @@ class Helmsnap::Command
21
19
 
22
20
  private
23
21
 
24
- attr_accessor :cmd, :stdout, :stderr, :allow_failure
22
+ attr_accessor :cmd, :output, :stdout, :stderr, :allow_failure
25
23
 
26
24
  def run_command
27
25
  Open3.popen3(cmd) do |_in, out, err, wait_thr|
28
- output = +""
29
-
30
26
  while (chunk = out.gets)
31
27
  Helmsnap::Console.print(stdout, chunk)
32
28
  output << chunk
@@ -34,15 +30,24 @@ class Helmsnap::Command
34
30
 
35
31
  exit_status = wait_thr.value
36
32
  success = exit_status.success?
37
-
38
- if !success && !allow_failure
39
- Helmsnap::Console.error(stderr, err.read)
40
- Helmsnap::Console.error(stderr, "Command failed with status #{exit_status.to_i}")
41
- exit 1
42
- end
33
+ handle_error!(exit_status, err.read) unless success
43
34
 
44
35
  Helmsnap::Console.print(stdout, "\n")
45
36
  Result.new(success, output)
46
37
  end
38
+ rescue SystemCallError => error
39
+ handle_error!(error.errno, error.message)
40
+ Result.new(false, error.message)
41
+ end
42
+
43
+ def handle_error!(exit_status, err_output)
44
+ Helmsnap::Console.error(stderr, err_output)
45
+
46
+ if allow_failure
47
+ output << err_output
48
+ else
49
+ Helmsnap::Console.error(stderr, "Command failed with status #{exit_status.to_i}")
50
+ exit 1
51
+ end
47
52
  end
48
53
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Helmsnap::Config
4
+ attr_reader :envs, :snapshots_path
5
+
6
+ DEFAULT_ENV = "default"
7
+
8
+ def initialize(config_path)
9
+ yaml = YAML.load_file(config_path.to_s)
10
+ self.envs = parse_envs(yaml)
11
+ self.snapshots_path = parse_snaphots_path(yaml)
12
+ end
13
+
14
+ private
15
+
16
+ attr_writer :envs, :snapshots_path
17
+
18
+ def parse_envs(yaml)
19
+ # TODO: check value type
20
+ value = yaml.fetch("envs", [DEFAULT_ENV])
21
+ value.map { |x| Helmsnap::Env.new(x) }
22
+ end
23
+
24
+ def parse_snaphots_path(yaml)
25
+ # TODO: chekc value type/presence
26
+ Pathname.new(yaml.fetch("snapshotsPath"))
27
+ end
28
+ 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.1"
4
+ VERSION = "0.6.0"
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.1
4
+ version: 0.6.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-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: