From 2dbe47f18d8df7ff22a7a4c96cd17b5d459aa112 Mon Sep 17 00:00:00 2001 From: Jari Kolehmainen Date: Mon, 30 Sep 2019 08:15:04 +0300 Subject: [PATCH] Allow to inject custom labels via --label option (#91) * allow to inject custom labels via --label option * allow to set labels via config * docs * rubocop --- README.md | 27 ++++++++--- lib/mortar/config.rb | 25 ++++++++++- lib/mortar/fire_command.rb | 33 ++++++++++++++ spec/config_spec.rb | 16 +++++-- spec/fire_spec.rb | 45 +++++++++++++++++++ spec/fixtures/config/config_labels.yaml | 2 + spec/fixtures/config/config_labels_error.yaml | 2 + 7 files changed, 140 insertions(+), 10 deletions(-) create mode 100644 spec/fixtures/config/config_labels.yaml create mode 100644 spec/fixtures/config/config_labels_error.yaml diff --git a/README.md b/README.md index f9b70f6..10d71d2 100644 --- a/README.md +++ b/README.md @@ -146,7 +146,7 @@ See examples at [examples/templates](examples/templates). As for any process, environment variables are also available for Mortar during template processing. -``` +```yaml kind: Pod apiVersion: v1 metadata: @@ -168,7 +168,7 @@ Another option to use variables is via command-line options. Use `mortar --var f Each of the variables defined will be available in the template via `var.`. -``` +```yaml kind: Pod apiVersion: v1 metadata: @@ -189,8 +189,9 @@ You could shoot this resource with `mortar --var port.name=some-port --var port. ### Shot configuration file -It is also possible to pass both [variables](#variables) and [overlays](#overlays) through a configuration file. As your templates complexity and the amount of variables used grows, it might be easier to manage the variables with an yaml configuration file. The config file has the following syntax: -``` +It is also possible to pass [variables](#variables), [overlays](#overlays) and [labels](#labels) through a configuration file. As your templates complexity and the amount of variables used grows, it might be easier to manage the variables with an yaml configuration file. The config file has the following syntax: + +```yaml variables: ports: - name: http @@ -202,12 +203,28 @@ overlays: - bar ``` -Both `variables` and `overlays` are optional. +`variables`, `overlays` and `labels` are optional. For variables the hash is translated into a `RecursiveOpenStruct` object. What that means is that you can access each element with dotted path notation, just like the vars given through `--var` option. And of course arrays can be looped etc.. Check examples folder how to use variables effectively. The configuration file can be given using `-c` option to `mortar fire` command. By default Mortar will look for `shot.yml` or `shot.yaml` files present in current working directory. +### Labels + +It's possible to set global labels for all resources in a shot via options or configuration file. + +#### Labels via options + +Use `mortar --label foo=bar my-app resource` to set label to all resources in a shot. + +#### Labels via configuration file + +```yaml +labels: + foo: bar + bar: baz +``` + ## Contributing Bug reports and pull requests are welcome on GitHub at https://github.com/kontena/mortar. diff --git a/lib/mortar/config.rb b/lib/mortar/config.rb index 73a7fda..3bf288f 100644 --- a/lib/mortar/config.rb +++ b/lib/mortar/config.rb @@ -10,14 +10,25 @@ def self.load(path) raise ConfigError, "Failed to load config, check config file syntax" unless cfg.is_a? Hash raise ConfigError, "Failed to load config, overlays needs to be an array" if cfg.key?('overlays') && !cfg['overlays'].is_a?(Array) - new(variables: cfg['variables'] || {}, overlays: cfg['overlays'] || []) + if cfg.key?('labels') + raise ConfigError, "Failed to load config, labels needs to be a hash" if !cfg['labels'].is_a?(Hash) + raise ConfigError, "Failed to load config, label values need to be strings" if cfg['labels'].values.any? { |value| !value.is_a?(String) } + end + + new( + variables: cfg['variables'] || {}, + overlays: cfg['overlays'] || [], + labels: cfg['labels'] || {} + ) end - def initialize(variables: {}, overlays: []) + def initialize(variables: {}, overlays: [], labels: {}) @variables = variables @overlays = overlays + @labels = labels end + # @param other [Hash] # @return [RecursiveOpenStruct] def variables(other = {}) hash = @variables.dup @@ -25,10 +36,20 @@ def variables(other = {}) RecursiveOpenStruct.new(hash, recurse_over_arrays: true) end + # @param other [Array] + # @return [Array] def overlays(other = []) return @overlays unless other (@overlays + other).uniq.compact end + + # @param other [Hash] + # @return [RecursiveOpenStruct] + def labels(other = {}) + hash = @labels.dup + hash.merge!(other) + RecursiveOpenStruct.new(hash) + end end end diff --git a/lib/mortar/fire_command.rb b/lib/mortar/fire_command.rb index 725c492..1f697ae 100644 --- a/lib/mortar/fire_command.rb +++ b/lib/mortar/fire_command.rb @@ -17,6 +17,7 @@ class FireCommand < Mortar::Command parameter "NAME", "deployment name" option ["--var"], "VAR", "set template variables", multivalued: true + option ["--label"], "LABEL", "extra labels that are set to all resources", multivalued: true option ["--output"], :flag, "only output generated yaml" option ["--[no-]prune"], :flag, "automatically delete removed resources", default: true option ["--overlay"], "OVERLAY", "overlay dirs", multivalued: true @@ -45,6 +46,7 @@ def execute load_config resources = process_overlays + resources = inject_extra_labels(resources, process_extra_labels) if output? puts resources_output(resources) @@ -89,6 +91,37 @@ def process_overlays resources end + # @return [Hash] + def extra_labels + return @extra_labels if @extra_labels + + @extra_labels = {} + label_list.each do |label| + key, value = label.split('=') + @extra_labels[key] = value + end + + @extra_labels + end + + # @return [Hash] + def process_extra_labels + @config.labels(extra_labels) + end + + # @param resources [Array] + # @param labels [Hash] + # @return [Array] + def inject_extra_labels(resources, labels) + resources.map { |resource| + resource.merge( + metadata: { + labels: labels + } + ) + } + end + # @return [RecursiveOpenStruct] def variables_struct @variables_struct ||= @configuration.variables(variables_hash) diff --git a/spec/config_spec.rb b/spec/config_spec.rb index 8a7d955..dce1660 100644 --- a/spec/config_spec.rb +++ b/spec/config_spec.rb @@ -1,5 +1,4 @@ RSpec.describe Mortar::Config do - describe '#self.load' do it 'raises on empty config' do expect { @@ -13,6 +12,7 @@ expect(vars.foo).to eq('bar') expect(vars.some.deeper).to eq('variable') expect(cfg.overlays).to eq([]) + expect(cfg.labels.to_h).to eq({}) end it 'loads overlays from file' do @@ -20,11 +20,21 @@ expect(cfg.overlays).to eq(['foo', 'bar']) end + it 'loads labels from file' do + cfg = described_class.load(fixture_path('config/config_labels.yaml')) + expect(cfg.labels.foo).to eq('bar') + end + it 'raises on non array overlays' do expect { described_class.load(fixture_path('config/config_overlays_error.yaml')) }.to raise_error(Mortar::Config::ConfigError, 'Failed to load config, overlays needs to be an array') end - end -end \ No newline at end of file + it 'raises on non hash labels' do + expect { + described_class.load(fixture_path('config/config_labels_error.yaml')) + }.to raise_error(Mortar::Config::ConfigError, 'Failed to load config, labels needs to be a hash') + end + end +end diff --git a/spec/fire_spec.rb b/spec/fire_spec.rb index c8efa1a..ef3db2a 100644 --- a/spec/fire_spec.rb +++ b/spec/fire_spec.rb @@ -89,4 +89,49 @@ expect(subject.variables_hash).to eq({}) end end + + describe "#extra_labels" do + let(:subject) { described_class.new('') } + + it 'returns empty hash by default' do + expect(subject.extra_labels).to eq({}) + end + + it 'returns label has if label options are given' do + subject.parse(["--label", "foo=bar", "--label", "bar=baz", "foobar", "foobar"]) + expect(subject.extra_labels).to eq({ + "foo" => "bar", "bar" => "baz" + }) + end + end + + describe "#inject_extra_labels" do + let(:subject) { described_class.new('') } + let(:resources) do + [ + K8s::Resource.new({ + metadata: { + labels: { + userlabel: 'test' + } + } + }), + K8s::Resource.new({ + metadata: { + name: 'foo' + } + }) + ] + end + + it 'injects labels to resources' do + extra_labels = { "foo" => "bar", "bar" => "baz" } + result = subject.inject_extra_labels(resources, extra_labels) + expect(result.first.metadata.labels.to_h).to eq({ + bar: "baz", + foo: "bar", + userlabel: "test" + }) + end + end end diff --git a/spec/fixtures/config/config_labels.yaml b/spec/fixtures/config/config_labels.yaml new file mode 100644 index 0000000..2f26422 --- /dev/null +++ b/spec/fixtures/config/config_labels.yaml @@ -0,0 +1,2 @@ +labels: + foo: bar diff --git a/spec/fixtures/config/config_labels_error.yaml b/spec/fixtures/config/config_labels_error.yaml new file mode 100644 index 0000000..099e077 --- /dev/null +++ b/spec/fixtures/config/config_labels_error.yaml @@ -0,0 +1,2 @@ +labels: +- foo=bar