def _push(self): last_push_duration = files.fetch_action_arg('push', 'last_push_duration') self.container_name = files.fetch_action_arg('build', 'last_container') self.started_push_time = time.time() # TODO: unify these commands by factoring out docker command # based on config if 'gceProject' in self.config: self._push_gke() else: self._push_docker() progress_bar.duration_progress( 'Pushing ', last_push_duration, lambda: self.push_process.poll() is not None) if self.push_process.poll() != 0: push_error = self.push_process.communicate() print(colored(push_error[0], 'red')) print(colored(push_error[1], 'red')) sys.exit(1) with open('.push.json', 'w') as f: f.write( json.dumps({ "last_remote_container": self.remote_container_name, "last_push_duration": time.time() - self.started_push_time })) print("Pushed to {}".format(self.remote_container_name))
def _build(self): last_build_duration = files.fetch_action_arg('build', 'last_build_duration') started_build_time = time.time() container_name = "{}:{}".format(self.config['name'], uuid.uuid4()) print("Starting build {}".format(container_name)) # Add bar build_process = process_helpers.run_popen( "CONTAINER_NAME={} make build".format(container_name), shell=True) progress_bar.duration_progress( 'Building', last_build_duration, lambda: build_process.poll() is not None) if build_process.poll() != 0: print( colored(build_process.communicate()[0].decode("utf-8"), 'red')) sys.exit(1) built_time = time.time() # Write last container to file with open('.build.json', 'w') as f: f.write( json.dumps({ "last_container": container_name, "last_build_duration": built_time - started_build_time })) print("Built {}".format(container_name))
def _deploy_new_container(self): """Substitutes image, app, run data into k8s-template selected. Can also launch user into interactive shell with --interactive flag """ app_name = self.config['name'] self.namespace = self.config['namespace'] remote_container_name = files.fetch_action_arg( 'push', 'last_remote_container') if remote_container_name is None: raise ValueError("No image found to deploy with. Run a plain " "`mlt deploy` to fix this. Most common reason " "for this is a --no-push was used before " "any image was available to use.") print("Deploying {}".format(remote_container_name)) kubernetes_helpers.ensure_namespace_exists(self.namespace) app_run_id = str(uuid.uuid4()) """ we'll keep track of the number of containers that would be deployed so we know if we should exec into 1 or not (only auto-exec if 1 made) if we have replicas (with value > 1) then we automatically won't go into most recent pod, because there will be > 1 container made if we find > 1 container regardless of replica, same logic applies """ self._replicas_found = False self._total_containers = 0 # deploy our normal template sub logic, then if `deploy` in Makefile # add whatever custom stuff is desired self._default_deploy(app_name=app_name, app_run_id=app_run_id, remote_container_name=remote_container_name) if files.is_custom("deploy:"): # execute the custom deploy code self._custom_deploy(app_name=app_name, app_run_id=app_run_id, remote_container_name=remote_container_name) self._update_app_run_id(app_run_id) print("\nInspect created objects by running:\n" "$ kubectl get --namespace={} all\n" "or \n$ mlt status\n".format(self.namespace)) if self.args["--interactive"] and not self._replicas_found \ and self._total_containers == 1: self._exec_into_pod(self._get_most_recent_podname()) elif self.args["--interactive"]: print("More than one container created." ".\nCall `kubectl exec -it {{pod_name_here}} " "--namespace={} /bin/bash` on a `Running` pod NAME " "below.\nIf no pods are running yet, run `mlt status` " "occasionally, or `watch -n1 mlt status` to watch until " "pods are `Running`.\n".format(self.namespace)) for line in self._get_pods_by_start_time(): print(line)
def test_fetch_action_arg_file_present(json_load_mock, isfile_mock, open_mock): isfile_mock.return_value = True action_data = {'somekey': 'someval'} json_load_mock.return_value.get.return_value = action_data result = fetch_action_arg('push', 'last_push_container') open_mock.assert_called_once() json_load_mock.assert_called_once() assert result == action_data
def _build(self): last_build_duration = files.fetch_action_arg( 'build', 'last_build_duration') schema.validate() started_build_time = time.time() container_name = "{}:{}".format(self.config['name'], uuid.uuid4()) print("Starting build {}".format(container_name)) template_parameters = config_helpers.\ get_template_parameters(self.config) params = "" for key, val in template_parameters.items(): params += "{}={} ".format(key.upper(), val) build_cmd = "CONTAINER_NAME={} {}make build".format( container_name, params) if self.args['--verbose']: build_process = process_helpers.run_popen(build_cmd, shell=True, stdout=True, stderr=True) else: build_process = process_helpers.run_popen(build_cmd, shell=True) with process_helpers.prevent_deadlock(build_process): progress_bar.duration_progress( 'Building {}'.format( self.config["name"]), last_build_duration, lambda: build_process.poll() is not None) if build_process.poll() != 0: # When we have an error, get the stdout and error output # and display them both with the error output in red. output, error_msg = build_process.communicate() if output: print(output.decode("utf-8")) if error_msg: error_handling.throw_error(error_msg.decode("utf-8"), 'red') built_time = time.time() # Write last container to file with open('.build.json', 'w') as f: f.write(json.dumps({ "last_container": container_name, "last_build_duration": built_time - started_build_time })) print("Built {}".format(container_name))
def _deploy_new_container(self): """Substitutes image, app, run data into k8s-template selected. Can also launch user into interactive shell with --interactive flag """ app_name = self.config['name'] self.namespace = self.config['namespace'] remote_container_name = files.fetch_action_arg( 'push', 'last_remote_container') if remote_container_name is None: raise ValueError("No image found to deploy with. Run a plain " "`mlt deploy` to fix this. Most common reason " "for this is a --no-push was used before " "any image was available to use.") print("Deploying {}".format(remote_container_name)) kubernetes_helpers.ensure_namespace_exists(self.namespace) # do template substitution across everything in `k8s-templates` dir # replaces things with $ with the vars from template.substitute # also patches deployment if interactive mode is set self.interactive_deployment_found = False app_run_id = str(uuid.uuid4()) for path, dirs, filenames in os.walk("k8s-templates"): self.file_count = len(filenames) for filename in filenames: with open(os.path.join(path, filename)) as f: template = Template(f.read()) out = template.substitute( image=remote_container_name, app=app_name, run=app_run_id, **config_helpers.get_template_parameters(self.config)) interactive, out = self._check_for_interactive_deployment( out, filename) self._apply_template(out, filename) if interactive: interactive_podname = self._get_most_recent_podname() print("\nInspect created objects by running:\n" "$ kubectl get --namespace={} all\n".format(self.namespace)) self._update_app_run_id(app_run_id) # After everything is deployed we'll make a kubectl exec # call into our debug container if interactive mode if self.args["--interactive"] and self.interactive_deployment_found: self._exec_into_pod(interactive_podname) elif not self.interactive_deployment_found and \ self.args['--interactive']: raise ValueError("Unable to find deployment to run interactively. " "Multiple deployment files found and bad " "<kube_spec> argument passed.")
def _poll_docker_proc(self): """used only in the case of non-verbose deploy mode to dump loading bar and any error that happened """ last_push_duration = files.fetch_action_arg( 'push', 'last_push_duration') with process_helpers.prevent_deadlock(self.push_process): progress_bar.duration_progress( 'Pushing {}'.format(self.config["name"]), last_push_duration, lambda: self.push_process.poll() is not None) # If the push fails, get stdout/stderr messages and display them # to the user, with the error message in red. if self.push_process.poll() != 0: push_stdout, push_error = self.push_process.communicate() print(push_stdout.decode("utf-8")) error_handling.throw_error(push_error.decode("utf-8"), 'red')
def _push(self): self.container_name = files.fetch_action_arg( 'build', 'last_container') self.started_push_time = time.time() self._push_docker() if not self.args['--verbose']: self._poll_docker_proc() with open('.push.json', 'w') as f: f.write(json.dumps({ "last_remote_container": self.remote_container_name, "last_push_duration": time.time() - self.started_push_time })) print("Pushed {} to {}".format( self.config["name"], self.remote_container_name))
def test_fetch_action_arg_file_nonexistent(open_mock): fetch_action_arg('build', 'last_build_container') open_mock.assert_not_called()