From 975ac7c001bf36ece65a1cb542c710ac0bef53fb Mon Sep 17 00:00:00 2001 From: Dougal Seeley Date: Wed, 13 Jan 2021 18:08:44 +0000 Subject: [PATCH] GCP account variables in cluster_vars; prefer merge_vars over include_vars (#76) * GCP account variables in cluster_vars; prefer merge_vars over include_vars. + Allow GCP account credentials to be defined as a variable (rather than as a file that needs to be retrieved); simplifies Jenkins automation. + Default to preferring merge_vars rather than include_vars. Update EXAMPLE/* to illustrate usage. Fix to merge_vars.py to allow directories. + Create deprecate_str.py which allows user to output a deprecation warning on command line. + Fix DNS dig regex matching on 100 on '10.' * Move GCP and AWS metadata into cluster_vars. Simplify gcp json data * gcp_credentials_file fix * Fix to show failure on block rescue Fix for _scheme_rmvm_rmdisk_only to not assert on tidy. Fixes for _scheme_rmvm_keepdisk_rollback to support canary properly Add 'deploy' and 'testsuite' Jenkinsfiles. Addresses (partially) https://github.com/sky-uk/clusterverse/issues/37 Co-authored-by: Dougal Seeley --- EXAMPLE/Pipfile | 1 + EXAMPLE/README.md | 61 ++----- .../_skel => cluster_defs}/app_vars.yml | 0 EXAMPLE/cluster_defs/aws/cluster_vars.yml | 31 ++++ .../aws/eu-west-1/cluster_vars.yml | 4 + .../aws/eu-west-1/sandbox/cluster_vars.yml | 14 ++ .../sandbox/testsuite/cluster_vars.yml | 65 +++++++ EXAMPLE/cluster_defs/cluster_vars.yml | 62 +++++++ EXAMPLE/cluster_defs/gcp/cluster_vars.yml | 36 ++++ .../gcp/europe-west1/sandbox/cluster_vars.yml | 10 ++ .../sandbox/testsuite/cluster_vars.yml | 30 ++++ .../test_aws_euw1/app_vars.yml | 6 +- .../test_aws_euw1/cluster_vars.yml | 56 +++--- .../test_gcp_euw1/app_vars.yml | 6 +- .../test_gcp_euw1/cluster_vars.yml | 52 +++--- EXAMPLE/group_vars/_skel/cluster_vars.yml | 135 -------------- EXAMPLE/group_vars/all.yml | 12 ++ EXAMPLE/jenkinsfiles/Jenkinsfile_exec_deploy | 165 ++++++++++-------- EXAMPLE/jenkinsfiles/Jenkinsfile_exec_release | 55 ------ .../Jenkinsfile_exec_release_deploy | 73 -------- .../jenkinsfiles/Jenkinsfile_exec_release_tag | 44 +++++ .../jenkinsfiles/Jenkinsfile_exec_testsuite | 142 +++++++++++++++ README.md | 144 +++++++++++---- .../action_plugins/README_merge_vars.md | 33 ---- _dependencies/action_plugins/merge_vars.py | 58 +++++- _dependencies/library/deprecate_str.py | 35 ++++ _dependencies/tasks/main.yml | 98 ++++++----- config/tasks/create_dns_a.yml | 4 +- config/tasks/filebeat.yml | 4 +- config/tasks/metricbeat.yml | 4 +- create/tasks/aws.yml | 3 +- create/tasks/gcp.yml | 10 +- .../tasks/main.yml | 6 +- .../tasks/main.yml | 9 +- .../tasks/main.yml | 8 +- .../tasks/preflight.yml | 6 + .../_scheme_rmvm_rmdisk_only/tasks/main.yml | 13 +- 37 files changed, 924 insertions(+), 571 deletions(-) rename EXAMPLE/{group_vars/_skel => cluster_defs}/app_vars.yml (100%) create mode 100644 EXAMPLE/cluster_defs/aws/cluster_vars.yml create mode 100644 EXAMPLE/cluster_defs/aws/eu-west-1/cluster_vars.yml create mode 100644 EXAMPLE/cluster_defs/aws/eu-west-1/sandbox/cluster_vars.yml create mode 100644 EXAMPLE/cluster_defs/aws/eu-west-1/sandbox/testsuite/cluster_vars.yml create mode 100644 EXAMPLE/cluster_defs/cluster_vars.yml create mode 100644 EXAMPLE/cluster_defs/gcp/cluster_vars.yml create mode 100644 EXAMPLE/cluster_defs/gcp/europe-west1/sandbox/cluster_vars.yml create mode 100644 EXAMPLE/cluster_defs/gcp/europe-west1/sandbox/testsuite/cluster_vars.yml rename EXAMPLE/{group_vars => cluster_defs}/test_aws_euw1/app_vars.yml (94%) rename EXAMPLE/{group_vars => cluster_defs}/test_aws_euw1/cluster_vars.yml (74%) rename EXAMPLE/{group_vars => cluster_defs}/test_gcp_euw1/app_vars.yml (94%) rename EXAMPLE/{group_vars => cluster_defs}/test_gcp_euw1/cluster_vars.yml (53%) delete mode 100644 EXAMPLE/group_vars/_skel/cluster_vars.yml create mode 100644 EXAMPLE/group_vars/all.yml delete mode 100644 EXAMPLE/jenkinsfiles/Jenkinsfile_exec_release delete mode 100644 EXAMPLE/jenkinsfiles/Jenkinsfile_exec_release_deploy create mode 100644 EXAMPLE/jenkinsfiles/Jenkinsfile_exec_release_tag create mode 100644 EXAMPLE/jenkinsfiles/Jenkinsfile_exec_testsuite delete mode 100644 _dependencies/action_plugins/README_merge_vars.md create mode 100644 _dependencies/library/deprecate_str.py diff --git a/EXAMPLE/Pipfile b/EXAMPLE/Pipfile index bed02bb4..0a3251d2 100644 --- a/EXAMPLE/Pipfile +++ b/EXAMPLE/Pipfile @@ -14,6 +14,7 @@ jmespath = "*" dnspython = "*" google-auth = "*" google-api-python-client = "*" +apache-libcloud = "*" [dev-packages] diff --git a/EXAMPLE/README.md b/EXAMPLE/README.md index 6f2e6c9c..f412fa12 100644 --- a/EXAMPLE/README.md +++ b/EXAMPLE/README.md @@ -11,60 +11,31 @@ Contributions are welcome and encouraged. Please see [CONTRIBUTING.md](https:// + Python >= 2.7 -## Usage -This example depends on the [clusterverse](https://github.com/sky-uk/clusterverse) role. It can be collected automatically using `ansible-galaxy`, or you could reference it using git sub-branches. This example uses `ansible-galaxy`. - -To import the [clusterverse](https://github.com/sky-uk/clusterverse) role into the current directory: -+ `ansible-galaxy install -r requirements.yml` - - -### Cluster Variables -One of the mandatory command-line variables is `clusterid`, which defines the name of the directory under `group_vars`, from which variable files will be imported. - -#### group_vars/\/cluster_vars.yml: -``` -app_name: "nginx" # The name of the application cluster (e.g. 'couchbase', 'nginx'); becomes part of cluster_name. -app_class: "webserver" # The class of application (e.g. 'database', 'webserver'); becomes part of the fqdn - -cluster_vars: - region: "" - image: "" - ... - : - hosttype_vars: - : {...} - ... -``` - -Variables defined in here override defaults in `roles/clusterverse/_dependencies/defaults/main.yml`, and can be overriden by defining them on the command-line. - -#### group_vars/\/app_vars.yml: -Contains your application-specific variables - --- ## Invocation examples: _deploy_, _scale_, _repair_ The `cluster.yml` sub-role immutably deploys a cluster from the config defined above. If it is run again it will do nothing. If the cluster_vars are changed (e.g. add a host), the cluster will reflect the new variables (e.g. a new host will be added to the cluster). ### AWS: ``` -ansible-playbook -u ubuntu --private-key=/home//.ssh/ cluster.yml -e buildenv=sandbox -e clusterid=vtp_aws_euw1 --vault-id=sandbox@.vaultpass-client.py -ansible-playbook -u ubuntu --private-key=/home//.ssh/ cluster.yml -e buildenv=sandbox -e clusterid=vtp_aws_euw1 --vault-id=sandbox@.vaultpass-client.py --tags=clusterverse_clean -e clean=_all_ -e release_version=v1.0.1 -ansible-playbook -u ubuntu --private-key=/home//.ssh/ cluster.yml -e buildenv=sandbox -e clusterid=vtp_aws_euw1 --vault-id=sandbox@.vaultpass-client.py -e clean=_all_ +ansible-playbook -u ubuntu --private-key=/home//.ssh/ cluster.yml -e buildenv=sandbox -e cloud_type=aws -e region=eu-west-1 -e clusterid=test --vault-id=sandbox@.vaultpass-client.py +ansible-playbook -u ubuntu --private-key=/home//.ssh/ cluster.yml -e buildenv=sandbox -e cloud_type=aws -e region=eu-west-1 -e clusterid=test --vault-id=sandbox@.vaultpass-client.py --tags=clusterverse_clean -e clean=_all_ +ansible-playbook -u ubuntu --private-key=/home//.ssh/ cluster.yml -e buildenv=sandbox -e clusterid=test_aws_euw1 --vault-id=sandbox@.vaultpass-client.py +ansible-playbook -u ubuntu --private-key=/home//.ssh/ cluster.yml -e buildenv=sandbox -e clusterid=test_aws_euw1 --vault-id=sandbox@.vaultpass-client.py --tags=clusterverse_clean -e clean=_all_ ``` ### GCP: ``` -ansible-playbook -u --private-key=/home//.ssh/ cluster.yml -e buildenv=sandbox -e clusterid=vtp_gcp_euw1 --vault-id=sandbox@.vaultpass-client.py -ansible-playbook -u --private-key=/home//.ssh/ cluster.yml -e buildenv=sandbox -e clusterid=vtp_gcp_euw1 --vault-id=sandbox@.vaultpass-client.py --tags=clusterverse_clean -e clean=_all_ -e release_version=v1.0.1 -ansible-playbook -u --private-key=/home//.ssh/ cluster.yml -e buildenv=sandbox -e clusterid=vtp_gcp_euw1 --vault-id=sandbox@.vaultpass-client.py -e clean=_all_ +ansible-playbook -u --private-key=/home//.ssh/ cluster.yml -e buildenv=sandbox -e clusterid=test -e cloud_type=gcp -e region=europe-west1 --vault-id=sandbox@.vaultpass-client.py +ansible-playbook -u --private-key=/home//.ssh/ cluster.yml -e buildenv=sandbox -e clusterid=test -e cloud_type=gcp -e region=europe-west1 --vault-id=sandbox@.vaultpass-client.py --tags=clusterverse_clean -e clean=_all_ +ansible-playbook -u --private-key=/home//.ssh/ cluster.yml -e buildenv=sandbox -e clusterid=test_gcp_euw1 --vault-id=sandbox@.vaultpass-client.py +ansible-playbook -u --private-key=/home//.ssh/ cluster.yml -e buildenv=sandbox -e clusterid=test_gcp_euw1 --vault-id=sandbox@.vaultpass-client.py --tags=clusterverse_clean -e clean=_all_ ``` ### Mandatory command-line variables: -+ `-e clusterid=` - A directory named `clusterid` must be present in `group_vars`. Holds the parameters that define the cluster; enables a multi-tenanted repository. -+ `-e buildenv=` - The environment (dev, stage, etc), which must be an attribute of `cluster_vars` defined in `group_vars//cluster_vars.yml` ++ `-e buildenv=` - The environment (dev, stage, etc), which must be an attribute of `cluster_vars` (i.e. `cluster_vars.{{build_env}}`) ### Optional extra variables: -+ `-e app_name=` - Normally defined in `group_vars//cluster_vars.yml`. The name of the application cluster (e.g. 'couchbase', 'nginx'); becomes part of cluster_name -+ `-e app_class=` - Normally defined in `group_vars//cluster_vars.yml`. The class of application (e.g. 'database', 'webserver'); becomes part of the fqdn ++ `-e app_name=` - Normally defined in `/cluster_defs/`. The name of the application cluster (e.g. 'couchbase', 'nginx'); becomes part of cluster_name ++ `-e app_class=` - Normally defined in `/cluster_defs/`. The class of application (e.g. 'database', 'webserver'); becomes part of the fqdn + `-e release_version=` - Identifies the application version that is being deployed. + `-e clean=[current|retiring|redeployfail|_all_]` - Deletes VMs in `lifecycle_state`, or `_all_`, as well as networking and security groups + `-e pkgupdate=[always|onCreate]` - Upgrade the OS packages (not good for determinism). `onCreate` only upgrades when creating the VM for the first time. @@ -75,10 +46,11 @@ ansible-playbook -u --private-key=/home//.ssh/ cluster + `-e metricbeat_install=false` - Does not install metricbeat + `-e wait_for_dns=false` - Does not wait for DNS resolution + `-e create_gcp_network=true` - Create GCP network and subnetwork (probably needed if creating from scratch and using public network) ++ `-e debug_nested_log_output=true` - Show the log output from nested calls to embedded Ansible playbooks (i.e. when redeploying) ### Tags + `clusterverse_clean`: Deletes all VMs and security groups (also needs `-e clean=[current|retiring|redeployfail|_all_]` on command line) -+ `clusterverse_create`: Creates only EC2 VMs, based on the hosttype_vars values in group_vars/all/cluster.yml ++ `clusterverse_create`: Creates only EC2 VMs, based on the hosttype_vars values in `/cluster_defs/` + `clusterverse_config`: Updates packages, sets hostname, adds hosts to DNS @@ -89,15 +61,16 @@ The `redeploy.yml` sub-role will completely redeploy the cluster; this is useful ### AWS: ``` -ansible-playbook -u ubuntu --private-key=/home//.ssh/ redeploy.yml -e buildenv=sandbox -e clusterid=vtp_aws_euw1 --vault-id=sandbox@.vaultpass-client.py -e canary=none +ansible-playbook -u ubuntu --private-key=/home//.ssh/ redeploy.yml -e buildenv=sandbox -e clusterid=test_aws_euw1 --vault-id=sandbox@.vaultpass-client.py -e canary=none +ansible-playbook -u ubuntu --private-key=/home//.ssh/ redeploy.yml -e buildenv=sandbox -e cloud_type=aws -e region=eu-west-1 -e clusterid=test --vault-id=sandbox@.vaultpass-client.py -e canary=none ``` ### GCP: ``` -ansible-playbook -u --private-key=/home//.ssh/ redeploy.yml -e buildenv=sandbox -e clusterid=vtp_gcp_euw1 --vault-id=sandbox@.vaultpass-client.py -e canary=none +ansible-playbook -u --private-key=/home//.ssh/ redeploy.yml -e buildenv=sandbox -e clusterid=test_aws_euw1 --vault-id=sandbox@.vaultpass-client.py -e canary=none +ansible-playbook -u --private-key=/home//.ssh/ redeploy.yml -e buildenv=sandbox -e clusterid=test -e cloud_type=gcp -e region=europe-west1 --vault-id=sandbox@.vaultpass-client.py -e canary=none ``` ### Mandatory command-line variables: -+ `-e clusterid=` - A directory named `clusterid` must be present in `group_vars`. Holds the parameters that define the cluster; enables a multi-tenanted repository. + `-e buildenv=` - The environment (dev, stage, etc), which must be an attribute of `cluster_vars` defined in `group_vars//cluster_vars.yml` + `-e canary=['start', 'finish', 'none', 'tidy']` - Specify whether to start or finish a canary deploy, or 'none' deploy diff --git a/EXAMPLE/group_vars/_skel/app_vars.yml b/EXAMPLE/cluster_defs/app_vars.yml similarity index 100% rename from EXAMPLE/group_vars/_skel/app_vars.yml rename to EXAMPLE/cluster_defs/app_vars.yml diff --git a/EXAMPLE/cluster_defs/aws/cluster_vars.yml b/EXAMPLE/cluster_defs/aws/cluster_vars.yml new file mode 100644 index 00000000..97458acc --- /dev/null +++ b/EXAMPLE/cluster_defs/aws/cluster_vars.yml @@ -0,0 +1,31 @@ +--- + +cluster_vars: + dns_cloud_internal_domain: "{{region}}.compute.internal" # The cloud-internal zone as defined by the cloud provider (e.g. GCP, AWS) + dns_nameserver_zone: &dns_nameserver_zone "" # The zone that dns_server will operate on. gcloud dns needs a trailing '.'. Leave blank if no external DNS (use IPs only) + dns_server: "" # Specify DNS server. nsupdate, route53 or clouddns. If empty string is specified, no DNS will be added. + route53_private_zone: no # Only used when cluster_vars.type == 'aws'. Defaults to true if not set. + assign_public_ip: "yes" + inventory_ip: "public" # 'public' or 'private', (private in case we're operating in a private LAN). If public, 'assign_public_ip' must be 'yes' + instance_profile_name: "" + user_data: |- + #cloud-config + system_info: + default_user: + name: ansible + ssh_whitelist: &ssh_whitelist ['10.0.0.0/8'] + secgroups_existing: [] + secgroup_new: + - proto: "tcp" + ports: ["22"] + cidr_ip: "{{_ssh_whitelist}}" + rule_desc: "SSH Access" +# - proto: all +# group_name: "{{cluster_name}}-sg" +# rule_desc: "Access from all VMs attached to the {{ cluster_name }}-sg group" +# - proto: "tcp" +# ports: ["{{ prometheus_node_exporter_port | default(9100) }}"] +# group_name: "{{buildenv}}-private-sg" +# rule_desc: "Prometheus instances attached to {{buildenv}}-private-sg can access the exporter port(s)." +_ssh_whitelist: *ssh_whitelist +_dns_nameserver_zone: *dns_nameserver_zone diff --git a/EXAMPLE/cluster_defs/aws/eu-west-1/cluster_vars.yml b/EXAMPLE/cluster_defs/aws/eu-west-1/cluster_vars.yml new file mode 100644 index 00000000..04fb3e90 --- /dev/null +++ b/EXAMPLE/cluster_defs/aws/eu-west-1/cluster_vars.yml @@ -0,0 +1,4 @@ +--- + +cluster_vars: + image: "ami-055958ae2f796344b" # eu-west-1, 20.04, amd64, hvm-ssd, 20201210. Ubuntu images can be located at https://cloud-images.ubuntu.com/locator/ diff --git a/EXAMPLE/cluster_defs/aws/eu-west-1/sandbox/cluster_vars.yml b/EXAMPLE/cluster_defs/aws/eu-west-1/sandbox/cluster_vars.yml new file mode 100644 index 00000000..a59ec061 --- /dev/null +++ b/EXAMPLE/cluster_defs/aws/eu-west-1/sandbox/cluster_vars.yml @@ -0,0 +1,14 @@ +--- + +cluster_vars: + sandbox: + aws_access_key: !vault | + $ANSIBLE_VAULT;1.1;AES256 + 7669080460651349243347331538721104778691266429457726036813912140404310 + aws_secret_key: !vault | + $ANSIBLE_VAULT;1.1;AES256 + 7669080460651349243347331538721104778691266429457726036813912140404310 + vpc_name: "test{{buildenv}}" + vpc_subnet_name_prefix: "{{buildenv}}-test-{{_region}}" + key_name: "test__id_rsa" + termination_protection: "no" diff --git a/EXAMPLE/cluster_defs/aws/eu-west-1/sandbox/testsuite/cluster_vars.yml b/EXAMPLE/cluster_defs/aws/eu-west-1/sandbox/testsuite/cluster_vars.yml new file mode 100644 index 00000000..cdfb8a7d --- /dev/null +++ b/EXAMPLE/cluster_defs/aws/eu-west-1/sandbox/testsuite/cluster_vars.yml @@ -0,0 +1,65 @@ +--- + +cluster_vars: + sandbox: + hosttype_vars: + sys: + auto_volumes: [ ] + flavor: t3a.nano + version: "{{sys_version | default('')}}" + vms_by_az: { a: 1, b: 1, c: 0 } + + sysdisks2: + auto_volumes: + - { device_name: "/dev/sda1", mountpoint: "/", fstype: "ext4", "volume_type": "gp2", "volume_size": 12, encrypted: True, "delete_on_termination": true } + - { device_name: "/dev/sdf", mountpoint: "/media/mysvc", fstype: "ext4", "volume_type": "gp2", "volume_size": 1, encrypted: True, "delete_on_termination": true, perms: { owner: "root", group: "sudo", mode: "775" } } + - { device_name: "/dev/sdg", mountpoint: "/media/mysvc2", fstype: "ext4", "volume_type": "gp2", "volume_size": 1, encrypted: True, "delete_on_termination": true } + flavor: t3a.nano + version: "{{sysdisks_version | default('')}}" + vms_by_az: { a: 1, b: 1, c: 0 } + +# sysdisks3: +# auto_volumes: +# - { device_name: "/dev/sdf", mountpoint: "/media/mysvc", fstype: "ext4", "volume_type": "gp2", "volume_size": 1, encrypted: True, "delete_on_termination": true } +# - { device_name: "/dev/sdg", mountpoint: "/media/mysvc2", fstype: "ext4", "volume_type": "gp2", "volume_size": 1, encrypted: True, "delete_on_termination": true } +# - { device_name: "/dev/sdh", mountpoint: "/media/mysvc3", fstype: "ext4", "volume_type": "gp2", "volume_size": 1, encrypted: True, "delete_on_termination": true } +# flavor: t3a.nano +# version: "{{sysdisks_version | default('')}}" +# vms_by_az: { a: 1, b: 0, c: 0 } +# +# hostnvme-multi: +# auto_volumes: +# - { device_name: "/dev/sdb", mountpoint: "/media/mysvc", fstype: "ext4", "volume_type": "ephemeral", ephemeral: ephemeral0 } +# - { device_name: "/dev/sdc", mountpoint: "/media/mysvc2", fstype: "ext4", "volume_type": "ephemeral", ephemeral: ephemeral1 } +# - { device_name: "/dev/sdf", mountpoint: "/media/mysvc8", fstype: "ext4", "volume_type": "gp2", "volume_size": 1, encrypted: True, "delete_on_termination": true } +# flavor: i3en.2xlarge +# version: "{{sys_version | default('')}}" +# vms_by_az: { a: 1, b: 0, c: 0 } +# +# hostnvme-lvm: +# auto_volumes: +# - { device_name: "/dev/sdb", mountpoint: "/media/data", fstype: "ext4", "volume_type": "ephemeral", ephemeral: ephemeral0 } +# - { device_name: "/dev/sdc", mountpoint: "/media/data", fstype: "ext4", "volume_type": "ephemeral", ephemeral: ephemeral1 } +# lvmparams: { vg_name: "vg0", lv_name: "lv0", lv_size: "+100%FREE" } +# flavor: i3en.2xlarge +# version: "{{sys_version | default('')}}" +# vms_by_az: { a: 1, b: 0, c: 0 } +# +# hosthdd-multi: +# auto_volumes: +# - { device_name: "/dev/sdb", mountpoint: "/media/mysvc", fstype: "ext4", "volume_type": "ephemeral", ephemeral: ephemeral0 } +# - { device_name: "/dev/sdc", mountpoint: "/media/mysvc2", fstype: "ext4", "volume_type": "ephemeral", ephemeral: ephemeral1 } +# - { device_name: "/dev/sdd", mountpoint: "/media/mysvc3", fstype: "ext4", "volume_type": "ephemeral", ephemeral: ephemeral2 } +# flavor: d2.xlarge +# version: "{{sys_version | default('')}}" +# vms_by_az: { a: 1, b: 0, c: 0 } +# +# hosthdd-lvm: +# auto_volumes: +# - { device_name: "/dev/sdb", mountpoint: "/media/data", fstype: "ext4", "volume_type": "ephemeral", ephemeral: ephemeral0 } +# - { device_name: "/dev/sdc", mountpoint: "/media/data", fstype: "ext4", "volume_type": "ephemeral", ephemeral: ephemeral1 } +# - { device_name: "/dev/sdd", mountpoint: "/media/data", fstype: "ext4", "volume_type": "ephemeral", ephemeral: ephemeral2 } +# lvmparams: { vg_name: "vg0", lv_name: "lv0", lv_size: "+100%FREE" } +# flavor: d2.xlarge +# version: "{{sys_version | default('')}}" +# vms_by_az: { a: 1, b: 0, c: 0 } diff --git a/EXAMPLE/cluster_defs/cluster_vars.yml b/EXAMPLE/cluster_defs/cluster_vars.yml new file mode 100644 index 00000000..cb269ceb --- /dev/null +++ b/EXAMPLE/cluster_defs/cluster_vars.yml @@ -0,0 +1,62 @@ +--- + +redeploy_schemes_supported: ['_scheme_addallnew_rmdisk_rollback', '_scheme_addnewvm_rmdisk_rollback', '_scheme_rmvm_rmdisk_only', '_scheme_rmvm_keepdisk_rollback'] + +#redeploy_scheme: _scheme_addallnew_rmdisk_rollback +#redeploy_scheme: _scheme_addnewvm_rmdisk_rollback +#redeploy_scheme: _scheme_rmvm_rmdisk_only +#redeploy_scheme: _scheme_rmvm_keepdisk_rollback + +app_name: "test" # The name of the application cluster (e.g. 'couchbase', 'nginx'); becomes part of cluster_name. +app_class: "test" # The class of application (e.g. 'database', 'webserver'); becomes part of the fqdn + +beats_config: + filebeat: +# output_logstash_hosts: ["localhost:5044"] # The destination hosts for filebeat-gathered logs +# extra_logs_paths: # The array is optional, if you need to add more paths or files to scrape for logs +# - /var/log/myapp/*.log + metricbeat: +# output_logstash_hosts: ["localhost:5044"] # The destination hosts for metricbeat-gathered metrics +# diskio: # Diskio retrieves metrics for all disks partitions by default. When diskio.include_devices is defined, only look for defined partitions +# include_devices: ["sda", "sdb", "nvme0n1", "nvme1n1", "nvme2n1"] + +## Vulnerability scanners - Tenable and/ or Qualys cloud agents: +cloud_agent: +# tenable: +# service: "nessusagent" +# debpackage: "" +# bin_path: "/opt/nessus_agent/sbin" +# nessus_key_id: "" +# nessus_group_id: "" +# proxy: {host: "", port: ""} +# qualys: +# service: "qualys-cloud-agent" +# debpackage: "" +# bin_path: "/usr/local/qualys/cloud-agent/bin" +# config_path: "/etc/default/qualys-cloud-agent" +# activation_id: "" +# customer_id: "" +# proxy: {host: "", port: ""} + +## Bind configuration and credentials, per environment +bind9: + sandbox: {server: "", key_name: "", key_secret: ""} + +cluster_name: "{{ app_name }}-{{ buildenv }}" # Identifies the cluster within the cloud environment + +cluster_vars: + type: "{{cloud_type}}" + region: "{{region}}" + dns_cloud_internal_domain: "" # The cloud-internal zone as defined by the cloud provider (e.g. GCP, AWS) + dns_nameserver_zone: &dns_nameserver_zone "" # The zone that dns_server will operate on. gcloud dns needs a trailing '.'. Leave blank if no external DNS (use IPs only) + dns_user_domain: "{%- if _dns_nameserver_zone -%}{{cloud_type}}-{{region}}.{{app_class}}.{{buildenv}}.{{_dns_nameserver_zone}}{%- endif -%}" # A user-defined _domain_ part of the FDQN, (if more prefixes are required before the dns_nameserver_zone) + dns_server: "" # Specify DNS server. nsupdate, route53 or clouddns. If empty string is specified, no DNS will be added. + custom_tagslabels: + inv_resident_id: "myresident" + inv_proposition_id: "myproposition" + inv_environment_id: "{{buildenv}}" + inv_service_id: "{{app_class}}" + inv_cluster_id: "{{cluster_name}}" + inv_cluster_type: "{{app_name}}" + inv_cost_centre: "1234" +_dns_nameserver_zone: *dns_nameserver_zone diff --git a/EXAMPLE/cluster_defs/gcp/cluster_vars.yml b/EXAMPLE/cluster_defs/gcp/cluster_vars.yml new file mode 100644 index 00000000..acaa6709 --- /dev/null +++ b/EXAMPLE/cluster_defs/gcp/cluster_vars.yml @@ -0,0 +1,36 @@ +--- + +cluster_vars: + service_account_rawtext: &service_account_rawtext !vault | + $ANSIBLE_VAULT;1.1;AES256 + 7669080460651349243347331538721104778691266429457726036813912140404310 + image: "projects/ubuntu-os-cloud/global/images/ubuntu-2004-focal-v20201211" # Ubuntu images can be located at https://cloud-images.ubuntu.com/locator/ +# image: "projects/ubuntu-os-cloud/global/images/ubuntu-1804-bionic-v20201211a" + dns_cloud_internal_domain: "c.{{ (_service_account_rawtext | string | from_json).project_id }}.internal" # The cloud-internal zone as defined by the cloud provider (e.g. GCP, AWS) + dns_nameserver_zone: &dns_nameserver_zone "zepkey.com." # The zone that dns_server will operate on. gcloud dns needs a trailing '.'. Leave blank if no external DNS (use IPs only) + dns_server: "clouddns" # Specify DNS server. nsupdate, route53 or clouddns. If empty string is specified, no DNS will be added. + assign_public_ip: "yes" + inventory_ip: "public" # 'public' or 'private', (private in case we're operating in a private LAN). If public, 'assign_public_ip' must be 'yes' + ip_forward: "false" + ssh_whitelist: &ssh_whitelist ['10.0.0.0/8', '82.69.177.168/29'] + metadata: + ssh-keys: "{{ cliargs.remote_user }}:{{ lookup('pipe', 'ssh-keygen -y -f ' + ansible_ssh_private_key_file) }} {{ cliargs.remote_user }}" + startup-script: "{%- if _ssh_whitelist is defined and _ssh_whitelist | length > 0 -%}#! /bin/bash\n\n#Whitelist my inbound IPs\n[ -f /etc/sshguard/whitelist ] && echo \"{{_ssh_whitelist | join ('\n')}}\" >>/etc/sshguard/whitelist && /bin/systemctl restart sshguard{%- endif -%}" + user-data: "" + network_fw_tags: ["{{cluster_name}}-nwtag"] + firewall_rules: + - name: "{{cluster_name}}-extssh" + allowed: [{ip_protocol: "tcp", ports: ["22"]}] + source_ranges: "{{_ssh_whitelist}}" + description: "SSH Access" + - name: "{{cluster_name}}-nwtag" + allowed: [{ip_protocol: "all"}] + source_tags: ["{{cluster_name}}-nwtag"] + description: "Access from all VMs attached to the {{cluster_name}}-nwtag group" +# - name: "{{cluster_name}}-prometheus-node-exporter" +# allowed: [{ip_protocol: "tcp", ports: ["{{ prometheus_node_exporter_port | default(9100) }}"]}] +# source_tags: ["{{cluster_name}}-nwtag"] +# description: "Prometheus instances attached to {{cluster_name}}-nwtag can access the exporter port(s)." +_service_account_rawtext: *service_account_rawtext +_ssh_whitelist: *ssh_whitelist +_dns_nameserver_zone: *dns_nameserver_zone diff --git a/EXAMPLE/cluster_defs/gcp/europe-west1/sandbox/cluster_vars.yml b/EXAMPLE/cluster_defs/gcp/europe-west1/sandbox/cluster_vars.yml new file mode 100644 index 00000000..76b36b9a --- /dev/null +++ b/EXAMPLE/cluster_defs/gcp/europe-west1/sandbox/cluster_vars.yml @@ -0,0 +1,10 @@ +--- + +cluster_vars: + sandbox: + vpc_project_id: "{{ (_service_account_rawtext | string | from_json).project_id }}" # AKA the 'service project' if Shared VPC (https://cloud.google.com/vpc/docs/shared-vpc) is in use. + vpc_host_project_id: "{{ (_service_account_rawtext | string | from_json).project_id }}" # Would differ from vpc_project_id if Shared VPC is in use, (the networking is in a separate project) + vpc_network_name: "dps-{{buildenv}}" + vpc_subnet_name: "" + preemptible: "no" + deletion_protection: "no" diff --git a/EXAMPLE/cluster_defs/gcp/europe-west1/sandbox/testsuite/cluster_vars.yml b/EXAMPLE/cluster_defs/gcp/europe-west1/sandbox/testsuite/cluster_vars.yml new file mode 100644 index 00000000..6f751b85 --- /dev/null +++ b/EXAMPLE/cluster_defs/gcp/europe-west1/sandbox/testsuite/cluster_vars.yml @@ -0,0 +1,30 @@ +--- + +cluster_vars: + sandbox: + hosttype_vars: + sys: + auto_volumes: [ ] + flavor: "e2-micro" + rootvol_size: "10" + version: "{{sys_version | default('')}}" + vms_by_az: { b: 1, c: 1, d: 0 } + + sysdisks2: + auto_volumes: + - { auto_delete: true, interface: "SCSI", volume_size: 2, mountpoint: "/media/mysvc", fstype: "ext4", perms: { owner: "root", group: "sudo", mode: "775" } } + - { auto_delete: true, interface: "SCSI", volume_size: 2, mountpoint: "/media/mysvc2", fstype: "ext4" } + flavor: "e2-micro" + rootvol_size: "10" + version: "{{sysdisks_version | default('')}}" + vms_by_az: { b: 1, c: 1, d: 0 } + +# sysdisks3: +# auto_volumes: +# - { auto_delete: true, interface: "SCSI", volume_size: 2, mountpoint: "/media/mysvc", fstype: "ext4" } +# - { auto_delete: true, interface: "SCSI", volume_size: 2, mountpoint: "/media/mysvc2", fstype: "ext4" } +# - { auto_delete: true, interface: "SCSI", volume_size: 3, mountpoint: "/media/mysvc3", fstype: "ext4" } +# flavor: "e2-micro" +# rootvol_size: "10" +# version: "{{sysdisks_version | default('')}}" +# vms_by_az: { b: 1, c: 1, d: 0 } diff --git a/EXAMPLE/group_vars/test_aws_euw1/app_vars.yml b/EXAMPLE/cluster_defs/test_aws_euw1/app_vars.yml similarity index 94% rename from EXAMPLE/group_vars/test_aws_euw1/app_vars.yml rename to EXAMPLE/cluster_defs/test_aws_euw1/app_vars.yml index fa0f904b..95f97c7f 100644 --- a/EXAMPLE/group_vars/test_aws_euw1/app_vars.yml +++ b/EXAMPLE/cluster_defs/test_aws_euw1/app_vars.yml @@ -1,4 +1,4 @@ ---- - -sys_version: "1_0_0" +--- + +sys_version: "1_0_0" sysdisks_version: "1_0_1" \ No newline at end of file diff --git a/EXAMPLE/group_vars/test_aws_euw1/cluster_vars.yml b/EXAMPLE/cluster_defs/test_aws_euw1/cluster_vars.yml similarity index 74% rename from EXAMPLE/group_vars/test_aws_euw1/cluster_vars.yml rename to EXAMPLE/cluster_defs/test_aws_euw1/cluster_vars.yml index 21d6443e..277a1f55 100644 --- a/EXAMPLE/group_vars/test_aws_euw1/cluster_vars.yml +++ b/EXAMPLE/cluster_defs/test_aws_euw1/cluster_vars.yml @@ -12,13 +12,13 @@ app_class: "test" # The class of application (e.g. 'database', ' beats_config: filebeat: -# output_logstash_hosts: ["localhost:5044"] # The destination hosts for filebeat-gathered logs -# extra_logs_paths: # The array is optional, if you need to add more paths or files to scrape for logs +# output_logstash_hosts: ["localhost:5044"] # The destination hosts for filebeat-gathered logs +# extra_logs_paths: # The array is optional, if you need to add more paths or files to scrape for logs # - /var/log/myapp/*.log metricbeat: -# output_logstash_hosts: ["localhost:5044"] # The destination hosts for metricbeat-gathered metrics -# diskio: # Diskio retrieves metrics for all disks partitions by default. When diskio.include_devices is defined, only look for defined partitions -# include_devices: ["sda", "sdb", "nvme0n1", "nvme1n1", "nvme2n1"] +# output_logstash_hosts: ["localhost:5044"] # The destination hosts for metricbeat-gathered metrics +# diskio: # Diskio retrieves metrics for all disks partitions by default. When diskio.include_devices is defined, only look for defined partitions +# include_devices: ["sda", "sdb", "nvme0n1", "nvme1n1", "nvme2n1"] ## Vulnerability scanners - Tenable and/ or Qualys cloud agents: cloud_agent: @@ -43,21 +43,25 @@ cloud_agent: bind9: sandbox: {server: "", key_name: "", key_secret: ""} -cluster_name: "{{app_name}}-{{buildenv}}" # Identifies the cluster within the cloud environment +cluster_name: "{{ app_name }}-{{ buildenv }}" # Identifies the cluster within the cloud environment cluster_vars: type: &cloud_type "aws" - image: "ami-0c4c42893066a139e" # eu-west-1, 20.04, amd64, hvm-ssd, 20200924. Ubuntu images can be located at https://cloud-images.ubuntu.com/locator/ -# image: "ami-06868ad5a3642e4d7" # eu-west-1, 18.04, amd64, hvm-ssd, 20200923. Ubuntu images can be located at https://cloud-images.ubuntu.com/locator/ - region: ®ion "eu-west-1" # eu-west-1, us-west-2 - dns_cloud_internal_domain: "{{_region}}.compute.internal" # The cloud-internal zone as defined by the cloud provider (e.g. GCP, AWS) - dns_nameserver_zone: &dns_nameserver_zone "" # The zone that dns_server will operate on. gcloud dns needs a trailing '.'. Leave blank if no external DNS (use IPs only) + image: "ami-055958ae2f796344b" # eu-west-1, 20.04, amd64, hvm-ssd, 20201210. Ubuntu images can be located at https://cloud-images.ubuntu.com/locator/ + region: ®ion "eu-west-1" + dns_cloud_internal_domain: "{{_region}}.compute.internal" # The cloud-internal zone as defined by the cloud provider (e.g. GCP, AWS) + dns_nameserver_zone: &dns_nameserver_zone "" # The zone that dns_server will operate on. gcloud dns needs a trailing '.'. Leave blank if no external DNS (use IPs only) dns_user_domain: "{%- if _dns_nameserver_zone -%}{{_cloud_type}}-{{_region}}.{{app_class}}.{{buildenv}}.{{_dns_nameserver_zone}}{%- endif -%}" # A user-defined _domain_ part of the FDQN, (if more prefixes are required before the dns_nameserver_zone) dns_server: "" # Specify DNS server. nsupdate, route53 or clouddns. If empty string is specified, no DNS will be added. - route53_private_zone: true # Only used when cluster_vars.type == 'aws'. Defaults to true if not set. + route53_private_zone: no # Only used when cluster_vars.type == 'aws'. Defaults to true if not set. assign_public_ip: "yes" inventory_ip: "public" # 'public' or 'private', (private in case we're operating in a private LAN). If public, 'assign_public_ip' must be 'yes' instance_profile_name: "" + user_data: |- + #cloud-config + system_info: + default_user: + name: ansible custom_tagslabels: inv_resident_id: "myresident" inv_proposition_id: "myproposition" @@ -66,22 +70,23 @@ cluster_vars: inv_cluster_id: "{{cluster_name}}" inv_cluster_type: "{{app_name}}" inv_cost_centre: "1234" + ssh_whitelist: &ssh_whitelist ['10.0.0.0/8'] secgroups_existing: [] secgroup_new: - proto: "tcp" ports: ["22"] - cidr_ip: "0.0.0.0/0" + cidr_ip: "{{_ssh_whitelist}}" rule_desc: "SSH Access" - - proto: "tcp" - ports: ["{{ prometheus_node_exporter_port | default(9100) }}"] - group_name: ["{{buildenv}}-private-sg"] - rule_desc: "Prometheus instances attached to {{buildenv}}-private-sg can access the exporter port(s)." - - proto: all - group_name: ["{{cluster_name}}-sg"] - rule_desc: "Access from all VMs attached to the {{ cluster_name }}-sg group" +# - proto: all +# group_name: "{{cluster_name}}-sg" +# rule_desc: "Access from all VMs attached to the {{ cluster_name }}-sg group" +# - proto: "tcp" +# ports: ["{{ prometheus_node_exporter_port | default(9100) }}"] +# group_name: "{{buildenv}}-private-sg" +# rule_desc: "Prometheus instances attached to {{buildenv}}-private-sg can access the exporter port(s)." sandbox: hosttype_vars: - sys: {vms_by_az: {a: 1, b: 1, c: 1}, flavor: t3a.nano, version: "{{sys_version | default('')}}", auto_volumes: []} + sys: {vms_by_az: {a: 1, b: 0, c: 0}, flavor: t3a.nano, version: "{{sys_version | default('')}}", auto_volumes: []} # sysdisks2: {vms_by_az: {a: 1, b: 0, c: 0}, flavor: t3a.nano, version: "{{sysdisks_version | default('')}}", auto_volumes: [{"device_name": "/dev/sdf", mountpoint: "/media/mysvc", fstype: "ext4", "volume_type": "gp2", "volume_size": 1, encrypted: True, "delete_on_termination": true, perms: {owner: "root", group: "sudo", mode: "775"} }, {"device_name": "/dev/sdg", mountpoint: "/media/mysvc2", fstype: "ext4", "volume_type": "gp2", "volume_size": 1, encrypted: True, "delete_on_termination": true}]} # sysdisks3: {vms_by_az: {a: 1, b: 0, c: 0}, flavor: t3a.nano, version: "{{sysdisks_version | default('')}}", auto_volumes: [{"device_name": "/dev/sdf", mountpoint: "/media/mysvc", fstype: "ext4", "volume_type": "gp2", "volume_size": 1, encrypted: True, "delete_on_termination": true, perms: {owner: "root", group: "sudo", mode: "775"} }, {"device_name": "/dev/sdg", mountpoint: "/media/mysvc2", fstype: "ext4", "volume_type": "gp2", "volume_size": 1, encrypted: True, "delete_on_termination": true}, {"device_name": "/dev/sdh", mountpoint: "/media/mysvc3", fstype: "ext4", "volume_type": "gp2", "volume_size": 1, encrypted: True, "delete_on_termination": true}]} # sysdisks-snapshot: {vms_by_az: {a: 1, b: 1, c: 0}, flavor: t3a.nano, version: "{{sys_version | default('')}}", auto_volumes: [{"snapshot_tags": {"tag:backup_id": "57180566894481854905"}, "device_name": "/dev/sdf", mountpoint: "/media/data", fstype: "ext4", "volume_type": "gp2", "volume_size": 1, encrypted: True, "delete_on_termination": true }]} @@ -89,12 +94,17 @@ cluster_vars: # hostnvme-lvm: {vms_by_az: {a: 1, b: 0, c: 0}, flavor: i3en.2xlarge, version: "{{sys_version | default('')}}", auto_volumes: [{device_name: "/dev/sdb", mountpoint: "/media/data", fstype: "ext4", "volume_type": "ephemeral", ephemeral: ephemeral0}, {device_name: "/dev/sdc", mountpoint: "/media/data", fstype: "ext4", "volume_type": "ephemeral", ephemeral: ephemeral1}], lvmparams: {vg_name: "vg0", lv_name: "lv0", lv_size: "+100%FREE"} } # hosthdd-multi: {vms_by_az: {a: 1, b: 0, c: 0}, flavor: d2.xlarge, version: "{{sys_version | default('')}}", auto_volumes: [{device_name: "/dev/sdb", mountpoint: "/media/mysvc", fstype: "ext4", "volume_type": "ephemeral", ephemeral: ephemeral0}, {device_name: "/dev/sdc", mountpoint: "/media/mysvc2", fstype: "ext4", "volume_type": "ephemeral", ephemeral: ephemeral1}, {device_name: "/dev/sdd", mountpoint: "/media/mysvc3", fstype: "ext4", "volume_type": "ephemeral", ephemeral: ephemeral2}] } # hosthdd-lvm: {vms_by_az: {a: 1, b: 0, c: 0}, flavor: d2.xlarge, version: "{{sys_version | default('')}}", auto_volumes: [{device_name: "/dev/sdb", mountpoint: "/media/data", fstype: "ext4", "volume_type": "ephemeral", ephemeral: ephemeral0}, {device_name: "/dev/sdc", mountpoint: "/media/data", fstype: "ext4", "volume_type": "ephemeral", ephemeral: ephemeral1}, {device_name: "/dev/sdd", mountpoint: "/media/data", fstype: "ext4", "volume_type": "ephemeral", ephemeral: ephemeral2}], lvmparams: {vg_name: "vg0", lv_name: "lv0", lv_size: "+100%FREE"} } - aws_access_key: "" - aws_secret_key: "" + aws_access_key: !vault | + $ANSIBLE_VAULT;1.1;AES256 + 7669080460651349243347331538721104778691266429457726036813912140404310 + aws_secret_key: !vault | + $ANSIBLE_VAULT;1.1;AES256 + 7669080460651349243347331538721104778691266429457726036813912140404310 vpc_name: "test{{buildenv}}" vpc_subnet_name_prefix: "{{buildenv}}-test-{{_region}}" key_name: "test__id_rsa" termination_protection: "no" _cloud_type: *cloud_type _region: *region +_ssh_whitelist: *ssh_whitelist _dns_nameserver_zone: *dns_nameserver_zone diff --git a/EXAMPLE/group_vars/test_gcp_euw1/app_vars.yml b/EXAMPLE/cluster_defs/test_gcp_euw1/app_vars.yml similarity index 94% rename from EXAMPLE/group_vars/test_gcp_euw1/app_vars.yml rename to EXAMPLE/cluster_defs/test_gcp_euw1/app_vars.yml index fa0f904b..95f97c7f 100644 --- a/EXAMPLE/group_vars/test_gcp_euw1/app_vars.yml +++ b/EXAMPLE/cluster_defs/test_gcp_euw1/app_vars.yml @@ -1,4 +1,4 @@ ---- - -sys_version: "1_0_0" +--- + +sys_version: "1_0_0" sysdisks_version: "1_0_1" \ No newline at end of file diff --git a/EXAMPLE/group_vars/test_gcp_euw1/cluster_vars.yml b/EXAMPLE/cluster_defs/test_gcp_euw1/cluster_vars.yml similarity index 53% rename from EXAMPLE/group_vars/test_gcp_euw1/cluster_vars.yml rename to EXAMPLE/cluster_defs/test_gcp_euw1/cluster_vars.yml index 2a8b5e8f..a140d87d 100644 --- a/EXAMPLE/group_vars/test_gcp_euw1/cluster_vars.yml +++ b/EXAMPLE/cluster_defs/test_gcp_euw1/cluster_vars.yml @@ -1,9 +1,5 @@ --- -# GCP credentials -gcp_credentials_file: "{{ lookup('env','GCP_CREDENTIALS') | default('/dev/null', true) }}" -gcp_credentials_json: "{{ lookup('file', gcp_credentials_file) | default({'project_id': 'GCP_CREDENTIALS__NOT_SET','client_email': 'GCP_CREDENTIALS__NOT_SET'}, true) }}" - redeploy_schemes_supported: ['_scheme_addallnew_rmdisk_rollback', '_scheme_addnewvm_rmdisk_rollback', '_scheme_rmvm_rmdisk_only', '_scheme_rmvm_keepdisk_rollback'] #redeploy_scheme: _scheme_addallnew_rmdisk_rollback @@ -16,12 +12,12 @@ app_class: "test" # The class of application (e.g. 'database', ' beats_config: filebeat: -# output_logstash_hosts: ["localhost:5044"] # The destination hosts for filebeat-gathered logs -# extra_logs_paths: # The array is optional, if you need to add more paths or files to scrape for logs +# output_logstash_hosts: ["localhost:5044"] # The destination hosts for filebeat-gathered logs +# extra_logs_paths: # The array is optional, if you need to add more paths or files to scrape for logs # - /var/log/myapp/*.log metricbeat: -# output_logstash_hosts: ["localhost:5044"] # The destination hosts for metricbeat-gathered metrics -# diskio: # Diskio retrieves metrics for all disks partitions by default. When diskio.include_devices is defined, only look for defined partitions +# output_logstash_hosts: ["localhost:5044"] # The destination hosts for metricbeat-gathered metrics +# diskio: # Diskio retrieves metrics for all disks partitions by default. When diskio.include_devices is defined, only look for defined partitions # include_devices: ["sda", "sdb", "nvme0n1", "nvme1n1", "nvme2n1"] ## Vulnerability scanners - Tenable and/ or Qualys cloud agents: @@ -47,21 +43,28 @@ cloud_agent: bind9: sandbox: {server: "", key_name: "", key_secret: ""} -cluster_name: "{{app_name}}-{{buildenv}}" # Identifies the cluster within the cloud environment +cluster_name: "{{ app_name }}-{{ buildenv }}" # Identifies the cluster within the cloud environment cluster_vars: type: &cloud_type "gcp" - image: "projects/ubuntu-os-cloud/global/images/ubuntu-2004-focal-v20200917" # Ubuntu images can be located at https://cloud-images.ubuntu.com/locator/ -# image: "projects/ubuntu-os-cloud/global/images/ubuntu-1804-bionic-v20200923" # Ubuntu images can be located at https://cloud-images.ubuntu.com/locator/ + service_account_rawtext: &service_account_rawtext !vault | + $ANSIBLE_VAULT;1.1;AES256 + 7669080460651349243347331538721104778691266429457726036813912140404310 + image: "projects/ubuntu-os-cloud/global/images/ubuntu-2004-focal-v20201211" # Ubuntu images can be located at https://cloud-images.ubuntu.com/locator/ +# image: "projects/ubuntu-os-cloud/global/images/ubuntu-1804-bionic-v20201211a" region: ®ion "europe-west1" - dns_cloud_internal_domain: "c.{{gcp_credentials_json.project_id}}.internal" # The cloud-internal zone as defined by the cloud provider (e.g. GCP, AWS) - dns_nameserver_zone: &dns_nameserver_zone "" # The zone that dns_server will operate on. gcloud dns needs a trailing '.'. Leave blank if no external DNS (use IPs only) + dns_cloud_internal_domain: "c.{{ (_service_account_rawtext | string | from_json).project_id }}.internal" # The cloud-internal zone as defined by the cloud provider (e.g. GCP, AWS) + dns_nameserver_zone: &dns_nameserver_zone "" # The zone that dns_server will operate on. gcloud dns needs a trailing '.'. Leave blank if no external DNS (use IPs only) dns_user_domain: "{%- if _dns_nameserver_zone -%}{{_cloud_type}}-{{_region}}.{{app_class}}.{{buildenv}}.{{_dns_nameserver_zone}}{%- endif -%}" # A user-defined _domain_ part of the FDQN, (if more prefixes are required before the dns_nameserver_zone) - dns_server: "" # Specify DNS server. nsupdate, route53 or clouddns. If empty string is specified, no DNS will be added. + dns_server: "" # Specify DNS server. nsupdate, route53 or clouddns. If empty string is specified, no DNS will be added. assign_public_ip: "yes" - inventory_ip: "public" # 'public' or 'private', (private in case we're operating in a private LAN). If public, 'assign_public_ip' must be 'yes' + inventory_ip: "public" # 'public' or 'private', (private in case we're operating in a private LAN). If public, 'assign_public_ip' must be 'yes' ip_forward: "false" - ssh_guard_whitelist: &ssh_guard_whitelist ['10.0.0.0/8'] # Put your public-facing IPs into this (if you're going to access it via public IP), to avoid rate-limiting. + ssh_whitelist: &ssh_whitelist ['10.0.0.0/8'] + metadata: + ssh-keys: "{{ cliargs.remote_user }}:{{ lookup('pipe', 'ssh-keygen -y -f ' + ansible_ssh_private_key_file) }} {{ cliargs.remote_user }}" + startup-script: "{%- if _ssh_whitelist is defined and _ssh_whitelist | length > 0 -%}#! /bin/bash\n\n#Whitelist my inbound IPs\n[ -f /etc/sshguard/whitelist ] && echo \"{{_ssh_whitelist | join ('\n')}}\" >>/etc/sshguard/whitelist && /bin/systemctl restart sshguard{%- endif -%}" + user-data: "" custom_tagslabels: inv_resident_id: "myresident" inv_proposition_id: "myproposition" @@ -74,27 +77,28 @@ cluster_vars: firewall_rules: - name: "{{cluster_name}}-extssh" allowed: [{ip_protocol: "tcp", ports: ["22"]}] - source_ranges: "{{_ssh_guard_whitelist}}" + source_ranges: "{{_ssh_whitelist}}" description: "SSH Access" - - name: "{{cluster_name}}-prometheus-node-exporter" - allowed: [{ip_protocol: "tcp", ports: ["{{ prometheus_node_exporter_port | default(9100) }}"]}] - source_tags: ["{{cluster_name}}-nwtag"] - description: "Prometheus instances attached to {{cluster_name}}-nwtag can access the exporter port(s)." - name: "{{cluster_name}}-nwtag" allowed: [{ip_protocol: "all"}] source_tags: ["{{cluster_name}}-nwtag"] description: "Access from all VMs attached to the {{cluster_name}}-nwtag group" +# - name: "{{cluster_name}}-prometheus-node-exporter" +# allowed: [{ip_protocol: "tcp", ports: ["{{ prometheus_node_exporter_port | default(9100) }}"]}] +# source_tags: ["{{cluster_name}}-nwtag"] +# description: "Prometheus instances attached to {{cluster_name}}-nwtag can access the exporter port(s)." sandbox: hosttype_vars: sys: {vms_by_az: {b: 1, c: 1, d: 1}, flavor: f1-micro, rootvol_size: "10", version: "{{sys_version | default('')}}", auto_volumes: []} #sysdisks: {vms_by_az: {b: 1, c: 1, d: 1}, flavor: f1-micro, rootvol_size: "10", version: "{{sysdisks_version | default('')}}", auto_volumes: [{auto_delete: true, interface: "SCSI", volume_size: 2, mountpoint: "/var/log/mysvc", fstype: "ext4", perms: {owner: "root", group: "sudo", mode: "775"}}, {auto_delete: true, interface: "SCSI", volume_size: 2, mountpoint: "/var/log/mysvc2", fstype: "ext4"}, {auto_delete: true, interface: "SCSI", volume_size: 3, mountpoint: "/var/log/mysvc3", fstype: "ext4"}]} - vpc_project_id: "{{gcp_credentials_json.project_id}}" # AKA the 'service project' if Shared VPC (https://cloud.google.com/vpc/docs/shared-vpc) is in use. - vpc_host_project_id: "{{gcp_credentials_json.project_id}}" # Would differ from vpc_project_id if Shared VPC is in use, (the networking is in a separate project) + vpc_project_id: "{{ (_service_account_rawtext | string | from_json).project_id }}" # AKA the 'service project' if Shared VPC (https://cloud.google.com/vpc/docs/shared-vpc) is in use. + vpc_host_project_id: "{{ (_service_account_rawtext | string | from_json).project_id }}" # Would differ from vpc_project_id if Shared VPC is in use, (the networking is in a separate project) vpc_network_name: "test-{{buildenv}}" vpc_subnet_name: "" preemptible: "no" deletion_protection: "no" _cloud_type: *cloud_type _region: *region -_ssh_guard_whitelist: *ssh_guard_whitelist +_service_account_rawtext: *service_account_rawtext +_ssh_whitelist: *ssh_whitelist _dns_nameserver_zone: *dns_nameserver_zone diff --git a/EXAMPLE/group_vars/_skel/cluster_vars.yml b/EXAMPLE/group_vars/_skel/cluster_vars.yml deleted file mode 100644 index a312769e..00000000 --- a/EXAMPLE/group_vars/_skel/cluster_vars.yml +++ /dev/null @@ -1,135 +0,0 @@ ---- - -redeploy_schemes_supported: [] - -# GCP credentials -gcp_credentials_file: "{{ lookup('env','GCP_CREDENTIALS') | default('/dev/null', true) }}" -gcp_credentials_json: "{{ lookup('file', gcp_credentials_file) | default({'project_id': 'GCP_CREDENTIALS__NOT_SET','client_email': 'GCP_CREDENTIALS__NOT_SET'}, true) }}" - -app_name: "test" # The name of the application cluster (e.g. 'couchbase', 'nginx'); becomes part of cluster_name. -app_class: "test" # The class of application (e.g. 'database', 'webserver'); becomes part of the fqdn - -beats_config: - filebeat: -# output_logstash_hosts: ["localhost:5044"] # The destination hosts for filebeat-gathered logs -# extra_logs_paths: # The array is optional, if you need to add more paths or files to scrape for logs -# - /var/log/myapp/*.log - metricbeat: -# output_logstash_hosts: ["localhost:5044"] # The destination hosts for metricbeat-gathered metrics - -## Vulnerability scanners - Tenable and/ or Qualys cloud agents: -cloud_agent: -# tenable: -# service: "nessusagent" -# debpackage: "" -# bin_path: "/opt/nessus_agent/sbin" -# nessus_key_id: "" -# nessus_group_id: "" -# proxy: {host: "", port: ""} -# qualys: -# service: "qualys-cloud-agent" -# github_release: "" -# bin_name: "" -# bin_path: "/usr/local/qualys/cloud-agent/bin" -# config_path: "/etc/default/qualys-cloud-agent" -# activation_id: "" -# customer_id: "" -# proxy: {host: "", port: ""} - -## Bind configuration and credentials, per environment -bind9: - sandbox: {server: "", key_name: "", key_secret: ""} - -cluster_name: "{{app_name}}-{{buildenv}}" # Identifies the cluster within the cloud environment - -### AWS example -#cluster_vars: -# type: &cloud_type "aws" -# image: "ami-0c4c42893066a139e" # eu-west-1, 20.04, amd64, hvm-ssd, 20200924. Ubuntu images can be located at https://cloud-images.ubuntu.com/locator/ -# image: "ami-06868ad5a3642e4d7" # eu-west-1, 18.04, amd64, hvm-ssd, 20200923. Ubuntu images can be located at https://cloud-images.ubuntu.com/locator/ -# region: ®ion "eu-west-1" # eu-west-1, us-west-2 -# dns_cloud_internal_domain: "{{_region}}.compute.internal" # The cloud-internal zone as defined by the cloud provider (e.g. GCP, AWS) -# dns_nameserver_zone: &dns_nameserver_zone "" # The zone that dns_server will operate on. gcloud dns needs a trailing '.'. Leave blank if no external DNS (use IPs only) -# dns_user_domain: "{%- if _dns_nameserver_zone -%}MY.OTHER.PREFIXES.{{_dns_nameserver_zone}}{%- endif -%}" # A user-defined _domain_ part of the FDQN, (if more prefixes are required before the dns_nameserver_zone) -# dns_server: "" # Specify DNS server. nsupdate, route53 or clouddns. If empty string is specified, no DNS will be added. -# route53_private_zone: true # Only used when cluster_vars.type == 'aws'. Defaults to true if not set. -# assign_public_ip: "yes" -# inventory_ip: "public" # 'public' or 'private', (private in case we're operating in a private LAN). If public, 'assign_public_ip' must be 'yes' -# instance_profile_name: "" -# custom_tagslabels: {inv_resident_id: "abc", inv_proposition_id: "def"} -# secgroups_existing: [] -# secgroup_new: -# - proto: "tcp" -# ports: ["22"] -# cidr_ip: "0.0.0.0/0" -# rule_desc: "SSH Access" -# - proto: "tcp" -# ports: ["{{ prometheus_node_exporter_port | default(9100) }}"] -# group_name: ["{{buildenv}}-private-sg"] -# rule_desc: "Prometheus instances attached to {{buildenv}}-private-sg can access the exporter port(s)." -# - proto: all -# group_name: ["{{cluster_name}}-sg"] -# rule_desc: "Access from all VMs attached to the {{ cluster_name }}-sg group" -# sandbox: -# hosttype_vars: -# sys: {vms_by_az: {a: 1, b: 0, c: 0}, flavor: t3a.nano, version: "{{sys_version | default('')}}", auto_volumes: []} -# #sysdisks2: {vms_by_az: {a: 1, b: 0, c: 0}, flavor: t3a.nano, version: "{{sysdisks_version | default('')}}", auto_volumes: [{"device_name": "/dev/sdf", mountpoint: "/media/mysvc", fstype: "ext4", "volume_type": "gp2", "volume_size": 1, encrypted: True, "delete_on_termination": true, perms: {owner: "root", group: "sudo", mode: "775"} }, {"device_name": "/dev/sdg", mountpoint: "/media/mysvc2", fstype: "ext4", "volume_type": "gp2", "volume_size": 1, encrypted: True, "delete_on_termination": true}]} -# #sysdisks3: {vms_by_az: {a: 1, b: 0, c: 0}, flavor: t3a.nano, version: "{{sysdisks_version | default('')}}", auto_volumes: [{"device_name": "/dev/sdf", mountpoint: "/media/mysvc", fstype: "ext4", "volume_type": "gp2", "volume_size": 1, encrypted: True, "delete_on_termination": true, perms: {owner: "root", group: "sudo", mode: "775"} }, {"device_name": "/dev/sdg", mountpoint: "/media/mysvc2", fstype: "ext4", "volume_type": "gp2", "volume_size": 1, encrypted: True, "delete_on_termination": true}, {"device_name": "/dev/sdh", mountpoint: "/media/mysvc3", fstype: "ext4", "volume_type": "gp2", "volume_size": 1, encrypted: True, "delete_on_termination": true}]} -# #sysdisks-snapshot: {vms_by_az: {a: 1, b: 1, c: 0}, flavor: t3a.nano, version: "{{sys_version | default('')}}", auto_volumes: [{"snapshot_tags": {"tag:backup_id": "57180566894481854905"}, "device_name": "/dev/sdf", mountpoint: "/media/data", fstype: "ext4", "volume_type": "gp2", "volume_size": 1, encrypted: True, "delete_on_termination": true }]} -# #hostnvme-multi: {vms_by_az: {a: 1, b: 0, c: 0}, flavor: i3en.2xlarge, version: "{{sys_version | default('')}}", auto_volumes: [{device_name: "/dev/sdb", mountpoint: "/media/mysvc", fstype: "ext4", "volume_type": "ephemeral", ephemeral: ephemeral0}, {device_name: "/dev/sdc", mountpoint: "/media/mysvc2", fstype: "ext4", "volume_type": "ephemeral", ephemeral: ephemeral1}, {"device_name": "/dev/sdf", mountpoint: "/media/mysvc8", fstype: "ext4", "volume_type": "gp2", "volume_size": 1, encrypted: True, "delete_on_termination": true }] } -# #hostnvme-lvm: {vms_by_az: {a: 1, b: 0, c: 0}, flavor: i3en.2xlarge, version: "{{sys_version | default('')}}", auto_volumes: [{device_name: "/dev/sdb", mountpoint: "/media/data", fstype: "ext4", "volume_type": "ephemeral", ephemeral: ephemeral0}, {device_name: "/dev/sdc", mountpoint: "/media/data", fstype: "ext4", "volume_type": "ephemeral", ephemeral: ephemeral1}], lvmparams: {vg_name: "vg0", lv_name: "lv0", lv_size: "+100%FREE"} } -# #hosthdd-multi: {vms_by_az: {a: 1, b: 0, c: 0}, flavor: d2.xlarge, version: "{{sys_version | default('')}}", auto_volumes: [{device_name: "/dev/sdb", mountpoint: "/media/mysvc", fstype: "ext4", "volume_type": "ephemeral", ephemeral: ephemeral0}, {device_name: "/dev/sdc", mountpoint: "/media/mysvc2", fstype: "ext4", "volume_type": "ephemeral", ephemeral: ephemeral1}, {device_name: "/dev/sdd", mountpoint: "/media/mysvc3", fstype: "ext4", "volume_type": "ephemeral", ephemeral: ephemeral2}] } -# #hosthdd-lvm: {vms_by_az: {a: 1, b: 0, c: 0}, flavor: d2.xlarge, version: "{{sys_version | default('')}}", auto_volumes: [{device_name: "/dev/sdb", mountpoint: "/media/data", fstype: "ext4", "volume_type": "ephemeral", ephemeral: ephemeral0}, {device_name: "/dev/sdc", mountpoint: "/media/data", fstype: "ext4", "volume_type": "ephemeral", ephemeral: ephemeral1}, {device_name: "/dev/sdd", mountpoint: "/media/data", fstype: "ext4", "volume_type": "ephemeral", ephemeral: ephemeral2}], lvmparams: {vg_name: "vg0", lv_name: "lv0", lv_size: "+100%FREE"} } -# aws_access_key: "" -# aws_secret_key: "" -# vpc_name: "test{{buildenv}}" -# vpc_subnet_name_prefix: "{{buildenv}}-test-{{_region}}" -# key_name: "test__id_rsa" -# termination_protection: "no" -#_cloud_type: *cloud_type -#_region: *region -#_dns_nameserver_zone: *dns_nameserver_zone - -### GCP example -#cluster_vars: -# type: &cloud_type "gcp" -# image: "projects/ubuntu-os-cloud/global/images/ubuntu-2004-focal-v20200917" # Ubuntu images can be located at https://cloud-images.ubuntu.com/locator/ -# image: "projects/ubuntu-os-cloud/global/images/ubuntu-1804-bionic-v20200923" # Ubuntu images can be located at https://cloud-images.ubuntu.com/locator/ -# region: ®ion "europe-west1" -# dns_cloud_internal_domain: "c.{{gcp_credentials_json.project_id}}.internal" # The cloud-internal zone as defined by the cloud provider (e.g. GCP, AWS) -# dns_nameserver_zone: &dns_nameserver_zone "" # The zone that dns_server will operate on. gcloud dns needs a trailing '.'. Leave blank if no external DNS (use IPs only) -# dns_user_domain: "{%- if _dns_nameserver_zone -%}CUSTOM.PREFIXES.{{_dns_nameserver_zone}}{%- endif -%}" # A user-defined _domain_ part of the FDQN, (if more prefixes are required before the dns_nameserver_zone) -# dns_server: "" # Specify DNS server. nsupdate, route53 or clouddns. If empty string is specified, no DNS will be added. -# assign_public_ip: "yes" -# inventory_ip: "public" # 'public' or 'private', (private in case we're operating in a private LAN). If public, 'assign_public_ip' must be 'yes' -# ip_forward: "false" -# ssh_guard_whitelist: &ssh_guard_whitelist ['10.0.0.0/8'] # Put your public-facing IPs into this (if you're going to access it via public IP), to avoid rate-limiting. -# custom_tagslabels: {inv_resident_id: "abc", inv_proposition_id: "def"} -# network_fw_tags: ["{{cluster_name}}-nwtag"] -# firewall_rules: -# - name: "{{cluster_name}}-extssh" -# allowed: [{ip_protocol: "tcp", ports: ["22"]}] -# source_ranges: "{{_ssh_guard_whitelist}}" -# description: "SSH Access" -# - name: "{{cluster_name}}-prometheus-node-exporter" -# allowed: [{ip_protocol: "tcp", ports: ["{{ prometheus_node_exporter_port | default(9100) }}"]}] -# source_tags: ["{{cluster_name}}-nwtag"] -# description: "Prometheus instances attached to {{cluster_name}}-nwtag can access the exporter port(s)." -# - name: "{{cluster_name}}-nwtag" -# allowed: [{ip_protocol: "all"}] -# source_tags: ["{{cluster_name}}-nwtag"] -# description: "Access from all VMs attached to the {{cluster_name}}-nwtag group" -# sandbox: -# hosttype_vars: -# sys: {vms_by_az: {b: 1, c: 1, d: 1}, flavor: f1-micro, rootvol_size: "10", version: "{{sys_version | default('')}}", auto_volumes: []} -# #sysdisks: {vms_by_az: {b: 1, c: 1, d: 1}, flavor: f1-micro, rootvol_size: "10", version: "{{sysdisks_version | default('')}}", auto_volumes: [{auto_delete: true, interface: "SCSI", volume_size: 2, mountpoint: "/var/log/mysvc", fstype: "ext4", perms: {owner: "root", group: "sudo", mode: "775"}}, {auto_delete: true, interface: "SCSI", volume_size: 2, mountpoint: "/var/log/mysvc2", fstype: "ext4"}, {auto_delete: true, interface: "SCSI", volume_size: 3, mountpoint: "/var/log/mysvc3", fstype: "ext4"}]} -# vpc_project_id: "{{gcp_credentials_json.project_id}}" # AKA the 'service project' if Shared VPC (https://cloud.google.com/vpc/docs/shared-vpc) is in use. -# vpc_host_project_id: "{{gcp_credentials_json.project_id}}" # Would differ from vpc_project_id if Shared VPC is in use, (the networking is in a separate project) -# vpc_network_name: "test-{{buildenv}}" -# vpc_subnet_name: "" -# preemptible: "no" -# deletion_protection: "no" -#_cloud_type: *cloud_type -#_region: *region -#_ssh_guard_whitelist: *ssh_guard_whitelist -#_dns_nameserver_zone: *dns_nameserver_zone diff --git a/EXAMPLE/group_vars/all.yml b/EXAMPLE/group_vars/all.yml new file mode 100644 index 00000000..c539a44b --- /dev/null +++ b/EXAMPLE/group_vars/all.yml @@ -0,0 +1,12 @@ +--- + +#merge_dict_vars_list: +# - "./cluster_defs/cluster_vars.yml" +# - "./cluster_defs/app_vars.yml" +# - "./cluster_defs/{{ cloud_type }}/" +# - "./cluster_defs/{{ cloud_type }}/{{ region }}/" +# - "./cluster_defs/{{ cloud_type }}/{{ region }}/{{ buildenv }}/" +# - "./cluster_defs/{{ cloud_type }}/{{ region }}/{{ buildenv }}/{{ clusterid }}/" + +#merge_dict_vars_list: +# - "./cluster_defs/{{ clusterid }}/" diff --git a/EXAMPLE/jenkinsfiles/Jenkinsfile_exec_deploy b/EXAMPLE/jenkinsfiles/Jenkinsfile_exec_deploy index 4d1f6c39..ce4e425c 100644 --- a/EXAMPLE/jenkinsfiles/Jenkinsfile_exec_deploy +++ b/EXAMPLE/jenkinsfiles/Jenkinsfile_exec_deploy @@ -1,96 +1,113 @@ +#!groovy +import groovy.json.JsonOutput + +String[] common_deploy_vars(params = null) { + println("common_deploy_vars params:" + params) + + String IAC_RELEASE = "" + if (params.RELEASE != "master" && params.RELEASE != null) { + GIT_TOKEN = credentials("GITHUB_SVC_USER") + sh "git remote set-url origin https://${GIT_TOKEN_USR}:${GIT_TOKEN_PSW}@github.com/sky-uk/clusterverse.git" + sh "git fetch --tags" + sh "git checkout ${params.RELEASE}" + IAC_RELEASE = ' -e release_version=' + params.RELEASE.replace('.', '_') as String + } + + String DNS_FORCE_DISABLE = "" + if (params.DNS_FORCE_DISABLE == true && params.DNS_FORCE_DISABLE != null) { + DNS_FORCE_DISABLE = " -e _dns_nameserver_zone=''" + } + + String MYHOSTTYPES = "" + if (params.MYHOSTTYPES != "" && params.MYHOSTTYPES != null) { + MYHOSTTYPES = ' -e myhosttypes=' + params.MYHOSTTYPES + } + + String APP_NAME = "" + if (params.APP_NAME != "" && params.APP_NAME != null) { + APP_NAME = " -e app_name=" + params.APP_NAME + if (params.APPEND_BUILD_NUMBER == true) { + APP_NAME = APP_NAME + '-' + env.BUILD_NUMBER + } + } + + def (CLOUD_TYPE, REGION) = params.CLOUD_REGION.split('/') + return [APP_NAME, DNS_FORCE_DISABLE, IAC_RELEASE, MYHOSTTYPES, CLOUD_TYPE, REGION] +} + pipeline { - agent any + agent { docker { image 'ubuntu_python' } } parameters { - booleanParam(name: 'GENUINE_BUILD', defaultValue: false, description: 'Tick the box to run the job') - string(name: 'CLUSTER_ID', defaultValue:"vtp_aws_euw1", description: "MANDATORY FIELD - 'vtp_aws_euw1', 'csc_aws_euw1', 'vtp_gcp_euw1', 'vtp_lsd_slo' - Specify which cloud/on-prem environment you want to deploy to") - choice(name: 'DEPLOY_ENV', choices: ['sandbox', 'dev', 'stage', 'prod'], description: "Choose an environment to deploy") - choice(name: 'DEPLOY_TYPE', choices: ['Deploy', 'ReDeploy', 'Clean'], description: "Choose the deploy type") - string(name: 'CANARY', defaultValue:"none", description: "MANDATORY FIELD - 'start', 'finish', 'none' - Specify whether to start or finish a canary deploy, or 'none' deploy") - string(name: 'MYHOSTTYPES', defaultValue:"", description: "master,slave - In redeployment you can define which host type you like to redeploy. If not defined it will redeploy all host types") + string(name: 'APP_NAME', description: "An optional custom app_name to override the default in the playbook") + booleanParam(name: 'APPEND_BUILD_NUMBER', defaultValue: false, description: 'Tick the box to append the Jenkins BUILD_NUMBER to APP_NAME') + choice(name: 'CLOUD_REGION', choices: ['esxifree/dougalab', 'aws/eu-west-1', 'gcp/europe-west1'], description: "Choose a cloud/region") + choice(name: 'BUILDENV', choices: ['sandbox', 'dev', 'stage', 'prod'], description: "Choose an environment to deploy") + choice(name: 'CLUSTER_ID', choices: ['', 'testsuite'], description: "Select a cluster_id to deploy") + booleanParam(name: 'DNS_FORCE_DISABLE', defaultValue: false, description: 'Tick the box to force disable the DNS as defined in playbook') + choice(name: 'DEPLOY_TYPE', choices: ['deploy', 'redeploy', 'clean'], description: "Choose the deploy type") + choice(name: 'REDEPLOY_SCHEME', choices: ['', '_scheme_addallnew_rmdisk_rollback', '_scheme_addnewvm_rmdisk_rollback', '_scheme_rmvm_rmdisk_only', '_scheme_rmvm_keepdisk_rollback'], description: "Choose the redeploy schemes") + choice(name: 'CANARY', choices: ['none', 'start', 'finish', 'tidy'], description: "Choose the canary type") + booleanParam(name: 'CANARY_TIDY_ON_SUCCESS', defaultValue: false, description: 'Tick the box to tidy up successful canary (none or finish) redeploys (by default, old machines are left powered off)') + string(name: 'MYHOSTTYPES', description: "comma-separated string, e.g. master,slave - In redeployment you can define which host type you like to redeploy. If not defined it will redeploy all host types") + gitParameter(name: 'RELEASE', type: 'PT_TAG', defaultValue: 'master', sortMode: 'DESCENDING_SMART', description: "Choose a version to deploy") + } + environment { + VAULT_PASSWORD_BUILDENV = credentials("VAULT_PASSWORD_${params.BUILDENV.toUpperCase()}") } stages { - stage('Init Environment') { - environment { - VAULT_PASSWORD_BUILDENV = credentials("VAULT_PASSWORD_${params.DEPLOY_ENV.toUpperCase()}") - GENUINE_BUILD="${params.GENUINE_BUILD}" - } + stage('Check Environment') { steps { - script{ - if (GENUINE_BUILD == "false"){ - error "Parameters not specified, Tick the GENUINE_BUILD box to run the job" + sh 'printenv | sort' + + /* NOTE: This checkout is only needed if cutting & pasting this file into a custom pipeline job in Jenkins (so we don't have to keep committing to git)*/ + //checkout changelog: false, poll: false, scm: [$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [[$class: 'WipeWorkspace']], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'GITHUB_SVC_USER', url: 'https://github.com/sky-uk/clusterverse_test']]] + + script { + if (params.CLUSTER_ID == '') { + error "CLUSTER_ID not defined" } - } - sh 'env' - sh 'pipenv install --python /usr/bin/python3' + if (params.APPEND_BUILD_NUMBER == true && params.APP_NAME == "") { + error "APP_NAME is required when APPEND_BUILD_NUMBER is set." + } + } } } - stage('Execute Deploy Playbook') { - environment { - VAULT_PASSWORD_BUILDENV = credentials("VAULT_PASSWORD_${params.DEPLOY_ENV.toUpperCase()}") - DEPLOY_ENV="${params.DEPLOY_ENV}" - } - when { - expression { params.DEPLOY_TYPE == 'Deploy' } - } + stage('deploy') { + when { expression { params.DEPLOY_TYPE == 'deploy' } } steps { - withCredentials([sshUserPrivateKey(credentialsId: "VTP_${params.DEPLOY_ENV.toUpperCase()}_SSH_KEY", keyFileVariable: 'keyfile', usernameVariable: 'sshuser')]) { - sh 'env' - sh 'echo "$DEPLOY_ENV: len $(echo -n $VAULT_PASSWORD_BUILDENV | /usr/bin/wc -c) sum $(echo -n $VAULT_PASSWORD_BUILDENV | /usr/bin/sum) "' - sh 'pipenv run ansible-playbook -u ${sshuser} --private-key=${keyfile} -e buildenv=$DEPLOY_ENV -e clusterid=$CLUSTER_ID --vault-id=$DEPLOY_ENV@.vaultpass-client.py cluster.yml' + script { + def (APP_NAME, DNS_FORCE_DISABLE, IAC_RELEASE, MYHOSTTYPES, CLOUD_TYPE, REGION) = common_deploy_vars(params) + withCredentials([sshUserPrivateKey(credentialsId: "SSH_KEY_${CLOUD_TYPE.toUpperCase()}_${REGION.toUpperCase()}_${params.BUILDENV.toUpperCase()}", keyFileVariable: 'keyfile', usernameVariable: 'sshuser')]) { + sh "ansible-playbook -u ${sshuser} --private-key=${keyfile} -e cloud_type=${CLOUD_TYPE} -e region=${REGION} -e buildenv=${params.BUILDENV} -e clusterid=${params.CLUSTER_ID} --vault-id=default@.vaultpass-client.py --vault-id=${params.BUILDENV}@.vaultpass-client.py cluster.yml $APP_NAME $DNS_FORCE_DISABLE $MYHOSTTYPES $IAC_RELEASE" + } } } } - stage('Execute ReDeploy Playbook with myhosttypes') { - environment { - VAULT_PASSWORD_BUILDENV = credentials("VAULT_PASSWORD_${params.DEPLOY_ENV.toUpperCase()}") - DEPLOY_ENV="${params.DEPLOY_ENV}" - CANARY="-e canary=${params.CANARY}" - MYHOSTTYPES="-e myhosttypes=${params.MYHOSTTYPES}" - } - when { - expression { params.DEPLOY_TYPE == 'ReDeploy' && params.MYHOSTTYPES != ""} - } + stage('redeploy') { + when { expression { params.DEPLOY_TYPE == 'redeploy' } } steps { - withCredentials([sshUserPrivateKey(credentialsId: "VTP_${params.DEPLOY_ENV.toUpperCase()}_SSH_KEY", keyFileVariable: 'keyfile', usernameVariable: 'sshuser')]) { - sh 'env' - sh 'echo "$DEPLOY_ENV: len $(echo -n $VAULT_PASSWORD_BUILDENV | /usr/bin/wc -c) sum $(echo -n $VAULT_PASSWORD_BUILDENV | /usr/bin/sum) "' - sh 'pipenv run ansible-playbook -u ${sshuser} --private-key=${keyfile} -e buildenv=$DEPLOY_ENV -e clusterid=$CLUSTER_ID --vault-id=$DEPLOY_ENV@.vaultpass-client.py redeploy.yml $CANARY $MYHOSTTYPES' + script { + if (params.REDEPLOY_SCHEME == '') { + error "REDEPLOY_SCHEME not defined" + } + def (APP_NAME, DNS_FORCE_DISABLE, IAC_RELEASE, MYHOSTTYPES, CLOUD_TYPE, REGION) = common_deploy_vars(params) + withCredentials([sshUserPrivateKey(credentialsId: "SSH_KEY_${CLOUD_TYPE.toUpperCase()}_${REGION.toUpperCase()}_${params.BUILDENV.toUpperCase()}", keyFileVariable: 'keyfile', usernameVariable: 'sshuser')]) { + sh "ansible-playbook -u ${sshuser} --private-key=${keyfile} -e cloud_type=${CLOUD_TYPE} -e region=${REGION} -e buildenv=${params.BUILDENV} -e clusterid=${params.CLUSTER_ID} --vault-id=default@.vaultpass-client.py --vault-id=${params.BUILDENV}@.vaultpass-client.py redeploy.yml -e canary=${params.CANARY} -e canary_tidy_on_success=${params.CANARY_TIDY_ON_SUCCESS} -e redeploy_scheme=${params.REDEPLOY_SCHEME} -e debug_nested_log_output=true $APP_NAME $DNS_FORCE_DISABLE $MYHOSTTYPES $IAC_RELEASE" + } } } } - stage('Execute ReDeploy Playbook without myhosttypes') { - environment { - VAULT_PASSWORD_BUILDENV = credentials("VAULT_PASSWORD_${params.DEPLOY_ENV.toUpperCase()}") - DEPLOY_ENV="${params.DEPLOY_ENV}" - CANARY="-e canary=${params.CANARY}" - } - when { - expression { params.DEPLOY_TYPE == 'ReDeploy' && params.MYHOSTTYPES == ""} - } + stage('clean') { + when { expression { params.DEPLOY_TYPE == 'clean' } } steps { - withCredentials([sshUserPrivateKey(credentialsId: "VTP_${params.DEPLOY_ENV.toUpperCase()}_SSH_KEY", keyFileVariable: 'keyfile', usernameVariable: 'sshuser')]) { - sh 'env' - sh 'echo "$DEPLOY_ENV: len $(echo -n $VAULT_PASSWORD_BUILDENV | /usr/bin/wc -c) sum $(echo -n $VAULT_PASSWORD_BUILDENV | /usr/bin/sum) "' - sh 'pipenv run ansible-playbook -u ${sshuser} --private-key=${keyfile} -e buildenv=$DEPLOY_ENV -e clusterid=$CLUSTER_ID --vault-id=$DEPLOY_ENV@.vaultpass-client.py redeploy.yml $CANARY' + script { + def (APP_NAME, DNS_FORCE_DISABLE, IAC_RELEASE, MYHOSTTYPES, CLOUD_TYPE, REGION) = common_deploy_vars(params) + withCredentials([sshUserPrivateKey(credentialsId: "SSH_KEY_${CLOUD_TYPE.toUpperCase()}_${REGION.toUpperCase()}_${params.BUILDENV.toUpperCase()}", keyFileVariable: 'keyfile', usernameVariable: 'sshuser')]) { + sh "ansible-playbook -u ${sshuser} --private-key=${keyfile} -e cloud_type=${CLOUD_TYPE} -e region=${REGION} -e buildenv=${params.BUILDENV} -e clusterid=${params.CLUSTER_ID} --vault-id=default@.vaultpass-client.py --vault-id=${params.BUILDENV}@.vaultpass-client.py cluster.yml --tags=clusterverse_clean -e clean=_all_ $APP_NAME" + } } } - } - stage('Execute Clean Playbook') { - environment { - VAULT_PASSWORD_BUILDENV = credentials("VAULT_PASSWORD_${params.DEPLOY_ENV.toUpperCase()}") - DEPLOY_ENV="${params.DEPLOY_ENV}" - } - when { - expression { params.DEPLOY_TYPE == 'Clean' } - } - steps { - withCredentials([sshUserPrivateKey(credentialsId: "VTP_${params.DEPLOY_ENV.toUpperCase()}_SSH_KEY", keyFileVariable: 'keyfile', usernameVariable: 'sshuser')]) { - sh 'env' - sh 'echo "$DEPLOY_ENV: len $(echo -n $VAULT_PASSWORD_BUILDENV | /usr/bin/wc -c) sum $(echo -n $VAULT_PASSWORD_BUILDENV | /usr/bin/sum) "' - sh 'pipenv run ansible-playbook -u ${sshuser} --private-key=${keyfile} -e buildenv=$DEPLOY_ENV -e clusterid=$CLUSTER_ID --vault-id=$DEPLOY_ENV@.vaultpass-client.py cluster.yml --tags=clusterverse_clean -e clean=_all_' - } - } } } -} +} \ No newline at end of file diff --git a/EXAMPLE/jenkinsfiles/Jenkinsfile_exec_release b/EXAMPLE/jenkinsfiles/Jenkinsfile_exec_release deleted file mode 100644 index 82d3acb6..00000000 --- a/EXAMPLE/jenkinsfiles/Jenkinsfile_exec_release +++ /dev/null @@ -1,55 +0,0 @@ -#!groovy -import groovy.json.JsonOutput - -def VERSION_TO_DEPLOY = '' - -pipeline { - agent any - parameters { - string(name: 'NEW_VERSION', defaultValue:"version", description: "Specify the version to be created eg: v1.0.0") - } - triggers { - cron('0 9-16/1 * * 1-5') - } - stages { - stage('Create Release') { - environment { - NEW_VERSION="${params.NEW_VERSION}" - GIT_TOKEN = credentials("GITHUB_SVC_USER") - } - steps { - script { - def apiUrl = "https://api.github.com/repos/sky-uk/clusterverse/releases" - def latestReleaseQuery = sh(returnStdout: true, script: "curl -s -H \"Authorization: Token ${env.GIT_TOKEN_PSW}\" -H \"Accept: application/json\" -H \"Content-type: application/json\" -X GET ${apiUrl}/latest").trim() - def latestRelease = readJSON text: "${latestReleaseQuery}" - if (NEW_VERSION == "version") { - String version=latestRelease.tag_name - String minor=version.substring(version.lastIndexOf('.')+1) - int m=minor.toInteger()+1 - int index=version.lastIndexOf('.')+1 - String major=version.substring(0,version.lastIndexOf('.')+1) - NEW_VERSION = "${major}${m}" - } - def body = sh(returnStdout: true, script: "git log ${latestRelease.tag_name}..HEAD --pretty=format:\"
  • %H - %s
  • \"").trim() - if (body != "") { - def payload = JsonOutput.toJson(["tag_name": "${NEW_VERSION}", "name": "${NEW_VERSION}", "body": "${body}"]) - def response = sh(returnStdout: true, script: "curl -s -H \"Authorization: Token ${env.GIT_TOKEN_PSW}\" -H \"Accept: application/json\" -H \"Content-type: application/json\" -X POST -d '${payload}' ${apiUrl}").trim() - echo "${NEW_VERSION} is now created" - VERSION_TO_DEPLOY = "${NEW_VERSION}" - } else { - error "No change since last release" - } - } - } - } - // Uncomment the stage below as to trigger a downstream deployment job. - // stage('Trigger deploy') { - // steps { - // script { - // println "Release ${VERSION_TO_DEPLOY} is being deployed in sandbox" - // build job: "clusterverse-release-deploy", wait: false, parameters: [string(name: 'GENUINE_BUILD', value: "true"), string(name: 'DEPLOY_ENV', value: "sandbox"), string(name: 'RELEASE', value: "${VERSION_TO_DEPLOY}")] - // } - // } - // } - } -} diff --git a/EXAMPLE/jenkinsfiles/Jenkinsfile_exec_release_deploy b/EXAMPLE/jenkinsfiles/Jenkinsfile_exec_release_deploy deleted file mode 100644 index a7b9e7ae..00000000 --- a/EXAMPLE/jenkinsfiles/Jenkinsfile_exec_release_deploy +++ /dev/null @@ -1,73 +0,0 @@ -#!groovy -import groovy.json.JsonOutput - - -pipeline { - agent any - parameters { - booleanParam(name: 'GENUINE_BUILD', defaultValue: false, description: 'Tick the box to run the job') - string(name: 'CLUSTER_ID', defaultValue:"vtp_aws_euw1", description: "MANDATORY FIELD - 'vtp_aws_euw1', 'csc_aws_euw1', 'vtp_gcp_euw1', 'vtp_lsd_slo' - Specify which cloud/on-prem environment you want to deploy your release to") - choice(name: 'DEPLOY_ENV', choices: ['sandbox', 'tools', 'dev', 'stage', 'prod'], description: "Choose an environment to deploy") - string(name: 'CANARY', defaultValue:"none", description: "MANDATORY FIELD - 'start', 'finish', 'none' - Specify whether to start or finish a canary deploy, or 'none' deploy") - string(name: 'MYHOSTTYPES', defaultValue:"", description: "master,slave - In redeployment you can define which host type you like to redeploy. If not defined it will redeploy all host types") - gitParameter(name: 'RELEASE', type: 'PT_TAG', defaultValue: 'master', sortMode: 'DESCENDING_SMART', description: "Choose the version to deploy") - } - stages { - stage('Init Environment') { - environment { - GENUINE_BUILD="${params.GENUINE_BUILD}" - } - steps { - script{ - if (GENUINE_BUILD == "false"){ - error "Parameters not specified, Tick the GENUINE_BUILD box to run the job" - } - } - sh 'env' - sh 'pipenv install --python /usr/bin/python3' - } - } - stage('ReDeploy Release') { - environment { - DEPLOY_ENV="${params.DEPLOY_ENV}" - GIT_RELEASE ="${params.RELEASE}" - IAC_RELEASE ="${params.RELEASE.replace('.', '_')}" - GIT_TOKEN = credentials("GITHUB_SVC_USER") - VAULT_PASSWORD_BUILDENV = credentials("VAULT_PASSWORD_${params.DEPLOY_ENV.toUpperCase()}") - CANARY="-e canary=${params.CANARY}" - MYHOSTTYPES="-e myhosttypes=${params.MYHOSTTYPES}" - } - steps { - script { - try { - echo "GIT_RELEASE= ${GIT_RELEASE}" - echo "IAC_RELEASE= ${IAC_RELEASE}" - sh "git remote set-url origin https://${GIT_TOKEN_USR}:${GIT_TOKEN_PSW}@github.com/sky-uk/clusterverse.git" - sh 'git fetch --tags' - sh 'git checkout ${GIT_RELEASE}' - withCredentials([sshUserPrivateKey(credentialsId: "VTP_${params.DEPLOY_ENV.toUpperCase()}_SSH_KEY", keyFileVariable: 'keyfile', usernameVariable: 'sshuser')]) { - sh 'env' - sh 'echo "$DEPLOY_ENV: len $(echo -n $VAULT_PASSWORD_BUILDENV | /usr/bin/wc -c) sum $(echo -n $VAULT_PASSWORD_BUILDENV | /usr/bin/sum)"' - sh 'pipenv run ansible-playbook -u ${sshuser} --private-key=${keyfile} -e buildenv=$DEPLOY_ENV -e clusterid=$CLUSTER_ID --vault-id=$DEPLOY_ENV@.vaultpass-client.py redeploy.yml $CANARY $MYHOSTTYPES -e release_version=$IAC_RELEASE' - } - } catch (err) { - // echo "Failed: ${err} - Version ${broken_version} will be deleted and the previous version will be deployed." - // def stable_version = sh(returnStdout: true, script: "git describe --abbrev=0 --tags `git rev-list --tags --skip=1 --max-count=1`").trim() - // def broken_version = sh(returnStdout: true, script: "git describe --tags `git rev-list --tags --max-count=1`").trim() - // sh "git checkout -b ${broken_version}-broken-version" - // sh "git push -u origin ${broken_version}-broken-version" - // sh "git push --delete origin ${broken_version}" - // sh 'git fetch --tags' - // sh "git checkout ${stable_version}" - // withCredentials([sshUserPrivateKey(credentialsId: "VTP_${params.DEPLOY_ENV.toUpperCase()}_SSH_KEY", keyFileVariable: 'keyfile', usernameVariable: 'sshuser')]) { - // sh 'env' - // sh 'echo "$DEPLOY_ENV: len $(echo -n $VAULT_PASSWORD_BUILDENV | /usr/bin/wc -c) sum $(echo -n $VAULT_PASSWORD_BUILDENV | /usr/bin/sum) "' - // sh 'pipenv run ansible-playbook -u ${sshuser} --private-key=${keyfile} -e buildenv=$DEPLOY_ENV -e clusterid=$CLUSTER_ID --vault-id=$DEPLOY_ENV@.vaultpass-client.py cluster.yml -e clean=_all_ --tags=clusterverse_clean' - // } - error "${GIT_RELEASE} deployment failed" - } - } - } - } - } -} diff --git a/EXAMPLE/jenkinsfiles/Jenkinsfile_exec_release_tag b/EXAMPLE/jenkinsfiles/Jenkinsfile_exec_release_tag new file mode 100644 index 00000000..1ad41005 --- /dev/null +++ b/EXAMPLE/jenkinsfiles/Jenkinsfile_exec_release_tag @@ -0,0 +1,44 @@ +#!groovy +import groovy.json.JsonOutput + +pipeline { + agent any + parameters { + string(name: 'NEW_VERSION', defaultValue: "", description: "Specify either the version to be created (e.g.: v1.0.0), or 'next' to apply the next patch version.") + } + stages { + stage('Create Release') { + environment { + GIT_TOKEN = credentials("GITHUB_SVC_USER") + } + steps { + script { + def new_version = params.NEW_VERSION + if (new_version == "") { + error "NEW_VERSION parameter not specified. Specify either the version to be created (e.g.: v1.0.0), or 'next' to apply the next patch version." + } else { + def apiUrl = "https://api.github.com/repos/sky-uk/clusterverse/releases" + def latestReleaseQuery = sh(returnStdout: true, script: "curl -s -H \"Authorization: Token ${env.GIT_TOKEN_PSW}\" -H \"Accept: application/json\" -H \"Content-type: application/json\" -X GET ${apiUrl}/latest").trim() + def latestRelease = readJSON text: "${latestReleaseQuery}" + if (new_version == "next") { + String version = latestRelease.tag_name + String minor = version.substring(version.lastIndexOf('.') + 1) + int m = minor.toInteger() + 1 + int index = version.lastIndexOf('.') + 1 + String major = version.substring(0, version.lastIndexOf('.') + 1) + new_version = "${major}${m}" + } + def body = sh(returnStdout: true, script: "git log ${latestRelease.tag_name}..HEAD --pretty=format:\"
  • %H - %s
  • \"").trim() + if (body != "") { + def payload = JsonOutput.toJson(["tag_name": new_version, "name": new_version, "body": "${body}"]) + def response = sh(returnStdout: true, script: "curl -s -H \"Authorization: Token ${env.GIT_TOKEN_PSW}\" -H \"Accept: application/json\" -H \"Content-type: application/json\" -X POST -d '${payload}' ${apiUrl}").trim() + echo "${new_version} is now created" + } else { + error "No change since last release" + } + } + } + } + } + } +} \ No newline at end of file diff --git a/EXAMPLE/jenkinsfiles/Jenkinsfile_exec_testsuite b/EXAMPLE/jenkinsfiles/Jenkinsfile_exec_testsuite new file mode 100644 index 00000000..752cee56 --- /dev/null +++ b/EXAMPLE/jenkinsfiles/Jenkinsfile_exec_testsuite @@ -0,0 +1,142 @@ +#!groovy +import java.security.MessageDigest + +String generateMD5(String s, int len = 31) { + MessageDigest.getInstance("MD5").digest(s.bytes).encodeHex().toString()[0..len] as String +} + +pipeline { + agent any + + //NOTE: As yet, you cannot use variable for the 'value' axis of a matrix build, so parameters aren't very useful. + parameters { + choice(name: 'CLOUD_REGION', choices: ['', 'aws/eu-west-1', 'gcp/europe-west1'], description: "Choose a cloud/region") + gitParameter(name: 'RELEASE', type: 'PT_TAG', defaultValue: 'master', sortMode: 'DESCENDING_SMART', description: "Choose a release to test") +// string(name: 'APP_NAME', description: "An optional custom app_name to override the default in the playbook") +// extendedChoice name: 'CLOUD_REGIONS', type: 'PT_MULTI_SELECT', value: 'esxifree/dougalab,aws/eu-west-1,gcp/europe-west1', defaultValue: '', description: 'Specify which cloud/region(s) to test', multiSelectDelimiter: ',', quoteValue: false, saveJSONParameterToFile: false, visibleItemCount: 5 +// extendedChoice name: 'BUILDENVS', type: 'PT_MULTI_SELECT', value: 'dev,sandbox', defaultValue: '', description: 'Specify which environment(s) to test', multiSelectDelimiter: ',', quoteValue: false, saveJSONParameterToFile: false, visibleItemCount: 5 +// extendedChoice name: 'CLUSTER_IDS', type: 'PT_MULTI_SELECT', value: 'testsuite', defaultValue: '', description: 'Specify which cluster_id(s) to test', multiSelectDelimiter: ',', quoteValue: false, saveJSONParameterToFile: false, visibleItemCount: 5 +// booleanParam(name: 'DNS_FORCE_DISABLE_TEST', defaultValue: false, description: 'Tick the box to test DNS on/off') +// extendedChoice name: 'REDEPLOY_SCHEMES', type: 'PT_MULTI_SELECT', value: '_scheme_addallnew_rmdisk_rollback,_scheme_addnewvm_rmdisk_rollback,_scheme_rmvm_rmdisk_only,_scheme_rmvm_keepdisk_rollback', defaultValue: '', description: 'Specify which redeploy scheme(s) to test', multiSelectDelimiter: ',', quoteValue: false, saveJSONParameterToFile: false, visibleItemCount: 5 +// string(name: 'MYHOSTTYPES', description: "comma-separated string, e.g. master,slave - In redeployment you can define which host type you like to redeploy. If not defined it will redeploy all host types") +// extendedChoice name: 'OS', type: 'PT_MULTI_SELECT', value: 'ubuntu,centos', defaultValue: '', description: 'Specify which OSs to test', multiSelectDelimiter: ',', quoteValue: false, saveJSONParameterToFile: false, visibleItemCount: 5 +//// extendedChoice bindings: '', description: '', groovyClasspath: '', groovyScript: ''' List artifacts = new ArrayList() +//// artifacts.add("dpstest_aws_euw1") +//// artifacts.add("dpstest_esxi_dougalab") +//// artifacts.add("dpstest_gcp_euw1") +//// return artifacts +////''', multiSelectDelimiter: ',', name: 'group_vars', quoteValue: false, saveJSONParameterToFile: false, type: 'PT_MULTI_SELECT', visibleItemCount: 4 + } + + stages { + stage('Check Environment') { + steps { + script { + if (params.CLOUD_REGION == '') { + error "CLOUD_REGION not defined" + } + } + } + } + stage('MatrixBuild') { + matrix { + axes { +// axis { +// name 'CLOUD_REGION' +// values 'gcp/europe-west1', 'aws/eu-west-1' +// } + axis { + name 'BUILDENV' + values 'sandbox' + } + axis { + name 'REDEPLOY_SCHEME' + values '_scheme_addallnew_rmdisk_rollback', '_scheme_addnewvm_rmdisk_rollback', '_scheme_rmvm_rmdisk_only', '_scheme_rmvm_keepdisk_rollback' + } + axis { + name 'DNS_FORCE_DISABLE' + values false, true + } + axis { + name 'CLUSTER_ID' + values 'testsuite' + } + axis { + name 'MYHOSTTYPES' + values '' + } + } + excludes { + exclude { + axis { + name 'DNS_FORCE_DISABLE' + values true + } + axis { + name 'CLOUD_REGION' + values 'esxifree/dougalab' + } + } + exclude { + axis { + name 'MYHOSTTYPES' + notValues '' + } + axis { + name 'REDEPLOY_SCHEME' + values '_scheme_addallnew_rmdisk_rollback' + } + } + } + environment { + //Setting this here to mimic matrix behaviour in case (https://issues.jenkins.io/browse/JENKINS-37984) gets fixed. We cannot run all clouds simultaneously in one matrix - get "java.lang.RuntimeException: Method code too large!" + CLOUD_REGION = "${params.CLOUD_REGION}" + VAULT_PASSWORD_BUILDENV = credentials("VAULT_PASSWORD_${env.BUILDENV.toUpperCase()}") + BUILD_HASH = generateMD5("${env.CLOUD_REGION} ${env.BUILDENV} ${env.REDEPLOY_SCHEME ? env.REDEPLOY_SCHEME : ''} ${env.DNS_FORCE_DISABLE} ${env.CLUSTER_ID} ${env.MYHOSTTYPES ? env.MYHOSTTYPES : ''}", 12) +// BUILD_COMBINATION = "CLOUD_REGION:${env.CLOUD_REGION} BUILDENV:${env.BUILDENV} REDEPLOY_SCHEME:${env.REDEPLOY_SCHEME ? env.REDEPLOY_SCHEME : ''} DNS_FORCE_DISABLE:${DNS_FORCE_DISABLE} CLUSTER_ID:${env.CLUSTER_ID} MYHOSTTYPES:${env.MYHOSTTYPES ? env.MYHOSTTYPES : ''}" + } + stages { + stage('Init') { + steps { + sh 'printenv | sort' + echo "BUILD_HASH = ${env.BUILD_HASH}" + echo "BUILD_COMBINATION = CLOUD_REGION:${env.CLOUD_REGION} BUILDENV:${env.BUILDENV} REDEPLOY_SCHEME:${env.REDEPLOY_SCHEME ? env.REDEPLOY_SCHEME : ''} DNS_FORCE_DISABLE:${DNS_FORCE_DISABLE} CLUSTER_ID:${env.CLUSTER_ID} MYHOSTTYPES:${env.MYHOSTTYPES ? env.MYHOSTTYPES : ''}" + } + } + stage('deploy') { + steps { + sh 'printenv | sort' + build job: 'clusterverse-deploy', parameters: [string(name: 'APP_NAME', value: "cvtest-${BUILD_NUMBER}-${env.BUILD_HASH}"), booleanParam(name: 'APPEND_BUILD_NUMBER', value: false), string(name: 'CLOUD_REGION', value: env.CLOUD_REGION), string(name: 'BUILDENV', value: env.BUILDENV), string(name: 'CLUSTER_ID', value: env.CLUSTER_ID), booleanParam(name: 'DNS_FORCE_DISABLE', value: env.DNS_FORCE_DISABLE), string(name: 'DEPLOY_TYPE', value: 'deploy'), string(name: 'REDEPLOY_SCHEME', value: ''), string(name: 'CANARY', value: 'none'), booleanParam(name: 'CANARY_TIDY_ON_SUCCESS', value: true), string(name: 'MYHOSTTYPES', value: ''), gitParameter(name: 'RELEASE', value: 'master')] + } + } + stage('redeploy canary=start,finish,tidy') { + steps { + sh 'printenv | sort' + build job: 'clusterverse-deploy', parameters: [string(name: 'APP_NAME', value: "cvtest-${env.BUILD_NUMBER}-${env.BUILD_HASH}"), booleanParam(name: 'APPEND_BUILD_NUMBER', value: false), string(name: 'CLOUD_REGION', value: env.CLOUD_REGION), string(name: 'BUILDENV', value: env.BUILDENV), string(name: 'CLUSTER_ID', value: env.CLUSTER_ID), booleanParam(name: 'DNS_FORCE_DISABLE', value: DNS_FORCE_DISABLE), string(name: 'DEPLOY_TYPE', value: 'redeploy'), string(name: 'REDEPLOY_SCHEME', value: (env.REDEPLOY_SCHEME ? env.REDEPLOY_SCHEME : '')), string(name: 'CANARY', value: 'start'), booleanParam(name: 'CANARY_TIDY_ON_SUCCESS', value: false), string(name: 'MYHOSTTYPES', value: (env.MYHOSTTYPES ? env.MYHOSTTYPES : '')), gitParameter(name: 'RELEASE', value: 'master')] + build job: 'clusterverse-deploy', parameters: [string(name: 'APP_NAME', value: "cvtest-${env.BUILD_NUMBER}-${env.BUILD_HASH}"), booleanParam(name: 'APPEND_BUILD_NUMBER', value: false), string(name: 'CLOUD_REGION', value: env.CLOUD_REGION), string(name: 'BUILDENV', value: env.BUILDENV), string(name: 'CLUSTER_ID', value: env.CLUSTER_ID), booleanParam(name: 'DNS_FORCE_DISABLE', value: DNS_FORCE_DISABLE), string(name: 'DEPLOY_TYPE', value: 'redeploy'), string(name: 'REDEPLOY_SCHEME', value: (env.REDEPLOY_SCHEME ? env.REDEPLOY_SCHEME : '')), string(name: 'CANARY', value: 'finish'), booleanParam(name: 'CANARY_TIDY_ON_SUCCESS', value: false), string(name: 'MYHOSTTYPES', value: (env.MYHOSTTYPES ? env.MYHOSTTYPES : '')), gitParameter(name: 'RELEASE', value: 'master')] + build job: 'clusterverse-deploy', parameters: [string(name: 'APP_NAME', value: "cvtest-${env.BUILD_NUMBER}-${env.BUILD_HASH}"), booleanParam(name: 'APPEND_BUILD_NUMBER', value: false), string(name: 'CLOUD_REGION', value: env.CLOUD_REGION), string(name: 'BUILDENV', value: env.BUILDENV), string(name: 'CLUSTER_ID', value: env.CLUSTER_ID), booleanParam(name: 'DNS_FORCE_DISABLE', value: DNS_FORCE_DISABLE), string(name: 'DEPLOY_TYPE', value: 'redeploy'), string(name: 'REDEPLOY_SCHEME', value: (env.REDEPLOY_SCHEME ? env.REDEPLOY_SCHEME : '')), string(name: 'CANARY', value: 'tidy'), booleanParam(name: 'CANARY_TIDY_ON_SUCCESS', value: false), string(name: 'MYHOSTTYPES', value: (env.MYHOSTTYPES ? env.MYHOSTTYPES : '')), gitParameter(name: 'RELEASE', value: 'master')] + } + } + stage('redeploy canary=none (tidy_on_success)') { + steps { + sh 'printenv | sort' + build job: 'clusterverse-deploy', parameters: [string(name: 'APP_NAME', value: "cvtest-${env.BUILD_NUMBER}-${env.BUILD_HASH}"), booleanParam(name: 'APPEND_BUILD_NUMBER', value: false), string(name: 'CLOUD_REGION', value: env.CLOUD_REGION), string(name: 'BUILDENV', value: env.BUILDENV), string(name: 'CLUSTER_ID', value: env.CLUSTER_ID), booleanParam(name: 'DNS_FORCE_DISABLE', value: env.DNS_FORCE_DISABLE), string(name: 'DEPLOY_TYPE', value: 'redeploy'), string(name: 'REDEPLOY_SCHEME', value: (env.REDEPLOY_SCHEME ? env.REDEPLOY_SCHEME : '')), string(name: 'CANARY', value: 'none'), booleanParam(name: 'CANARY_TIDY_ON_SUCCESS', value: true), string(name: 'MYHOSTTYPES', value: (env.MYHOSTTYPES ? env.MYHOSTTYPES : '')), gitParameter(name: 'RELEASE', value: 'master')] + } + } + stage('deploy on top') { + steps { + sh 'printenv | sort' + build job: 'clusterverse-deploy', parameters: [string(name: 'APP_NAME', value: "cvtest-${env.BUILD_NUMBER}-${env.BUILD_HASH}"), booleanParam(name: 'APPEND_BUILD_NUMBER', value: false), string(name: 'CLOUD_REGION', value: env.CLOUD_REGION), string(name: 'BUILDENV', value: env.BUILDENV), string(name: 'CLUSTER_ID', value: env.CLUSTER_ID), booleanParam(name: 'DNS_FORCE_DISABLE', value: env.DNS_FORCE_DISABLE), string(name: 'DEPLOY_TYPE', value: 'deploy'), string(name: 'REDEPLOY_SCHEME', value: ''), string(name: 'CANARY', value: 'none'), booleanParam(name: 'CANARY_TIDY_ON_SUCCESS', value: true), string(name: 'MYHOSTTYPES', value: ''), gitParameter(name: 'RELEASE', value: 'master')] + } + } + stage('clean') { + steps { + sh 'printenv | sort' + build job: 'clusterverse-deploy', parameters: [string(name: 'APP_NAME', value: "cvtest-${env.BUILD_NUMBER}-${env.BUILD_HASH}"), booleanParam(name: 'APPEND_BUILD_NUMBER', value: false), string(name: 'CLOUD_REGION', value: env.CLOUD_REGION), string(name: 'BUILDENV', value: env.BUILDENV), string(name: 'CLUSTER_ID', value: env.CLUSTER_ID), booleanParam(name: 'DNS_FORCE_DISABLE', value: env.DNS_FORCE_DISABLE), string(name: 'DEPLOY_TYPE', value: 'clean'), string(name: 'REDEPLOY_SCHEME', value: ''), string(name: 'CANARY', value: 'none'), booleanParam(name: 'CANARY_TIDY_ON_SUCCESS', value: true), string(name: 'MYHOSTTYPES', value: ''), gitParameter(name: 'RELEASE', value: 'master')] + } + } + } + } + } + } +} \ No newline at end of file diff --git a/README.md b/README.md index d963d3d9..8c95cc35 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A full-lifecycle, immutable cloud infrastructure cluster management **role**, using Ansible. + **Multi-cloud:** clusterverse can manage cluster lifecycle in AWS and GCP + **Deploy:** You define your infrastructure as code (in Ansible yaml), and clusterverse will deploy it -+ **Scale (e.g. add a node):** If you change the config yaml and rerun the deploy, new nodes will be added. ++ **Scale-up:** If you change the cluster definitions and rerun the deploy, new nodes will be added. + **Redeploy (e.g. up-version):** If you need to up-version, the `redeploy.yml` playbook will replace each node in turn, (with optional callbacks), and rollback if any failures occur. **clusterverse** is designed to manage base-vm infrastructure that underpins cluster-based infrastructure, for example, Couchbase, Kafka, Elasticsearch, or Cassandra. @@ -13,7 +13,7 @@ Contributions are welcome and encouraged. Please see [CONTRIBUTING.md](https:// ## Requirements ### Python dependencies -Dependencies are managed via Pipenv: +Dependencies are managed via pipenv: + `pipenv install` will create a Python virtual environment with dependencies specified in the Pipfile To active the pipenv: @@ -21,28 +21,113 @@ To active the pipenv: + or prepend the ansible-playbook commands with: `pipenv run` ### AWS -+ AWS account with IAM rights to create EC2 VMs + Security Groups in the chosen VPC/Subnets -+ Preexisting VPCs -+ Preexisting subnets ++ AWS account with IAM rights to create EC2 VMs and security groups in the chosen VPCs/subnets. Place the credentials in: + + `cluster_vars//aws_access_key:` + + `cluster_vars//aws_secret_key:` ++ Preexisting VPCs: + + `cluster_vars//vpc_name: my-vpc-{{buildenv}}` ++ Preexisting subnets. This is a prefix - the cloud availability zone will be appended to the end (e.g. `a`, `b`, `c`). + + `cluster_vars//vpc_subnet_name_prefix: my-subnet-{{region}}` ++ Preexisting keys (in AWS IAM): + + `cluster_vars//key_name: my_key__id_rsa` ### GCP + Create a gcloud account. -+ Create a service account in `IAM & Admin` / `Service Accounts`. Download the json file locally. - + This file is used in the `GCP_CREDENTIALS` environment variable that is read in `group_vars//cluster_vars.yml`. - + You need to export this variable (e.g. `export GCP_CREDENTIALS=/home//src/gcp.json`). ++ Create a service account in `IAM & Admin` / `Service Accounts`. Download the json file locally. ++ Store the contents within the `cluster_vars/service_account_rawtext` variable. + + During execution, the json file will be copied locally because the Ansible GCP modules often require the file as input. + Google Cloud SDK needs to be installed to run gcloud command-line (e.g. to disable delete protection) - this is handled by `pipenv install` ### DNS -DNS is optional. If unset, no DNS names will be created. If required, you will need a DNS zone delegated to one of the following: -+ Bind9 +DNS is optional. If unset, no DNS names will be created. If DNS is required, you will need a DNS zone delegated to one of the following: ++ nsupdate (e.g. bind9) + AWS Route53 + Google Cloud DNS -Credentials to the DNS server will also be required. These are specified in the `cluster_vars.yml` file described below. +Credentials to the DNS server will also be required. These are specified in the `cluster_vars` variable described below. -### Cloud credential management + +### Cluster Definition Variables +Clusters are defined as code within Ansible yaml files that are imported at runtime. Because clusters are built from scratch on the localhost, the automatic Ansible `group_vars` inclusion cannot work with anything except the special `all.yml` group (actual `groups` need to be in the inventory, which cannot exist until the cluster is built). The `group_vars/all.yml` file is instead used to bootstrap _merge_vars_. + +#### merge_vars +Clusterverse is designed to be used to deploy the same clusters in multiple clouds and multiple environments, potentially using similar configurations. In order to avoid duplicating configuration (adhering to the [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) principle), a new [action plugin](https://docs.ansible.com/ansible/latest/dev_guide/developing_plugins.html#action-plugins) has been developed (called `merge_vars`) to use in place of the standard `include_vars`, which allows users to define the variables hierarchically, and include (and potentially override) those defined before them. This plugin is similar to `include_vars`, but when it finds dictionaries that have already been defined, it _combines_ them instead of replacing them. + +```yaml +- merge_vars: + ignore_missing_files: True + from: "{{ merge_dict_vars_list }}" #defined in `group_vars/all.yml` +``` + + The variable _ignore_missing_files_ can be set such that any files or directories that are not found in the defined 'from' list will not raise an error. + +
    + +##### merge_dict_vars_list - hierarchical: +In the case of a fully hierarchical set of cluster definitions where each directory is a variable, (e.g. _cloud_ (aws or gcp), _region_ (eu-west-1) and _cluster_id_ (test)), the folders may look like: + +```text +|-- aws +| |-- eu-west-1 +| | |-- sandbox +| | | |-- test +| | | | `-- cluster_vars.yml +| | | `-- cluster_vars.yml +| | `-- cluster_vars.yml +| `-- cluster_vars.yml +|-- gcp +| |-- europe-west1 +| | `-- sandbox +| | |-- test +| | | `-- cluster_vars.yml +| | `-- cluster_vars.yml +| `-- cluster_vars.yml +|-- app_vars.yml +`-- cluster_vars.yml +``` + +`group_vars/all.yml` would contain `merge_dict_vars_list` with the files and directories, listed from top to bottom in the order in which they should override their predecessor: +```yaml +merge_dict_vars_list: + - "./cluster_defs/cluster_vars.yml" + - "./cluster_defs/app_vars.yml" + - "./cluster_defs/{{ cloud_type }}/" + - "./cluster_defs/{{ cloud_type }}/{{ region }}/" + - "./cluster_defs/{{ cloud_type }}/{{ region }}/{{ buildenv }}/" + - "./cluster_defs/{{ cloud_type }}/{{ region }}/{{ buildenv }}/{{ clusterid }}/" +``` + +
    + +##### merge_dict_vars_list - flat: + +It is also valid to define all the variables in a single sub-directory: +```text +cluster_defs/ +|-- test_aws_euw1 +| |-- app_vars.yml +| +-- cluster_vars.yml ++-- test_gcp_euw1 + |-- app_vars.yml + +-- cluster_vars.yml +``` +In this case, `merge_dict_vars_list` would be only the top-level directory (using `cluster_id` as a variable). `merge_vars` does not recurse through directories. +```yaml +merge_dict_vars_list: + - "./cluster_defs/{{ clusterid }}" +``` + +
    + +#### /group_vars/{{cluster_id}}/*.yml: +If `merge_dict_vars_list` is not defined, it is still possible to put the flat variables in `/group_vars/{{cluster_id}}`, where they will be imported using the standard `include_vars` plugin. + +This functionality offers no advantages over simply defining the same cluster yaml files in the directory structure defined in `merge_dict_vars_list - flat` merge_vars technique above, and that is considered preferred. + +
    + +### Cloud Credential Management Credentials can be encrypted inline in the playbooks using [ansible-vault](https://docs.ansible.com/ansible/latest/user_guide/vault.html). -+ Because multiple environments are supported, it is recommended to use [vault-ids](https://docs.ansible.com/ansible/latest/user_guide/vault.html#multiple-vault-passwords), and have credentials per environment (e.g. to help avoid accidentally running a deploy on prod). ++ Because multiple environments are supported, it is recommended to use [vault-ids](https://docs.ansible.com/ansible/latest/user_guide/vault.html#managing-multiple-passwords-with-vault-ids), and have credentials per environment (e.g. to help avoid accidentally running a deploy on prod). + There is a small script (`.vaultpass-client.py`) that returns a password stored in an environment variable (`VAULT_PASSWORD_BUILDENV`) to ansible. Setting this variable is mandatory within Clusterverse as if you need to decrypt sensitive data within `ansible-vault`, the password set within the variable will be used. This is particularly useful for running within Jenkins. + `export VAULT_PASSWORD_BUILDENV=<'dev/stage/prod' password>` + To encrypt sensitive information, you must ensure that your current working dir can see the script `.vaultpass-client.py` and `VAULT_PASSWORD_BUILDENV` has been set: @@ -56,7 +141,7 @@ Credentials can be encrypted inline in the playbooks using [ansible-vault](https ``` aws_secret_key: !vault |- $ANSIBLE_VAULT;1.2;AES256;sandbox - 306164313163633832323236323462333438323061663737666331366631303735666466626434393830356461363464633264623962343262653433383130390a343964393336343564393862316132623734373132393432396366626231376232636131666430666366636466393664353435323561326338333863633131620a66393563663736353032313730613762613864356364306163363338353330383032313130663065666264396433353433363062626465303134613932373934 + 7669080460651349243347331538721104778691266429457726036813912140404310 ``` + Notice `!vault |-` this is compulsory in order for the hash to be successfully decrypted + To decrypt, either run the playbook with the correct `VAULT_PASSWORD_BUILDENV` and just `debug: msg={{myvar}}`, or: @@ -77,16 +162,9 @@ To import the role into your project, create a [`requirements.yml`](https://gith version: master ## branch, hash, or tag name: clusterverse ``` -If you use a `cluster.yml` file similar to the example found in [EXAMPLE/cluster.yml](https://github.com/sky-uk/clusterverse/blob/master/EXAMPLE/cluster.yml), clusterverse will be installed automatically on each run of the playbook. - -To install it manually: -+ `ansible-galaxy install -r requirements.yml -p //roles/` - ++ If you use a `cluster.yml` file similar to the example found in [EXAMPLE/cluster.yml](https://github.com/sky-uk/clusterverse/blob/master/EXAMPLE/cluster.yml), clusterverse will be installed from Ansible Galaxy _automatically_ on each run of the playbook. -### Cluster Variables -+ The clusters are defined as code, within Ansible yaml files that are automatically imported. -+ One of the mandatory command-line variables is `clusterid`, which defines the name of the directory under `group_vars`, from which variable files will be imported. -+ Please see the full AWS and GCP [example group_vars](https://github.com/sky-uk/clusterverse/tree/master/EXAMPLE/group_vars/) ++ To install it manually: `ansible-galaxy install -r requirements.yml -p //roles/` ### Invocation @@ -96,7 +174,7 @@ _**For full invocation examples and command-line arguments, please see the [exam The role is designed to run in two modes: #### Deploy (also performs _scaling_ and _repairs_) + A playbook based on the [cluster.yml example](https://github.com/sky-uk/clusterverse/tree/master/EXAMPLE/cluster.yml) will be needed. -+ The `cluster.yml` sub-role immutably deploys a cluster from the config defined above. If it is run again it will do nothing. If the cluster_vars are changed (e.g. add a host), the cluster will reflect the new variables (e.g. a new host will be added to the cluster). ++ The `cluster.yml` sub-role immutably deploys a cluster from the config defined above. If it is run again (with no changes to variables), it will do nothing. If the cluster variables are changed (e.g. add a host), the cluster will reflect the new variables (e.g. a new host will be added to the cluster. Note: it _will not remove_ nodes, nor, usually, will it reflect changes to disk volumes - these are limitations of the underlying cloud modules). #### Redeploy @@ -108,9 +186,10 @@ The role is designed to run in two modes: + `predeleterole`: This is the name of a role that should be called prior to deleting VMs; it is used for example to eject nodes from a Couchbase cluster. It takes a list of `hosts_to_remove` VMs. + It supports pluggable redeployment schemes. The following are provided: + **_scheme_rmvm_rmdisk_only** - + This is a very basic rolling redeployment of the cluster. + + This is a very basic rolling redeployment of the cluster. + + Canary **is not** supported. + _Supports redploying to bigger, but not smaller clusters_ - + **It assumes a resilient deployment (it can tolerate one node being deleted from the cluster). There is no rollback in case of failure** + + **It assumes a resilient deployment (it can tolerate one node being deleted from the cluster). There is no rollback in case of failure.** + For each node in the cluster: + Run `predeleterole` + Delete/ terminate the node (note, this is _irreversible_). @@ -123,17 +202,19 @@ The role is designed to run in two modes: + Create a new VM + Run `predeleterole` on the previous node + Shut down the previous node. + + If `canary=start`, only the first node is redeployed. If `canary=finish`, only the remaining (non-first), nodes are redeployed. If `canary=none`, all nodes are redeployed. + If the process fails for any reason, the old VMs are reinstated, and any new VMs that were built are stopped (rollback) + To delete the old VMs, either set '-e canary_tidy_on_success=true', or call redeploy.yml with '-e canary=tidy' + **_scheme_addallnew_rmdisk_rollback** + _Supports redploying to bigger or smaller clusters_ - + A full mirror of the cluster is deployed. - + If the process proceeds correctly: - + `predeleterole` is called with a list of the old VMs. - + The old VMs are stopped. + + If `canary=start` or `canary=none` + + A full mirror of the cluster is deployed. + + If `canary=finish` or `canary=none`: + + `predeleterole` is called with a list of the old VMs. + + The old VMs are stopped. + If the process fails for any reason, the old VMs are reinstated, and the new VMs stopped (rollback) + To delete the old VMs, either set '-e canary_tidy_on_success=true', or call redeploy.yml with '-e canary=tidy' - + **_scheme_rmvm_keepdisk_rollback (AWS only so far)** + + **_scheme_rmvm_keepdisk_rollback** + Redeploys the nodes one by one, and moves the secondary (non-root) disks from the old to the new (note, only non-ephemeral disks can be moved). + _Cluster topology must remain identical. More disks may be added, but none may change or be removed._ + **It assumes a resilient deployment (it can tolerate one node being removed from the cluster).** @@ -143,5 +224,6 @@ The role is designed to run in two modes: + Detach the disks from the old node + Run the main cluster.yml to create a new node + Attach disks to new node + + If `canary=start`, only the first node is redeployed. If `canary=finish`, only the remaining (non-first), nodes are replaced. If `canary=none`, all nodes are redeployed. + If the process fails for any reason, the old VMs are reinstated (and the disks reattached to the old nodes), and the new VMs are stopped (rollback) + To delete the old VMs, either set '-e canary_tidy_on_success=true', or call redeploy.yml with '-e canary=tidy' diff --git a/_dependencies/action_plugins/README_merge_vars.md b/_dependencies/action_plugins/README_merge_vars.md deleted file mode 100644 index d7dcbfdb..00000000 --- a/_dependencies/action_plugins/README_merge_vars.md +++ /dev/null @@ -1,33 +0,0 @@ -### Action Plugin Merge_Vars - -this plugin similar to `include_vars`, but designed to merge all the variables(including dicts) in the files specified by from rather than replacing(default ansible behaviour for dicts). - -This plugin is designed to support the inclusion of DRY application configuration within ansible vars. For example, if you have multiple porjects (foo, bar), and multiple environments (dev, qa, prod), and some vars are shared at various levels (project, or environment), and you want to keep your configuration DRY. - -Example: - - ``` - environment/ - └── aws - ├── cbe - │ └── clusterid - │ ├── app_vars.yml - │ ├── cluster.yml - │ └── dev_metadata.yml - └── dev.yml - ``` - -#### How to use: - -``` - - name: Merge dict - merge_vars: - ignore_missing_files: True - from: - - "test1.yml" - - "test2.yml" -``` - -where, - ignore_missing_files if false - raise an error, default behaviour - from - list of files to be merged - order matters. \ No newline at end of file diff --git a/_dependencies/action_plugins/merge_vars.py b/_dependencies/action_plugins/merge_vars.py index e3e63ac8..9f623181 100644 --- a/_dependencies/action_plugins/merge_vars.py +++ b/_dependencies/action_plugins/merge_vars.py @@ -1,3 +1,53 @@ +# Copyright (c) 2020, Sky UK Ltd +# BSD 3-Clause License +# +# This plugin is similar to include_vars, but when it finds variables that have already been defined, it combines them instead of overwriting them. +# By splitting different cluster configurations across tiered files, applications can adhere to the "Don't Repeat Yourself" principle. +# +# cluster_defs/ +# |-- all.yml +# |-- aws +# | |-- all.yml +# | `-- eu-west-1 +# | |-- all.yml +# | `-- sandbox +# | |-- all.yml +# | `-- cluster_vars.yml +# `-- gcp +# |-- all.yml +# `-- europe-west1 +# `-- sandbox +# |-- all.yml +# `-- cluster_vars.yml +# +# These files could be combined (in the order defined) in the application code, using variables to differentiate between cloud (aws or gcp), region and cluster_id. +# A variable 'ignore_missing_files' can be set such that any files or directories that are not found in the defined 'from' list will not raise an error. +# - merge_vars: +# ignore_missing_files: True +# from: +# - "./cluster_defs/all.yml" +# - "./cluster_defs/{{ cloud_type }}/all.yml" +# - "./cluster_defs/{{ cloud_type }}/{{ region }}/all.yml" +# - "./cluster_defs/{{ cloud_type }}/{{ region }}/{{ buildenv }}/all.yml" +# - "./cluster_defs/{{ cloud_type }}/{{ region }}/{{ buildenv }}/{{ clusterid }}.yml" +# +# +# It is also valid to define all the variables in a single sub-directory: +# cluster_defs/ +# |-- test_aws_euw1 +# | |-- app_vars.yml +# | `-- cluster_vars.yml +# |-- test_gcp_euw1 +# | |-- app_vars.yml +# | `-- cluster_vars.yml +# +# In this case, the 'from' list, would be only the top-level directory (using cluster_id as a variable). merge_vars does not recurse through directories. +# - merge_vars: +# ignore_missing_files: True +# from: +# - "./cluster_defs/{{ clusterid }}" +# + from __future__ import (absolute_import, division, print_function) __metaclass__ = type @@ -31,13 +81,11 @@ def run(self, tmp=None, task_vars=None): for source in self._task.args['from']: if path.isfile(source): files.append(source) - elif not path.isfile(source) and self.ignore_missing_files: - continue elif path.isdir(source): - dirfiles = [path.join(source, filename) for filename in listdir(source)] + dirfiles = [path.join(source, filename) for filename in listdir(source) if path.isfile(path.join(source, filename))] dirfiles.sort() - files.append(dirfiles) - elif not path.isdir(source) and self.ignore_missing_files: + files = files + dirfiles + elif not (path.isfile(source) or path.isdir(source)) and self.ignore_missing_files: continue else: failed = True diff --git a/_dependencies/library/deprecate_str.py b/_dependencies/library/deprecate_str.py new file mode 100644 index 00000000..1a3821da --- /dev/null +++ b/_dependencies/library/deprecate_str.py @@ -0,0 +1,35 @@ +# Copyright 2020 Dougal Seeley + +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: deprecate_str +version_added: 1.0.0 +description: + - Print a deprecation warning to the console on demand +authors: + - Dougal Seeley +''' + +EXAMPLES = ''' +- deprecate_str: + msg: "asdf is deprecated" + version: "9.8" +''' + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + module = AnsibleModule(argument_spec={"msg": {"type": "str", "default": "Deprecate, world"}, "version": {"type": "str", 'default': None}}) + + module.deprecate(msg=module.params['msg'], version=module.params['version']) + + module.exit_json(changed=False) + + +if __name__ == '__main__': + main() diff --git a/_dependencies/tasks/main.yml b/_dependencies/tasks/main.yml index 2d300f62..ce7428fb 100644 --- a/_dependencies/tasks/main.yml +++ b/_dependencies/tasks/main.yml @@ -1,40 +1,58 @@ ---- - -- name: Load cluster definitions - block: - - name: Load native cluster definitions by forcing include of group_vars on localhost (no inventory yet, so cannot import automatically) - include_vars: { dir: "{{ playbook_dir }}/group_vars/{{ clusterid }}" } - when: - - cluster_vars_format|default('native') == 'native' - - - name: Derive cluster definitions by merging tiered configuration files - merge_vars: - ignore_missing_files: True - from: "{{ merge_dict_vars_list }}" - when: - - cluster_vars_format|default('native') == 'tiered' - - merge_dict_vars_list is defined and merge_dict_vars_list | length > 0 - - -- name: Preflight check - block: - - assert: { that: "ansible_version.full is version_compare('2.9', '>=')", fail_msg: "Ansible >=2.9 required." } - - assert: { that: "app_name is defined and app_name != ''", fail_msg: "Please define app_name" } - - assert: { that: "app_class is defined and app_class != ''", fail_msg: "Please define app_class" } - - assert: { that: "clusterid is defined and cluster_vars is defined", fail_msg: "Please define clusterid" } - - assert: { that: "buildenv is defined and cluster_vars[buildenv] is defined", fail_msg: "Please define buildenv" } - - ## Tags/ labels must be compatible with GCP and AWS - check everything that goes into a label. - - assert: { that: "release_version is regex('^[a-z\\d\\-_]{0,63}$')", fail_msg: "Please ensure release_version ({{release_version}}) is in the set [a-z\\d\\-_], and <63 characters long." } - when: release_version is defined - - assert: { that: "cluster_suffix is regex('^[a-z\\d\\-_]{0,63}$')", fail_msg: "Please ensure cluster_suffix ({{cluster_suffix}}) is in the set[a-z\\d\\-_], and <63 characters long." } - when: cluster_suffix is defined - - assert: { that: "'{%- for label in cluster_vars.custom_tagslabels -%}{% if not cluster_vars.custom_tagslabels[label] is regex('^[a-z\\d\\-_]{0,63}$') %}{{label}}: {{cluster_vars.custom_tagslabels[label]}}{% endif %}{%- endfor -%}' == ''", fail_msg: "Please ensure all cluster_vars.custom_tagslabels are in the set [a-z\\d\\-_], and <63 characters long." } - when: "'custom_tagslabels' in cluster_vars" - - assert: { that: "'{%- for hosttype in cluster_vars[buildenv].hosttype_vars -%}{% if ('version' in cluster_vars[buildenv].hosttype_vars[hosttype]) and (not cluster_vars[buildenv].hosttype_vars[hosttype].version is regex('^[a-z\\d\\-_]{0,63}$')) %}{{cluster_vars[buildenv].hosttype_vars[hosttype].version}}{% endif %}{%- endfor -%}' == ''", fail_msg: "Please ensure cluster_vars[{{buildenv}}].hosttype_vars[hosttype].version is in the set [a-z\\d\\-_], and <63 characters long." } - - - assert: { that: "(cluster_vars.assign_public_ip == 'yes' and cluster_vars.inventory_ip == 'public') or (cluster_vars.inventory_ip == 'private')", fail_msg: "If inventory_ip=='public', 'assign_public_ip' must be 'yes'" } - when: cluster_vars.type == "gcp" or cluster_vars.type == "aws" - - - assert: { that: "cluster_vars[buildenv] | json_query(\"hosttype_vars.*.auto_volumes[] | [?contains(`/dev/sdb,/dev/sdc,/dev/sdd,/dev/sde`, device_name) && volume_type!='ephemeral']\") | length == 0", fail_msg: "device_names /dev/sd[b-e] are only allowed for ephemeral volumes in AWS cluster_vars[buildenv].hosttype_vars. Please start non-ephemeral devices at /dev/sdf." } - when: cluster_vars.type == "aws" +--- + +- name: Load cluster definitions + block: + - name: Derive cluster definitions by merging tiered configuration files + merge_vars: + from: "{{ merge_dict_vars_list }}" + ignore_missing_files: True + when: merge_dict_vars_list is defined and merge_dict_vars_list | length > 0 + + - block: + - name: Load native cluster definitions by forcing include of group_vars on localhost (no inventory yet, so cannot import automatically) + include_vars: { dir: "{{ playbook_dir }}/group_vars/{{ clusterid }}" } + + - deprecate_str: { msg: "Loading variables via group_vars/clusterid is deprecated. Please use merge_vars via cluster_defs in future" } + when: merge_dict_vars_list is not defined + + +- name: Preflight check + block: + - assert: { that: "ansible_version.full is version_compare('2.9', '>=')", fail_msg: "Ansible >=2.9 required." } + - assert: { that: "app_name is defined and app_name != ''", fail_msg: "Please define app_name" } + - assert: { that: "app_class is defined and app_class != ''", fail_msg: "Please define app_class" } + - assert: { that: "cluster_vars is defined", fail_msg: "Please define cluster_vars" } + - assert: { that: "clusterid is defined", fail_msg: "Please define clusterid" } + - assert: { that: "buildenv is defined and cluster_vars[buildenv] is defined", fail_msg: "Please define buildenv" } + + ## Tags/ labels must be compatible with GCP and AWS - check everything that goes into a label. + - assert: { that: "release_version is regex('^[a-z\\d\\-_]{0,63}$')", fail_msg: "Please ensure release_version ({{release_version}}) is in the set [a-z\\d\\-_], and <63 characters long." } + when: release_version is defined + - assert: { that: "cluster_suffix is regex('^[a-z\\d\\-_]{0,63}$')", fail_msg: "Please ensure cluster_suffix ({{cluster_suffix}}) is in the set[a-z\\d\\-_], and <63 characters long." } + when: cluster_suffix is defined + - assert: { that: "'{%- for label in cluster_vars.custom_tagslabels -%}{% if not cluster_vars.custom_tagslabels[label] is regex('^[a-z\\d\\-_]{0,63}$') %}{{label}}: {{cluster_vars.custom_tagslabels[label]}}{% endif %}{%- endfor -%}' == ''", fail_msg: "Please ensure all cluster_vars.custom_tagslabels are in the set [a-z\\d\\-_], and <63 characters long." } + when: "'custom_tagslabels' in cluster_vars" + - assert: { that: "'{%- for hosttype in cluster_vars[buildenv].hosttype_vars -%}{% if ('version' in cluster_vars[buildenv].hosttype_vars[hosttype]) and (not cluster_vars[buildenv].hosttype_vars[hosttype].version is regex('^[a-z\\d\\-_]{0,63}$')) %}{{cluster_vars[buildenv].hosttype_vars[hosttype].version}}{% endif %}{%- endfor -%}' == ''", fail_msg: "Please ensure cluster_vars[{{buildenv}}].hosttype_vars[hosttype].version is in the set [a-z\\d\\-_], and <63 characters long." } + + - assert: { that: "(cluster_vars.assign_public_ip == 'yes' and cluster_vars.inventory_ip == 'public') or (cluster_vars.inventory_ip == 'private')", fail_msg: "If inventory_ip=='public', 'assign_public_ip' must be 'yes'" } + when: cluster_vars.type == "gcp" or cluster_vars.type == "aws" + + - assert: { that: "cluster_vars[buildenv] | json_query(\"hosttype_vars.*.auto_volumes[] | [?contains(`/dev/sdb,/dev/sdc,/dev/sdd,/dev/sde`, device_name) && volume_type!='ephemeral']\") | length == 0", fail_msg: "device_names /dev/sd[b-e] are only allowed for ephemeral volumes in AWS cluster_vars[buildenv].hosttype_vars. Please start non-ephemeral devices at /dev/sdf." } + when: cluster_vars.type == "aws" + + +- name: Create gcp service account contents file from cluster_vars.service_account_rawtext (unless already defined by user) + block: + - name: "set gcp_credentials_file fact" + set_fact: + gcp_credentials_file: "gcp__{{ (cluster_vars.service_account_rawtext | string | from_json).project_id }}.json" + when: gcp_credentials_file is not defined + + - name: dynamic_inventory | stat the gcp_credentials_file + stat: path={{gcp_credentials_file}} + register: r__stat_gcp_credentials_file + + - name: "Copy credentials into gcp_credentials_file as {{gcp_credentials_file}}" + local_action: copy content={{cluster_vars.service_account_rawtext}} dest={{gcp_credentials_file}} + when: not (stat_inventory_file.stat is defined and stat_inventory_file.stat.exists|bool == false ) + when: cluster_vars.type == "gcp" diff --git a/config/tasks/create_dns_a.yml b/config/tasks/create_dns_a.yml index 01733673..c04afc24 100644 --- a/config/tasks/create_dns_a.yml +++ b/config/tasks/create_dns_a.yml @@ -91,12 +91,12 @@ - block: - name: config/dns/a/dig | debug whether we'll use external_dns_resolver debug: - msg: "{% if not hostvars[item.hostname].ansible_host | regex_search('^(10.|192.168|172.1[6-9].|172.2[0-9].|172.3[01].).*') %}@{{external_dns_resolver}}{% endif %}" + msg: "{% if not hostvars[item.hostname].ansible_host | regex_search('^(10\\.|192.168|172.1[6-9]\\.|172.2[0-9]\\.|172.3[01]\\.).*') %}@{{external_dns_resolver}}{% endif %}" with_items: "{{ cluster_hosts_target }}" run_once: true - name: "config/dns/a/dig | Check that DNS has updated (or otherwise wait for it to do so) [Note: lookup('dig', new_fqdn) doesn't work - seems to cache - https://github.com/ansible/ansible/issues/44128]. NOTE: A short TTL on the SOA helps if a negative cache is created. If this is an external IP, check external DNS (otherwise only the internal VPC IP will be returned)" - shell: "dig {{new_fqdn}} +short {% if not hostvars[item.hostname].ansible_host | regex_search('^(10.|192.168|172.1[6-9].|172.2[0-9].|172.3[01].).*') %}@{{external_dns_resolver}}{% endif %}" + shell: "dig {{new_fqdn}} +short {% if not hostvars[item.hostname].ansible_host | regex_search('^(10\\.|192.168|172.1[6-9]\\.|172.2[0-9]\\.|172.3[01]\\.).*') %}@{{external_dns_resolver}}{% endif %}" register: dig_result until: "dig_result.stdout == new_ip" retries: 31 diff --git a/config/tasks/filebeat.yml b/config/tasks/filebeat.yml index b5ce84b8..4358c550 100644 --- a/config/tasks/filebeat.yml +++ b/config/tasks/filebeat.yml @@ -18,7 +18,6 @@ retries: 5 when: ansible_os_family == 'RedHat' -# the variable "beats_target_hosts" will be deprecated in Clusterverse 4.0 - name: Filebeat | Configure filebeat block: - name: Filebeat | Copy filebeat configuration @@ -34,4 +33,7 @@ src: lib/systemd/system//filebeat.service.j2 dest: "/lib/systemd/system/filebeat.service" notify: Filebeat | Restart and enable filebeat + + - deprecate_str: {msg: "beats_target_hosts is deprecated. Please use beats_config.filebeat.output_logstash_hosts in future", version: "6"} + when: (beats_target_hosts is defined and (beats_target_hosts | length)) when: (beats_target_hosts is defined and (beats_target_hosts | length)) or (beats_config.filebeat.output_logstash_hosts is defined and (beats_config.filebeat.output_logstash_hosts | length)) diff --git a/config/tasks/metricbeat.yml b/config/tasks/metricbeat.yml index 52fe2a55..631f5c6f 100644 --- a/config/tasks/metricbeat.yml +++ b/config/tasks/metricbeat.yml @@ -18,7 +18,6 @@ retries: 5 when: ansible_os_family == 'RedHat' -# the variable "beats_target_hosts" will be deprecated in Clusterverse 4.0 - name: Metricbeat | Configure metricbeat block: - name: Metricbeat | Copy metricbeat configuration @@ -41,4 +40,7 @@ src: lib/systemd/system//metricbeat.service.j2 dest: "/lib/systemd/system/metricbeat.service" notify: Metricbeat | Restart and enable metricbeat + + - deprecate_str: {msg: "beats_target_hosts is deprecated. Please use beats_config.metricbeat.output_logstash_hosts in future", version: "6"} + when: (beats_target_hosts is defined and (beats_target_hosts | length)) when: (beats_target_hosts is defined and (beats_target_hosts | length)) or (beats_config.metricbeat.output_logstash_hosts is defined and (beats_config.metricbeat.output_logstash_hosts | length)) diff --git a/create/tasks/aws.yml b/create/tasks/aws.yml index 792dcaec..04203fc9 100644 --- a/create/tasks/aws.yml +++ b/create/tasks/aws.yml @@ -39,7 +39,7 @@ region: "{{cluster_vars.region}}" key_name: "{{cluster_vars[buildenv].key_name}}" instance_type: "{{item.flavor}}" - instance_profile_name: "{{cluster_vars.instance_profile_name | default(omit)}}" + instance_profile_name: "{{cluster_vars.instance_profile_name | default(omit)}}" image: "{{cluster_vars.image}}" vpc_subnet_id: "{{item.vpc_subnet_id}}" assign_public_ip: "{{cluster_vars.assign_public_ip}}" @@ -47,6 +47,7 @@ wait: yes instance_tags: "{{ _instance_tags | combine(cluster_vars.custom_tagslabels | default({})) }}" termination_protection: "{{cluster_vars[buildenv].termination_protection}}" + user_data: "{{ cluster_vars.user_data | default(omit) }}" volumes: "{{ item.auto_volumes | selectattr('src', 'undefined') | list | default([]) }}" count_tag: { Name: "{{item.hostname}}" } exact_count: 1 diff --git a/create/tasks/gcp.yml b/create/tasks/gcp.yml index 84824773..e423fae4 100644 --- a/create/tasks/gcp.yml +++ b/create/tasks/gcp.yml @@ -73,7 +73,7 @@ - name: create/gcp | Detach volumes from previous instances (during the _scheme_rmvm_keepdisk_rollback redeploy, we only redeploy one host at a time, and it is already powered off) gce_pd: credentials_file: "{{gcp_credentials_file}}" - service_account_email: "{{gcp_credentials_json.client_email}}" + service_account_email: "{{ (lookup('file', gcp_credentials_file) | from_json).client_email }}" project_id: "{{cluster_vars[buildenv].vpc_project_id}}" zone: "{{cluster_vars.region}}-{{item.az_name}}" detach_only : yes @@ -91,9 +91,7 @@ name: "{{item.hostname}}" machine_type: "{{item.flavor}}" disks: "{{ [_bootdisk] + (_autodisks | default([])) }}" - metadata: - startup-script: "{%- if cluster_vars.ssh_guard_whitelist is defined and cluster_vars.ssh_guard_whitelist | length > 0 -%}#! /bin/bash\n\n#Whitelist my inbound IPs\n[ -f /etc/sshguard/whitelist ] && echo \"{{cluster_vars.ssh_guard_whitelist | join ('\n')}}\" >>/etc/sshguard/whitelist && /bin/systemctl restart sshguard{%- endif -%}" - ssh-keys: "{{ cliargs.remote_user }}:{{ lookup('pipe', 'ssh-keygen -y -f ' + ansible_ssh_private_key_file) }} {{ cliargs.remote_user }}" + metadata: "{{ cluster_vars.metadata | default(omit) }}" labels: "{{ _labels | combine(cluster_vars.custom_tagslabels | default({})) }}" network_interfaces: - network: "{{ r__gcp_compute_network_info['resources'][0] | default({}) }}" @@ -143,7 +141,6 @@ gce_labels: project_id: "{{cluster_vars[buildenv].vpc_project_id}}" credentials_file: "{{gcp_credentials_file}}" - service_account_email: "{{gcp_credentials_json.client_email}}" resource_url: "{{item.resource_url}}" labels: "{{ _labels | combine(cluster_vars.custom_tagslabels | default({})) }}" with_items: "{{_ec2_vols_denormalised_by_device}}" @@ -166,7 +163,7 @@ # - name: create/gcp | Attach (or create) volumes where 'src' is present (e.g. inserted as part of _scheme_rmvm_keepdisk_rollback scheme) # gce_pd: # credentials_file: "{{gcp_credentials_file}}" -# service_account_email: "{{gcp_credentials_json.client_email}}" +# service_account_email: "{{ (lookup('file', gcp_credentials_file) | from_json).client_email }}" # project_id: "{{cluster_vars[buildenv].vpc_project_id}}" # zone: "{{cluster_vars.region}}-{{item.az_name}}" # delete_on_termination: yes @@ -208,7 +205,6 @@ # gce_labels: # project_id: "{{cluster_vars[buildenv].vpc_project_id}}" # credentials_file: "{{gcp_credentials_file}}" -# service_account_email: "{{gcp_credentials_json.client_email}}" # resource_url: "{{item.resource_url}}" # labels: "{{ _labels | combine(cluster_vars.custom_tagslabels | default({})) }}" # with_items: "{{_ec2_vols_denormalised_by_device}}" diff --git a/redeploy/_scheme_addallnew_rmdisk_rollback/tasks/main.yml b/redeploy/_scheme_addallnew_rmdisk_rollback/tasks/main.yml index 728a5618..c46016d4 100644 --- a/redeploy/_scheme_addallnew_rmdisk_rollback/tasks/main.yml +++ b/redeploy/_scheme_addallnew_rmdisk_rollback/tasks/main.yml @@ -16,8 +16,8 @@ - name: rescue include_tasks: rescue.yml - - name: rescue | end_play to prevent tidying of pre-rescued VMs - meta: end_play + - name: rescue | force fail from block so Jenkins gets error code. (If we prefer not to fail, we should otherwise 'meta end_play' to prevent tidying of pre-rescued VMs) + fail: { msg: "Task '{{ansible_failed_task.name}}' failed in block. Error message was: '{{ansible_failed_result.msg}}'" } when: canary!="tidy" @@ -39,5 +39,5 @@ msg: "tidy | No hosts to tidy. Only powered-down, non-current machines with be tidied; to clean other machines, please use the '-e clean=' extra variable." when: hosts_to_clean | length == 0 vars: - hosts_to_clean: "{{ cluster_hosts_state | json_query(\"[?tagslabels.lifecycle_state!='current' && !(contains('RUNNING,running', instance_state))]\") }}" + hosts_to_clean: "{{ cluster_hosts_state | json_query(\"[?tagslabels.lifecycle_state!='current' && !(contains('RUNNING,running,poweredOn', instance_state)) && ('\"+ myhosttypes|default('') + \"' == '' || contains('\"+ myhosttypes|default('') + \"', tagslabels.hosttype)) ]\") }}" when: canary=="tidy" or ((canary=="none" or canary=="finish") and canary_tidy_on_success is defined and canary_tidy_on_success|bool) diff --git a/redeploy/_scheme_addnewvm_rmdisk_rollback/tasks/main.yml b/redeploy/_scheme_addnewvm_rmdisk_rollback/tasks/main.yml index d3c7b9d3..87fd028a 100644 --- a/redeploy/_scheme_addnewvm_rmdisk_rollback/tasks/main.yml +++ b/redeploy/_scheme_addnewvm_rmdisk_rollback/tasks/main.yml @@ -5,6 +5,7 @@ - assert: { that: "non_current_hosts | length == 0", msg: "ERROR - All VMs must be in the 'current' lifecycle_state. Those not [{{non_current_hosts | join(',')}}]" } vars: non_current_hosts: "{{ cluster_hosts_state | json_query(\"[?tagslabels.lifecycle_state!='current'].name\") }}" + # TODO: remove myhosttypes not defined and replace json_query "{{ cluster_hosts_state | json_query(\"[?tagslabels.lifecycle_state!='current' && ('\"+ myhosttypes|default('') + \"' == '' || contains('\"+ myhosttypes|default('') + \"', tagslabels.hosttype))].name\") }}" when: (canary=="start" or canary=="none") and (myhosttypes is not defined or myhosttypes=='') @@ -39,7 +40,7 @@ name: "{{predeleterole}}" when: predeleterole is defined and predeleterole != "" vars: - hosts_to_remove: "{{ hosts_to_stop | json_query(\"[?contains('RUNNING,running', instance_state)]\") }}" + hosts_to_remove: "{{ hosts_to_stop | json_query(\"[?contains('RUNNING,running,poweredOn', instance_state)]\") }}" - name: Power off any other retiring VM(s) that might exist if we're redeploying to a smaller topology. include_role: @@ -60,8 +61,8 @@ - name: rescue include_tasks: rescue.yml - - name: rescue | end_play to prevent tidying of pre-rescued VMs - meta: end_play + - name: rescue | force fail from block so Jenkins gets error code. (If we prefer not to fail, we should otherwise 'meta end_play' to prevent tidying of pre-rescued VMs) + fail: { msg: "Task '{{ansible_failed_task.name}}' failed in block. Error message was: '{{ansible_failed_result.msg}}'" } when: canary!="tidy" @@ -83,5 +84,5 @@ msg: "tidy | No hosts to tidy. Only powered-down, non-current machines with be tidied; to clean other machines, please use the '-e clean=' extra variable." when: hosts_to_clean | length == 0 vars: - hosts_to_clean: "{{ cluster_hosts_state | json_query(\"[?tagslabels.lifecycle_state!='current' && !(contains('RUNNING,running', instance_state))]\") }}" + hosts_to_clean: "{{ cluster_hosts_state | json_query(\"[?tagslabels.lifecycle_state!='current' && !(contains('RUNNING,running,poweredOn', instance_state)) && ('\"+ myhosttypes|default('') + \"' == '' || contains('\"+ myhosttypes|default('') + \"', tagslabels.hosttype)) ]\") }}" when: canary=="tidy" or ((canary=="none" or canary=="finish") and canary_tidy_on_success is defined and canary_tidy_on_success|bool) diff --git a/redeploy/_scheme_rmvm_keepdisk_rollback/tasks/main.yml b/redeploy/_scheme_rmvm_keepdisk_rollback/tasks/main.yml index 65fb69d7..840f8f5b 100644 --- a/redeploy/_scheme_rmvm_keepdisk_rollback/tasks/main.yml +++ b/redeploy/_scheme_rmvm_keepdisk_rollback/tasks/main.yml @@ -21,7 +21,7 @@ name: clusterverse/cluster_hosts public: yes - - assert: { that: "cluster_hosts_state | json_query(\"[?tagslabels.cluster_suffix == '\"+ cluster_suffix +\"']\") | length == 0", msg: "Please ensure cluster_suffix ({{cluster_suffix}}) is not already set on the cluster" } + - assert: { that: "cluster_hosts_state | json_query(\"[?tagslabels.cluster_suffix == '\"+ cluster_suffix +\"' && ('\"+ myhosttypes|default('') + \"' == '' || contains('\"+ myhosttypes|default('') + \"', tagslabels.hosttype))]\") | length == 0", msg: "Please ensure cluster_suffix ({{cluster_suffix}}) is not already set on the cluster" } when: cluster_suffix is defined when: (canary=="start" or canary=="none") @@ -88,8 +88,8 @@ cluster_hosts_target_by_hosttype: "{{cluster_hosts_target | dict_agg('hosttype')}}" myhosttypes_array: "{%- if myhosttypes is defined -%} {{ myhosttypes.split(',') }} {%- else -%} {{ cluster_hosts_target_by_hosttype.keys() | list }} {%- endif -%}" - - name: rescue | end_play to prevent tidying of pre-rescued VMs - meta: end_play + - name: rescue | force fail from block so Jenkins gets error code. (If we prefer not to fail, we should otherwise 'meta end_play' to prevent tidying of pre-rescued VMs) + fail: { msg: "Task '{{ansible_failed_task.name}}' failed in block. Error message was: '{{ansible_failed_result.msg}}'" } when: canary!="tidy" @@ -111,5 +111,5 @@ msg: "tidy | No hosts to tidy. Only powered-down, non-current machines with be tidied; to clean other machines, please use the '-e clean=' extra variable." when: hosts_to_clean | length == 0 vars: - hosts_to_clean: "{{ cluster_hosts_state | json_query(\"[?tagslabels.lifecycle_state!='current' && !(contains('RUNNING,running', instance_state))]\") }}" + hosts_to_clean: "{{ cluster_hosts_state | json_query(\"[?tagslabels.lifecycle_state!='current' && !(contains('RUNNING,running,poweredOn', instance_state)) && ('\"+ myhosttypes|default('') + \"' == '' || contains('\"+ myhosttypes|default('') + \"', tagslabels.hosttype)) ]\") }}" when: canary=="tidy" or ((canary=="none" or canary=="finish") and canary_tidy_on_success is defined and canary_tidy_on_success|bool) diff --git a/redeploy/_scheme_rmvm_keepdisk_rollback/tasks/preflight.yml b/redeploy/_scheme_rmvm_keepdisk_rollback/tasks/preflight.yml index 251e7cf9..ce39128e 100644 --- a/redeploy/_scheme_rmvm_keepdisk_rollback/tasks/preflight.yml +++ b/redeploy/_scheme_rmvm_keepdisk_rollback/tasks/preflight.yml @@ -30,8 +30,14 @@ {{ testloop.is_not_subset }} when: cluster_vars.type == "aws" + - assert: { that: "_scheme_rmvm_keepdisk_rollback__copy_or_move is defined and _scheme_rmvm_keepdisk_rollback__copy_or_move in ['copy', 'move']", fail_msg: "ERROR - _scheme_rmvm_keepdisk_rollback__copy_or_move must be defined and set to either 'copy' or 'move'" } + when: cluster_vars.type == "esxifree" + - assert: { that: "non_current_hosts | length == 0", fail_msg: "ERROR - All VMs must be in the 'current' lifecycle_state. Those not [{{non_current_hosts | join(',')}}]" } vars: { non_current_hosts: "{{ cluster_hosts_state | json_query(\"[?tagslabels.lifecycle_state!='current'].name\") }}" } + # TODO: remove myhosttypes not defined and replace json_query "{{ cluster_hosts_state | json_query(\"[?tagslabels.lifecycle_state!='current' && ('\"+ myhosttypes|default('') + \"' == '' || contains('\"+ myhosttypes|default('') + \"', tagslabels.hosttype))].name\") }}" when: (canary=="start" or canary=="none") and (myhosttypes is not defined or myhosttypes=='') - assert: { that: "(cluster_hosts_state | selectattr('tagslabels.lifecycle_state', '==', 'current') | list | length) == (cluster_hosts_target | length)", fail_msg: "Cannot use this scheme to redeploy to a different-sized cluster" } + # TODO: remove myhosttypes not defined and replace json_query "{{ cluster_hosts_state | json_query(\"[?tagslabels.lifecycle_state!='current' && ('\"+ myhosttypes|default('') + \"' == '' || contains('\"+ myhosttypes|default('') + \"', tagslabels.hosttype))].name\") }}" + when: (canary=="start" or canary=="none") and (myhosttypes is not defined or myhosttypes=='') diff --git a/redeploy/_scheme_rmvm_rmdisk_only/tasks/main.yml b/redeploy/_scheme_rmvm_rmdisk_only/tasks/main.yml index 75e78f71..01710c33 100644 --- a/redeploy/_scheme_rmvm_rmdisk_only/tasks/main.yml +++ b/redeploy/_scheme_rmvm_rmdisk_only/tasks/main.yml @@ -1,5 +1,11 @@ --- +- name: Skip this play if we're running canary=tidy + block: + - debug: msg="canary=tidy is not valid for this redeploy scheme" + - meta: end_play + when: "canary == 'tidy'" + - name: Preflight check block: - assert: { that: "non_current_hosts | length == 0", msg: "ERROR - All VMs must be in the 'current' lifecycle_state. Those not [{{non_current_hosts | join(',')}}]" } @@ -7,14 +13,11 @@ non_current_hosts: "{{ cluster_hosts_state | json_query(\"[?tagslabels.lifecycle_state!='current'].name\") }}" when: canary=="start" or canary=="none" - - assert: { that: "{{chs_hosts | difference(chf_hosts) | length==0}}", fail_msg: "Cannot use this scheme to redeploy to smaller cluster; [{{ chs_hosts | join(',') }}] > [{{ chf_hosts | join(',') }}]" } + - assert: { that: "{{chs_hosts | difference(cht_hosts) | length==0}}", fail_msg: "Cannot use this scheme to redeploy to smaller cluster; [{{ chs_hosts | join(',') }}] > [{{ cht_hosts | join(',') }}]" } vars: - chf_hosts: "{{ cluster_hosts_target | json_query(\"[].hostname\") | map('regex_replace', '-(?!.*-).*') | list }}" + cht_hosts: "{{ cluster_hosts_target | json_query(\"[].hostname\") | map('regex_replace', '-(?!.*-).*') | list }}" chs_hosts: "{{ cluster_hosts_state | json_query(\"[].name\") | map('regex_replace', '-(?!.*-).*') | list }}" - - assert: { that: "canary != 'tidy'", fail_msg: "'tidy' is not valid for this redeploy scheme" } - - - name: Run redeploy per hosttype. Delete one at a time, then reprovision. include_tasks: by_hosttype.yml with_items: "{{ myhosttypes_array }}"