Example #1
0
    def get_helm_chart(self):
        '''
        Return a helm chart object
        '''

        if self._helm_chart:
            return self._helm_chart
        # dependencies
        # [process_chart(x, is_dependency=True) for x in chart.dependencies]
        dependencies = []

        for chart in self.chart.dependencies:
            LOG.info("Building dependency chart %s for release %s", chart.name,
                     self.chart.release_name)
            dependencies.append(ChartBuilder(chart).get_helm_chart())

        helm_chart = Chart(
            metadata=self.get_metadata(),
            templates=self.get_templates(),
            dependencies=dependencies,
            values=self.get_values(),
            files=self.get_files(),
        )

        self._helm_chart = helm_chart
        return helm_chart
Example #2
0
    def get_templates(self):
        '''
        Return all the chart templates
        '''

        # process all files in templates/ as a template to attach to the chart
        # building a Template object
        templates = []
        if not os.path.exists(os.path.join(self.source_directory,
                                           'templates')):
            LOG.warn(
                "Chart %s has no templates directory,"
                "no templates will be deployed", self.chart.name)
        for root, _, files in os.walk(os.path.join(self.source_directory,
                                                   'templates'),
                                      topdown=True):
            for tpl_file in files:
                tname = os.path.relpath(
                    os.path.join(root, tpl_file),
                    os.path.join(self.source_directory, 'templates'))

                templates.append(
                    Template(name=tname,
                             data=open(os.path.join(root, tpl_file),
                                       'r').read()))
        return templates
Example #3
0
    def _pre_update_actions(self, actions, namespace):
        '''
        :params actions - array of items actions
        :params namespace - name of pod for actions
        '''
        try:
            for action in actions.get('delete', []):
                name = action.get("name")
                action_type = action.get("type")
                if "job" in action_type:
                    LOG.info("Deleting %s in namespace: %s", name, namespace)
                    self.k8s.delete_job_action(name, namespace)
                    continue
                LOG.error("Unable to execute name: %s type: %s ", name, type)
        except Exception:
            LOG.debug("PRE: Could not delete anything, please check yaml")

        try:
            for action in actions.get('create', []):
                name = action.get("name")
                action_type = action.get("type")
                if "job" in action_type:
                    LOG.info("Creating %s in namespace: %s", name, namespace)
                    self.k8s.create_job_action(name, action_type)
                    continue
        except Exception:
            LOG.debug("PRE: Could not create anything, please check yaml")
Example #4
0
 def _post_update_actions(self, actions, namespace):
     try:
         for action in actions.get('create', []):
             name = action.get("name")
             action_type = action.get("type")
             if "job" in action_type:
                 LOG.info("Creating %s in namespace: %s", name, namespace)
                 self.k8s.create_job_action(name, action_type)
                 continue
     except Exception:
         LOG.debug("POST: Could not create anything, please check yaml")
Example #5
0
 def delete_job_action(self, name, namespace="default"):
     '''
     :params name - name of the job
     :params namespace - name of pod that job
     '''
     try:
         body = client.V1DeleteOptions()
         self.api_client.delete_namespaced_job(name=name,
                                               namespace=namespace,
                                               body=body)
     except ApiException as e:
         LOG.error("Exception when deleting a job: %s", e)
Example #6
0
    def show_diff(self, chart, installed_chart, installed_values, target_chart,
                  target_values):
        '''
        Produce a unified diff of the installed chart vs our intention

        TODO(alanmeadows): This needs to be rewritten to produce better
        unified diff output and avoid the use of print
        '''

        chart_diff = list(
            difflib.unified_diff(
                installed_chart.SerializeToString().split('\n'),
                target_chart.split('\n')))
        if len(chart_diff) > 0:
            LOG.info("Chart Unified Diff (%s)", chart.release_name)
            for line in chart_diff:
                LOG.debug(line)
        values_diff = list(
            difflib.unified_diff(installed_values.split('\n'),
                                 yaml.safe_dump(target_values).split('\n')))
        if len(values_diff) > 0:
            LOG.info("Values Unified Diff (%s)", chart.release_name)
            for line in values_diff:
                LOG.debug(line)

        return (len(chart_diff) > 0) or (len(values_diff) > 0)
Example #7
0
    def get_values(self):
        '''
        Return the chart (default) values
        '''

        # create config object representing unmarshaled values.yaml
        if os.path.exists(os.path.join(self.source_directory, 'values.yaml')):
            raw_values = open(
                os.path.join(self.source_directory, 'values.yaml')).read()
        else:
            LOG.warn("No values.yaml in %s, using empty values",
                     self.source_directory)
            raw_values = ''

        return Config(raw=raw_values)
Example #8
0
    def chart_cleanup(self, prefix, charts):
        '''
        :params charts - list of yaml charts
        :params known_release - list of releases in tiller

        :result - will remove any chart that is not present in yaml
        '''
        def release_prefix(prefix, chart):
            '''
            how to attach prefix to chart
            '''
            return "{}-{}".format(prefix, chart["chart"]["release_name"])

        valid_charts = [release_prefix(prefix, chart) for chart in charts]
        actual_charts = [x.name for x in self.list_releases()]
        chart_diff = list(set(actual_charts) - set(valid_charts))

        for chart in chart_diff:
            if chart.startswith(prefix):
                LOG.debug("Release: %s will be removed", chart)
                self.uninstall_release(chart)
Example #9
0
    def source_clone(self):
        '''
        Clone the charts source

        We only support a git source type right now, which can also
        handle git:// local paths as well
        '''

        if self.chart.source.type == 'git':

            tmpdir = tempfile.mkdtemp(prefix='armada', dir='/tmp')
            self._source_tmp_dir = tmpdir
            if self.parent:
                LOG.info("Cloning %s/%s as dependency for %s",
                         self.chart.source.location, self.chart.source.subpath,
                         self.parent)
            else:
                LOG.info("Cloning %s/%s for release %s",
                         self.chart.source.location, self.chart.source.subpath,
                         self.chart.release_name)

            pygit2.clone_repository(self.chart.source.location, tmpdir)
            return os.path.join(tmpdir, self.chart.source.subpath)

        else:
            LOG.exception("Unknown source type %s for chart %s",
                          self.chart.name, self.chart.source.type)
Example #10
0
    def source_clone(self):
        '''
        Clone the charts source

        We only support a git source type right now, which can also
        handle git:// local paths as well
        '''

        if self.chart.source.type == 'git':
            if self.parent:
                LOG.info("Cloning %s/%s as dependency for %s",
                         self.chart.source.location, self.chart.source.subpath,
                         self.parent)
            else:
                LOG.info("Cloning %s/%s for release %s",
                         self.chart.source.location, self.chart.source.subpath,
                         self.chart.release_name)

            self._source_tmp_dir = git_clone(self.chart.source.location,
                                             self.chart.source.reference)

            return os.path.join(self._source_tmp_dir,
                                self.chart.source.subpath)

        else:
            LOG.exception("Unknown source type %s for chart %s",
                          self.chart.name, self.chart.source.type)
Example #11
0
 def create_job_action(self, name, namespace="default"):
     '''
     :params name - name of the job
     :params namespace - name of pod that job
     '''
     LOG.debug(" %s in namespace: %s", name, namespace)
Example #12
0
    def sync(self):
        '''
        Syncronize Helm with the Armada Config(s)
        '''
        def release_prefix(prefix, chart):
            '''
            how to attach prefix to chart
            '''
            return "{}-{}".format(prefix, chart)

        # extract known charts on tiller right now
        known_releases = self.tiller.list_charts()
        prefix = self.config.get('armada').get('release_prefix')

        for release in known_releases:
            LOG.debug("Release %s, Version %s found on tiller", release[0],
                      release[1])

        for entry in self.config['armada']['charts']:

            chart = dotify(entry['chart'])
            values = entry['chart']['values']
            pre_actions = {}
            post_actions = {}

            if chart.release_name is None:
                continue

            # initialize helm chart and request a
            # protoc helm chart object which will
            # pull the sources down and walk the
            # dependencies
            chartbuilder = ChartBuilder(chart)
            protoc_chart = chartbuilder.get_helm_chart()

            # determine install or upgrade by examining known releases
            LOG.debug("RELEASE: %s", chart.release_name)

            if release_prefix(prefix, chart.release_name) in [
                    x[0] for x in known_releases
            ]:

                # indicate to the end user what path we are taking
                LOG.info("Upgrading release %s", chart.release_name)
                # extract the installed chart and installed values from the
                # latest release so we can compare to the intended state
                installed_chart, installed_values = self.find_release_chart(
                    known_releases, release_prefix(prefix, chart.release_name))

                if not self.disable_update_pre:
                    pre_actions = getattr(chart.upgrade, 'pre', {})

                if not self.disable_update_post:
                    post_actions = getattr(chart.upgrade, 'post', {})

                # show delta for both the chart templates and the chart values
                # TODO(alanmeadows) account for .files differences
                # once we support those

                upgrade_diff = self.show_diff(chart, installed_chart,
                                              installed_values,
                                              chartbuilder.dump(), values)

                if not upgrade_diff:
                    LOG.info("There are no updates found in this chart")
                    continue

                # do actual update
                self.tiller.update_release(
                    protoc_chart,
                    self.dry_run,
                    chart.release_name,
                    chart.namespace,
                    prefix,
                    pre_actions,
                    post_actions,
                    disable_hooks=chart.upgrade.no_hooks,
                    values=yaml.safe_dump(values))

            # process install
            else:
                LOG.info("Installing release %s", chart.release_name)
                self.tiller.install_release(protoc_chart,
                                            self.dry_run,
                                            chart.release_name,
                                            chart.namespace,
                                            prefix,
                                            values=yaml.safe_dump(values))

            LOG.debug("Cleaning up chart source in %s",
                      chartbuilder.source_directory)

            chartbuilder.source_cleanup()

        if self.enable_chart_cleanup:
            self.tiller.chart_cleanup(prefix, self.config['armada']['charts'])
Example #13
0
    def sync(self):
        '''
        Syncronize Helm with the Armada Config(s)
        '''

        # extract known charts on tiller right now
        known_releases = self.tiller.list_charts()
        for release in known_releases:
            LOG.debug("Release %s, Version %s found on tiller", release[0],
                      release[1])

        for entry in self.config['armada']['charts']:

            chart = dotify(entry['chart'])
            values = entry['chart']['values']

            if chart.release_name is None:
                continue

            # initialize helm chart and request a
            # protoc helm chart object which will
            # pull the sources down and walk the
            # dependencies
            chartbuilder = ChartBuilder(chart)
            protoc_chart = chartbuilder.get_helm_chart()

            # determine install or upgrade by examining known releases
            if chart.release_name in [x[0] for x in known_releases]:

                # indicate to the end user what path we are taking
                LOG.info("Upgrading release %s", chart.release_name)

                # extract the installed chart and installed values from the
                # latest release so we can compare to the intended state
                installed_chart, installed_values = self.find_release_chart(
                    known_releases, chart.release_name)

                # show delta for both the chart templates and the chart values
                # TODO(alanmeadows) account for .files differences
                # once we support those
                self.show_diff(chart, installed_chart, installed_values,
                               chartbuilder.dump(), values)

                # do actual update
                self.tiller.update_release(
                    protoc_chart,
                    self.args.dry_run,
                    chart.release_name,
                    disable_hooks=chart.upgrade.no_hooks,
                    values=yaml.safe_dump(values))

            # process install
            else:
                LOG.info("Installing release %s", chart.release_name)
                self.tiller.install_release(protoc_chart,
                                            self.args.dry_run,
                                            chart.release_name,
                                            chart.namespace,
                                            values=yaml.safe_dump(values))

            LOG.debug("Cleaning up chart source in %s",
                      chartbuilder.source_directory)
            chartbuilder.source_cleanup()