Skip to content

Commit

Permalink
use liquid for template evaluation
Browse files Browse the repository at this point in the history
  • Loading branch information
wr0ngway committed Apr 29, 2021
1 parent d3aaaa5 commit 9ef2fd1
Show file tree
Hide file tree
Showing 8 changed files with 154 additions and 79 deletions.
2 changes: 2 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ PATH
gem_logger
graphql-client
kubeclient
liquid
logging

GEM
Expand Down Expand Up @@ -67,6 +68,7 @@ GEM
jsonpath (~> 1.0)
recursive-open-struct (~> 1.1, >= 1.1.1)
rest-client (~> 2.0)
liquid (5.0.1)
little-plugger (1.1.4)
logging (2.3.0)
little-plugger (~> 1.1)
Expand Down
18 changes: 14 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,13 +118,23 @@ Note that Kubetruth watches for changes to ProjectMappings, so touching any of
them wakes it up from a polling sleep. This makes it quick and easy to test out
configuration changes without having a short polling interval.

To customize how things are named, edit the `*_template` properties in the
ProjectMappings. These templates are processed using the [Liquid template
language](https://shopify.github.io/liquid/), and can reference the `project`
the `key` or any other named references from the `_selector` regexes. In
addition to the built in liquid filters, kubetruth also define a few custom
ones:

* dns_safe - ensures the string is safe for use as a kubernetes resource name (i.e. Namespace/ConfigMap/Secret names)
* env_safe - ensures the string is safe for setting as a shell environment variable

### Example Config

The `projectmapping` resource has a shortname of `pm` for convenience when using kubectl.

To create kubernetes Resources in namespaces named after each Project:
```
kubectl patch pm kubetruth-root --type json --patch '[{"op": "replace", "path": "/spec/namespace_template", "value": "%{project}"}]'
kubectl patch pm kubetruth-root --type json --patch '[{"op": "replace", "path": "/spec/namespace_template", "value": "{{project | dns_safe}}"}]'
```

To include the parameters from a Project named `Base` into all other projects, without creating Resources for `Base` itself:
Expand Down Expand Up @@ -195,15 +205,15 @@ Kind: ProjectMapping
Metadata:
...
Spec:
configmap_name_template: %{project}
configmap_name_template: {{project}}
included_projects:
key_filter:
key_selector:
key_template: %{key}
key_template: {{key}}
namespace_template:
project_selector:
Scope: root
secret_name_template: %{project}
secret_name_template: {{project}}
Skip: false
skip_secrets: false
Events: <none>
Expand Down
6 changes: 3 additions & 3 deletions helm/kubetruth/templates/rootprojectmapping.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ spec:
project_selector: ""
key_selector: ""
key_filter: ""
configmap_name_template: "%{project}"
secret_name_template: "%{project}"
configmap_name_template: "{{ "{{" }}project | dns_safe}}"
secret_name_template: "{{ "{{" }}project | dns_safe}}"
namespace_template: ""
key_template: "%{key}"
key_template: "{{ "{{" }}key}}"
skip: false
skip_secrets: false
included_projects: []
1 change: 1 addition & 0 deletions kubetruth.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,6 @@ Gem::Specification.new do |spec|
spec.add_dependency 'clamp'
spec.add_dependency 'graphql-client'
spec.add_dependency 'kubeclient'
spec.add_dependency 'liquid'

end
6 changes: 3 additions & 3 deletions lib/kubetruth/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ class Config
project_selector: '',
key_selector: '',
key_filter: '',
configmap_name_template: '%{project}',
secret_name_template: '%{project}',
configmap_name_template: '{{project | dns_safe}}',
secret_name_template: '{{project | dns_safe}}',
namespace_template: '',
key_template: '%{key}',
key_template: '{{key}}',
skip: false,
skip_secrets: false,
included_projects: []
Expand Down
47 changes: 34 additions & 13 deletions lib/kubetruth/etl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,37 @@
require_relative 'ctapi'
require_relative 'kubeapi'
require 'active_support/core_ext/hash/keys'
require 'liquid'

module Kubetruth
class ETL
include GemLogger::LoggerSupport

# From kubernetes error message
DNS_VALIDATION_RE = /^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$/
module CustomLiquidFilters

# From kubernetes error message
DNS_VALIDATION_RE = /^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$/
ENV_VALIDATION_RE = /^[A-Z_][A-Z0-9_]*$/

def dns_safe(str)
return str if str =~ DNS_VALIDATION_RE
result = str.to_s.downcase.gsub(/[^-.a-z0-9)]+/, '-')
result = result.gsub(/(^[^a-z0-9]+)|([^a-z0-9]+$)/, '')
result
end

def env_safe(str)
return str if str =~ ENV_VALIDATION_RE
result = str.upcase
result = result.gsub(/(^\W+)|(\W+$)/, '')
result = result.gsub(/\W+/, '_')
result = result.sub(/^\d/, '_\&')
result
end

end

Liquid::Template.register_filter(CustomLiquidFilters)

def initialize(ct_context:, kube_context:, dry_run: false)
@ct_context = ct_context
Expand Down Expand Up @@ -77,6 +101,10 @@ def with_polling(interval, &block)
end
end

def template_eval(tmpl, **kwargs)
Liquid::Template.parse(tmpl).render(**kwargs.stringify_keys)
end

def load_config
mappings = kubeapi(@kube_context[:namespace]).get_project_mappings
Kubetruth::Config.new(mappings)
Expand Down Expand Up @@ -112,9 +140,9 @@ def apply
matches_hash = Hash[*matches_hash.collect {|k, v| [k, v, "#{k}_upcase".to_sym, v.upcase]}.flatten]

project_data[project] ||= {}
project_data[project][:namespace] = dns_friendly(project_spec.namespace_template % matches_hash)
project_data[project][:configmap_name] = dns_friendly(project_spec.configmap_name_template % matches_hash)
project_data[project][:secret_name] = dns_friendly(project_spec.secret_name_template % matches_hash)
project_data[project][:namespace] = template_eval(project_spec.namespace_template, **matches_hash)
project_data[project][:configmap_name] = template_eval(project_spec.configmap_name_template, **matches_hash)
project_data[project][:secret_name] = template_eval(project_spec.secret_name_template, **matches_hash)

params = get_params(project, project_spec, template_matches: matches_hash)
project_data[project][:params] = params
Expand Down Expand Up @@ -192,7 +220,7 @@ def get_params(project, project_spec, template_matches: {})

logger.debug {"Pattern matches '#{param.key}' with: #{matches_hash}"}

key = project_spec.key_template % matches_hash
key = template_eval(project_spec.key_template, **matches_hash)
param.original_key, param.key = param.key, key

result << param
Expand All @@ -204,13 +232,6 @@ def get_params(project, project_spec, template_matches: {})
result
end

def dns_friendly(str)
return str if str =~ DNS_VALIDATION_RE
dns_friendly = str.to_s.downcase.gsub(/[^-.a-z0-9)]+/, '-')
dns_friendly = dns_friendly.gsub(/(^[^a-z0-9]+)|([^a-z0-9]+$)/, '')
dns_friendly
end

def params_to_hash(param_list)
Hash[param_list.collect {|param| [param.key, param.value]}]
end
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 9ef2fd1

Please sign in to comment.