From 2f518e4867991fa1c9ac299c8d0786280b83a1a0 Mon Sep 17 00:00:00 2001 From: Maarten de Waard <maarten@greenhost.nl> Date: Fri, 1 May 2020 16:09:14 +0200 Subject: [PATCH] truncate branch names from within CLI, use dynamic environments --- .gitlab-ci.yml | 46 +++++++++++++++++--------------- .gitlab/ci_scripts/create_vps.sh | 3 ++- openappstack/__main__.py | 43 +++++++++++++++++++++++++++-- openappstack/cluster.py | 25 +++++++++++++++++ 4 files changed, 92 insertions(+), 25 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fb0307744..1c4735cfb 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -9,9 +9,9 @@ - | echo "Env vars:" echo + echo "CLUSTER_NAME: $CLUSTER_NAME" + echo "IP_ADDRESS: $IP_ADDRESS" echo "HOSTNAME: $HOSTNAME" - echo "SUBDOMAIN: $SUBDOMAIN" - echo "DOMAIN: $DOMAIN" echo "FQDN: $FQDN" echo "CLUSTER_DIR: $CLUSTER_DIR" echo "ANSIBLE_HOST_KEY_CHECKING: $ANSIBLE_HOST_KEY_CHECKING" @@ -74,6 +74,9 @@ create-vps: - clusters expire_in: 1 month when: always + reports: + dotenv: + $CLUSTER_DIR/.env only: changes: - .gitlab-ci.yml @@ -89,6 +92,12 @@ create-vps: paths: - clusters/$HOSTNAME/** key: ${CI_COMMIT_REF_SLUG} + environment: + name: staging/$CI_COMMIT_REF_SLUG + url: https://$FQDN + on_stop: terminate-droplet + auto_stop_in: 1 week + setup-openappstack: stage: setup-cluster @@ -275,28 +284,21 @@ behave-grafana: - openappstack/**/* extends: .ssh_setup -terminate-mr-droplet-after-merge: - stage: cleanup - before_script: - - echo "We leave MR droplets running even when the pipeline is successful \ - to be able to investigate a MR. We need to terminate them when the MR \ - is merged into master." +# Terminates a droplet once the branch for it is deleted +terminate-droplet: + # Stage has to be the same as the step that created the VPS + # https://docs.gitlab.com/ee/ci/environments.html#automatically-stopping-an-environment + stage: create-vps + # Gets triggered by on_stop of create-vps job + when: manual + variables: + GIT_STRATEGY: none script: - *debug_information - - | - if [ "$(git show -s --pretty=%p HEAD | wc -w)" -gt 1 ] - then - commit_message="$(git show -s --format=%s)" - tmp="${commit_message#*\'}" - merged_branch="${tmp%%\'*}" - echo "Current HEAD is a merge commit, removing droplet from related merge request branch name '#${merged_branch}'." - python3 -c "import greenhost_cloud; greenhost_cloud.terminate_droplets_by_name(\"^${merged_branch}\.\")" - else - echo "Current HEAD is NOT a merge commit, nothing to do." - fi - only: - refs: - - master + - python3 -c "import greenhost_cloud; greenhost_cloud.terminate_droplets_by_name(\"^${CI_COMMIT_REF_SLUG}\")" + environment: + name: staging/$CI_COMMIT_REF_NAME + action: stop terminate-old-droplets: stage: cleanup diff --git a/.gitlab/ci_scripts/create_vps.sh b/.gitlab/ci_scripts/create_vps.sh index a949eec13..88ff6efc1 100644 --- a/.gitlab/ci_scripts/create_vps.sh +++ b/.gitlab/ci_scripts/create_vps.sh @@ -26,4 +26,5 @@ python3 -m openappstack $HOSTNAME create \ --create-hostname $HOSTNAME \ --ssh-key-id $SSH_KEY_ID \ --create-domain-records \ - --subdomain $SUBDOMAIN + --subdomain $SUBDOMAIN \ + --truncate-subdomain diff --git a/openappstack/__main__.py b/openappstack/__main__.py index 04400584e..9efad5bc2 100755 --- a/openappstack/__main__.py +++ b/openappstack/__main__.py @@ -28,6 +28,10 @@ from openappstack import name, cluster, ansible ALL_TESTS = ['behave'] +# We're limiting to 56, because we use subdomains, the longest of which +# is 7 characters (`office.`). Max CN length is 63 characters. +MAX_DOMAIN_LENGTH = 56 + def main(): # pylint: disable=too-many-statements,too-many-branches,too-many-locals """ @@ -115,6 +119,14 @@ def main(): # pylint: disable=too-many-statements,too-many-branches,too-many-lo help=('Use a custom subdomain for the generated domain records. ' 'Defaults to no subdomain')) + # Use this option to make sure that you won't run into troubles with + # certificates. Let's Encrypt requires a CN for each certificate, and a + # CN has a max length of 63 characters. + droplet_creation_group.add_argument( + '--truncate-subdomain', + action='store_true', + help=('Add a subdomain that is shorter than 56 characters')) + droplet_creation_group.add_argument( '--acme-staging', action='store_true', @@ -251,10 +263,37 @@ def create(clus, args): # pylint: disable=too-many-branches sys.exit(1) if args.subdomain: + # Check if {subdomain}.{domain} is not too long to fit in CN + if len(args.domain) > MAX_DOMAIN_LENGTH: + log.error(('ERROR: domain argument is too long. Domain will not ' + 'fit in a CN, please make sure your subdomain + ' + 'domain + 1 do not exceed %d ' + 'characters'), MAX_DOMAIN_LENGTH) + sys.exit(1) + + if len(args.domain) + len(args.subdomain) + 1 > MAX_DOMAIN_LENGTH: + if args.truncate_subdomain: + required_length = MAX_DOMAIN_LENGTH - len(args.domain) - 1 + # UGLY WORKAROUND, REMOVE BEFORE MERGING: + if args.subdomain[-3:] == '.ci': + subdomain = args.subdomain[0:required_length-3] + '.ci' + else: + # END OF UGLY WORKAROUND + subdomain = args.subdomain[0:required_length] + log.warning('Subdomain truncated to "%s"', subdomain) + else: + log.error(('ERROR: --subdomain argument is too long. Domain ' + 'will not fit in a CN, please make sure your ' + 'subdoman + domain + 1 do not exceed %d ' + 'characters'), MAX_DOMAIN_LENGTH) + sys.exit(1) + domain = '{subdomain}.{domain}'.format( - subdomain=args.subdomain, domain=args.domain) + subdomain=subdomain, domain=args.domain) else: domain = args.domain + subdomain = None + clus.domain = domain # Set acme_staging to False so we use Let's Encrypt's live environment @@ -283,7 +322,7 @@ def create(clus, args): # pylint: disable=too-many-branches if args.create_domain_records: create_domain_records( - args.domain, clus.ip_address, subdomain=args.subdomain) + args.domain, clus.ip_address, subdomain=subdomain) if args.verbose: greenhost_cloud.list_domain_records(args.domain) diff --git a/openappstack/cluster.py b/openappstack/cluster.py index 444f5e162..aada9794f 100644 --- a/openappstack/cluster.py +++ b/openappstack/cluster.py @@ -165,6 +165,25 @@ class Cluster: stream.write(file_contents) log.info("Created %s", self.settings_file) + dotenv_file = """CLUSTER_NAME={name} +CLUSTER_DIR={cluster_dir} +IP_ADDRESS={ip_address} +HOSTNAME={hostname} +DOMAIN={domain} +LOCAL_FLUX={local_flux} +""" + + with open(self.dotenv_file, 'w') as stream: + stream.write(dotenv_file.format( + name=self.name, + cluster_dir=self.cluster_dir, + ip_address=self.ip_address, + hostname=self.hostname, + domain=self.domain, + local_flux=self.local_flux + )) + log.info("Created %s", self.dotenv_file) + # Set self.data_loaded to True because the data in the class now # reflects the data in the file. self.data_loaded = True @@ -184,6 +203,12 @@ class Cluster: return os.path.join(self.cluster_dir, 'group_vars', 'all', 'settings.yml') + @property + def dotenv_file(self): + """Path to the .env file that can be used by gitlab-ci""" + return os.path.join(self.cluster_dir, '.env') + + @property def behave_file(self): """Path to 'behave.ini' which is used for acceptance tests""" -- GitLab