kubernetes-deploy 0.5.0 → 0.6.0

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
2
  SHA1:
3
- metadata.gz: d1e66d28cabf4ab39da9f1418817e5466ea2e73f
4
- data.tar.gz: 69967786f96afae72c074a49c44763e1e0377d1e
3
+ metadata.gz: d6c0635aa2dd4ca87b4cea59f90b783251f93418
4
+ data.tar.gz: 9859f840e6b082188e50e8545546b756b399e95c
5
5
  SHA512:
6
- metadata.gz: 1aba4d518ff8cb0afad70fd9d5a2710ee6696f02c06fe497907a1ac593fc94191e970b1afabe119032dbf5ad361dd9fc06255d1ca817748e36d9c66b581037e9
7
- data.tar.gz: d4213a7380ac484572837a8723713ebfb8cc93448cab122e89a80c55d36bd0399397f8d93e8a27b858fae34dcaa6a4783a65d220e1b561079f5c323eb890105d
6
+ metadata.gz: f637386dec36fb619f319d22109f113b621587c0e1b6f8774d76801582a3c349355ed2f2ac3de57e7d2c1f30792e9c796abd27fde46c58b1bd558ec849290209
7
+ data.tar.gz: 57c73ae638bab026343598ae9f503f9a1af48d11a77ceed8e4c73238d3795314ff1eacf775101dcc19cdf76c2fd3ef584c13c53ca9910db8f69b61bf6a8f7cd4
data/.gitignore CHANGED
@@ -12,3 +12,4 @@
12
12
  .rubocop-http---shopify-github-io-ruby-style-guide-rubocop-yml
13
13
 
14
14
  .byebug_history
15
+ .ruby-version
data/README.md CHANGED
@@ -40,6 +40,27 @@ 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
+ ### Running one off tasks
44
+
45
+ To trigger a one-off job such as a rake task _outside_ of a deploy, use the following command:
46
+
47
+ `kubernetes-run <kube namespace> <kube context> <arguments> --entrypoint=/bin/bash`
48
+
49
+ This command assumes that you've already deployed a `PodTemplate` named `task-runner-template`, which contains a full pod specification in its `data`. The pod specification in turn must have a container named `task-runner`. Based on this specification `kubernetes-run` will create a new pod with the entrypoint of the task-runner container overriden with the supplied arguments.
50
+
51
+ #### Creating a PodTemplate
52
+
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`.
54
+
55
+ #### Providing multiple different task-runner configurations
56
+
57
+ If your application requires task runner templates you can specify which template to use by using the `--template` option. All templates are expected to provide a container called `task-runner`.
58
+
59
+ #### Specifying environment variables for the container
60
+
61
+ If you also need to specify environment variables on top of the arguments, you can specify the `--env-vars` flag which accepts a comma separated list of environment variables like so: `--env-vars="ENV=VAL,ENV2=VAL"`
62
+
63
+
43
64
  ## Development
44
65
 
45
66
  After checking out the repo, run `bin/setup` to install dependencies. You currently need to [manually install kubectl version 1.6.0 or higher](https://kubernetes.io/docs/user-guide/prereqs/) as well if you don't already have it.
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'kubernetes-deploy'
5
+ require 'kubernetes-deploy/runner_task'
6
+ require 'optparse'
7
+
8
+ template = "task-runner-template"
9
+ entrypoint = nil
10
+ env_vars = []
11
+
12
+ ARGV.options do |opts|
13
+ opts.on("--template=TEMPLATE") { |n| template = n }
14
+ opts.on("--env-vars=ENV_VARS") { |vars| env_vars = n.split(",")}
15
+ opts.on("--entrypoint=ENTRYPOINT") { |c| entrypoint = [c] }
16
+ opts.parse!
17
+ end
18
+
19
+ runner = KubernetesDeploy::RunnerTask.new(
20
+ namespace: ARGV[0],
21
+ context: ARGV[1],
22
+ )
23
+
24
+ KubernetesDeploy::Runner.with_friendly_errors do
25
+ runner.run(
26
+ task_template: template,
27
+ entrypoint: entrypoint,
28
+ args: ARGV[2..-1],
29
+ env_vars: env_vars
30
+ )
31
+ end
@@ -10,5 +10,12 @@ require 'kubernetes-deploy/runner'
10
10
 
11
11
  module KubernetesDeploy
12
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
+
13
20
  include Logger
14
21
  end
@@ -35,6 +35,7 @@ module KubernetesDeploy
35
35
  when 'ingress' then Ingress.new(name, namespace, context, file)
36
36
  when 'persistentvolumeclaim' then PersistentVolumeClaim.new(name, namespace, context, file)
37
37
  when 'service' then Service.new(name, namespace, context, file)
38
+ when 'podtemplate' then PodTemplate.new(name, namespace, context, file)
38
39
  else self.new(name, namespace, context, file).tap { |r| r.type = type }
39
40
  end
40
41
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+ module KubernetesDeploy
3
+ class PodTemplate < KubernetesResource
4
+ def initialize(name, namespace, context, file)
5
+ @name = name
6
+ @namespace = namespace
7
+ @context = context
8
+ @file = file
9
+ end
10
+
11
+ def sync
12
+ _, _err, st = run_kubectl("get", type, @name)
13
+ @status = st.success? ? "Available" : "Unknown"
14
+ @found = st.success?
15
+ log_status
16
+ end
17
+
18
+ def deploy_succeeded?
19
+ exists?
20
+ end
21
+
22
+ def deploy_failed?
23
+ false
24
+ end
25
+
26
+ def exists?
27
+ @found
28
+ end
29
+ end
30
+ end
@@ -14,12 +14,6 @@ module KubernetesDeploy
14
14
  end
15
15
  end
16
16
 
17
- class NamespaceNotFoundError < FatalDeploymentError
18
- def initialize(name, context)
19
- super("Namespace `#{name}` not found in context `#{context}`. Aborting the task.")
20
- end
21
- end
22
-
23
17
  class RestartError < FatalDeploymentError
24
18
  def initialize(deployment_name, response)
25
19
  super("Failed to restart #{deployment_name}. " \
@@ -5,7 +5,6 @@ require 'erb'
5
5
  require 'yaml'
6
6
  require 'shellwords'
7
7
  require 'tempfile'
8
-
9
8
  require 'kubernetes-deploy/kubernetes_resource'
10
9
  %w(
11
10
  cloudsql
@@ -16,6 +15,7 @@ require 'kubernetes-deploy/kubernetes_resource'
16
15
  pod
17
16
  redis
18
17
  service
18
+ pod_template
19
19
  bugsnag
20
20
  ).each do |subresource|
21
21
  require "kubernetes-deploy/kubernetes_resource/#{subresource}"
@@ -116,6 +116,7 @@ MSG
116
116
  if PROTECTED_NAMESPACES.include?(@namespace) && @prune
117
117
  raise FatalDeploymentError, "Refusing to deploy to protected namespace '#{@namespace}' with pruning enabled"
118
118
  end
119
+
119
120
  deploy_resources(resources, prune: @prune)
120
121
 
121
122
  return unless wait_for_completion?
@@ -0,0 +1,152 @@
1
+ # frozen_string_literal: true
2
+ require 'tempfile'
3
+
4
+ require 'kubernetes-deploy/kubeclient_builder'
5
+ require 'kubernetes-deploy/ui_helpers'
6
+ require 'kubernetes-deploy/kubectl'
7
+
8
+ module KubernetesDeploy
9
+ class RunnerTask
10
+ include KubeclientBuilder
11
+ include Kubectl
12
+ include UIHelpers
13
+
14
+ class FatalTaskRunError < FatalDeploymentError; end
15
+ class TaskTemplateMissingError < FatalDeploymentError
16
+ def initialize(task_template, namespace, context)
17
+ super("Pod template `#{task_template}` cannot be found in namespace: `#{namespace}`, context: `#{context}`")
18
+ end
19
+ end
20
+
21
+ def initialize(namespace:, context:, logger: KubernetesDeploy.logger)
22
+ @logger = logger
23
+ @namespace = namespace
24
+ @kubeclient = build_v1_kubeclient(context)
25
+ @context = context
26
+ end
27
+
28
+ def run(task_template:, entrypoint:, args:, env_vars: [])
29
+ phase_heading("Validating configuration")
30
+ validate_configuration(task_template, args)
31
+
32
+ phase_heading("Fetching task template")
33
+ raw_template = get_template(task_template)
34
+
35
+ phase_heading("Constructing final pod specification")
36
+ rendered_template = build_pod_template(raw_template, entrypoint, args, env_vars)
37
+
38
+ validate_pod_spec(rendered_template)
39
+
40
+ phase_heading("Creating pod")
41
+ KubernetesDeploy.logger.info("Starting task runner pod: '#{rendered_template.metadata.name}'")
42
+ @kubeclient.create_pod(rendered_template)
43
+ end
44
+
45
+ private
46
+
47
+ def validate_configuration(task_template, args)
48
+ errors = []
49
+
50
+ if task_template.blank?
51
+ errors << "Task template name can't be nil"
52
+ end
53
+
54
+ if @namespace.blank?
55
+ errors << "Namespace can't be empty"
56
+ end
57
+
58
+ if args.blank?
59
+ errors << "Args can't be nil"
60
+ end
61
+
62
+ begin
63
+ @kubeclient.get_namespace(@namespace)
64
+ rescue KubeException => e
65
+ errors << if e.error_code == 404
66
+ "Namespace was not found"
67
+ else
68
+ "Could not connect to kubernetes cluster"
69
+ end
70
+ end
71
+
72
+ raise FatalTaskRunError, "Configuration invalid: #{errors.join(', ')}" unless errors.empty?
73
+ end
74
+
75
+ def get_template(template_name)
76
+ KubernetesDeploy.logger.info(
77
+ "Fetching task runner pod template: '#{template_name}' in namespace: '#{@namespace}'"
78
+ )
79
+
80
+ pod_template = @kubeclient.get_pod_template(template_name, @namespace)
81
+
82
+ pod_template.template
83
+ rescue KubeException => error
84
+ if error.error_code == 404
85
+ raise TaskTemplateMissingError.new(template_name, @namespace, @context)
86
+ else
87
+ raise
88
+ end
89
+ end
90
+
91
+ def build_pod_template(base_template, entrypoint, args, env_vars)
92
+ KubernetesDeploy.logger.info("Rendering template for task runner pod")
93
+
94
+ rendered_template = base_template.dup
95
+ rendered_template.kind = 'Pod'
96
+ rendered_template.apiVersion = 'v1'
97
+
98
+ container = rendered_template.spec.containers.find { |cont| cont.name == 'task-runner' }
99
+
100
+ raise FatalTaskRunError, "Pod spec does not contain a template container called 'task-runner'" if container.nil?
101
+
102
+ container.command = entrypoint
103
+ container.args = args
104
+ container.env ||= []
105
+
106
+ env_args = env_vars.map do |key, value|
107
+ key, value = env.split('=', 2)
108
+ { name: key, value: value }
109
+ end
110
+
111
+ container.env = container.env.map(&:to_h) + env_args
112
+
113
+ unique_name = rendered_template.metadata.name + "-" + SecureRandom.hex(8)
114
+
115
+ KubernetesDeploy.logger.warn("Name is too long, using '#{unique_name[0..62]}'") if unique_name.length > 63
116
+ rendered_template.metadata.name = unique_name[0..62]
117
+ rendered_template.metadata.namespace = @namespace
118
+
119
+ rendered_template
120
+ end
121
+
122
+ def validate_pod_spec(pod)
123
+ f = Tempfile.new(pod.metadata.name)
124
+ f.write recursive_to_h(pod).to_json
125
+ f.close
126
+
127
+ _out, err, status = Kubectl.run_kubectl(
128
+ "apply", "--dry-run", "-f", f.path,
129
+ namespace: @namespace,
130
+ context: @context
131
+ )
132
+
133
+ unless status.success?
134
+ raise FatalTaskRunError, "Invalid pod spec: #{err}"
135
+ end
136
+ end
137
+
138
+ def recursive_to_h(struct)
139
+ if struct.is_a?(Array)
140
+ return struct.map { |v| v.is_a?(OpenStruct) || v.is_a?(Array) || v.is_a?(Hash) ? recursive_to_h(v) : v }
141
+ end
142
+
143
+ hash = {}
144
+
145
+ struct.each_pair do |k, v|
146
+ recursive_val = v.is_a?(OpenStruct) || v.is_a?(Array) || v.is_a?(Hash)
147
+ hash[k] = recursive_val ? recursive_to_h(v) : v
148
+ end
149
+ hash
150
+ end
151
+ end
152
+ end
@@ -1,3 +1,3 @@
1
1
  module KubernetesDeploy
2
- VERSION = "0.5.0"
2
+ VERSION = "0.6.0"
3
3
  end
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.5.0
4
+ version: 0.6.0
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-04-21 00:00:00.000000000 Z
13
+ date: 2017-04-27 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activesupport
@@ -102,6 +102,7 @@ email:
102
102
  executables:
103
103
  - kubernetes-deploy
104
104
  - kubernetes-restart
105
+ - kubernetes-run
105
106
  extensions: []
106
107
  extra_rdoc_files: []
107
108
  files:
@@ -115,6 +116,7 @@ files:
115
116
  - bin/setup
116
117
  - exe/kubernetes-deploy
117
118
  - exe/kubernetes-restart
119
+ - exe/kubernetes-run
118
120
  - kubernetes-deploy.gemspec
119
121
  - lib/kubernetes-deploy.rb
120
122
  - lib/kubernetes-deploy/kubeclient_builder.rb
@@ -127,12 +129,14 @@ files:
127
129
  - lib/kubernetes-deploy/kubernetes_resource/ingress.rb
128
130
  - lib/kubernetes-deploy/kubernetes_resource/persistent_volume_claim.rb
129
131
  - lib/kubernetes-deploy/kubernetes_resource/pod.rb
132
+ - lib/kubernetes-deploy/kubernetes_resource/pod_template.rb
130
133
  - lib/kubernetes-deploy/kubernetes_resource/redis.rb
131
134
  - lib/kubernetes-deploy/kubernetes_resource/service.rb
132
135
  - lib/kubernetes-deploy/logger.rb
133
136
  - lib/kubernetes-deploy/resource_watcher.rb
134
137
  - lib/kubernetes-deploy/restart_task.rb
135
138
  - lib/kubernetes-deploy/runner.rb
139
+ - lib/kubernetes-deploy/runner_task.rb
136
140
  - lib/kubernetes-deploy/ui_helpers.rb
137
141
  - lib/kubernetes-deploy/version.rb
138
142
  homepage: https://github.com/Shopify/kubernetes-deploy
@@ -155,7 +159,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
155
159
  version: '0'
156
160
  requirements: []
157
161
  rubyforge_project:
158
- rubygems_version: 2.5.1
162
+ rubygems_version: 2.6.10
159
163
  signing_key:
160
164
  specification_version: 4
161
165
  summary: Kubernetes deploy scripts