-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathbundlectl
182 lines (160 loc) · 6.64 KB
/
bundlectl
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
#!/usr/bin/env python
import argparse
import os
import sys
import pathlib
import yaml
import re
import base64
from getpass import getpass
import subprocess
import bundle
#package_path = pathlib.Path(bundle.__file__).parent.parent
package_path = pathlib.Path(__file__).parent
path_matcher = re.compile(r'\$\{([^}^{]+)\}')
def path_constructor(loader, node):
''' Extract the matched value, expand env variable, and replace the match '''
value = node.value
match = path_matcher.match(value)
env_var = match.group()[2:-1]
return os.environ.get(env_var) + value[match.end():]
yaml.add_implicit_resolver('!path', path_matcher)
yaml.add_constructor('!path', path_constructor)
def base(value):
i = value.find('/')
if i==-1:
return value
return value[:i]
def path(value):
i = value.find('/')
if i==-1:
return None
return value[i+1:]
def abspath(value):
return os.path.abspath(value)
def b64encode(val):
message_bytes = val.encode('ascii')
base64_bytes = base64.b64encode(message_bytes)
base64_message = base64_bytes.decode('ascii')
return base64_message
def registry(image):
registry = image.split("/")[0]
if registry in ["docker.io", "index.docker.io"]:
return "https://index.docker.io/v1/"
return registry
def build(args):
global config
templates_path = os.environ.get("BUNDLE_TEMPLATES")
if templates_path:
templates_path = pathlib.Path(templates_path)
else:
print("BUNDLE_TEMPLATES env variable not set. Looking for templates in {}".format(str(package_path)))
templates_path = package_path / "templates"
if not templates_path.exists():
print("Looking for templates in {}".format(str(package_path.parent)))
templates_path = package_path.parent / "templates"
if not templates_path.exists():
templates_path = "templates"
with open(args.config) as file:
config = yaml.load(file, Loader=yaml.FullLoader)
template_path = templates_path / config["template"]
from shutil import copytree, ignore_patterns
proj_dest = config["name"] + "/project"
config["proj_dest"] = proj_dest
print("Copying project tree")
if "ignore" in config:
copytree(args.source, proj_dest, ignore=ignore_patterns(*config["ignore"], proj_dest),
dirs_exist_ok=True)
else:
copytree(args.source, proj_dest, dirs_exist_ok=True)
templates = template_path.glob('**/*')
templates = [x for x in templates if x.is_file()]
from jinja2 import Template, Environment, FileSystemLoader
file_loader = FileSystemLoader(str(template_path))
env = Environment(loader=file_loader)
env.filters['base'] = base
env.filters['path'] = path
env.filters['abspath'] = abspath
env.filters['b64encode'] = b64encode
env.globals.update(input=input)
env.globals.update(getpass=getpass)
env.globals.update(introspect=bundle.introspect)
env.globals.update(get_module=bundle.get_module)
env.globals.update(exists=os.path.exists)
env.globals.update(registry=registry)
for tf in templates:
tmp = tf.relative_to(template_path)
if str(tmp) == "config.yaml":
with open(str(tf)) as file:
base_config = yaml.load(file, Loader=yaml.FullLoader)
config = bundle.merge(base_config, config)
with open(config["name"] + "/" + tmp, "w") as text_file:
yaml.dump(config, text_file)
local_req = pathlib.Path(proj_dest) / "requirements.txt"
if local_req.exists():
print(local_req)
config["extra_req_files"] = ["project/requirements.txt"]
for tf in templates:
print("Processing template file " + str(tf))
tmp = tf.relative_to(template_path)
template = env.get_template(str(tmp))
res = template.render(**config)
if res:
with open(config["name"] / tmp, "w") as text_file:
text_file.write(res)
global_req = pathlib.Path(config["name"]) / "requirements.txt"
subprocess.run(["pipreqs", proj_dest, "--savepath", str(global_req)])
import tarfile
filename = config["name"] + '/context.tar.gz'
EXCLUDE_FILES = ["context.tar.gz", ".cache"]
def filter_function(tarinfo):
if tarinfo.name in EXCLUDE_FILES:
return None
else:
return tarinfo
if os.path.exists(filename):
os.remove(filename)
with tarfile.open(filename, mode='w:gz') as archive:
print("Archiving")
archive.add(config["name"], recursive=True, arcname="", filter=filter_function)
print("Build finished")
def apply(args):
build(args)
deploy(args)
def deploy(args):
if os.path.isdir(args.config):
args.config = args.config + "/bundle.yaml"
with open(args.config) as file:
config = yaml.load(file, Loader=yaml.FullLoader)
if os.path.exists(config["name"] + "/deploy.sh"):
subprocess.run(["./deploy.sh"], cwd=config["name"])
elif os.path.exists(config["name"] + "/kustomization.yaml"):
subprocess.run(["kubectl", "apply", "-k", "./"], cwd=config["name"])
def delete(args):
if os.path.isdir(args.config):
args.config = args.config + "/bundle.yaml"
with open(args.config) as file:
config = yaml.load(file, Loader=yaml.FullLoader)
if os.path.exists(config["name"] + "/delete.sh"):
subprocess.run(["./delete.sh"], cwd=config["name"])
elif os.path.exists(config["name"] + "/kustomization.yaml"):
subprocess.run(["kubectl", "delete", "-k", "./"], cwd=config["name"])
parser = argparse.ArgumentParser(description='Bundles python project into dockerized kubernetes app')
subparsers = parser.add_subparsers(help='sub-command help',dest='command')
parser_build = subparsers.add_parser('build', help='builds a bundle')
parser_build.add_argument("source", help="project input path", type=str)
parser_build.add_argument("--config", help="config path", type=str)
parser_build.set_defaults(func=build)
parser_apply = subparsers.add_parser('apply', help='builds and deploys bundle')
parser_apply.add_argument("source", help="project input path", type=str)
parser_apply.add_argument("--config", help="config path", type=str)
parser_apply.set_defaults(func=apply)
parser_deploy = subparsers.add_parser('deploy', help='deploys bundle')
parser_deploy.add_argument("config", help="config path", type=str)
parser_deploy.set_defaults(func=deploy)
parser_delete = subparsers.add_parser('delete', help='deletes bundle')
parser_delete.add_argument("config", help="config path", type=str)
parser_delete.set_defaults(func=delete)
args = parser.parse_args()
args.config = args.config if args.config else args.source + "/bundle.yaml"
args.func(args)