Example #1
0
def generate_delaying_proxy_ingress(concourse_cfg: ConcourseConfig):
    ensure_not_none(concourse_cfg)

    proxy_url = concourse_cfg.proxy_url()
    host = urlparse(proxy_url).netloc
    tls_secret_name = concourse_cfg.tls_secret_name()

    return V1beta1Ingress(
        kind='Ingress',
        metadata=V1ObjectMeta(
            name='delaying-proxy',
            annotations={'kubernetes.io/ingress.class': 'nginx'},
        ),
        spec=V1beta1IngressSpec(
            rules=[
                V1beta1IngressRule(
                    host=host,
                    http=V1beta1HTTPIngressRuleValue(paths=[
                        V1beta1HTTPIngressPath(backend=V1beta1IngressBackend(
                            service_name='delaying-proxy-svc',
                            service_port=80,
                        ), ),
                    ], ),
                ),
            ],
            tls=[
                V1beta1IngressTLS(hosts=[host], secret_name=tls_secret_name),
            ],
        ),
    )
Example #2
0
def create_image_pull_secret(
    credentials: GcrCredentials,
    image_pull_secret_name: str,
    namespace: str,
):
    """Create an image pull secret in the K8s cluster to allow pods to download images from gcr"""
    ensure_not_none(credentials)
    ensure_not_empty(image_pull_secret_name)
    ensure_not_empty(namespace)

    ctx = kubeutil.ctx
    namespace_helper = ctx.namespace_helper()
    namespace_helper.create_if_absent(namespace)

    secret_helper = ctx.secret_helper()
    if not secret_helper.get_secret(image_pull_secret_name, namespace):
        secret_helper.create_gcr_secret(
            namespace=namespace,
            name=image_pull_secret_name,
            password=credentials.passwd(),
            user_name=credentials.username(),
            email=credentials.email(),
            server_url=credentials.host(),
        )

        service_account_helper = ctx.service_account_helper()
        service_account_helper.patch_image_pull_secret_into_service_account(
            name="default",
            namespace=namespace,
            image_pull_secret_name=image_pull_secret_name)
Example #3
0
def deploy_secrets_server(secrets_server_config: SecretsServerConfig):
    ensure_not_none(secrets_server_config)

    ctx = kubeutil.ctx
    service_helper = ctx.service_helper()
    deployment_helper = ctx.deployment_helper()
    secrets_helper = ctx.secret_helper()
    namespace_helper = ctx.namespace_helper()

    namespace = secrets_server_config.namespace()
    namespace_helper.create_if_absent(namespace)

    secret_name = secrets_server_config.secrets().concourse_secret_name()
    # Deploy an empty secret if none exists so that the secrets-server can start.
    # However, if there is already a secret we should not purge its contents.
    if not secrets_helper.get_secret(secret_name, namespace):
        secrets_helper.put_secret(
            name=secret_name,
            data={},
            namespace=namespace,
        )

    service = generate_secrets_server_service(secrets_server_config)
    deployment = generate_secrets_server_deployment(secrets_server_config)

    service_helper.replace_or_create_service(namespace, service)
    deployment_helper.replace_or_create_deployment(namespace, deployment)
Example #4
0
def create_tls_secret(
    tls_config: TlsConfig,
    tls_secret_name: str,
    namespace: str,
):
    """Creates the configured TLS secret for the Concourse web-component in the K8s cluster"""
    ensure_not_none(tls_config)
    ensure_not_empty(tls_secret_name)
    ensure_not_empty(namespace)

    ctx = kubeutil.ctx
    namespace_helper = ctx.namespace_helper()
    namespace_helper.create_if_absent(namespace)

    secret_helper = ctx.secret_helper()
    if not secret_helper.get_secret(tls_secret_name, namespace):
        data = {
            'tls.key': tls_config.private_key(),
            'tls.crt': tls_config.certificate(),
        }
        secret_helper.put_secret(
            name=tls_secret_name,
            data=data,
            namespace=namespace,
        )
Example #5
0
def generate_delaying_proxy_deployment(concourse_cfg: ConcourseConfig):
    ensure_not_none(concourse_cfg)

    external_url = concourse_cfg.external_url()
    label = {'app': 'delaying-proxy'}

    return V1Deployment(
        kind='Deployment',
        metadata=V1ObjectMeta(name='delaying-proxy'),
        spec=V1DeploymentSpec(
            replicas=1,
            selector=V1LabelSelector(match_labels=label),
            template=V1PodTemplateSpec(
                metadata=V1ObjectMeta(labels=label),
                spec=V1PodSpec(containers=[
                    V1Container(
                        image=
                        'eu.gcr.io/gardener-project/cc/github-enterprise-proxy:0.1.0',
                        image_pull_policy='IfNotPresent',
                        name='delaying-proxy',
                        ports=[
                            V1ContainerPort(container_port=8080),
                        ],
                        liveness_probe=V1Probe(
                            tcp_socket=V1TCPSocketAction(port=8080),
                            initial_delay_seconds=10,
                            period_seconds=10,
                        ),
                        env=[
                            V1EnvVar(name='CONCOURSE_URL', value=external_url),
                        ],
                    ),
                ], ))))
Example #6
0
    def create(name: str, variant_name: str, args_dict: dict):
        if not name in TRAITS:
            raise ModelValidationError('no such trait: ' + str(name))
        ensure_not_none(args_dict)

        ctor = TRAITS[name]

        return ctor(name=name, variant_name=variant_name, raw_dict=args_dict)
Example #7
0
    def create_deployment(self, namespace: str, deployment: V1Deployment):
        '''Create a deployment in a given namespace. Raises an `ApiException` if such a deployment
        already exists.'''
        ensure_not_empty(namespace)
        ensure_not_none(deployment)

        self.apps_api.create_namespaced_deployment(namespace=namespace,
                                                   body=deployment)
Example #8
0
    def create_ingress(self, namespace: str, ingress: V1beta1Ingress):
        '''Create an ingress in a given namespace. Raises an `ApiException` if such an ingress
        already exists.'''
        ensure_not_empty(namespace)
        ensure_not_none(ingress)

        self.extensions_v1beta1_api.create_namespaced_ingress(
            namespace=namespace, body=ingress)
Example #9
0
    def create_service(self, namespace: str, service: V1Service):
        '''Create a service in a given namespace. Raises an `ApiException` if such a Service
        already exists.
        '''
        ensure_not_empty(namespace)
        ensure_not_none(service)

        self.core_api.create_namespaced_service(namespace=namespace,
                                                body=service)
Example #10
0
    def set_kubecfg(self, kubeconfig_dict: dict):
        ensure_not_none(kubeconfig_dict)

        configuration = kubernetes.client.Configuration()
        cfg_loader = KubeConfigLoader(dict(kubeconfig_dict))
        cfg_loader.load_and_set(configuration)
        # pylint: disable=no-member
        kubernetes.client.Configuration.set_default(configuration)
        # pylint: enable=no-member
        self.kubeconfig = configuration
Example #11
0
    def replace_or_create_service(self, namespace: str, service: V1Service):
        '''Create a service in a given namespace. If the service already exists,
        the previous version will be deleted beforehand
        '''
        ensure_not_empty(namespace)
        ensure_not_none(service)

        service_name = service.metadata.name
        existing_service = self.get_service(namespace=namespace,
                                            name=service_name)
        if existing_service:
            self.core_api.delete_namespaced_service(namespace=namespace,
                                                    name=service_name)
        self.create_service(namespace=namespace, service=service)
Example #12
0
def ensure_cluster_version(kubernetes_config: KubernetesConfig):
    ensure_not_none(kubernetes_config)

    cluster_version_info = kubeutil.get_cluster_version_info()
    configured_version_info = kubernetes_config.cluster_version()

    if (cluster_version_info.major != configured_version_info['major']
            or cluster_version_info.minor != configured_version_info['minor']):
        fail(
            'Incompatible k8s-cluster-version "Major: {a_major} Minor: {a_minor}". Expected "Major: {e_major} Minor: {e_minor}".'
            .format(
                a_major=cluster_version_info.major,
                a_minor=cluster_version_info.minor,
                e_major=configured_version_info['major'],
                e_minor=configured_version_info['minor'],
            ))
Example #13
0
    def replace_or_create_deployment(self, namespace: str,
                                     deployment: V1Deployment):
        '''Create a deployment in a given namespace. If the deployment already exists,
        the previous version will be deleted beforehand.
        '''
        ensure_not_empty(namespace)
        ensure_not_none(deployment)

        deployment_name = deployment.metadata.name
        existing_deployment = self.get_deployment(namespace=namespace,
                                                  name=deployment_name)
        if existing_deployment:
            self.apps_api.delete_namespaced_deployment(
                namespace=namespace,
                name=deployment_name,
                body=kubernetes.client.V1DeleteOptions())
        self.create_deployment(namespace=namespace, deployment=deployment)
Example #14
0
    def replace_or_create_ingress(self, namespace: str,
                                  ingress: V1beta1Ingress):
        '''Create an ingress in a given namespace. If the ingress already exists,
        the previous version will be deleted beforehand.
        '''
        ensure_not_empty(namespace)
        ensure_not_none(ingress)

        ingress_name = ingress.metadata.name
        existing_ingress = self.get_ingress(namespace=namespace,
                                            name=ingress_name)
        if existing_ingress:
            self.extensions_v1beta1_api.delete_namespaced_ingress(
                namespace=namespace,
                name=ingress_name,
                body=kubernetes.client.V1DeleteOptions())
        self.create_ingress(namespace=namespace, ingress=ingress)
Example #15
0
def _send_mail(
    email_cfg: EmailConfig,
    recipients: typing.Iterable[str],
    mail_template: str,
    subject: str,
    replace_tokens: dict={},
    cc_recipients: typing.Iterable[str]=[],
):
    ensure_not_none(email_cfg)
    ensure_not_empty(recipients)
    ensure_not_none(mail_template)
    ensure_not_empty(subject)

    # create body from template
    mail_body = mailer.create_body(
        mail_template=mail_template,
        replace_tokens=replace_tokens,
    )

    # create mail envelope
    mail = mailer.create_mail(
        subject=subject,
        sender=email_cfg.sender_name(),
        recipients=recipients,
        cc_recipients=cc_recipients,
        text=mail_body
    )

    if email_cfg.use_tls():
        smtp_server = smtplib.SMTP_SSL(email_cfg.smtp_host())
    else:
        smtp_server = smtplib.SMTP(email_cfg.smtp_host())

    credentials = email_cfg.credentials()
    smtp_server.login(user=credentials.username(), password=credentials.passwd())

    recipients = set(recipients)
    recipients.update(cc_recipients)

    mailer.send_mail(
        smtp_server=smtp_server,
        msg=mail,
        sender=credentials.username(),
        recipients=recipients
    )
Example #16
0
def set_teams(config: ConcourseConfig):
    ensure_not_none(config)

    # Use main-team, i.e. the team that can change the other teams' credentials
    main_team_credentials = config.main_team_credentials()

    concourse_api = client.ConcourseApi(
        base_url=config.external_url(),
        team_name=main_team_credentials.teamname(),
    )
    concourse_api.login(
        team=main_team_credentials.teamname(),
        username=main_team_credentials.username(),
        passwd=main_team_credentials.passwd(),
    )
    for team in config.all_team_credentials():
        # We skip the main team here since we cannot update all its credentials at this time.
        if team.teamname == "main":
            continue
        concourse_api.set_team(team)
Example #17
0
def generate_secrets_server_service(
    secrets_server_config: SecretsServerConfig, ):
    ensure_not_none(secrets_server_config)

    # We need to ensure that the labels and selectors match between the deployment and the service,
    # therefore we base them on the configured service name.
    service_name = secrets_server_config.service_name()
    selector = {'app': service_name}

    return V1Service(
        kind='Service',
        metadata=V1ObjectMeta(name=service_name, ),
        spec=V1ServiceSpec(
            type='ClusterIP',
            ports=[
                V1ServicePort(protocol='TCP', port=80, target_port=8080),
            ],
            selector=selector,
            session_affinity='None',
        ),
    )
Example #18
0
def create_instance_specific_helm_values(concourse_cfg: ConcourseConfig):
    '''
    Creates a dict containing instance specific helm values not explicitly stated in
    the `ConcourseConfig`'s helm_chart_values.
    '''
    ensure_not_none(concourse_cfg)

    # 'main'-team credentials need to be included in the values.yaml, unlike the other teams
    creds = concourse_cfg.team_credentials('main')
    external_url = concourse_cfg.external_url()
    external_host = urlparse(external_url).netloc
    concourse_tls_secret_name = concourse_cfg.tls_secret_name()

    instance_specific_values = {
        'concourse': {
            'externalURL': external_url,
        },
        'secrets': {
            'basicAuthUsername': creds.username(),
            'basicAuthPassword': creds.passwd(),
            'githubAuthAuthUrl': creds.github_auth_auth_url(),
            'githubAuthTokenUrl': creds.github_auth_token_url(),
            'githubAuthApiUrl': creds.github_auth_api_url(),
            'githubAuthClientId': creds.github_auth_client_id(),
            'githubAuthClientSecret': creds.github_auth_client_secret(),
            'githubAuthTeam': creds.github_auth_team(),
        },
        'web': {
            'ingress': {
                'hosts': [external_host],
                'tls': [{
                    'secretName': concourse_tls_secret_name,
                    'hosts': [external_host],
                }],
            }
        }
    }
    return instance_specific_values
Example #19
0
def deploy_delaying_proxy(
    concourse_cfg: ConcourseConfig,
    deployment_name: str,
):
    ensure_not_none(concourse_cfg)
    ensure_not_empty(deployment_name)

    ctx = kubeutil.ctx
    service_helper = ctx.service_helper()
    deployment_helper = ctx.deployment_helper()
    namespace_helper = ctx.namespace_helper()
    ingress_helper = ctx.ingress_helper()

    namespace = deployment_name
    namespace_helper.create_if_absent(namespace)

    service = generate_delaying_proxy_service()
    deployment = generate_delaying_proxy_deployment(concourse_cfg)
    ingress = generate_delaying_proxy_ingress(concourse_cfg)

    service_helper.replace_or_create_service(namespace, service)
    deployment_helper.replace_or_create_deployment(namespace, deployment)
    ingress_helper.replace_or_create_ingress(namespace, ingress)
Example #20
0
    def __init__(self, cfg_factory, cfg_name, *args, **kwargs):
        self.cfg_factory = ensure_not_none(cfg_factory)
        super().__init__(name=cfg_name, *args, **kwargs)

        # normalise cfg mappings
        for cfg_type_name, entry in self.raw.items():
            if type(entry) == dict:
                entry = {
                    'config_names': entry['config_names'],
                    'default': entry.get('default', None)
                }
            elif type(entry) == str:
                entry = {'config_names': [entry], 'default': entry}

            self.raw[cfg_type_name] = entry
Example #21
0
 def __init__(self, raw_dict: dict):
     self.raw = ensure_not_none(raw_dict)
     if not self.CFG_TYPES in self.raw:
         raise ValueError('missing required attribute: {ct}'.format(ct=self.CFG_TYPES))
Example #22
0
def deploy_or_upgrade_concourse(
    default_helm_values: NamedModelElement,
    custom_helm_values: NamedModelElement,
    concourse_cfg: ConcourseConfig,
    kubernetes_config: KubernetesConfig,
    deployment_name: str = 'concourse',
):
    """Deploys (or upgrades) Concourse using the Helm CLI"""
    ensure_not_none(default_helm_values)
    ensure_not_none(custom_helm_values)
    ensure_not_none(concourse_cfg)
    helm_executable = ensure_helm_setup()

    namespace = deployment_name

    # create namespace if absent
    namespace_helper = kubeutil.ctx.namespace_helper()
    if not namespace_helper.get_namespace(namespace):
        namespace_helper.create_namespace(namespace)

    DEFAULT_HELM_VALUES_FILE_NAME = 'default_helm_values'
    CUSTOM_HELM_VALUES_FILE_NAME = 'custom_helm_values'
    INSTANCE_SPECIFIC_HELM_VALUES_FILE_NAME = 'instance_specific_helm_values'
    KUBECONFIG_FILE_NAME = 'kubecfg'

    # prepare subprocess args using relative file paths for the values files
    subprocess_args = [
        helm_executable,
        "upgrade",
        "--install",
        "--recreate-pods",
        "--wait",
        "--namespace",
        namespace,
        # Use Helm's value-rendering mechanism to merge the different value-sources.
        # This requires one values-file per source, with later value-files taking precedence.
        "--values",
        DEFAULT_HELM_VALUES_FILE_NAME,
        "--values",
        CUSTOM_HELM_VALUES_FILE_NAME,
        "--values",
        INSTANCE_SPECIFIC_HELM_VALUES_FILE_NAME,
        "--version",
        CONCOURSE_HELM_CHART_VERSION,
        namespace,  # release name is the same as namespace name
        "stable/concourse"
    ]

    helm_env = os.environ.copy()
    # set KUBECONFIG env-var in the copy to relative file path
    helm_env['KUBECONFIG'] = KUBECONFIG_FILE_NAME

    # create temp dir containing all previously referenced files
    with tempfile.TemporaryDirectory() as temp_dir:
        with open(os.path.join(temp_dir, DEFAULT_HELM_VALUES_FILE_NAME),
                  'w') as f:
            yaml.dump(default_helm_values, f)
        with open(os.path.join(temp_dir, CUSTOM_HELM_VALUES_FILE_NAME),
                  'w') as f:
            yaml.dump(custom_helm_values, f)
        with open(
                os.path.join(temp_dir,
                             INSTANCE_SPECIFIC_HELM_VALUES_FILE_NAME),
                'w') as f:
            yaml.dump(
                create_instance_specific_helm_values(
                    concourse_cfg=concourse_cfg), f)
        with open(os.path.join(temp_dir, KUBECONFIG_FILE_NAME), 'w') as f:
            yaml.dump(kubernetes_config.kubeconfig(), f)

        # run helm from inside the temporary directory so that the prepared file paths work
        subprocess.run(subprocess_args, check=True, cwd=temp_dir, env=helm_env)
Example #23
0
def generate_secrets_server_deployment(
    secrets_server_config: SecretsServerConfig, ):
    ensure_not_none(secrets_server_config)

    service_name = secrets_server_config.service_name()
    secret_name = secrets_server_config.secrets().concourse_secret_name()
    # We need to ensure that the labels and selectors match for both the deployment and the service,
    # therefore we base them on the configured service name.
    labels = {'app': service_name}

    return V1Deployment(
        kind='Deployment',
        metadata=V1ObjectMeta(name=service_name, labels=labels),
        spec=V1DeploymentSpec(
            replicas=1,
            selector=V1LabelSelector(match_labels=labels),
            template=V1PodTemplateSpec(
                metadata=V1ObjectMeta(labels=labels),
                spec=V1PodSpec(containers=[
                    V1Container(
                        image='eu.gcr.io/gardener-project/cc/job-image:0.20.0',
                        image_pull_policy='IfNotPresent',
                        name='secrets-server',
                        resources=V1ResourceRequirements(
                            requests={
                                'cpu': '50m',
                                'memory': '50Mi'
                            },
                            limits={
                                'cpu': '50m',
                                'memory': '50Mi'
                            },
                        ),
                        command=['bash'],
                        args=[
                            '-c', '''
                                # switch to secrets serving directory (create it if missing, i.e. if no other secrets are mounted there)
                                mkdir -p /secrets && cd /secrets
                                # make Kubernetes serviceaccount secrets available by default
                                cp -r /var/run/secrets/kubernetes.io/serviceaccount serviceaccount
                                # store Kubernetes service endpoint env as file for consumer
                                env | grep KUBERNETES_SERVICE > serviceaccount/env
                                # launch minimalistic python server in that directory serving requests across all network interfaces
                                python3 -m http.server 8080
                                '''
                        ],
                        ports=[
                            V1ContainerPort(container_port=8080),
                        ],
                        liveness_probe=V1Probe(
                            tcp_socket=V1TCPSocketAction(port=8080),
                            initial_delay_seconds=10,
                            period_seconds=10,
                        ),
                        volume_mounts=[
                            V1VolumeMount(
                                name=secret_name,
                                mount_path='/secrets/concourse-secrets',
                                read_only=True,
                            ),
                        ],
                    ),
                ],
                               volumes=[
                                   V1Volume(name=secret_name,
                                            secret=V1SecretVolumeSource(
                                                secret_name=secret_name, ))
                               ]))))
Example #24
0
 def __init__(self, cfg_sets: 'ConfigurationSet', cfg_factory: ConfigFactory):
     self.cfg_sets = ensure_not_none(cfg_sets)
     self.cfg_factory = ensure_not_none(cfg_factory)
Example #25
0
 def __init__(self, name, raw_dict, *args, **kwargs):
     self._name = ensure_not_none(name)
     super().__init__(raw_dict=raw_dict, *args, **kwargs)
Example #26
0
    def from_dict(raw_dict: dict):
        raw = ensure_not_none(raw_dict)

        return ConfigFactory(raw_dict=raw)
Example #27
0
 def __init__(self, trait: PublishTrait, *args, **kwargs):
     super().__init__(*args, **kwargs)
     self.trait = ensure_not_none(trait)
Example #28
0
 def _preprocess(self):
     return self.raw.get('preprocess', 'inject-commit-hash')
     self.args = ensure_not_none(trait_args)
Example #29
0
 def __init__(self, raw_dict):
     self.raw = ensure_not_none(raw_dict)
     self.snd = SimpleNamespaceDict(raw_dict)
     self._validate_dict()