Skip to content
This repository was archived by the owner on Jun 29, 2023. It is now read-only.

Commit f54fa9d

Browse files
Create artifact registry driver interface. (#44)
- Create artifact registry driver interface, such that a new driver needs to implement methods like `generate`, `publish`, and `download` - Add artifact_registries & artifact_registry configuration to deploy.yaml - Add GCS driver This is a breaking change to deploy.yaml, so we upgrade KDT and deploy.yaml to v3. This change includes some cleanups we have for v3 in general including: - Remove unused deploy.yaml from KDT - Remove old KDT v1 aliases
1 parent 96d4d4c commit f54fa9d

25 files changed

+651
-502
lines changed

bin/deploy

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33

44
require 'tmpdir'
55

6-
require 'kube_deploy_tools/deploy_artifact'
76
require 'kube_deploy_tools/formatted_logger'
87
require 'kube_deploy_tools/kubectl'
98
require 'kube_deploy_tools/shellrunner'
109
require 'kube_deploy_tools/deploy'
1110
require 'kube_deploy_tools/deploy/options'
11+
require 'kube_deploy_tools/deploy_config_file'
12+
require 'kube_deploy_tools/artifact_registry'
1213

1314
def options
1415
@options ||= KubeDeployTools::Deploy::Optparser.new.parse(ARGV)
@@ -21,25 +22,28 @@ namespace = KubeDeployTools::Deploy::kube_namespace(context: context, kubeconfig
2122
KubeDeployTools::Logger.logger = KubeDeployTools::FormattedLogger.build(context: context, namespace: namespace)
2223
kubectl = KubeDeployTools::Kubectl.new(
2324
context: options.context,
24-
kubeconfig: options.kubeconfig,
25+
kubeconfig: options.kubeconfig
2526
)
2627

27-
Dir.mktmpdir { |tmpdir|
28+
config = KubeDeployTools::DeployConfigFile.new('deploy.yaml')
29+
artifact_registry = config.artifact_registries[config.artifact_registry].driver
30+
31+
Dir.mktmpdir do |tmpdir|
2832
if options.from_files
29-
deploy_artifact_path = KubeDeployTools::DeployArtifact.new(
30-
input_path: options.from_files,
31-
).path
33+
deploy_artifact_path = options.from_files
3234
else
33-
deploy_artifact_path = KubeDeployTools::DeployArtifact.new(
34-
input_path: KubeDeployTools.get_remote_deploy_artifact_url(
35-
project: options.project,
36-
build_number: options.build_number,
37-
flavor: options.flavor,
38-
name: options.artifact,
39-
),
40-
output_dir_path: tmpdir,
41-
pre_apply_hook: options.pre_apply_hook
42-
).path
35+
if options.build_number == 'latest'
36+
build_number = artifact_registry.get_latest_build_number(options.project)
37+
end
38+
39+
deploy_artifact_path = artifact_registry.download(
40+
project: options.project,
41+
build_number: build_number,
42+
flavor: options.flavor,
43+
name: options.artifact,
44+
pre_apply_hook: options.pre_apply_hook,
45+
output_dir: tmpdir
46+
)
4347
end
4448

4549
deploy = KubeDeployTools::Deploy.new(
@@ -54,4 +58,4 @@ Dir.mktmpdir { |tmpdir|
5458
dry_run: options.dry_run,
5559
send_report: options.send_report
5660
)
57-
}
61+
end

bin/publish

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@ end
1818
KubeDeployTools::Logger.logger = KubeDeployTools::FormattedLogger.build
1919
KubeDeployTools::Shellrunner.shellrunner = KubeDeployTools::Shellrunner.new
2020

21+
config = KubeDeployTools::DeployConfigFile.new(options.manifest_file)
22+
artifact_registry = config.artifact_registries[config.artifact_registry]
23+
2124
KubeDeployTools::Publish.new(
2225
manifest: options.manifest_file,
26+
artifact_registry: artifact_registry,
2327
output_dir: options.output_path,
2428
).publish

deploy.yaml

Lines changed: 0 additions & 10 deletions
This file was deleted.

lib/kube_deploy_tools.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11

2-
require 'kube_deploy_tools/deploy_artifact'
32
require 'kube_deploy_tools/formatted_logger'
43
require 'kube_deploy_tools/kubectl'
54
require 'kube_deploy_tools/shellrunner'
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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+
@prefix == o.prefix
28+
@config == o.config
29+
end
30+
end
31+
end
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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 commit comments

Comments
 (0)