Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to inject custom labels via --label option #91

Merged
merged 7 commits into from
Sep 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 22 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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.<variable name>`.

```
```yaml
kind: Pod
apiVersion: v1
metadata:
Expand All @@ -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
Expand All @@ -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.
Expand Down
25 changes: 23 additions & 2 deletions lib/mortar/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,46 @@ 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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
raise ConfigError, "Failed to load config, labels needs to be a hash" if !cfg['labels'].is_a?(Hash)
raise ConfigError, "Failed to load config, labels needs to be a hash" unless cfg['labels'].is_a?(Hash)

Minor nitpick

raise ConfigError, "Failed to load config, label values need to be strings" if cfg['labels'].values.any? { |value| !value.is_a?(String) }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
raise ConfigError, "Failed to load config, label values need to be strings" if cfg['labels'].values.any? { |value| !value.is_a?(String) }
raise ConfigError, "Failed to load config, label values need to be strings" unless cfg['labels'].values.all? { |value| value.is_a?(String) }

Minor nitpick

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
hash.deep_merge!(other)
RecursiveOpenStruct.new(hash, recurse_over_arrays: true)
end

# @param other [Array<Object>]
# @return [Array<Object>]
def overlays(other = [])
return @overlays unless other

(@overlays + other).uniq.compact
end

# @param other [Hash]
# @return [RecursiveOpenStruct]
def labels(other = {})
hash = @labels.dup
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor nitpick:

dup + merge! could just be @labels.merge(other)

hash.merge!(other)
RecursiveOpenStruct.new(hash)
end
end
end
33 changes: 33 additions & 0 deletions lib/mortar/fire_command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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<K8s::Resource>]
# @param labels [Hash]
# @return [Array<K8s::Resource>]
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)
Expand Down
16 changes: 13 additions & 3 deletions spec/config_spec.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
RSpec.describe Mortar::Config do

describe '#self.load' do
it 'raises on empty config' do
expect {
Expand All @@ -13,18 +12,29 @@
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
cfg = described_class.load(fixture_path('config/config_overlays.yaml'))
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
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
45 changes: 45 additions & 0 deletions spec/fire_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 2 additions & 0 deletions spec/fixtures/config/config_labels.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
labels:
foo: bar
2 changes: 2 additions & 0 deletions spec/fixtures/config/config_labels_error.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
labels:
- foo=bar