virtual-machine.jinja (383 lines of code) (raw):

{% set COMPUTE_URL_BASE = 'https://www.googleapis.com/compute/v1/' %} {% set BASE_NAME = properties['baseName'] + '-' + env['name'] %} {% set REGION = properties['zone'][:-2] %} {% set DISK_SIZE = properties['zone'][:-2] %} {% set GOOGLE_FACTORY = '1.0.16' %} {% set NGINX_IMAGE = 'jwilder/nginx-proxy' %} {% set LETSENCRYPT_IMAGE = 'jrcs/letsencrypt-nginx-proxy-companion' %} {% set NGINX = 'nginx' %} {% set LETSENCRYPT = 'letsencrypt' %} {%- if properties['enableHttps'] %} {% set SERVER_URL = 'https://' + properties['domainName'] %} {%- else %} {% set SERVER_URL = 'http://${COREOS_GCE_HOSTNAME}' %} {%- endif %} {% if properties['size'] == 'small' %} {% set DISK_SIZE = 30 %} {% set DISK_TYPE = 'pd-standard' %} {% set MACHINE_TYPE = 'custom-1-3072' %} {% elif properties['size'] == 'large' %} {% set DISK_SIZE = 100 %} {% set DISK_TYPE = 'pd-ssd' %} {% set MACHINE_TYPE = 'custom-4-8192' %} {% else %} {% set DISK_SIZE = 50 %} {% set DISK_TYPE = 'pd-ssd' %} {% set MACHINE_TYPE = 'custom-2-4096' %} {% endif %} {% macro GlobalComputeUrl(project, collection, name) -%} {{ COMPUTE_URL_BASE }}projects/{{ project }}/global/{{ collection }}/{{ name }} {%- endmacro %} {% macro ZonalComputeUrl(project, zone, collection, name) -%} {{ COMPUTE_URL_BASE }}projects/{{ project }}/zones/{{ zone }}/{{ collection }}/{{ name }} {%- endmacro %} {% macro RegionalComputeUrl(project, region, collection, name) -%} {{ COMPUTE_URL_BASE }}projects/{{ project }}/regions/{{ region }}/{{ collection }}/{{ name }} {%- endmacro %} {% macro SqlInstanceName(project, region, dbName) -%} {{ project }}:{{ region }}:{{ dbName }} {%- endmacro %} resources: - name: {{ BASE_NAME }}-data type: compute.v1.disk properties: zone: {{ properties["zone"] }} sizeGb: {{ DISK_SIZE }} type: {{ ZonalComputeUrl(env['project'], properties['zone'], 'diskTypes', DISK_TYPE) }} {%- if not properties['ipAddress'] %} - name: {{ BASE_NAME }}-address type: compute.v1.address properties: name: {{ BASE_NAME }}-address region: {{ REGION }} {%- endif %} - name: {{ BASE_NAME }} type: compute.v1.instance properties: zone: {{ properties['zone'] }} machineType: {{ ZonalComputeUrl(env['project'], properties['zone'], 'machineTypes', MACHINE_TYPE) }} serviceAccounts: - email: {{ properties['serviceAccount'] }} scopes: - "https://www.googleapis.com/auth/cloud-platform" metadata: items: - key: user-data value: | #cloud-config write_files: - path: "/etc/teamcity/version" content: | TEAMCITY_VERSION={{ properties['teamcityVersion'] }} - path: "/etc/teamcity/database.properties" content: | # Database: MySQL connectionUrl=jdbc:mysql://google/{{ properties['database']['name'] }}?cloudSqlInstance={{ env['project'] }}:{{ REGION }}:{{ properties['database']['instance'] }}&socketFactory=com.google.cloud.sql.mysql.SocketFactory&useSSL=false connectionProperties.user={{ properties['database']['user'] }} maxConnections=50 testOnBorrow=true - path: "/etc/teamcity/disabled-plugins.xml" content: | <?xml version="1.0" encoding="UTF-8"?> <disabled-plugins> <disabled-plugin name="cloud-amazon" /> <disabled-plugin name="s3-artifact-storage" /> <disabled-plugin name="vsphere" /> </disabled-plugins> - path: "/etc/flatcar/update.conf" content: | REBOOT_STRATEGY="off" {%- if properties['enableHttps'] %} - path: "/etc/teamcity/checkCertificate.sh" permissions: 0755 content: | #!/bin/bash attempt=0 while docker exec {{ LETSENCRYPT }} /app/cert_status | grep {{ properties['domainName'] }} > /dev/null; [[ $? -ne 0 ]]; do retry_in_mins=$((3**$attempt)) echo "Will try to renew certificate for {{ SERVER_URL }} in $retry_in_mins minutes" sleep $((60 * $retry_in_mins)) echo "Trying to renew certificate for {{ SERVER_URL }}..." docker exec {{ LETSENCRYPT }} /app/signal_le_service sleep 30 attempt=$(($attempt + 1)) done {% endif %} coreos: units: - name: "format-mnt-data.service" enable: true content: | [Unit] Requires=network-online.target Before=teamcity-server.service mnt-data.mount ConditionPathExists=!/dev/mapper/app-data [Service] Type=oneshot ExecStart=/bin/bash -c \ '/usr/sbin/pvcreate /dev/disk/by-id/google-{{ BASE_NAME }}-data && \ /usr/sbin/vgcreate app /dev/disk/by-id/google-{{ BASE_NAME }}-data && \ /usr/sbin/lvcreate -l 100%%FREE -n data app && \ /usr/sbin/mkfs.ext4 /dev/mapper/app-data' [Install] WantedBy=multi-user.target - name: "mnt-data.mount" enable: true content: | [Unit] Before=teamcity-server.service After=format-mnt-data.service Requires=format-mnt-data.service ConditionVirtualization=!container Conflicts=umount.target [Mount] What=/dev/mapper/app-data Where=/mnt/data Type=ext4 Options= [Install] RequiredBy=teamcity-server.service - name: "get-mysql-connector.service" enable: true content: | [Unit] Before=teamcity-server.service After=mnt-data.mount docker.service Requires=mnt-data.mount docker.service ConditionPathExists=!/mnt/data/teamcity-server/data/lib/jdbc/ [Service] Type=oneshot ExecStart=/bin/mkdir -p /mnt/data/teamcity-server/data/lib/jdbc ExecStart=/bin/wget -O /mnt/data/teamcity-server/data/lib/jdbc/mysql-socket-factory.jar \ https://repo.maven.apache.org/maven2/com/google/cloud/sql/mysql-socket-factory/{{ GOOGLE_FACTORY }}/mysql-socket-factory-{{ GOOGLE_FACTORY }}.jar ExecStart=/bin/wget -O pom.xml https://repo.maven.apache.org/maven2/com/google/cloud/sql/mysql-socket-factory/{{ GOOGLE_FACTORY }}/mysql-socket-factory-{{ GOOGLE_FACTORY }}.pom ExecStart=/bin/bash -c 'docker run --rm -v "$(pwd)":/opt/maven -v /mnt/data/teamcity-server/data/lib/jdbc:/opt/maven/target/dependency -w /opt/maven maven:alpine mvn dependency:copy-dependencies && docker rmi maven:alpine' ExecStart=/bin/rm pom.xml [Install] WantedBy=multi-user.target - name: "get-google-plugins.service" enable: true content: | [Unit] Before=teamcity-server.service After=mnt-data.mount Requires=mnt-data.mount ConditionPathExists=!/mnt/data/teamcity-server/data/plugins/google-plugins.txt [Service] Type=oneshot ExecStart=/bin/mkdir -p /mnt/data/teamcity-server/data/plugins ExecStart=/bin/curl https://raw.githubusercontent.com/JetBrains/teamcity-google-template/master/google-plugins.txt \ -o /mnt/data/teamcity-server/data/plugins/google-plugins.txt ExecStart=/bin/bash -c 'cd /mnt/data/teamcity-server/data/plugins && curl -K google-plugins.txt' [Install] WantedBy=multi-user.target - name: "prepare-config.service" enable: true content: | [Unit] Before=teamcity-server.service After=mnt-data.mount Requires=mnt-data.mount network-online.target ConditionPathExists=!/mnt/data/teamcity-server/data/config [Service] Type=oneshot ExecStart=/bin/mkdir -p /mnt/data/teamcity-server/data/config ExecStart=/bin/mv /etc/teamcity/database.properties /mnt/data/teamcity-server/data/config/ ExecStart=/bin/mv /etc/teamcity/disabled-plugins.xml /mnt/data/teamcity-server/data/config/ [Install] WantedBy=multi-user.target - name: "teamcity-server.service" command: "start" content: | [Unit] Description=TeamCity Server After=docker.service mnt-data.mount get-mysql-connector.service get-google-plugins.service prepare-config.service Requires=docker.service mnt-data.mount get-mysql-connector.service get-google-plugins.service prepare-config.service [Service] TimeoutStartSec=1200s EnvironmentFile=/etc/teamcity/version {%- raw %} ExecStartPre=/bin/sh -c "docker images --filter 'before=jetbrains/teamcity-server:${TEAMCITY_VERSION}' --format '{{.ID}} {{.Repository}}' | grep 'jetbrains/teamcity-server' | grep -Eo '^[^ ]+' | xargs -r docker rmi" {%- endraw %} ExecStartPre=/usr/bin/docker create \ -e TEAMCITY_SERVER_MEM_OPTS="-Xmx$(($(grep MemTotal /proc/meminfo | awk '{print $2}') / 2))k -XX:MaxPermSize=270m -XX:ReservedCodeCacheSize=350m" \ {%- if properties['enableHttps'] %} -e VIRTUAL_PORT=8111 \ -e VIRTUAL_HOST={{ properties['domainName'] }} \ -e LETSENCRYPT_HOST={{ properties['domainName'] }} \ {%- if properties['domainOwnerEmail'] %} -e LETSENCRYPT_EMAIL={{ properties['domainOwnerEmail'] }} \ {%- endif %} {%- else %} -p 80:8111 \ {%- endif %} -v /mnt/data/teamcity-server/data:/data/teamcity_server/datadir \ -v /mnt/data/teamcity-server/logs:/opt/teamcity/logs \ -v /mnt/resource/teamcity-server/temp:/opt/teamcity/temp \ --name teamcity-server \ jetbrains/teamcity-server:${TEAMCITY_VERSION} ExecStartPre=/bin/sh -c "echo 'gcp' > dist && docker cp dist teamcity-server:/opt/teamcity/webapps/ROOT/WEB-INF/DistributionType.txt && rm dist" ExecStart=/usr/bin/docker start teamcity-server -a ExecStop=-/usr/bin/docker exec teamcity-server /opt/teamcity/bin/teamcity-server.sh stop 60 ExecStopPost=-/usr/bin/docker stop teamcity-server ExecStopPost=-/usr/bin/docker rm teamcity-server Restart=always [Install] WantedBy=multi-user.target - name: "teamcity-agent.service" command: "start" content: | [Unit] Description=TeamCity Agent After=teamcity-server.service coreos-metadata.service Requires=teamcity-server.service coreos-metadata.service [Service] TimeoutStartSec=1200s EnvironmentFile=/etc/teamcity/version EnvironmentFile=/run/metadata/coreos {%- raw %} ExecStartPre=/bin/sh -c "docker images --filter 'before=jetbrains/teamcity-agent:${TEAMCITY_VERSION}' --format '{{.ID}} {{.Repository}}' | grep 'jetbrains/teamcity-agent' | grep -Eo '^[^ ]+' | xargs -r docker rmi" %} {%- endraw %} ExecStart=/usr/bin/docker run \ -v /mnt/data/teamcity-agent/conf:/opt/buildagent/conf \ -v /mnt/data/teamcity-agent/logs:/opt/buildagent/logs \ -v /mnt/data/teamcity-agent/plugins:/opt/buildagent/plugins \ -v /mnt/data/teamcity-agent/system:/opt/buildagent/system \ -v /mnt/resource/teamcity-agent/temp:/opt/buildagent/temp \ -v /mnt/resource/teamcity-server/temp:/opt/teamcity/temp \ -v /mnt/data/teamcity-agent/tools:/opt/buildagent/tools \ --privileged \ -e DOCKER_IN_DOCKER=start \ -e SERVER_URL={{ SERVER_URL }} \ -e AGENT_NAME=Default \ --name teamcity-agent \ jetbrains/teamcity-agent:${TEAMCITY_VERSION} ExecStop=-/usr/bin/docker exec teamcity-agent /opt/buildagent/bin/agent.sh stop ExecStopPost=-/usr/bin/docker stop teamcity-agent ExecStopPost=-/usr/bin/docker rm teamcity-agent Restart=always [Install] WantedBy=multi-user.target {%- if properties['enableHttps'] %} - name: "{{ NGINX }}-directories.service" enable: true content: | [Unit] Before={{ NGINX }}.service After=mnt-data.mount docker.service Requires=mnt-data.mount docker.service ConditionPathExists=!/opt/domains/vhost.d/default [Service] Type=oneshot ExecStart=/bin/mkdir -p /opt/domains/certs ExecStart=/bin/mkdir -p /opt/domains/vhost.d ExecStart=/bin/mkdir -p /opt/domains/nginx/html ExecStart=/bin/sh -c "echo 'proxy_read_timeout 1200;' >> /opt/domains/vhost.d/default" ExecStart=/bin/sh -c "echo 'proxy_connect_timeout 240;' >> /opt/domains/vhost.d/default" ExecStart=/bin/sh -c "echo 'client_max_body_size 0;' >> /opt/domains/vhost.d/default" [Install] WantedBy=multi-user.target - name: "{{ NGINX }}.service" command: "start" content: | [Unit] Description=NGINX reverse proxy Requires=docker.service {{ NGINX }}-directories.service [Service] TimeoutStartSec=1200s ExecStart=/usr/bin/docker run \ -p 80:80 \ -p 443:443 \ -v /opt/domains/certs:/etc/nginx/certs:ro \ -v /opt/domains/vhost.d:/etc/nginx/vhost.d \ -v /opt/domains/nginx/html:/usr/share/nginx/html \ -v /var/run/docker.sock:/tmp/docker.sock:ro \ --name {{ NGINX }} \ {{ NGINX_IMAGE }} ExecStop=-/usr/bin/docker stop {{ NGINX }} ExecStopPost=-/usr/bin/docker rm {{ NGINX }} Restart=always [Install] WantedBy=multi-user.target - name: "{{ LETSENCRYPT }}.service" command: "start" content: | [Unit] Description=Let's Encrypt Requires=docker.service After={{ NGINX }}.service Before=teamcity-server.service [Service] TimeoutStartSec=1200s ExecStart=/usr/bin/docker run \ -v /opt/domains/certs:/etc/nginx/certs:rw \ --volumes-from {{ NGINX }} \ -v /var/run/docker.sock:/var/run/docker.sock:ro \ --name {{ LETSENCRYPT }} \ {{ LETSENCRYPT_IMAGE }} ExecStop=-/usr/bin/docker stop {{ LETSENCRYPT }} ExecStopPost=-/usr/bin/docker rm {{ LETSENCRYPT }} Restart=always [Install] WantedBy=multi-user.target {%- endif %} - name: "report-waiter-status.service" enable: true command: "start" content: | [Unit] {%- if properties['enableHttps'] %} Requires=docker.service teamcity-server.service {{ NGINX }}.service {{ LETSENCRYPT }}.service {%- else %} Requires=docker.service teamcity-server.service {%- endif %} Before=teamcity-agent.service ConditionPathExists=!/etc/teamcity/waiter [Service] Type=oneshot EnvironmentFile=/run/metadata/coreos {%- if properties['enableHttps'] %} ExecStartPre=/bin/bash /etc/teamcity/checkCertificate.sh {%- endif %} ExecStartPre=/bin/bash -c "until $(curl --output /dev/null --silent --fail {{ SERVER_URL }}); do echo 'Waiting for success response from {{ SERVER_URL }}'; sleep 10; done;" ExecStart=/usr/bin/docker run --rm -e "CLOUDSDK_CORE_DISABLE_PROMPTS=1" google/cloud-sdk:alpine gcloud beta runtime-config configs variables set success/{{ properties['waiter']['name'] }} success --config-name {{ properties['waiter']['config'] }} ExecStartPost=/usr/bin/docker rmi google/cloud-sdk:alpine ExecStartPost=/bin/touch /etc/teamcity/waiter [Install] WantedBy=multi-user.target disks: - deviceName: boot type: PERSISTENT autoDelete: true boot: true initializeParams: diskName: {{ BASE_NAME }}-disk sourceImage: {{ GlobalComputeUrl('teamcitytest-166414', 'images', 'flatcar-stable') }} diskSizeGb: 14 - deviceName: {{ BASE_NAME }}-data type: PERSISTENT autoDelete: true source: $(ref.{{ BASE_NAME }}-data.selfLink) networkInterfaces: - accessConfigs: - name: external-nat type: ONE_TO_ONE_NAT {%- if properties['ipAddress'] %} natIP: {{ properties['ipAddress'] }} {%- else %} natIP: $(ref.{{ BASE_NAME }}-address.address) {%- endif %} network: {{ GlobalComputeUrl(env['project'], 'networks', properties['network']) }} {%- if properties['subnetwork'] %} subnetwork: {{ RegionalComputeUrl(env['project'], REGION, 'subnetworks', properties['subnetwork']) }} {%- endif %} tags: items: - http-server {%- if properties['enableHttps'] %} - https-server {%- endif %} - ssh-server outputs: - name: ip value: $(ref.{{ BASE_NAME }}.networkInterfaces[0].accessConfigs[0].natIP)