configgin 0.18.4 → 0.18.5

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
- 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