configgin 0.18.4 → 0.18.5

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
- SHA1:
3
- metadata.gz: 6fc09ccb349dbfe9d897aba65564973ed6efc1da
4
- data.tar.gz: 173d309aa8fa267b705ee87d7843ba2b43483fb1
2
+ SHA256:
3
+ metadata.gz: 2d250aa3c47fcfe842c8c4f3e486c16ec9b48ea99fa0efd61f5d4719cbf74049
4
+ data.tar.gz: 7f60d97717a06afffd187383774b88ce34235629f28767a6bb26701c6b32e89a
5
5
  SHA512:
6
- metadata.gz: 7795d12409c699470b202e97c3f3e829bf7bf7001a57ec00f03aa18e32027a4dc922b4fa44bc24fce99ba306cfc47aa9dde10a206d820af21da26d1a1e12a09c
7
- data.tar.gz: 57d88d76d052d4b279bf714cc3b63decff84c83e1227b3a454995fce41f9e2ca6b1ebfc966299a30fc7284539f42c00a1dd1ae9c3d87bbd365c905b3df8217c4
6
+ metadata.gz: bef1e310e3a86b54779c77afb415172176abe2156a17ae086d59d355d09aafbe05bb0217c97af3a77a6563f46237fca34b4ee02ba6c335e919c9049f27301b48
7
+ data.tar.gz: c5849fc9480b269426d0dd1dd9de590ff54045c6e536fffdfa5d6492c212b3499a0833b265b8f3b3b18835239d77c592257b86085875e188db93786dc99907c9
data/Gemfile CHANGED
@@ -3,8 +3,8 @@ source 'https://rubygems.org'
3
3
  gemspec
4
4
 
5
5
  group :test do
6
- gem 'rspec'
7
- gem 'rubocop'
8
6
  gem 'guard'
9
7
  gem 'guard-rspec'
8
+ gem 'rspec'
9
+ gem 'rubocop'
10
10
  end
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- configgin (0.18.3)
4
+ configgin (0.18.5)
5
5
  bosh-template (~> 2.0)
6
6
  deep_merge (~> 1.1)
7
7
  kubeclient (~> 2.0)
@@ -11,7 +11,7 @@ PATH
11
11
  GEM
12
12
  remote: https://rubygems.org/
13
13
  specs:
14
- addressable (2.5.2)
14
+ addressable (2.6.0)
15
15
  public_suffix (>= 2.0.2, < 4.0)
16
16
  ast (2.4.0)
17
17
  bosh-template (2.2.0)
@@ -59,7 +59,7 @@ GEM
59
59
  method_source (0.9.0)
60
60
  mime-types (3.2.2)
61
61
  mime-types-data (~> 3.2015)
62
- mime-types-data (3.2018.0812)
62
+ mime-types-data (3.2019.0331)
63
63
  mustache (1.1.0)
64
64
  nenv (0.3.0)
65
65
  netrc (0.11.0)
@@ -129,4 +129,4 @@ DEPENDENCIES
129
129
  rubocop
130
130
 
131
131
  BUNDLED WITH
132
- 1.16.4
132
+ 1.17.3
data/Guardfile CHANGED
@@ -1,5 +1,5 @@
1
- guard :rspec, cmd: "bundle exec rspec" do
2
- require "guard/rspec/dsl"
1
+ guard :rspec, cmd: 'bundle exec rspec' do
2
+ require 'guard/rspec/dsl'
3
3
  dsl = Guard::RSpec::Dsl.new(self)
4
4
 
5
5
  # RSpec files
@@ -1,6 +1,4 @@
1
- # coding: utf-8
2
-
3
- lib = File.expand_path('../lib', __FILE__)
1
+ lib = File.expand_path('lib', __dir__)
4
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
3
  require 'configgin/version'
6
4
 
@@ -25,8 +23,8 @@ Gem::Specification.new do |spec|
25
23
  spec.add_development_dependency 'rake', '~> 10.0'
26
24
 
27
25
  spec.add_dependency 'bosh-template', '~> 2.0'
28
- spec.add_dependency 'rainbow', '~>2.0', '!=2.2.1'
29
26
  spec.add_dependency 'deep_merge', '~> 1.1'
30
- spec.add_dependency 'mustache', '~> 1.0'
31
27
  spec.add_dependency 'kubeclient', '~>2.0'
28
+ spec.add_dependency 'mustache', '~> 1.0'
29
+ spec.add_dependency 'rainbow', '~>2.0', '!=2.2.1'
32
30
  end
data/lib/cli.rb CHANGED
@@ -13,6 +13,7 @@ module Cli
13
13
  raise ArgMissingError, key.to_s
14
14
  end
15
15
  end
16
+ options[:bosh_deployment_manifest] ||= nil
16
17
  end
17
18
 
18
19
  # Make an option parser bound to the hash passed in.
@@ -1,9 +1,12 @@
1
+ require 'json'
2
+
1
3
  require_relative 'cli'
2
4
  require_relative 'job'
3
5
  require_relative 'environment_config_transmogrifier'
4
6
  require_relative 'bosh_deployment_manifest_config_transmogrifier'
5
7
  require_relative 'kube_link_generator'
6
8
  require_relative 'bosh_deployment_manifest'
9
+ require_relative 'property_digest'
7
10
 
8
11
  # Configgin is the main class which puts all the pieces together and configures
9
12
  # the container according to the options.
@@ -11,16 +14,18 @@ class Configgin
11
14
  # SVC_ACC_PATH is the location of the service account secrets
12
15
  SVC_ACC_PATH = '/var/run/secrets/kubernetes.io/serviceaccount'.freeze
13
16
 
14
- def initialize(options)
15
- @job_configs = JSON.parse(File.read(options[:jobs]))
16
- @templates = YAML.load_file(options[:env2conf])
17
- @bosh_deployment_manifest = options[:bosh_deployment_manifest]
17
+ def initialize(jobs:, env2conf:, bosh_deployment_manifest:, self_name: ENV['HOSTNAME'])
18
+ @job_configs = JSON.parse(File.read(jobs))
19
+ @templates = YAML.load_file(env2conf)
20
+ @bosh_deployment_manifest = bosh_deployment_manifest
21
+ @self_name = self_name
18
22
  end
19
23
 
20
24
  def run
21
25
  jobs = generate_jobs(@job_configs, @templates)
22
- set_job_metadata(jobs)
26
+ job_digests = patch_job_metadata(jobs)
23
27
  render_job_templates(jobs, @job_configs)
28
+ restart_affected_pods expected_annotations(@job_configs, job_digests)
24
29
  end
25
30
 
26
31
  def generate_jobs(job_configs, templates)
@@ -41,19 +46,37 @@ class Configgin
41
46
  exit 1
42
47
  end
43
48
 
44
- jobs[job] = Job.new(bosh_spec, kube_namespace, kube_client, kube_client_stateful_set)
49
+ jobs[job] = Job.new(
50
+ spec: bosh_spec,
51
+ namespace: kube_namespace,
52
+ client: kube_client,
53
+ client_stateful_set: kube_client_stateful_set,
54
+ self_name: @self_name
55
+ )
45
56
  end
46
57
  jobs
47
58
  end
48
59
 
49
- def set_job_metadata(jobs)
60
+ # Set the exported properties and their digests, and return the digests.
61
+ def patch_job_metadata(jobs)
62
+ digests = {}
50
63
  jobs.each do |name, job|
64
+ digest = property_digest(job.exported_properties)
51
65
  kube_client.patch_pod(
52
- ENV['HOSTNAME'],
53
- { metadata: { annotations: { :"skiff-exported-properties-#{name}" => job.exported_properties.to_json } } },
66
+ @self_name,
67
+ {
68
+ metadata: {
69
+ annotations: {
70
+ :"skiff-exported-properties-#{name}" => job.exported_properties.to_json,
71
+ :"skiff-exported-digest-#{name}" => digest
72
+ }
73
+ }
74
+ },
54
75
  kube_namespace
55
76
  )
77
+ digests[name] = digest
56
78
  end
79
+ digests
57
80
  end
58
81
 
59
82
  def render_job_templates(jobs, job_configs)
@@ -66,6 +89,55 @@ class Configgin
66
89
  end
67
90
  end
68
91
 
92
+ # Some pods might have depended on the properties exported by this pod; given
93
+ # the annotations expected on the pods (keyed by the instance group name),
94
+ # patch the StatefulSets such that they will be restarted.
95
+ def restart_affected_pods(expected_annotations)
96
+ expected_annotations.each_pair do |instance_group_name, digests|
97
+ begin
98
+ kube_client_stateful_set.patch_stateful_set(
99
+ instance_group_name,
100
+ { spec: { template: { metadata: { annotations: digests } } } },
101
+ kube_namespace
102
+ )
103
+ warn "Patched StatefulSet #{instance_group_name} for new exported digests"
104
+ rescue KubeException => e
105
+ begin
106
+ begin
107
+ response = JSON.parse(e.response || '')
108
+ rescue JSON::ParseError
109
+ response = {}
110
+ end
111
+ if response['reason'] == 'NotFound'
112
+ # The StatefulSet can be missing if we're configured to not have an
113
+ # optional instance group.
114
+ warn "Skipping patch of non-existant StatefulSet #{instance_group_name}"
115
+ next
116
+ end
117
+ warn "Error patching #{instance_group_name}: #{response.to_json}"
118
+ raise
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ # Given the active jobs, and a hash of the expected annotations for each,
125
+ # return the annotations we expect to be on each pod based on what properties
126
+ # each job imports.
127
+ def expected_annotations(job_configs, job_digests)
128
+ instance_groups_to_examine = Hash.new { |h, k| h[k] = {} }
129
+ job_configs.values.each do |job_config|
130
+ base_config = JSON.parse(File.read(job_config['base']))
131
+ base_config.fetch('consumed_by', {}).each_pair do |provider_name, consumer_jobs|
132
+ consumer_jobs.each do |consumer_job|
133
+ digest_key = "skiff-imported-properties-#{instance_group}-#{provider_name}"
134
+ instance_groups_to_examine[consumer_job['role']][digest_key] = job_digests[provider_name]
135
+ end
136
+ end
137
+ end
138
+ instance_groups_to_examine
139
+ end
140
+
69
141
  def kube_namespace
70
142
  @kube_namespace ||= File.read("#{SVC_ACC_PATH}/namespace")
71
143
  end
@@ -76,13 +148,14 @@ class Configgin
76
148
 
77
149
  private
78
150
 
79
- def kube_client
80
- @kube_client ||= Kubeclient::Client.new(
151
+ def create_kube_client(path: nil, version: 'v1')
152
+ Kubeclient::Client.new(
81
153
  URI::HTTPS.build(
82
154
  host: ENV['KUBERNETES_SERVICE_HOST'],
83
- port: ENV['KUBERNETES_SERVICE_PORT_HTTPS']
155
+ port: ENV['KUBERNETES_SERVICE_PORT_HTTPS'],
156
+ path: path
84
157
  ),
85
- 'v1',
158
+ version,
86
159
  ssl_options: {
87
160
  ca_file: "#{SVC_ACC_PATH}/ca.crt",
88
161
  verify_ssl: OpenSSL::SSL::VERIFY_PEER
@@ -93,26 +166,16 @@ class Configgin
93
166
  )
94
167
  end
95
168
 
169
+ def kube_client
170
+ @kube_client ||= create_kube_client
171
+ end
172
+
96
173
  def kube_client_stateful_set
97
- @kube_client_stateful_set ||= Kubeclient::Client.new(
98
- URI::HTTPS.build(
99
- host: ENV['KUBERNETES_SERVICE_HOST'],
100
- port: ENV['KUBERNETES_SERVICE_PORT_HTTPS'],
101
- path: '/apis/apps'
102
- ),
103
- 'v1beta1',
104
- ssl_options: {
105
- ca_file: "#{SVC_ACC_PATH}/ca.crt",
106
- verify_ssl: OpenSSL::SSL::VERIFY_PEER
107
- },
108
- auth_options: {
109
- bearer_token: kube_token
110
- }
111
- )
174
+ @kube_client_stateful_set ||= create_kube_client(path: '/apis/apps')
112
175
  end
113
176
 
114
177
  def instance_group
115
- pod = kube_client.get_pod(ENV['HOSTNAME'], kube_namespace)
178
+ pod = kube_client.get_pod(@self_name, kube_namespace)
116
179
  pod['metadata']['labels']['app.kubernetes.io/component']
117
180
  end
118
181
  end
@@ -1,3 +1,3 @@
1
1
  class Configgin
2
- VERSION = '0.18.4'.freeze
2
+ VERSION = '0.18.5'.freeze
3
3
  end
@@ -17,6 +17,7 @@ class EnvironmentConfigTransmogrifier < BaseTransmogrifier
17
17
  def escapeHTML(str)
18
18
  str
19
19
  end
20
+ # rubocop:enable MethodName
20
21
  end
21
22
 
22
23
  # Processes the mustache templates and injects new keys into the configuration
@@ -27,7 +28,7 @@ class EnvironmentConfigTransmogrifier < BaseTransmogrifier
27
28
  input_hash = ENV.to_hash
28
29
 
29
30
  # load secrets
30
- extendReplace(input_hash, secrets) if secrets && File.directory?(secrets)
31
+ extend_replace(input_hash, secrets) if secrets && File.directory?(secrets)
31
32
 
32
33
  # remove empty values
33
34
  input_hash.reject! { |_, v| v.nil? || v.empty? }
@@ -41,7 +42,7 @@ class EnvironmentConfigTransmogrifier < BaseTransmogrifier
41
42
  # by default, all values are strings, because they come from the environment
42
43
  # we need to unmarshall them using YAML.load
43
44
  loop do
44
- value = processMustacheTemplate(value, input_hash, key)
45
+ value = process_mustache_template(value, input_hash, key)
45
46
  break unless value.respond_to?(:include?) && value.include?('((')
46
47
  end
47
48
 
@@ -52,7 +53,7 @@ class EnvironmentConfigTransmogrifier < BaseTransmogrifier
52
53
  base_config
53
54
  end
54
55
 
55
- def self.processMustacheTemplate(value, input_hash, key)
56
+ def self.process_mustache_template(value, input_hash, key)
56
57
  val = @@memoize_mustache.fetch(value, {})[input_hash]
57
58
  return val if val
58
59
 
@@ -63,14 +64,14 @@ class EnvironmentConfigTransmogrifier < BaseTransmogrifier
63
64
  mustache_value = mustache_value.to_s.gsub("\n", "\n\n")
64
65
  @@memoize_mustache[value] ||= {}
65
66
  @@memoize_mustache[value][input_hash] = YAML.safe_load(mustache_value)
66
- rescue => e
67
- msg = mustacheMessageFromError(e)
67
+ rescue StandardError => e
68
+ msg = mustache_message_from_error(e)
68
69
  raise LoadYamlFromMustacheError, "Could not load config key '#{key}': #{msg}"
69
70
  end
70
71
  end
71
72
 
72
- def self.mustacheMessageFromError(e)
73
- lines = e.message.split(/\n/)
73
+ def self.mustache_message_from_error(error)
74
+ lines = error.message.split(/\n/)
74
75
  return 'No reason for failure given by mustache library' if lines.empty?
75
76
  return lines.first if lines.size < 4
76
77
 
@@ -84,7 +85,7 @@ class EnvironmentConfigTransmogrifier < BaseTransmogrifier
84
85
  lines[0] + ": Error at or near position #{caret_pos - leading_junk} of template value."
85
86
  end
86
87
 
87
- def self.extendReplace(hash, path)
88
+ def self.extend_replace(hash, path)
88
89
  # This code assumes that 'path' points to a directory of files.
89
90
  # The name of each file is the key into the hash, and the contents
90
91
  # of the file are the value to enter, as-is. The order of the
data/lib/job.rb CHANGED
@@ -4,10 +4,11 @@ require_relative 'kube_link_generator'
4
4
 
5
5
  # Job describes a single BOSH job
6
6
  class Job
7
- def initialize(spec, namespace, client, client_stateful_set)
7
+ def initialize(spec:, namespace:, client:, client_stateful_set:, self_name: ENV['HOSTNAME'])
8
8
  @spec = spec
9
9
  @namespace = namespace
10
10
  @client = client
11
+ @self_name = self_name
11
12
  links = @spec['links'] = KubeLinkSpecs.new(@spec, @namespace, @client, client_stateful_set)
12
13
 
13
14
  # Figure out whether _this_ should bootstrap
@@ -19,25 +20,24 @@ class Job
19
20
  attr_reader :spec
20
21
 
21
22
  def exported_properties
22
- return @exported_propertes if @exported_properties
23
- exported_properties = {}
24
- spec['exported_properties'].each do |prop|
25
- src = spec['properties']
26
- dst = exported_properties
27
- keys = prop.split('.')
28
- leaf = keys.pop
29
- keys.each do |key|
30
- dst[key] ||= {}
31
- dst = dst[key]
32
- src = src.fetch(key, {})
23
+ @exported_properties ||= {}.tap do |exported_properties|
24
+ spec['exported_properties'].each do |prop|
25
+ src = spec['properties']
26
+ dst = exported_properties
27
+ keys = prop.split('.')
28
+ leaf = keys.pop
29
+ keys.each do |key|
30
+ dst[key] ||= {}
31
+ dst = dst[key]
32
+ src = src.fetch(key, {})
33
+ end
34
+ dst[leaf] = src[leaf]
33
35
  end
34
- dst[leaf] = src[leaf]
35
36
  end
36
- @exported_properties = exported_properties
37
37
  end
38
38
 
39
39
  def self_pod
40
- @self_pod ||= @client.get_pod(ENV['HOSTNAME'], @namespace)
40
+ @self_pod ||= @client.get_pod(@self_name, @namespace)
41
41
  end
42
42
 
43
43
  def self_role
@@ -82,7 +82,7 @@ class Job
82
82
  out_file = nil
83
83
  raise "failed to open output file #{output_file_path}: #{e}"
84
84
  ensure
85
- out_file.close unless out_file.nil?
85
+ out_file&.close
86
86
  end
87
87
  end
88
88
  end
@@ -1,3 +1,4 @@
1
+ require 'digest/sha1'
1
2
  require 'kubeclient'
2
3
  require 'uri'
3
4
  require_relative 'exceptions'
@@ -28,6 +29,7 @@ class KubeLinkSpecs
28
29
  def pod_index(name)
29
30
  index = name.rpartition('-').last
30
31
  return index.to_i if /^\d+$/ =~ index
32
+
31
33
  # The pod name is something like role-abcxyz
32
34
  # Derive the index from the randomness that went into the suffix.
33
35
  # chars are the characters kubernetes might use to generate names
@@ -49,6 +51,7 @@ class KubeLinkSpecs
49
51
  good_pods = pods.select do |pod|
50
52
  next false unless pod.status.podIP
51
53
  next true if pod.metadata.annotations["skiff-exported-properties-#{job}"]
54
+
52
55
  # Fall back to non-job-specific properties, for upgrades from older versions
53
56
  pod.metadata.annotations['skiff-exported-properties']
54
57
  end
@@ -66,17 +69,27 @@ class KubeLinkSpecs
66
69
  end
67
70
  end
68
71
 
69
- def get_exported_properties(pod, job)
70
- if pod.metadata.annotations["skiff-exported-properties-#{job}"]
71
- JSON.parse(pod.metadata.annotations["skiff-exported-properties-#{job}"])
72
- elsif pod.metadata.annotations["skiff-exported-properties"]
73
- JSON.parse(pod.metadata.annotations["skiff-exported-properties"])[job]
72
+ def get_exported_properties(role_name, pod, job_name)
73
+ if pod.metadata.annotations["skiff-exported-properties-#{job_name}"]
74
+ if pod.metadata.annotations["skiff-exported-digest-#{job_name}"]
75
+ # Copy the digest over, so that if the source role changes we can be
76
+ # restarted.
77
+ digest = pod.metadata.annotations["skiff-exported-digest-#{job_name}"]
78
+ client.patch_pod(
79
+ ENV['HOSTNAME'],
80
+ { metadata: { annotations: { :"skiff-imported-properties-#{role_name}-#{job_name}" => digest } } },
81
+ namespace
82
+ )
83
+ end
84
+ JSON.parse(pod.metadata.annotations["skiff-exported-properties-#{job_name}"])
85
+ elsif pod.metadata.annotations['skiff-exported-properties']
86
+ JSON.parse(pod.metadata.annotations['skiff-exported-properties'])[job_name]
74
87
  else
75
88
  {}
76
89
  end
77
90
  end
78
91
 
79
- def get_pod_instance_info(pod, job, pods_per_image)
92
+ def get_pod_instance_info(role_name, pod, job, pods_per_image)
80
93
  index = pod_index(pod.metadata.name)
81
94
  # Use pod DNS name for address field, instead of IP address which may change
82
95
  {
@@ -85,7 +98,7 @@ class KubeLinkSpecs
85
98
  'id' => pod.metadata.name,
86
99
  'az' => pod.metadata.annotations['failure-domain.beta.kubernetes.io/zone'] || 'az0',
87
100
  'address' => "#{pod.metadata.name}.#{pod.spec.subdomain}.#{ENV['KUBERNETES_NAMESPACE']}.svc.#{ENV['KUBERNETES_CLUSTER_DOMAIN']}",
88
- 'properties' => get_exported_properties(pod, job),
101
+ 'properties' => get_exported_properties(role_name, pod, job),
89
102
  'bootstrap' => pods_per_image[pod.metadata.uid] < 2
90
103
  }
91
104
  end
@@ -97,6 +110,7 @@ class KubeLinkSpecs
97
110
  keys = {}
98
111
  pods.each do |pod|
99
112
  next if pod.status.containerStatuses.nil?
113
+
100
114
  key = pod.status.containerStatuses.map(&:imageID).sort.join("\n")
101
115
  sets[key] += 1
102
116
  keys[pod.metadata.uid] = key
@@ -116,7 +130,7 @@ class KubeLinkSpecs
116
130
  'id' => svc.metadata.name,
117
131
  'az' => pod.metadata.annotations['failure-domain.beta.kubernetes.io/zone'] || 'az0',
118
132
  'address' => svc.spec.clusterIP,
119
- 'properties' => get_exported_properties(pod, job),
133
+ 'properties' => get_exported_properties(role_name, pod, job),
120
134
  'bootstrap' => true
121
135
  }
122
136
  end
@@ -132,7 +146,7 @@ class KubeLinkSpecs
132
146
  'id' => ss.metadata.name,
133
147
  'az' => pod.metadata.annotations['failure-domain.beta.kubernetes.io/zone'] || 'az0',
134
148
  'address' => "#{ss.metadata.name}-#{i}.#{ss.spec.serviceName}",
135
- 'properties' => get_exported_properties(pod, job),
149
+ 'properties' => get_exported_properties(role_name, pod, job),
136
150
  'bootstrap' => i.zero?
137
151
  }
138
152
  end
@@ -151,27 +165,27 @@ class KubeLinkSpecs
151
165
  # Resolve the role we're looking for
152
166
  provider = spec['consumes'][key]
153
167
  unless provider
154
- $stderr.puts "No link provider found for #{key}"
168
+ warn "No link provider found for #{key}"
155
169
  return @links[key] = nil
156
170
  end
157
171
 
158
172
  if provider['role'] == this_name
159
- $stderr.puts "Resolving link #{key} via self provider #{provider}"
173
+ warn "Resolving link #{key} via self provider #{provider}"
160
174
  pods = get_pods_for_role(provider['role'], provider['job'], wait_for_all: true)
161
175
  pods_per_image = get_pods_per_image(pods)
162
- instances = pods.map { |p| get_pod_instance_info(p, provider['job'], pods_per_image) }
176
+ instances = pods.map { |p| get_pod_instance_info(provider['role'], p, provider['job'], pods_per_image) }
163
177
  elsif service? provider['role']
164
178
  # Getting pods for a different service; since we have kube services, we don't handle it in configgin
165
- $stderr.puts "Resolving link #{key} via service #{provider}"
179
+ warn "Resolving link #{key} via service #{provider}"
166
180
  instances = [get_svc_instance_info(provider['role'], provider['job'])]
167
181
  else
168
182
  # If there's no service associated, check the statefulset instead
169
- $stderr.puts "Resolving link #{key} via statefulset #{provider}"
183
+ warn "Resolving link #{key} via statefulset #{provider}"
170
184
  instances = get_statefulset_instance_info(provider['role'], provider['job'])
171
185
  end
172
186
 
173
187
  # Underscores aren't valid hostnames, so jobs are transformed in fissile to use dashes
174
- job_name = provider['job'].gsub('_', '-')
188
+ job_name = provider['job'].tr('_', '-')
175
189
  service_name = provider['service_name'] || "#{provider['role']}-#{job_name}"
176
190
 
177
191
  @links[key] = {
@@ -0,0 +1,18 @@
1
+ require 'digest/sha1'
2
+ require 'json'
3
+
4
+ # Given a properties (a JSON-serializable Hash), generate a digest that will be
5
+ # consistent across runs even if the enumeration order of hashes changes.
6
+ def property_digest(properties)
7
+ normalize_object = lambda do |obj|
8
+ case obj
9
+ when Hash
10
+ {}.tap { |result| obj.sort.each { |k, v| result[k] = normalize_object.call(v) } }
11
+ when Enumerable
12
+ obj.map(&normalize_object)
13
+ else
14
+ obj
15
+ end
16
+ end
17
+ "sha1:#{Digest::SHA1.hexdigest(normalize_object.call(properties).to_json)}"
18
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: configgin
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.18.4
4
+ version: 0.18.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - SUSE
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-11-07 00:00:00.000000000 Z
11
+ date: 2019-04-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -53,39 +53,33 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: '2.0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: rainbow
56
+ name: deep_merge
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '2.0'
62
- - - "!="
63
- - !ruby/object:Gem::Version
64
- version: 2.2.1
61
+ version: '1.1'
65
62
  type: :runtime
66
63
  prerelease: false
67
64
  version_requirements: !ruby/object:Gem::Requirement
68
65
  requirements:
69
66
  - - "~>"
70
67
  - !ruby/object:Gem::Version
71
- version: '2.0'
72
- - - "!="
73
- - !ruby/object:Gem::Version
74
- version: 2.2.1
68
+ version: '1.1'
75
69
  - !ruby/object:Gem::Dependency
76
- name: deep_merge
70
+ name: kubeclient
77
71
  requirement: !ruby/object:Gem::Requirement
78
72
  requirements:
79
73
  - - "~>"
80
74
  - !ruby/object:Gem::Version
81
- version: '1.1'
75
+ version: '2.0'
82
76
  type: :runtime
83
77
  prerelease: false
84
78
  version_requirements: !ruby/object:Gem::Requirement
85
79
  requirements:
86
80
  - - "~>"
87
81
  - !ruby/object:Gem::Version
88
- version: '1.1'
82
+ version: '2.0'
89
83
  - !ruby/object:Gem::Dependency
90
84
  name: mustache
91
85
  requirement: !ruby/object:Gem::Requirement
@@ -101,12 +95,15 @@ dependencies:
101
95
  - !ruby/object:Gem::Version
102
96
  version: '1.0'
103
97
  - !ruby/object:Gem::Dependency
104
- name: kubeclient
98
+ name: rainbow
105
99
  requirement: !ruby/object:Gem::Requirement
106
100
  requirements:
107
101
  - - "~>"
108
102
  - !ruby/object:Gem::Version
109
103
  version: '2.0'
104
+ - - "!="
105
+ - !ruby/object:Gem::Version
106
+ version: 2.2.1
110
107
  type: :runtime
111
108
  prerelease: false
112
109
  version_requirements: !ruby/object:Gem::Requirement
@@ -114,6 +111,9 @@ dependencies:
114
111
  - - "~>"
115
112
  - !ruby/object:Gem::Version
116
113
  version: '2.0'
114
+ - - "!="
115
+ - !ruby/object:Gem::Version
116
+ version: 2.2.1
117
117
  description: A simple cli app in Ruby to generate configurations using BOSH ERB templates
118
118
  and a BOSH spec, but also using configurations based on environment variables, processed
119
119
  using a set of templates.
@@ -150,6 +150,7 @@ files:
150
150
  - lib/exceptions.rb
151
151
  - lib/job.rb
152
152
  - lib/kube_link_generator.rb
153
+ - lib/property_digest.rb
153
154
  - make/include/darwin-support
154
155
  - make/lint
155
156
  - make/test
@@ -181,7 +182,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
181
182
  version: '0'
182
183
  requirements: []
183
184
  rubyforge_project:
184
- rubygems_version: 2.6.13
185
+ rubygems_version: 2.7.6
185
186
  signing_key:
186
187
  specification_version: 4
187
188
  summary: A simple cli app in Ruby to generate configurations using BOSH ERB templates