Scott Baker | 7581c25 | 2016-05-27 13:12:47 -0700 | [diff] [blame] | 1 | import os |
| 2 | import base64 |
Scott Baker | aab8a29 | 2016-06-03 16:32:45 -0700 | [diff] [blame] | 3 | import jinja2 |
Scott Baker | 7581c25 | 2016-05-27 13:12:47 -0700 | [diff] [blame] | 4 | import string |
| 5 | import sys |
Scott Baker | b0eb23e | 2016-06-01 16:08:04 -0700 | [diff] [blame] | 6 | import urllib2 |
| 7 | import urlparse |
Scott Baker | 7581c25 | 2016-05-27 13:12:47 -0700 | [diff] [blame] | 8 | import xmlrpclib |
| 9 | |
Scott Baker | 7581c25 | 2016-05-27 13:12:47 -0700 | [diff] [blame] | 10 | from xos.config import Config |
Scott Baker | aab8a29 | 2016-06-03 16:32:45 -0700 | [diff] [blame] | 11 | from core.models import Service, ServiceController, ServiceControllerResource, XOS |
Scott Baker | 7581c25 | 2016-05-27 13:12:47 -0700 | [diff] [blame] | 12 | from xos.logger import Logger, logging |
| 13 | |
| 14 | logger = Logger(level=logging.INFO) |
| 15 | |
| 16 | class XOSBuilder(object): |
Scott Baker | 037f679 | 2016-06-02 16:47:45 -0700 | [diff] [blame] | 17 | UI_KINDS=["models", "admin", "django_library", "rest", "tosca_custom_types", "tosca_resource","public_key"] |
Scott Baker | 18c8917 | 2016-06-02 16:40:25 -0700 | [diff] [blame] | 18 | SYNC_CONTROLLER_KINDS=["synchronizer", "private_key", "public_key"] |
Scott Baker | 513ea45 | 2016-06-02 16:03:26 -0700 | [diff] [blame] | 19 | SYNC_ALLCONTROLLER_KINDS=["models", "django_library"] |
Scott Baker | b0eb23e | 2016-06-01 16:08:04 -0700 | [diff] [blame] | 20 | |
Scott Baker | 7581c25 | 2016-05-27 13:12:47 -0700 | [diff] [blame] | 21 | def __init__(self): |
Scott Baker | b0eb23e | 2016-06-01 16:08:04 -0700 | [diff] [blame] | 22 | self.source_ui_image = "xosproject/xos" |
| 23 | self.source_sync_image = "xosproject/xos-synchronizer-openstack" |
| 24 | self.build_dir = "/opt/xos/BUILD/" |
| 25 | |
| 26 | # stuff that has to do with downloading |
| 27 | |
| 28 | def get_dest_dir(self, scr): |
| 29 | xos_base = "opt/xos" |
| 30 | service_name = scr.service_controller.name |
| 31 | base_dirs = {"models": "%s/services/%s/" % (xos_base, service_name), |
| 32 | "admin": "%s/services/%s/" % (xos_base, service_name), |
| 33 | "django_library": "%s/services/%s/" % (xos_base, service_name), |
| 34 | "synchronizer": "%s/synchronizers/%s/" % (xos_base, service_name), |
| 35 | "tosca_custom_types": "%s/tosca/custom_types/" % (xos_base), |
Scott Baker | 18c8917 | 2016-06-02 16:40:25 -0700 | [diff] [blame] | 36 | "tosca_resource": "%s/tosca/resources/" % (xos_base), |
| 37 | "private_key": "%s/services/%s/keys" % (xos_base, service_name), |
| 38 | "public_key": "%s/services/%s/keys/" % (xos_base, service_name)} |
Scott Baker | b0eb23e | 2016-06-01 16:08:04 -0700 | [diff] [blame] | 39 | return base_dirs[scr.kind] |
| 40 | |
| 41 | def get_build_fn(self, scr): |
| 42 | dest_dir = self.get_dest_dir(scr) |
| 43 | dest_fn = os.path.split(urlparse.urlsplit(scr.full_url).path)[-1] |
| 44 | return os.path.join(dest_dir, dest_fn) |
| 45 | |
| 46 | def get_download_fn(self, scr): |
| 47 | dest_fn = self.get_build_fn(scr) |
| 48 | return os.path.join(self.build_dir, dest_fn) |
| 49 | |
| 50 | def read_manifest(self, scr, fn): |
| 51 | manifest = [] |
| 52 | manifest_lines = file(fn).readlines() |
| 53 | manifest_lines = [x.strip() for x in manifest_lines] |
Scott Baker | c2a2fe9 | 2016-06-02 13:19:10 -0700 | [diff] [blame] | 54 | manifest_lines = [x for x in manifest_lines if x] |
Scott Baker | b0eb23e | 2016-06-01 16:08:04 -0700 | [diff] [blame] | 55 | for line in manifest_lines: |
Scott Baker | c2a2fe9 | 2016-06-02 13:19:10 -0700 | [diff] [blame] | 56 | url_parts = urlparse.urlsplit(scr.full_url) |
| 57 | new_path = os.path.join(os.path.join(*os.path.split(url_parts.path)[:-1]),line) |
| 58 | url = urlparse.urlunsplit( (url_parts.scheme, url_parts.netloc, new_path, url_parts.query, url_parts.fragment) ) |
Scott Baker | b0eb23e | 2016-06-01 16:08:04 -0700 | [diff] [blame] | 59 | |
| 60 | build_fn = os.path.join(self.get_dest_dir(scr), line) |
Scott Baker | c2a2fe9 | 2016-06-02 13:19:10 -0700 | [diff] [blame] | 61 | download_fn = os.path.join(self.build_dir, build_fn) |
Scott Baker | b0eb23e | 2016-06-01 16:08:04 -0700 | [diff] [blame] | 62 | |
| 63 | manifest.append( (url, download_fn, build_fn) ) |
| 64 | return manifest |
| 65 | |
| 66 | def download_file(self, url, dest_fn): |
| 67 | logger.info("Download %s to %s" % (url, dest_fn)) |
| 68 | if not os.path.exists(os.path.dirname(dest_fn)): |
| 69 | os.makedirs(os.path.dirname(dest_fn)) |
| 70 | obj = urllib2.urlopen(url) |
| 71 | file(dest_fn,"w").write(obj.read()) |
| 72 | |
Scott Baker | c2a2fe9 | 2016-06-02 13:19:10 -0700 | [diff] [blame] | 73 | # make python files executable |
| 74 | if dest_fn.endswith(".py"): # and contents.startswith("#!"): |
| 75 | os.chmod(dest_fn, 0755) |
| 76 | |
Scott Baker | b0eb23e | 2016-06-01 16:08:04 -0700 | [diff] [blame] | 77 | def download_resource(self, scr): |
| 78 | if scr.format == "manifest": |
| 79 | manifest_fn = self.get_download_fn(scr) |
| 80 | self.download_file(scr.full_url, manifest_fn) |
Scott Baker | c2a2fe9 | 2016-06-02 13:19:10 -0700 | [diff] [blame] | 81 | manifest = self.read_manifest(scr, manifest_fn) |
Scott Baker | b0eb23e | 2016-06-01 16:08:04 -0700 | [diff] [blame] | 82 | for (url, download_fn, build_fn) in manifest: |
| 83 | self.download_file(url, download_fn) |
| 84 | else: |
| 85 | self.download_file(scr.full_url, self.get_download_fn(scr)) |
| 86 | |
| 87 | def get_docker_lines(self, scr): |
| 88 | if scr.format == "manifest": |
| 89 | manifest_fn = self.get_download_fn(scr) |
| 90 | manifest = self.read_manifest(scr, manifest_fn) |
| 91 | lines = [] |
| 92 | for (url, download_fn, build_fn) in manifest: |
| 93 | lines.append("ADD %s /%s" % (build_fn, build_fn)) |
| 94 | return lines |
| 95 | else: |
| 96 | build_fn = self.get_build_fn(scr) |
| 97 | return ["ADD %s /%s" % (build_fn, build_fn)] |
| 98 | |
| 99 | def get_controller_docker_lines(self, controller, kinds): |
| 100 | dockerfile=[] |
| 101 | for scr in controller.service_controller_resources.all(): |
| 102 | if scr.kind in kinds: |
| 103 | lines = self.get_docker_lines(scr) |
| 104 | dockerfile = dockerfile + lines |
| 105 | return dockerfile |
| 106 | |
| 107 | # stuff that has to do with building |
| 108 | |
Scott Baker | 513ea45 | 2016-06-02 16:03:26 -0700 | [diff] [blame] | 109 | def create_xos_app_data(self, name, dockerfile, app_list, migration_list): |
Scott Baker | ae196c3 | 2016-06-01 23:29:22 -0700 | [diff] [blame] | 110 | if not os.path.exists(os.path.join(self.build_dir,"opt/xos/xos")): |
| 111 | os.makedirs(os.path.join(self.build_dir,"opt/xos/xos")) |
| 112 | |
| 113 | if app_list: |
Scott Baker | 513ea45 | 2016-06-02 16:03:26 -0700 | [diff] [blame] | 114 | dockerfile.append("COPY opt/xos/xos/%s_xosbuilder_app_list /opt/xos/xos/xosbuilder_app_list" % name) |
| 115 | file(os.path.join(self.build_dir, "opt/xos/xos/%s_xosbuilder_app_list") % name, "w").write("\n".join(app_list)+"\n") |
Scott Baker | ae196c3 | 2016-06-01 23:29:22 -0700 | [diff] [blame] | 116 | |
| 117 | if migration_list: |
Scott Baker | 513ea45 | 2016-06-02 16:03:26 -0700 | [diff] [blame] | 118 | dockerfile.append("COPY opt/xos/xos/%s_xosbuilder_migration_list /opt/xos/xos/xosbuilder_migration_list" % name) |
| 119 | file(os.path.join(self.build_dir, "opt/xos/xos/%s_xosbuilder_migration_list") % name, "w").write("\n".join(migration_list)+"\n") |
Scott Baker | ae196c3 | 2016-06-01 23:29:22 -0700 | [diff] [blame] | 120 | |
Scott Baker | b0eb23e | 2016-06-01 16:08:04 -0700 | [diff] [blame] | 121 | def create_ui_dockerfile(self): |
| 122 | dockerfile_fn = "Dockerfile.UI" |
| 123 | |
Scott Baker | ae196c3 | 2016-06-01 23:29:22 -0700 | [diff] [blame] | 124 | app_list = [] |
| 125 | migration_list = [] |
| 126 | |
Scott Baker | b0eb23e | 2016-06-01 16:08:04 -0700 | [diff] [blame] | 127 | dockerfile = ["FROM %s" % self.source_ui_image] |
| 128 | for controller in ServiceController.objects.all(): |
| 129 | dockerfile = dockerfile + self.get_controller_docker_lines(controller, self.UI_KINDS) |
Scott Baker | ae196c3 | 2016-06-01 23:29:22 -0700 | [diff] [blame] | 130 | if controller.service_controller_resources.filter(kind="models").exists(): |
| 131 | app_list.append("services." + controller.name) |
| 132 | migration_list.append(controller.name) |
| 133 | |
Scott Baker | 513ea45 | 2016-06-02 16:03:26 -0700 | [diff] [blame] | 134 | self.create_xos_app_data("ui", dockerfile, app_list, migration_list) |
Scott Baker | b0eb23e | 2016-06-01 16:08:04 -0700 | [diff] [blame] | 135 | |
| 136 | file(os.path.join(self.build_dir, dockerfile_fn), "w").write("\n".join(dockerfile)+"\n") |
| 137 | |
Scott Baker | 8d252b6 | 2016-06-01 23:08:04 -0700 | [diff] [blame] | 138 | return {"dockerfile_fn": dockerfile_fn, |
| 139 | "docker_image_name": "xosproject/xos-ui"} |
Scott Baker | b0eb23e | 2016-06-01 16:08:04 -0700 | [diff] [blame] | 140 | |
| 141 | def create_synchronizer_dockerfile(self, controller): |
Scott Baker | 513ea45 | 2016-06-02 16:03:26 -0700 | [diff] [blame] | 142 | # bake in the synchronizer from this controller |
| 143 | sync_lines = self.get_controller_docker_lines(controller, self.SYNC_CONTROLLER_KINDS) |
| 144 | if not sync_lines: |
Scott Baker | b0eb23e | 2016-06-01 16:08:04 -0700 | [diff] [blame] | 145 | return [] |
| 146 | |
| 147 | dockerfile_fn = "Dockerfile.%s" % controller.name |
| 148 | dockerfile = ["FROM %s" % self.source_sync_image] |
Scott Baker | 513ea45 | 2016-06-02 16:03:26 -0700 | [diff] [blame] | 149 | |
| 150 | # Now bake in models from this controller as well as the others |
| 151 | # It's important to bake all services in, because some services' |
| 152 | # synchronizers may depend on models from another service. |
| 153 | app_list = [] |
| 154 | for c in ServiceController.objects.all(): |
| 155 | dockerfile = dockerfile + self.get_controller_docker_lines(c, self.SYNC_ALLCONTROLLER_KINDS) |
| 156 | if controller.service_controller_resources.filter(kind="models").exists(): |
| 157 | app_list.append("services." + controller.name) |
| 158 | |
| 159 | self.create_xos_app_data(controller.name, dockerfile, app_list, None) |
| 160 | |
| 161 | dockerfile = dockerfile + sync_lines |
Scott Baker | c2a2fe9 | 2016-06-02 13:19:10 -0700 | [diff] [blame] | 162 | file(os.path.join(self.build_dir, dockerfile_fn), "w").write("\n".join(dockerfile)+"\n") |
Scott Baker | b0eb23e | 2016-06-01 16:08:04 -0700 | [diff] [blame] | 163 | |
Scott Baker | 8d252b6 | 2016-06-01 23:08:04 -0700 | [diff] [blame] | 164 | return {"dockerfile_fn": dockerfile_fn, |
| 165 | "docker_image_name": "xosproject/xos-synchronizer-%s" % controller.name} |
Scott Baker | b0eb23e | 2016-06-01 16:08:04 -0700 | [diff] [blame] | 166 | |
Scott Baker | aab8a29 | 2016-06-03 16:32:45 -0700 | [diff] [blame] | 167 | def create_docker_compose(self): |
| 168 | xos = XOS.objects.all()[0] |
| 169 | |
| 170 | volume_list = [] # FINISH ME |
| 171 | |
| 172 | containers = {} |
| 173 | |
| 174 | containers["xos_db"] = \ |
| 175 | {"image": "xosproject/xos-postgres", |
| 176 | "expose": [5432]} |
| 177 | |
| 178 | containers["xos_ui"] = \ |
| 179 | {"image": "xosproject/xos-ui", |
| 180 | "command": "python /opt/xos/manage.py runserver 0.0.0.0:%d --insecure --makemigrations" % xos.ui_port, |
| 181 | "ports": {"%d"%xos.ui_port : "%d"%xos.ui_port}, |
| 182 | "links": ["xos_db"], |
| 183 | "volumes": volume_list} |
| 184 | |
Scott Baker | be41a12 | 2016-06-06 10:40:40 -0700 | [diff] [blame^] | 185 | containers["xos_bootstrap_ui"] = {"image": "xosproject/xos-ui", |
| 186 | "command": "python /opt/xos/manage.py runserver 0.0.0.0:%d --insecure --makemigrations" % xos.bootstrap_ui_port, |
| 187 | "ports": {"%d"%xos.bootstrap_ui_port : "%d"%xos.bootstrap_ui_port}, |
| 188 | "links": ["xos_db"], |
| 189 | "volumes": volume_list} |
| 190 | |
Scott Baker | aab8a29 | 2016-06-03 16:32:45 -0700 | [diff] [blame] | 191 | for c in ServiceController.objects.all(): |
| 192 | containers["xos_synchronizer_%s" % c.name] = \ |
| 193 | {"image": "xosproject/xos-synchronizer-%s" % controller.name, |
| 194 | "command": 'bash -c "sleep 120; bash /opt/xos/synchronizers/%s/run.sh"', |
| 195 | "links": ["xos_db"], |
| 196 | "volumes": volume_list} |
| 197 | |
| 198 | vars = { "containers": containers } |
| 199 | |
| 200 | template_loader = jinja2.FileSystemLoader( "/opt/xos/synchronizers/onboarding/templates/" ) |
| 201 | template_env = jinja2.Environment(loader=template_loader) |
| 202 | template = template_env.get_template("docker-compose.yml.j2") |
| 203 | buffer = template.render(vars) |
| 204 | |
| 205 | if not os.path.exists("/opt/xos/synchronizers/onboarding/docker-compose"): |
| 206 | os.makedirs("/opt/xos/synchronizers/onboarding/docker-compose") |
| 207 | file("/opt/xos/synchronizers/onboarding/docker-compose/docker-compose.yml", "w").write(buffer) |
| 208 | |
Scott Baker | 8d252b6 | 2016-06-01 23:08:04 -0700 | [diff] [blame] | 209 | # def build_xos(self): |
| 210 | # dockerfiles=[] |
| 211 | # dockerfiles.append(self.create_ui_dockerfile()) |
| 212 | # |
| 213 | # for controller in ServiceController.objects.all(): |
| 214 | # dockerfiles.append(self.create_synchronizer_dockerfile(controller)) |
Scott Baker | b0eb23e | 2016-06-01 16:08:04 -0700 | [diff] [blame] | 215 | |
| 216 | |
| 217 | |