kubernetes-deploy 0.6.1 → 0.6.2
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 +4 -4
- data/Gemfile +1 -0
- data/README.md +37 -1
- data/exe/kubernetes-restart +2 -7
- data/kubernetes-deploy.gemspec +2 -0
- data/lib/kubernetes-deploy.rb +2 -8
- data/lib/kubernetes-deploy/ejson_secret_provisioner.rb +168 -0
- data/lib/kubernetes-deploy/errors.rb +10 -0
- data/lib/kubernetes-deploy/restart_task.rb +17 -5
- data/lib/kubernetes-deploy/runner.rb +14 -0
- data/lib/kubernetes-deploy/version.rb +1 -1
- metadata +33 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 55540c9fa5a002f87eba1c06883e0b237d194803
|
4
|
+
data.tar.gz: 5d2e45261bb51d18d34f5337eb7006892885b99e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0fb77ac54ebf8f56eb7c116a29ef373da76d694c4977a5c704f78cafacf9c97c2cad25e5137e9991eaf689661323ab889b205da8db96a87c02c0ce94d641f023
|
7
|
+
data.tar.gz: 2e00d11e27cd3965e30592552adcf77ea7b599cb120a376d2c32b226b7bd07f4c0a1040da24534304b9910ed8c95cf4ad447937a3773d01cc11d5e1b5eac3e2a
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -40,6 +40,42 @@ The following command will restart all pods in the `web` and `jobs` deployments:
|
|
40
40
|
|
41
41
|
`kubernetes-restart <kube namespace> <kube context> --deployments=web,jobs`
|
42
42
|
|
43
|
+
### Deploying Kubernetes secrets
|
44
|
+
|
45
|
+
**Note: If you're a Shopify employee using our cloud platform, this setup has already been done for you. Please consult the CloudPlatform User Guide for usage instructions.**
|
46
|
+
|
47
|
+
Since their data is only base64 encoded, Kubernetes secrets should not be committed to your repository. Instead, `kubernetes-deploy` supports generating secrets from an encrypted [ejson](https://github.com/Shopify/ejson) file in your template directory. Here's how to use this feature:
|
48
|
+
|
49
|
+
1. Install the ejson gem: `gem install ejson`
|
50
|
+
2. Generate a new keypair: `ejson keygen` (prints the keypair to stdout)
|
51
|
+
3. Create a Kubernetes secret in your target namespace with the new keypair: `kubectl create secret generic ejson-keys --from-literal=YOUR_PUBLIC_KEY=YOUR_PRIVATE_KEY --namespace=TARGET_NAMESPACE`
|
52
|
+
4. (optional but highly recommended) Back up the keypair somewhere secure, such as a password manager, for disaster recovery purposes.
|
53
|
+
5. In your template directory (alongside your Kubernetes templates), create `secrets.ejson` with the format shown below. The `_type` key should have the value “kubernetes.io/tls” for TLS secrets and “Opaque” for all others. The `data` key must be a json object, but its keys and values can be whatever you need.
|
54
|
+
|
55
|
+
```json
|
56
|
+
{
|
57
|
+
"_public_key": "YOUR_PUBLIC_KEY",
|
58
|
+
"kubernetes_secrets": {
|
59
|
+
"catphotoscom": {
|
60
|
+
"_type": "kubernetes.io/tls",
|
61
|
+
"data": {
|
62
|
+
"tls.crt": "cert-data-here",
|
63
|
+
"tls.key": "key-data-here"
|
64
|
+
}
|
65
|
+
},
|
66
|
+
"monitoring-token": {
|
67
|
+
"_type": "Opaque",
|
68
|
+
"data": {
|
69
|
+
"api-token": "token-value-here"
|
70
|
+
}
|
71
|
+
}
|
72
|
+
}
|
73
|
+
}
|
74
|
+
```
|
75
|
+
|
76
|
+
6. Encrypt the file: `ejson encrypt /PATH/TO/secrets.ejson`
|
77
|
+
7. Commit the encrypted file and deploy as usual. The deploy will create secrets from the data in the `kubernetes_secrets` key.
|
78
|
+
|
43
79
|
### Running one off tasks
|
44
80
|
|
45
81
|
To trigger a one-off job such as a rake task _outside_ of a deploy, use the following command:
|
@@ -50,7 +86,7 @@ This command assumes that you've already deployed a `PodTemplate` named `task-ru
|
|
50
86
|
|
51
87
|
#### Creating a PodTemplate
|
52
88
|
|
53
|
-
The [`PodTemplate`](https://kubernetes.io/docs/api-reference/v1.6/#podtemplate-v1-core) object should have a field `template` containing a `Pod` specification which does not include the `apiVersion` or `kind` parameters. An example is provided in this repo in `test/fixtures/hello-cloud/template-runner.yml`.
|
89
|
+
The [`PodTemplate`](https://kubernetes.io/docs/api-reference/v1.6/#podtemplate-v1-core) object should have a field `template` containing a `Pod` specification which does not include the `apiVersion` or `kind` parameters. An example is provided in this repo in `test/fixtures/hello-cloud/template-runner.yml`.
|
54
90
|
|
55
91
|
#### Providing multiple different task-runner configurations
|
56
92
|
|
data/exe/kubernetes-restart
CHANGED
@@ -8,16 +8,11 @@ require 'kubernetes-deploy/restart_task'
|
|
8
8
|
|
9
9
|
raw_deployments = nil
|
10
10
|
ARGV.options do |opts|
|
11
|
-
opts.on("--deployments=LIST") { |v| raw_deployments = v }
|
11
|
+
opts.on("--deployments=LIST") { |v| raw_deployments = v.split(",") }
|
12
12
|
opts.parse!
|
13
13
|
end
|
14
14
|
|
15
|
-
if raw_deployments.nil?
|
16
|
-
puts "Failed: specify at least one deployment to restart with --deployments flag"
|
17
|
-
exit 1
|
18
|
-
end
|
19
|
-
|
20
15
|
KubernetesDeploy::Runner.with_friendly_errors do
|
21
16
|
restart = KubernetesDeploy::RestartTask.new(namespace: ARGV[0], context: ARGV[1])
|
22
|
-
restart.perform(raw_deployments
|
17
|
+
restart.perform(raw_deployments)
|
23
18
|
end
|
data/kubernetes-deploy.gemspec
CHANGED
@@ -23,10 +23,12 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.add_dependency "activesupport", ">= 4.2"
|
24
24
|
spec.add_dependency "kubeclient", "~> 2.3"
|
25
25
|
spec.add_dependency "googleauth", ">= 0.5"
|
26
|
+
spec.add_dependency "ejson", "1.0.1"
|
26
27
|
|
27
28
|
spec.add_development_dependency "bundler", "~> 1.13"
|
28
29
|
spec.add_development_dependency "rake", "~> 10.0"
|
29
30
|
spec.add_development_dependency "minitest", "~> 5.0"
|
30
31
|
spec.add_development_dependency "minitest-stub-const", "~> 0.6"
|
31
32
|
spec.add_development_dependency "webmock", "~> 3.0"
|
33
|
+
spec.add_development_dependency "mocha", "~> 1.1"
|
32
34
|
end
|
data/lib/kubernetes-deploy.rb
CHANGED
@@ -4,18 +4,12 @@ require 'active_support/core_ext/hash/slice'
|
|
4
4
|
require 'active_support/core_ext/numeric/time'
|
5
5
|
require 'active_support/core_ext/string/inflections'
|
6
6
|
require 'active_support/core_ext/string/strip'
|
7
|
+
require 'active_support/core_ext/hash/keys'
|
7
8
|
|
9
|
+
require 'kubernetes-deploy/errors'
|
8
10
|
require 'kubernetes-deploy/logger'
|
9
11
|
require 'kubernetes-deploy/runner'
|
10
12
|
|
11
13
|
module KubernetesDeploy
|
12
|
-
class FatalDeploymentError < StandardError; end
|
13
|
-
|
14
|
-
class NamespaceNotFoundError < FatalDeploymentError
|
15
|
-
def initialize(name, context)
|
16
|
-
super("Namespace `#{name}` not found in context `#{context}`. Aborting the task.")
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
14
|
include Logger
|
21
15
|
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'json'
|
3
|
+
require 'base64'
|
4
|
+
require 'open3'
|
5
|
+
require 'kubernetes-deploy/logger'
|
6
|
+
|
7
|
+
module KubernetesDeploy
|
8
|
+
class EjsonSecretError < FatalDeploymentError
|
9
|
+
def initialize(msg)
|
10
|
+
super("Creation of Kubernetes secrets from ejson failed: #{msg}")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class EjsonSecretProvisioner
|
15
|
+
MANAGEMENT_ANNOTATION = "kubernetes-deploy.shopify.io/ejson-secret"
|
16
|
+
MANAGED_SECRET_EJSON_KEY = "kubernetes_secrets"
|
17
|
+
EJSON_SECRETS_FILE = "secrets.ejson"
|
18
|
+
EJSON_KEYS_SECRET = "ejson-keys"
|
19
|
+
|
20
|
+
def initialize(namespace:, template_dir:, client:)
|
21
|
+
@namespace = namespace
|
22
|
+
@ejson_file = "#{template_dir}/#{EJSON_SECRETS_FILE}"
|
23
|
+
@kubeclient = client
|
24
|
+
end
|
25
|
+
|
26
|
+
def secret_changes_required?
|
27
|
+
File.exist?(@ejson_file) || managed_secrets_exist?
|
28
|
+
end
|
29
|
+
|
30
|
+
def run
|
31
|
+
create_secrets
|
32
|
+
prune_managed_secrets
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def create_secrets
|
38
|
+
with_decrypted_ejson do |decrypted|
|
39
|
+
secrets = decrypted[MANAGED_SECRET_EJSON_KEY]
|
40
|
+
unless secrets.present?
|
41
|
+
KubernetesDeploy.logger.warn("#{EJSON_SECRETS_FILE} does not have key #{MANAGED_SECRET_EJSON_KEY}."\
|
42
|
+
"No secrets will be created.")
|
43
|
+
return
|
44
|
+
end
|
45
|
+
|
46
|
+
secrets.each do |secret_name, secret_spec|
|
47
|
+
validate_secret_spec(secret_name, secret_spec)
|
48
|
+
create_or_update_secret(secret_name, secret_spec["_type"], secret_spec["data"])
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def prune_managed_secrets
|
54
|
+
ejson_secret_names = encrypted_ejson.fetch(MANAGED_SECRET_EJSON_KEY, {}).keys
|
55
|
+
live_secrets = @kubeclient.get_secrets(namespace: @namespace)
|
56
|
+
|
57
|
+
live_secrets.each do |secret|
|
58
|
+
secret_name = secret.metadata.name
|
59
|
+
next unless secret_managed?(secret)
|
60
|
+
next if ejson_secret_names.include?(secret_name)
|
61
|
+
|
62
|
+
KubernetesDeploy.logger.info("Pruning secret #{secret_name}")
|
63
|
+
@kubeclient.delete_secret(secret_name, @namespace)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def managed_secrets_exist?
|
68
|
+
all_secrets = @kubeclient.get_secrets(namespace: @namespace)
|
69
|
+
all_secrets.any? { |secret| secret_managed?(secret) }
|
70
|
+
end
|
71
|
+
|
72
|
+
def secret_managed?(secret)
|
73
|
+
secret.metadata.annotations.to_h.stringify_keys.key?(MANAGEMENT_ANNOTATION)
|
74
|
+
end
|
75
|
+
|
76
|
+
def encrypted_ejson
|
77
|
+
@encrypted_ejson ||= load_ejson_from_file
|
78
|
+
end
|
79
|
+
|
80
|
+
def public_key
|
81
|
+
encrypted_ejson["_public_key"]
|
82
|
+
end
|
83
|
+
|
84
|
+
def private_key
|
85
|
+
@private_key ||= fetch_private_key_from_secret
|
86
|
+
end
|
87
|
+
|
88
|
+
def validate_secret_spec(secret_name, spec)
|
89
|
+
errors = []
|
90
|
+
errors << "secret type unspecified" if spec["_type"].blank?
|
91
|
+
errors << "no data provided" if spec["data"].blank?
|
92
|
+
|
93
|
+
unless errors.empty?
|
94
|
+
raise EjsonSecretError, "Ejson incomplete for secret #{secret_name}: #{errors.join(', ')}"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def create_or_update_secret(secret_name, secret_type, data)
|
99
|
+
metadata = {
|
100
|
+
name: secret_name,
|
101
|
+
labels: { "name" => secret_name },
|
102
|
+
namespace: @namespace,
|
103
|
+
annotations: { MANAGEMENT_ANNOTATION => "true" }
|
104
|
+
}
|
105
|
+
secret = Kubeclient::Secret.new(type: secret_type, stringData: data, metadata: metadata)
|
106
|
+
if secret_exists?(secret)
|
107
|
+
KubernetesDeploy.logger.info("Updating secret #{secret_name}")
|
108
|
+
@kubeclient.update_secret(secret)
|
109
|
+
else
|
110
|
+
KubernetesDeploy.logger.info("Creating secret #{secret_name}")
|
111
|
+
@kubeclient.create_secret(secret)
|
112
|
+
end
|
113
|
+
rescue KubeException => e
|
114
|
+
raise unless e.error_code == 400
|
115
|
+
raise EjsonSecretError, "Data for secret #{secret_name} was invalid: #{e}"
|
116
|
+
end
|
117
|
+
|
118
|
+
def secret_exists?(secret)
|
119
|
+
@kubeclient.get_secret(secret.metadata.name, @namespace)
|
120
|
+
true
|
121
|
+
rescue KubeException => error
|
122
|
+
raise unless error.error_code == 404
|
123
|
+
false
|
124
|
+
end
|
125
|
+
|
126
|
+
def load_ejson_from_file
|
127
|
+
return {} unless File.exist?(@ejson_file)
|
128
|
+
JSON.parse(File.read(@ejson_file))
|
129
|
+
rescue JSON::ParserError => e
|
130
|
+
raise EjsonSecretError, "Failed to parse encrypted ejson:\n #{e}"
|
131
|
+
end
|
132
|
+
|
133
|
+
def with_decrypted_ejson
|
134
|
+
return unless File.exist?(@ejson_file)
|
135
|
+
|
136
|
+
Dir.mktmpdir("ejson_keydir") do |key_dir|
|
137
|
+
File.write(File.join(key_dir, public_key), private_key)
|
138
|
+
decrypted = decrypt_ejson(key_dir)
|
139
|
+
yield decrypted
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def decrypt_ejson(key_dir)
|
144
|
+
KubernetesDeploy.logger.info("Decrypting #{EJSON_SECRETS_FILE}")
|
145
|
+
# ejson seems to dump both errors and output to STDOUT
|
146
|
+
out_err, st = Open3.capture2e("EJSON_KEYDIR=#{key_dir} ejson decrypt #{@ejson_file}")
|
147
|
+
raise EjsonSecretError, out_err unless st.success?
|
148
|
+
JSON.parse(out_err)
|
149
|
+
rescue JSON::ParserError => e
|
150
|
+
raise EjsonSecretError, "Failed to parse decrypted ejson:\n #{e}"
|
151
|
+
end
|
152
|
+
|
153
|
+
def fetch_private_key_from_secret
|
154
|
+
KubernetesDeploy.logger.info("Fetching ejson private key from secret #{EJSON_KEYS_SECRET}")
|
155
|
+
secret = @kubeclient.get_secret(EJSON_KEYS_SECRET, @namespace)
|
156
|
+
encoded_private_key = secret["data"][public_key]
|
157
|
+
unless encoded_private_key
|
158
|
+
raise EjsonSecretError, "Private key for #{public_key} not found in #{EJSON_KEYS_SECRET} secret"
|
159
|
+
end
|
160
|
+
|
161
|
+
Base64.decode64(encoded_private_key)
|
162
|
+
rescue KubeException => error
|
163
|
+
raise unless error.error_code == 404
|
164
|
+
secret_missing_err = "Failed to decrypt ejson: secret #{EJSON_KEYS_SECRET} not found in namespace #{@namespace}."
|
165
|
+
raise EjsonSecretError, secret_missing_err
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module KubernetesDeploy
|
3
|
+
class FatalDeploymentError < StandardError; end
|
4
|
+
|
5
|
+
class NamespaceNotFoundError < FatalDeploymentError
|
6
|
+
def initialize(name, context)
|
7
|
+
super("Namespace `#{name}` not found in context `#{context}`. Aborting the task.")
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -23,6 +23,7 @@ module KubernetesDeploy
|
|
23
23
|
end
|
24
24
|
|
25
25
|
HTTP_OK_RANGE = 200..299
|
26
|
+
ANNOTATION = "shipit.shopify.io/restart"
|
26
27
|
|
27
28
|
def initialize(context:, namespace:, logger: KubernetesDeploy.logger)
|
28
29
|
@context = context
|
@@ -32,22 +33,33 @@ module KubernetesDeploy
|
|
32
33
|
@v1beta1_kubeclient = build_v1beta1_kubeclient(context)
|
33
34
|
end
|
34
35
|
|
35
|
-
def perform(deployments_names)
|
36
|
+
def perform(deployments_names = nil)
|
36
37
|
verify_namespace
|
37
38
|
|
38
|
-
if deployments_names
|
39
|
-
|
39
|
+
if deployments_names
|
40
|
+
deployments = fetch_deployments(deployments_names.uniq)
|
41
|
+
|
42
|
+
if deployments.none?
|
43
|
+
raise ArgumentError, "no deployments with names #{deployments_names} found in namespace #{@namespace}"
|
44
|
+
end
|
45
|
+
else
|
46
|
+
deployments = @v1beta1_kubeclient
|
47
|
+
.get_deployments(namespace: @namespace)
|
48
|
+
.select { |d| d.metadata.annotations[ANNOTATION] }
|
49
|
+
|
50
|
+
if deployments.none?
|
51
|
+
raise ArgumentError, "no deployments found in namespace #{@namespace} with #{ANNOTATION} annotation available"
|
52
|
+
end
|
40
53
|
end
|
41
54
|
|
42
55
|
phase_heading("Triggering restart by touching ENV[RESTARTED_AT]")
|
43
|
-
deployments = fetch_deployments(deployments_names.uniq)
|
44
56
|
patch_kubeclient_deployments(deployments)
|
45
57
|
|
46
58
|
phase_heading("Waiting for rollout")
|
47
59
|
wait_for_rollout(deployments)
|
48
60
|
|
49
61
|
names = deployments.map { |d| "`#{d.metadata.name}`" }
|
50
|
-
@logger.info "Restart of #{names.join(', ')} deployments succeeded"
|
62
|
+
@logger.info "Restart of #{names.sort.join(', ')} deployments succeeded"
|
51
63
|
end
|
52
64
|
|
53
65
|
private
|
@@ -23,10 +23,13 @@ end
|
|
23
23
|
require 'kubernetes-deploy/resource_watcher'
|
24
24
|
require "kubernetes-deploy/ui_helpers"
|
25
25
|
require 'kubernetes-deploy/kubectl'
|
26
|
+
require 'kubernetes-deploy/kubeclient_builder'
|
27
|
+
require 'kubernetes-deploy/ejson_secret_provisioner'
|
26
28
|
|
27
29
|
module KubernetesDeploy
|
28
30
|
class Runner
|
29
31
|
include UIHelpers
|
32
|
+
include KubeclientBuilder
|
30
33
|
|
31
34
|
PREDEPLOY_SEQUENCE = %w(
|
32
35
|
Cloudsql
|
@@ -39,6 +42,7 @@ module KubernetesDeploy
|
|
39
42
|
PROTECTED_NAMESPACES = %w(
|
40
43
|
default
|
41
44
|
kube-system
|
45
|
+
kube-public
|
42
46
|
)
|
43
47
|
|
44
48
|
# Things removed from default prune whitelist:
|
@@ -109,6 +113,16 @@ MSG
|
|
109
113
|
phase_heading("Checking initial resource statuses")
|
110
114
|
resources.each(&:sync)
|
111
115
|
|
116
|
+
ejson = EjsonSecretProvisioner.new(
|
117
|
+
namespace: @namespace,
|
118
|
+
template_dir: @template_dir,
|
119
|
+
client: build_v1_kubeclient(@context)
|
120
|
+
)
|
121
|
+
if ejson.secret_changes_required?
|
122
|
+
phase_heading("Deploying kubernetes secrets from #{EjsonSecretProvisioner::EJSON_SECRETS_FILE}")
|
123
|
+
ejson.run
|
124
|
+
end
|
125
|
+
|
112
126
|
phase_heading("Predeploying priority resources")
|
113
127
|
predeploy_priority_resources(resources)
|
114
128
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kubernetes-deploy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kir Shatrov
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: exe
|
12
12
|
cert_chain: []
|
13
|
-
date: 2017-05-
|
13
|
+
date: 2017-05-08 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activesupport
|
@@ -54,6 +54,20 @@ dependencies:
|
|
54
54
|
- - ">="
|
55
55
|
- !ruby/object:Gem::Version
|
56
56
|
version: '0.5'
|
57
|
+
- !ruby/object:Gem::Dependency
|
58
|
+
name: ejson
|
59
|
+
requirement: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - '='
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: 1.0.1
|
64
|
+
type: :runtime
|
65
|
+
prerelease: false
|
66
|
+
version_requirements: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - '='
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: 1.0.1
|
57
71
|
- !ruby/object:Gem::Dependency
|
58
72
|
name: bundler
|
59
73
|
requirement: !ruby/object:Gem::Requirement
|
@@ -124,6 +138,20 @@ dependencies:
|
|
124
138
|
- - "~>"
|
125
139
|
- !ruby/object:Gem::Version
|
126
140
|
version: '3.0'
|
141
|
+
- !ruby/object:Gem::Dependency
|
142
|
+
name: mocha
|
143
|
+
requirement: !ruby/object:Gem::Requirement
|
144
|
+
requirements:
|
145
|
+
- - "~>"
|
146
|
+
- !ruby/object:Gem::Version
|
147
|
+
version: '1.1'
|
148
|
+
type: :development
|
149
|
+
prerelease: false
|
150
|
+
version_requirements: !ruby/object:Gem::Requirement
|
151
|
+
requirements:
|
152
|
+
- - "~>"
|
153
|
+
- !ruby/object:Gem::Version
|
154
|
+
version: '1.1'
|
127
155
|
description: Kubernetes deploy scripts
|
128
156
|
email:
|
129
157
|
- ops-accounts+shipit@shopify.com
|
@@ -147,6 +175,8 @@ files:
|
|
147
175
|
- exe/kubernetes-run
|
148
176
|
- kubernetes-deploy.gemspec
|
149
177
|
- lib/kubernetes-deploy.rb
|
178
|
+
- lib/kubernetes-deploy/ejson_secret_provisioner.rb
|
179
|
+
- lib/kubernetes-deploy/errors.rb
|
150
180
|
- lib/kubernetes-deploy/kubeclient_builder.rb
|
151
181
|
- lib/kubernetes-deploy/kubeclient_builder/google_friendly_config.rb
|
152
182
|
- lib/kubernetes-deploy/kubectl.rb
|
@@ -188,7 +218,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
188
218
|
version: '0'
|
189
219
|
requirements: []
|
190
220
|
rubyforge_project:
|
191
|
-
rubygems_version: 2.
|
221
|
+
rubygems_version: 2.5.1
|
192
222
|
signing_key:
|
193
223
|
specification_version: 4
|
194
224
|
summary: Kubernetes deploy scripts
|