-
Notifications
You must be signed in to change notification settings - Fork 55
/
Copy pathdeploy
executable file
·271 lines (220 loc) · 9.76 KB
/
deploy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
#!/usr/bin/python3
'''
Convenient wrapper around `oc process/create/replace`. Can be run multiple
times; subsequent runs will replace existing resources.
Example usage:
./deploy --update \
--pipeline https://github.com/jlebon/fedora-coreos-pipeline \
--config https://github.com/jlebon/fedora-coreos-config@wip \
--cosa-img quay.io/jlebon/coreos-assembler:main
# OR
--cosa https://github.com/jlebon/coreos-assembler
Deleting developer pipeline:
./deploy --delete-devel
'''
import os
import sys
import json
import yaml
import argparse
import subprocess
# the code supports multiple prod pipelines for easing migrations
PROD_CLUSTERS = {
"https://api.ocp.ci.centos.org:6443": "fedora-coreos",
"https://api.ocp.fedoraproject.org:6443": "fedora-coreos-pipeline"
}
def main():
args = parse_args()
if targeting_official_namespace(args) and not args.official:
eprint("Refusing to create developer resource in official namespace.")
eprint("Use --official to create official resources.")
return 1
resources = process_template(args)
if args.update:
update_resources(args, resources)
# hack the name; should extract from manifest
pipeline_name = "fedora-coreos-pipeline"
if not args.official:
pipeline_name = f"{args.prefix}{pipeline_name}"
if args.start:
print("Starting:")
out = subprocess.run([args.oc_cmd, 'start-build', pipeline_name],
check=True, stdout=subprocess.PIPE,
encoding='utf-8')
print(f" {out.stdout.strip()}")
print()
else:
print("You may start your developer pipeline with:")
print(f" oc start-build {pipeline_name}")
print()
else:
assert args.delete_devel
delete_developer_resources(args, resources)
def targeting_official_namespace(args):
ctx_name = subprocess.check_output([args.oc_cmd, 'config', 'current-context'],
encoding='utf-8').strip()
cfg = yaml.safe_load(subprocess.check_output([args.oc_cmd, 'config', 'view'],
encoding='utf-8'))
ctx = [c['context'] for c in cfg['contexts'] if c['name'] == ctx_name]
assert len(ctx) == 1, f"Found {len(ctx)} contexts named '{ctx_name}'"
ctx = ctx[0]
cluster_name = ctx['cluster']
namespace = ctx['namespace']
url = [c['cluster']['server'] for c in cfg['clusters']
if c['name'] == cluster_name]
assert len(url) == 1, f"Found {len(url)} clusters named '{cluster_name}'"
url = url[0]
if url not in PROD_CLUSTERS:
return False
return namespace == PROD_CLUSTERS[url]
def parse_args():
parser = argparse.ArgumentParser()
action = parser.add_mutually_exclusive_group(required=True)
action.add_argument("--update", action='store_true',
help="Create or update resources")
action.add_argument("--delete-devel", action='store_true',
help="Delete developer resources")
parser.add_argument("--official", action='store_true',
help="Whether to update official resources "
"(implies --all)")
parser.add_argument("--all", action='store_true',
help="Whether to update all resources")
parser.add_argument("--dry-run", action='store_true',
help="Only print what would happen")
parser.add_argument("--prefix", help="Developer prefix for resources",
default=get_username())
parser.add_argument("--start", action='store_true',
help="Start pipeline after updating it")
parser.add_argument("--pipeline", metavar='<URL>[@REF]',
help="Repo and ref to use for pipeline code")
parser.add_argument("--config", metavar='<URL>[@REF]',
help="Repo and ref to use for FCOS config")
parser.add_argument("--bucket", metavar='BUCKET',
help="AWS S3 bucket to use")
parser.add_argument("--gcp-gs-bucket", metavar='GCP_GS_BUCKET',
help="GCP GS bucket to use for image uploads during import")
parser.add_argument("--cosa-img", metavar='FQIN',
help="Pullspec to use for COSA image")
parser.add_argument("--pvc-size", metavar='SIZE',
help="Size of PVC to create for cache")
parser.add_argument("--oc-cmd", default='oc',
help="The path to the oc binary")
# XXX: to add as a mutually exclusive option with above
# parser.add_argument("--cosa", metavar='<URL>[@REF]',
# help="Repo and ref to use for COSA image",
# default=DEFAULT_COSA_IMG)
args = parser.parse_args()
if args.official:
args.all = True
# just sanity check we have a prefix
assert len(args.prefix)
assert not args.prefix.endswith('-'), "Prefix must not end with dash"
# e.g. jlebon --> jlebon-fedora-coreos-pipeline
args.prefix += '-'
return args
def get_username():
import pwd
return pwd.getpwuid(os.getuid()).pw_name
def process_template(args):
params = {}
if not args.official:
params['DEVELOPER_PREFIX'] = args.prefix
if args.pipeline:
params.update(params_from_git_refspec(args.pipeline, 'JENKINS_S2I'))
params.update(params_from_git_refspec(args.pipeline, 'JENKINS_JOBS'))
if args.config:
params.update(params_from_git_refspec(args.config, 'FCOS_CONFIG'))
if args.bucket:
params['S3_BUCKET'] = args.bucket
if args.cosa_img:
params['COREOS_ASSEMBLER_IMAGE'] = args.cosa_img
if args.pvc_size:
params['PVC_SIZE'] = args.pvc_size
if args.gcp_gs_bucket:
params['GCP_GS_BUCKET'] = args.gcp_gs_bucket
print("Parameters:")
for k, v in params.items():
print(f" {k}={v}")
print()
def gen_param_args(selected):
selected_params = {(k, v) for k, v in params.items() if k in selected}
return [q for k, v in selected_params for q in ['--param', f'{k}={v}']]
resources = []
for template in ['pipeline.yaml', 'jenkins-s2i.yaml']:
# we only want to pass the params which each template actually
# supports, so filter it down for this specific template
with open(f'manifests/{template}') as f:
t = yaml.safe_load(f)
tparams = [p['name'] for p in t.get('parameters', [])]
# and generate the --param FOO=bar ... args for this template
param_args = gen_param_args(tparams)
j = json.loads(subprocess.check_output(
[args.oc_cmd, 'process', '--filename', f'manifests/{template}'] + param_args))
resources += j['items']
return resources
def update_resources(args, resources):
print("Updating:")
for resource in resources:
if args.all or is_default_resource(resource):
action = resource_action(args, resource)
if action == 'skip':
continue
if args.dry_run:
print(f"Would {action} {resource['kind'].lower()} {resource['metadata']['name']}")
continue
out = subprocess.run([args.oc_cmd, action, '--filename', '-'],
input=json.dumps(resource), encoding='utf-8',
check=True, stdout=subprocess.PIPE)
print(f" {out.stdout.strip()}")
print()
def is_default_resource(resource):
annos = resource['metadata'].get('annotations', {})
return annos.get('coreos.com/deploy-default') == "true"
def resource_action(args, resource):
if resource_exists(args, resource):
# Some resources don't support being updated post-creation; let's just
# skip those for now if they already exist.
kind = resource['kind'].lower()
if kind in ['persistentvolumeclaim']:
print(f" {kind} \"{resource['metadata']['name']}\" skipped")
return 'skip'
return 'replace'
return 'create'
def resource_exists(args, resource):
return subprocess.call([args.oc_cmd, 'get', resource['kind'],
resource['metadata']['name']],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL) == 0
def delete_developer_resources(args, resources):
# only delete prefixed resources
print("Deleting:")
for resource in resources:
if resource['metadata']['name'].startswith(args.prefix):
out = subprocess.run([args.oc_cmd, 'delete', '--filename', '-'],
input=json.dumps(resource), encoding='utf-8',
check=True, stdout=subprocess.PIPE)
print(f" {out.stdout.strip()}")
print()
def params_from_git_refspec(refspec, param_prefix):
url, ref = parse_git_refspec(refspec)
return {f'{param_prefix}_URL': url,
f'{param_prefix}_REF': ref}
def parse_git_refspec(refspec):
if '@' not in refspec:
return (refspec, get_default_branch(refspec))
return tuple(refspec.split('@'))
def get_default_branch(repo):
output = subprocess.check_output(['git', 'ls-remote', '--symref',
repo, 'HEAD'],
encoding='utf-8')
for line in output.splitlines():
if line.startswith('ref: '):
ref, symref = line[len('ref: '):].split()
if symref != "HEAD":
continue
assert ref.startswith("refs/heads/")
return ref[len("refs/heads/"):]
def eprint(*args):
print(*args, file=sys.stderr)
if __name__ == "__main__":
sys.exit(main())