kube_deploy_tools 3.0.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.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +177 -0
  3. data/README.md +142 -0
  4. data/bin/deploy +60 -0
  5. data/bin/generate +28 -0
  6. data/bin/kdt +17 -0
  7. data/bin/make_configmap +20 -0
  8. data/bin/publish +28 -0
  9. data/bin/push +57 -0
  10. data/bin/render_deploys_hook +18 -0
  11. data/bin/templater +34 -0
  12. data/bin/upgrade +23 -0
  13. data/lib/kube_deploy_tools.rb +17 -0
  14. data/lib/kube_deploy_tools/artifact_registry.rb +30 -0
  15. data/lib/kube_deploy_tools/artifact_registry/driver.rb +13 -0
  16. data/lib/kube_deploy_tools/artifact_registry/driver_artifactory.rb +155 -0
  17. data/lib/kube_deploy_tools/artifact_registry/driver_base.rb +37 -0
  18. data/lib/kube_deploy_tools/artifact_registry/driver_gcs.rb +120 -0
  19. data/lib/kube_deploy_tools/built_artifacts_file.rb +28 -0
  20. data/lib/kube_deploy_tools/concurrency.rb +18 -0
  21. data/lib/kube_deploy_tools/deferred_summary_logging.rb +69 -0
  22. data/lib/kube_deploy_tools/deploy.rb +215 -0
  23. data/lib/kube_deploy_tools/deploy/options.rb +114 -0
  24. data/lib/kube_deploy_tools/deploy_config_file.rb +286 -0
  25. data/lib/kube_deploy_tools/deploy_config_file/deep_merge.rb +38 -0
  26. data/lib/kube_deploy_tools/deploy_config_file/util.rb +39 -0
  27. data/lib/kube_deploy_tools/errors.rb +5 -0
  28. data/lib/kube_deploy_tools/file_filter.rb +43 -0
  29. data/lib/kube_deploy_tools/formatted_logger.rb +59 -0
  30. data/lib/kube_deploy_tools/generate.rb +145 -0
  31. data/lib/kube_deploy_tools/generate/options.rb +66 -0
  32. data/lib/kube_deploy_tools/image_registry.rb +30 -0
  33. data/lib/kube_deploy_tools/image_registry/driver.rb +18 -0
  34. data/lib/kube_deploy_tools/image_registry/driver/aws.rb +121 -0
  35. data/lib/kube_deploy_tools/image_registry/driver/base.rb +50 -0
  36. data/lib/kube_deploy_tools/image_registry/driver/gcp.rb +71 -0
  37. data/lib/kube_deploy_tools/image_registry/driver/login.rb +26 -0
  38. data/lib/kube_deploy_tools/image_registry/driver/noop.rb +15 -0
  39. data/lib/kube_deploy_tools/image_registry/image.rb +17 -0
  40. data/lib/kube_deploy_tools/kdt.rb +52 -0
  41. data/lib/kube_deploy_tools/kubectl.rb +25 -0
  42. data/lib/kube_deploy_tools/kubernetes_resource.rb +57 -0
  43. data/lib/kube_deploy_tools/kubernetes_resource/deployment.rb +56 -0
  44. data/lib/kube_deploy_tools/make_configmap.rb +51 -0
  45. data/lib/kube_deploy_tools/make_configmap/options.rb +39 -0
  46. data/lib/kube_deploy_tools/object.rb +11 -0
  47. data/lib/kube_deploy_tools/publish.rb +40 -0
  48. data/lib/kube_deploy_tools/publish/options.rb +34 -0
  49. data/lib/kube_deploy_tools/push.rb +129 -0
  50. data/lib/kube_deploy_tools/push/options.rb +46 -0
  51. data/lib/kube_deploy_tools/render_deploys_hook.rb +95 -0
  52. data/lib/kube_deploy_tools/shellrunner.rb +46 -0
  53. data/lib/kube_deploy_tools/tag.rb +33 -0
  54. data/lib/kube_deploy_tools/templater.rb +63 -0
  55. data/lib/kube_deploy_tools/templater/options.rb +74 -0
  56. data/lib/kube_deploy_tools/version.rb +3 -0
  57. metadata +191 -0
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+
5
+ require 'kube_deploy_tools/publish'
6
+ require 'kube_deploy_tools/publish/options'
7
+
8
+ require 'kube_deploy_tools/formatted_logger'
9
+ require 'kube_deploy_tools/shellrunner'
10
+
11
+ def options
12
+ $options ||= begin
13
+ parser = KubeDeployTools::Publish::Optparser.new
14
+ parser.parse(ARGV)
15
+ end
16
+ end
17
+
18
+ KubeDeployTools::Logger.logger = KubeDeployTools::FormattedLogger.build
19
+ KubeDeployTools::Shellrunner.shellrunner = KubeDeployTools::Shellrunner.new
20
+
21
+ config = KubeDeployTools::DeployConfigFile.new(options.manifest_file)
22
+ artifact_registry = config.artifact_registries[config.artifact_registry]
23
+
24
+ KubeDeployTools::Publish.new(
25
+ manifest: options.manifest_file,
26
+ artifact_registry: artifact_registry,
27
+ output_dir: options.output_path,
28
+ ).publish
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env ruby
2
+ # Utility for templating Kubernetes manifests with ERB.
3
+ #
4
+ # Example usage:
5
+ #
6
+ # push \
7
+ # --registry aws \
8
+ # --local-prefix local-registry/ \
9
+ # --tag my-custom-tag \
10
+ # image1 image2 image3
11
+ #
12
+ # Will tag local-registry/image1:latest, local-registry/image2:latest etc. as
13
+ # 123456.dkr.ecr.us-west-2.amazonaws.com/image1:my-custom-tag.
14
+ # Default values for every option are probably what you want to use most of the time.
15
+
16
+ require 'optparse'
17
+
18
+ require 'kube_deploy_tools/push'
19
+ require 'kube_deploy_tools/push/options'
20
+
21
+ require 'kube_deploy_tools/formatted_logger'
22
+ require 'kube_deploy_tools/shellrunner'
23
+
24
+ def options
25
+ $options ||= begin
26
+ parser = KubeDeployTools::Push::Optparser.new
27
+ parser.parse(ARGV)
28
+ end
29
+ end
30
+
31
+ def images
32
+ # Make sure options have been parsed
33
+ options
34
+
35
+ # The rest of ARGV is now the list of images to retag
36
+ if ARGV.empty?
37
+ msg = %q(Must supply at least one image
38
+
39
+ Example usage:
40
+
41
+ push \
42
+ --local-prefix local-registry/ \
43
+ --tag my-custom-tag \
44
+ image1 image2 image3
45
+ )
46
+ puts msg
47
+ exit(1)
48
+ end
49
+
50
+ ARGV
51
+ end
52
+
53
+ KubeDeployTools::Logger.logger = KubeDeployTools::FormattedLogger.build
54
+ KubeDeployTools::Shellrunner.shellrunner = KubeDeployTools::Shellrunner.new
55
+ config = KubeDeployTools::DeployConfigFile.new(options.manifest_file)
56
+
57
+ KubeDeployTools::Push.new(config, options.local_prefix, options.registries, images, options.tag).publish
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+ # Default rendering hook. Uses built in `templater` to render out all files
3
+ # underneath kubernetes/ directory, recursively.
4
+
5
+ require 'kube_deploy_tools/formatted_logger'
6
+ require 'kube_deploy_tools/render_deploys_hook'
7
+
8
+ KubeDeployTools::Logger.logger = KubeDeployTools::FormattedLogger.build
9
+ KubeDeployTools::Logger.warn "This subcommand will be disappearing in kdt v3.0."
10
+
11
+ def main(argv)
12
+ config = argv[0]
13
+ input_dir = argv[1]
14
+ output_root = argv[2]
15
+ KubeDeployTools::RenderDeploysHook.render_deploys(config, input_dir, output_root)
16
+ end
17
+
18
+ main(ARGV)
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env ruby
2
+ # Utility for templating Kubernetes manifests with ERB.
3
+ #
4
+ # Example usage:
5
+ #
6
+ # templater \
7
+ # --template kubernetes/ds-datadog.yaml.erb \
8
+ # --set environment=staging \
9
+ # --set tag=$(git rev-parse --short=7 HEAD) \
10
+ # --values staging.yaml \
11
+ # > build/kubernetes/ds-datadog.yaml
12
+ #
13
+ # Values can be passed to templater to expose variables available in
14
+ # the ERB templates, such as a git tag or the environment name.
15
+ # Values can be passed with:
16
+ # * 1 or more key-value pairs e.g. --set git_tag=123456 --set environment=local
17
+ # * a YAML file e.g. --values production.yaml
18
+
19
+ require 'kube_deploy_tools/formatted_logger'
20
+ require 'kube_deploy_tools/templater'
21
+ require 'kube_deploy_tools/templater/options'
22
+
23
+ KubeDeployTools::Logger.logger = KubeDeployTools::FormattedLogger.build
24
+ KubeDeployTools::Logger.warn "This subcommand will be disappearing in kdt v3.0."
25
+
26
+ def options
27
+ parser = KubeDeployTools::Templater::Optparser.new
28
+ ops = parser.parse(ARGV)
29
+ ops.merge_values
30
+ ops
31
+ end
32
+
33
+ templater = KubeDeployTools::Templater.new
34
+ templater.template_to_file(options.template, options.values, options.output)
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+
5
+ require 'kube_deploy_tools/formatted_logger'
6
+ require 'kube_deploy_tools/shellrunner'
7
+ require 'kube_deploy_tools/deploy_config_file'
8
+
9
+ KubeDeployTools::Logger.logger = KubeDeployTools::FormattedLogger.build
10
+ KubeDeployTools::Shellrunner.shellrunner = KubeDeployTools::Shellrunner.new
11
+
12
+ options = {
13
+ :file => 'deploy.yml',
14
+ }
15
+ OptionParser.new do |opts|
16
+ opts.on('-f', '--file file', 'Config file path') do |file|
17
+ options[:file] = file
18
+ end
19
+ end.parse!
20
+
21
+ config_file_path = options[:file] || ARGV[0]
22
+
23
+ KubeDeployTools::DeployConfigFile.new(config_file_path).upgrade!
@@ -0,0 +1,17 @@
1
+
2
+ require 'kube_deploy_tools/formatted_logger'
3
+ require 'kube_deploy_tools/kubectl'
4
+ require 'kube_deploy_tools/shellrunner'
5
+
6
+ require 'kube_deploy_tools/deploy'
7
+ require 'kube_deploy_tools/deploy/options'
8
+
9
+ require 'kube_deploy_tools/templater'
10
+ require 'kube_deploy_tools/templater/options'
11
+
12
+ require 'kube_deploy_tools/generate'
13
+ require 'kube_deploy_tools/generate/options'
14
+
15
+ module KubeDeployTools
16
+ end
17
+
@@ -0,0 +1,30 @@
1
+ require 'kube_deploy_tools/artifact_registry/driver'
2
+ require 'kube_deploy_tools/formatted_logger'
3
+
4
+ module KubeDeployTools
5
+ # Read-only model for artifact_registries[] array element in KDT deploy.yaml
6
+ # configuration file.
7
+ class ArtifactRegistry
8
+ attr_accessor :name, :driver_name, :config, :driver
9
+
10
+ def initialize(h)
11
+ @name = h['name']
12
+ @driver_name = h['driver']
13
+ @config = h['config']
14
+
15
+ if !ArtifactRegistry::Driver::MAPPINGS.key?(@driver_name)
16
+ Logger.warn("Unsupported .artifact_registries.driver: #{@driver_name}")
17
+ else
18
+ @driver = ArtifactRegistry::Driver::MAPPINGS
19
+ .fetch(@driver_name)
20
+ .new(config: @config)
21
+ end
22
+ end
23
+
24
+ def ==(o)
25
+ @name == o.name
26
+ @driver == o.driver
27
+ @config == o.config
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,13 @@
1
+ require_relative 'driver_artifactory'
2
+ require_relative 'driver_gcs'
3
+
4
+ module KubeDeployTools
5
+ class ArtifactRegistry
6
+ module Driver
7
+ MAPPINGS = {
8
+ 'artifactory' => Artifactory,
9
+ 'gcs' => GCS,
10
+ }
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,155 @@
1
+ require_relative 'driver_base'
2
+
3
+ require 'kube_deploy_tools/formatted_logger'
4
+ require 'kube_deploy_tools/shellrunner'
5
+ require 'kube_deploy_tools/object'
6
+
7
+ require 'artifactory'
8
+ require 'fileutils'
9
+ require 'uri'
10
+
11
+
12
+ EXT_TAR_GZ = ".tar.gz"
13
+
14
+ module KubeDeployTools
15
+ class ArtifactRegistry::Driver::Artifactory < ArtifactRegistry::Driver::Base
16
+ # Artifactory configuration is configurable by environment variables
17
+ # by default:
18
+ # export ARTIFACTORY_ENDPOINT=http://my.storage.server/artifactory
19
+ # export ARTIFACTORY_USERNAME=admin
20
+ # export ARTIFACTORY_PASSWORD=password
21
+ # See https://github.com/chef/artifactory-client#create-a-connection.
22
+
23
+ def initialize(config:)
24
+ @config = config
25
+
26
+ Artifactory.endpoint = @config.fetch('endpoint', '')
27
+ @repo = @config.fetch('repo', '')
28
+ end
29
+
30
+
31
+ def get_local_artifact_path(name:, flavor:, local_dir:)
32
+ artifact_name = get_artifact_name(name: name, flavor: flavor)
33
+
34
+ local_artifact_path = File.join(local_dir, artifact_name)
35
+
36
+ local_artifact_path
37
+ end
38
+
39
+ def get_registry_artifact_path(name:, flavor:, project:, build_number:)
40
+ # NOTE(joshk): If the naming format changes, it represents a breaking
41
+ # change where all past clients will not be able to download new builds and
42
+ # new clients will not be able to download old builds. Change with caution.
43
+ "#{project}/#{build_number}/#{get_artifact_name(name: name, flavor: flavor)}"
44
+ end
45
+
46
+ def upload(local_dir:, name:, flavor:, project:, build_number:)
47
+ # Pack up contents of each flavor_dir to a correctly named artifact.
48
+ flavor_dir = File.join(local_dir, "#{name}_#{flavor}")
49
+
50
+ package(
51
+ name: name,
52
+ flavor: flavor,
53
+ input_dir: flavor_dir,
54
+ output_dir: local_dir,
55
+ )
56
+
57
+ local_artifact_path = get_local_artifact_path(
58
+ local_dir: local_dir,
59
+ name: name,
60
+ flavor: flavor,
61
+ )
62
+
63
+ registry_artifact_path = get_registry_artifact_path(
64
+ project: project,
65
+ name: name,
66
+ flavor: flavor,
67
+ build_number: build_number,
68
+ )
69
+ artifactory_url = "#{Artifactory.endpoint}/#{@repo}/#{registry_artifact_path}"
70
+ Logger.info("Uploading #{local_artifact_path} to #{artifactory_url}")
71
+ artifact = Artifactory::Resource::Artifact.new(local_path: local_artifact_path)
72
+ artifact.upload(@repo, registry_artifact_path)
73
+ end
74
+
75
+ def get_artifact_name(name:, flavor:)
76
+ "manifests_#{name}_#{flavor}#{EXT_TAR_GZ}"
77
+ end
78
+
79
+ def package(name:, flavor:, input_dir:, output_dir:)
80
+ local_artifact_path = get_local_artifact_path(name: name, flavor: flavor, local_dir: output_dir)
81
+
82
+ Shellrunner.check_call('tar', '-C', input_dir, '-czf', local_artifact_path, '.')
83
+
84
+ local_artifact_path
85
+ end
86
+
87
+ def download(project:, build_number:, flavor:, name:, pre_apply_hook:, output_dir:)
88
+ registry_artifact_path = get_registry_artifact_path(
89
+ name: name, flavor: flavor, project: project, build_number: build_number)
90
+
91
+ registry_artifact_full_path = [
92
+ Artifactory.endpoint,
93
+ @repo,
94
+ registry_artifact_path,
95
+ ].join('/')
96
+
97
+ local_artifact_path = download_artifact(registry_artifact_full_path, output_dir)
98
+ local_artifact_path = uncompress_artifact(local_artifact_path, output_dir)
99
+
100
+ if pre_apply_hook
101
+ out, err, status = Shellrunner.run_call(pre_apply_hook, local_artifact_path)
102
+ if !status.success?
103
+ raise "Failed to run post download hook #{pre_apply_hook}"
104
+ end
105
+ end
106
+
107
+ local_artifact_path
108
+ end
109
+
110
+ def get_latest_build_number(project)
111
+ project_url = [
112
+ Artifactory.endpoint,
113
+ @repo,
114
+ "#{project}/"
115
+ ].join('/')
116
+ project_builds_html = Shellrunner.run_call('curl', project_url).first
117
+ # store build entries string from html into an array
118
+ build_links_pattern = /(?<=">).+(?=\s{4})/
119
+ build_entries = project_builds_html.scan(build_links_pattern) # example of element: 10/</a> 13-Nov-2017 13:51
120
+ build_number_pattern = /^\d+/
121
+ build_number = build_entries.
122
+ map { |x| x.match(build_number_pattern).to_s.to_i }.
123
+ max.
124
+ to_s
125
+ if build_number.empty?
126
+ raise "Failed to find a valid build number. Project URL: #{project_url}"
127
+ end
128
+ build_number
129
+ end
130
+
131
+ def download_artifact(input_path, output_dir_path)
132
+ uri = URI.parse(input_path)
133
+ filename = File.basename(uri.path)
134
+ output_path = File.join(output_dir_path, filename)
135
+ out, err, status = Shellrunner.run_call('curl', '-o', output_path, input_path, '--silent', '--fail')
136
+ if !status.success?
137
+ raise "Failed to download remote deploy artifact #{uri}"
138
+ end
139
+
140
+ output_path
141
+ end
142
+
143
+ def uncompress_artifact(input_path, output_dir_path)
144
+ dirname = File.basename(input_path).chomp(EXT_TAR_GZ)
145
+ output_path = File.join(output_dir_path, dirname)
146
+ FileUtils.mkdir_p(output_path)
147
+ out, err, status = Shellrunner.run_call('tar', '-xzf', input_path, '-C', output_path)
148
+ if !status.success?
149
+ raise "Failed to uncompress deploy artifact #{input_path}"
150
+ end
151
+
152
+ output_path
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,37 @@
1
+ require 'kube_deploy_tools/formatted_logger'
2
+ require 'kube_deploy_tools/shellrunner'
3
+
4
+ # Abstract Driver class that specific implementations inherit
5
+ module KubeDeployTools
6
+ class ArtifactRegistry
7
+ module Driver
8
+ class Base
9
+ def initialize(config:)
10
+ @config = config
11
+ end
12
+
13
+ # upload should publish the artifact identified by the given name and flavor
14
+ # in the input directory to the corresponding location in the artifact
15
+ # registry. The project and build number should be included in the
16
+ # namespace of the artifact registry path for this artifact.
17
+ def upload(local_dir:, name:, flavor:, project:, build_number:)
18
+ raise "#{self.class}#publish not implemented"
19
+ end
20
+
21
+ # download should retrieve the artifact namespaced with the given
22
+ # project and build number and identified by the name and flavor.
23
+ # The artifact should be put into the output directory.
24
+ # An optional pre-apply hook will process each artifact at the end.
25
+ def download(project:, build_number:, flavor:, name:, pre_apply_hook:, output_dir:)
26
+ raise "#{self.class}#download not implemented"
27
+ end
28
+
29
+ # get_latest_build_number should find the artifact from the most recent
30
+ # build
31
+ def get_latest_build_number(project)
32
+ raise "#{self.class}#get_latest_build_number not implemented"
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,120 @@
1
+ require_relative 'driver_base'
2
+
3
+ require 'kube_deploy_tools/formatted_logger'
4
+ require 'kube_deploy_tools/shellrunner'
5
+ require 'kube_deploy_tools/object'
6
+
7
+ require 'fileutils'
8
+ require 'uri'
9
+ require 'find'
10
+
11
+
12
+ module KubeDeployTools
13
+ class ArtifactRegistry::Driver::GCS < ArtifactRegistry::Driver::Base
14
+ def initialize(config:)
15
+ @config = config
16
+
17
+ @bucket = @config.fetch('bucket')
18
+ prefix = @config.fetch('prefix', '')
19
+ if !prefix.empty?
20
+ @bucket = "#{@bucket}/#{prefix}"
21
+ end
22
+ end
23
+
24
+ def get_local_artifact_path(name:, flavor:, local_dir:)
25
+ artifact_name = get_artifact_name(name: name, flavor: flavor)
26
+
27
+ local_artifact_path = File.join(local_dir, artifact_name)
28
+
29
+ local_artifact_path
30
+ end
31
+
32
+ def get_registry_artifact_path(name:, flavor:, project:, build_number:)
33
+ # NOTE(joshk): If the naming format changes, it represents a breaking
34
+ # change where all past clients will not be able to download new builds and
35
+ # new clients will not be able to download old builds. Change with caution.
36
+ #
37
+ "#{@bucket}/project/#{project}/build/#{build_number}/artifact/#{get_artifact_name(name: name, flavor: flavor)}"
38
+ end
39
+
40
+ def get_artifact_name(name:, flavor:)
41
+ "manifests_#{name}_#{flavor}.yaml"
42
+ end
43
+
44
+ def package(name:, flavor:, input_dir:, output_dir:)
45
+ local_artifact_path = get_local_artifact_path(name: name, flavor: flavor, local_dir: output_dir)
46
+ File.open(local_artifact_path, 'w') do |merged|
47
+ Find.find(input_dir).
48
+ select { |path| path =~ /.*\.yaml$/ }.
49
+ each do |e|
50
+ contents = File.open(e, 'r').read
51
+ contents.each_line do |line|
52
+ merged << line
53
+ end
54
+ end
55
+ end
56
+ local_artifact_path
57
+ end
58
+
59
+ def download(project:, build_number:, flavor:, name:, pre_apply_hook:, output_dir:)
60
+ registry_artifact_path = get_registry_artifact_path(
61
+ name: name, flavor: flavor, project: project, build_number: build_number)
62
+
63
+ local_artifact_path = download_artifact(registry_artifact_path, output_dir)
64
+
65
+ if pre_apply_hook
66
+ out, err, status = Shellrunner.run_call(pre_apply_hook, local_artifact_path)
67
+ if !status.success?
68
+ raise "Failed to run post download hook #{pre_apply_hook}"
69
+ end
70
+ end
71
+
72
+ local_artifact_path
73
+ end
74
+
75
+ def download_artifact(input_path, output_dir_path)
76
+ filename = File.basename(input_path)
77
+ output_path = File.join(output_dir_path, filename)
78
+ out, err, status = Shellrunner.run_call('gsutil', 'cp', input_path, output_path)
79
+
80
+ if !status.success?
81
+ raise "Failed to download remote deploy artifact #{input_path}"
82
+ end
83
+
84
+ output_path
85
+ end
86
+
87
+ def upload(local_dir:, name:, flavor:, project:, build_number:)
88
+ # Pack up contents of each flavor_dir to a correctly named artifact.
89
+ flavor_dir = File.join(local_dir, "#{name}_#{flavor}")
90
+
91
+ package(
92
+ name: name,
93
+ flavor: flavor,
94
+ input_dir: flavor_dir,
95
+ output_dir: local_dir,
96
+ )
97
+
98
+ local_artifact_path = get_local_artifact_path(
99
+ local_dir: local_dir,
100
+ name: name,
101
+ flavor: flavor,
102
+ )
103
+
104
+ registry_artifact_path = get_registry_artifact_path(
105
+ project: project,
106
+ name: name,
107
+ flavor: flavor,
108
+ build_number: build_number,
109
+ )
110
+
111
+ Logger.info("Uploading #{local_artifact_path} to #{registry_artifact_path}")
112
+ out, err, status = Shellrunner.run_call('gsutil', '-m', 'cp', local_artifact_path, registry_artifact_path)
113
+ if !status.success?
114
+ raise "Failed to upload remote deploy artifact from #{local_artifact_path} to #{registry_artifact_path}"
115
+ end
116
+
117
+ registry_artifact_path
118
+ end
119
+ end
120
+ end