[
  {
    "path": ".gitignore",
    "content": "InstallHalyard.sh\nsamples/helloworldwebapp/templates/pipelines/deployprod.json\nsamples/helloworldwebapp/templates/pipelines/deploystaging.json\nscripts/expose/*_expanded.*\nscripts/install/properties\nscripts/install/properties.*\nscripts/install/InstallHalyard.sh\nscripts/install/spinnakerAuditLog/config.json\nscripts/install/spinnakerAuditLog/index.js\nscripts/manage/*_expanded.*\nscripts/manage/delete-all_*.*\n.idea\n"
  },
  {
    "path": "README.md",
    "content": "# Install and manage Spinnaker on Google Cloud Platform\n\nSpinnaker on Google Cloud Platform is a tool for easily installing a production-ready instance of Spinnaker, and for managing that instance over time.\n\n## Do I want to use this solution?\n\nThis solution is for…\n\n* Anyone who wants an easy path to install open-source Spinnaker, in a\nproduction-ready configuration, on Google Cloud Platform\n\n* Anyone who wants to \"kick the tires\" of Spinnaker, to decide if it's the right\nCD solution for their needs\n\n* Administrators who will manage one or more long-running instances of\nSpinnaker, including adding additional administrators, adding accounts,\nupgrading, and so on\n\nThis solution gives you...\n\n* Google recommendations and best practices for installing and running Spinnaker\non GCP\n\n* Pre-integration with many other services that Spinnaker is commonly used with\n\n* Sample applications and other helpers for a smoother experience\n\n\n## What is this solution?\n\nSpinnaker for Google Cloud Platform is a solution for installing and managing\nSpinnaker on Google Cloud Platform. It consists of an installation and\nmanagement console, Spinnaker and its microservices, and sample applications.\n\n### What is Spinnaker?\n\nSpinnaker is an open source, multi-cloud continuous delivery platform for\nreleasing software changes with high velocity and confidence.\n\nIf you would like to learn more about Spinnaker, please visit the\n[Spinnaker website](https://www.spinnaker.io).\n\n### What is Deck?\n\nDeck is the Spinnaker UI. You access Deck in one of the following ways:\n\n* Via port forwarding\n\n  The management console provides a command for forwarding port 8080, and a\n  button to click to access Deck via that port.\n\n* Over the internet, on a publicly available domain\n\n  This domain is secured with [Identity-Aware Proxy](https://cloud.google.com/iap).\n\n### The management console\n\nThe management console makes it easy for you to do the following:\n\n* Install Spinnaker\n\n  Spinnaker for Google Cloud Platform makes it easy to get a working version of\n  open-source Spinnaker running on Google Kubernetes Engine. After it's\n  installed, you can make it available to your users. The installation flow\n  begins in the management console after you start the solution.\n\n* Manage Spinnaker\n\n  Use this same management console to manage/operate your Spinnaker\n  installation, including adding administrators, and creating accounts for\n  deploying to additional GKE clusters or other providers.\n\n  The management flow begins after you finish installing Spinnaker. You can also\n  open it directly via a link from the GKE Applications page in the Google Cloud\n  Console.\n\nThe management console uses Cloud Shell, with instructions shown in a guide on\nthe right-hand side of the window. The guide shows the commands that will be\nrun, and you can click those commands to copy them into Cloud Shell and run them\nthere.\n\n### What is Cloud Shell?\n\n[Cloud Shell](https://cloud.google.com/shell) is a tool in Google Cloud Platform\nthat provides command-line access to GCP.\n\n### How do I find and restore the instructions?\n\n* If the instructions in the right-hand pane disappear, just enter the following\ncommand in Cloud Shell:\n\n```bash\ncloudshell launch-tutorial ~/spinnaker-for-gcp/scripts/install/provision-spinnaker.md\n```\n\n* If you need to find your way back to the management console, you can relaunch\nit by following the instructions under Install Spinnaker on Google Cloud\nPlatform.\n\n* Refer back to this document if you get lost.\n\n## Am I billed for this?\n\nYou are billed for Google Cloud Platform resources that are installed as part of\nSpinnaker for Google Cloud Platform.\n\n* [Google Kubernetes Engine](https://cloud.google.com/kubernetes-engine/)\n* [Cloud Memorystore for Redis](https://cloud.google.com/memorystore/docs/redis/)\n* [Google Cloud Load Balancing](https://cloud.google.com/load-balancing/)\n\n...and possibly other resources, depending on the options you select when you\ninstall and configure Spinnaker. You can use the [Google Cloud Platform Pricing\nCalculator](https://cloud.google.com/products/calculator/) to estimate the cost\nof this solution.\n\n[Learn more about Google Cloud pricing](https://cloud.google.com/pricing/) &\n[free trial](https://cloud.google.com/developers/startups/).\n\n## Install and use Spinnaker on Google Cloud Platform\n\nYou access this solution by clicking the **Go to Spinnaker for Google Cloud\nPlatform** button on the [Spinnaker for GCP \npage](https://console.cloud.google.com/marketplace/details/google-cloud-platform/spinnaker)\nin Marketplace.\n\nAfter you've installed Spinnaker for Google Cloud Platform, you can access\nSpinnaker and the management console from [Google Cloud\nConsole](https://console.cloud.google.com/kubernetes/application).\n\n> Note: Spinnaker for Google Cloud Platform doesn't support regional clusters.\n> If you intend to [install Spinnaker on an existing\n> cluster](#install_spinnaker_on_existing_cluster), it must be zonal.\n\n> Note: Google recommends that you deploy your resources using an account other\n> than `spinnaker-install-account`. That account is used to install your\n> spinnaker instance, and resources deployed using that account are installed\n> into the spinnaker namespace by default. This namespace is not indexed, so\n> your deployments will time out before they are deemed stable.\n\n### Install Spinnaker on Google Cloud Platform\n\n1. Start the solution from the [Spinnaker for GCP Marketplace\npage](https://console.cloud.google.com/marketplace/details/google-cloud-platform/spinnaker)\nby clicking the **Go to Spinnaker for Google Cloud Platform** button.\n\n1. When prompted to Open in Cloud Shell, click **Proceed**.\n\n  Cloud Shell opens, along with a file tree showing the files in the Spinnaker\n  repository, and instructions.\n\n![The management console](resources/initial_mgmt_console.png)\n\n  > **Important:** If you've launched the management console at least once before,\n  > you might be prompted, in the shell, to resume with the clone you created\n  > before, update that clone, or clone a new copy of the repository. The first\n  > option is best (`cd` into the existing directory). Don't clone a new copy.\n\n  The spinnaker-for-gcp repository is cloned into your Cloud Shell.\n\n1. Follow the instructions shown on the screen.\n\nThe flow in the management console guides you through the installation process,\npresenting you with commands, which you can copy to the Cloud Shell prompt and\nthen execute by pressing **Enter**. The commands run scripts that automate the\nprocess of installing Spinnaker on GKE.\n\nIf the instruction pane disappears at any time, you can restore it using the\nfollowing command, from Cloud Shell:\n\n```bash\ncloudshell launch-tutorial ~/spinnaker-for-gcp/scripts/install/provision-spinnaker.md\n```\n\n### Access Spinnaker\n\nAfter you've installed Spinnaker, you can execute a command to forward ports,\nwhich allows you to access the Deck UI and start using Spinnaker. You can share\nthe port-forwarding command with your users, and if they have access to the GKE\ncluster, they can reach Deck (the Spinnaker UI) on port 8080.\n\nAlternatively, you can expose Spinnaker over the public internet, secured using\n[Identity-Aware Proxy](https://cloud.google.com/docs/ci-cd/spinnaker/spinnaker-for-gcp#expose_iap).\n\nBoth alternatives are described below.\n\n#### Access Spinnaker by forwarding ports\n\nYou can run a command in Cloud Shell in the management console, to forward ports\nso you can access Spinnaker from localhost:8080.\n\n1. Click to copy the `connect_unsecured.sh` command in the management console,\nand press **Enter**.\n\n  This forwards the local port 8080 to port 9000 (the port Deck uses) on the pod\n  running Deck.\n\n1. Click the \"Connect to Spinnaker…\" link. This highlights the Preview button.\n\n1. Click the highlighted preview button, and select **Preview on port 8080**.\n\n![Click to preview on port 8080](resources/preview_on_8080.png)\n\n  > **Note:**There is a \"Connect to Spinnaker\" link displayed. If you click it,\n  > it highlights the preview button, which you then click to select the port.\n\nDeck, the Spinnaker user interface, opens in your browser. The Spinnaker\ndocumentation site has instructions for using Spinnaker.\n\nBack in the management console, there are a few other things you can do:\n\n* Make Spinnaker securely available to your teams without having to forward\nports\n* View the Spinnaker audit log\n* View logs from Spinnaker microservices\n* Click **Next** to move on to the Spinnaker management portion of the solution.\n* Share the port-forwarding command with your users If they have access to the\nGKE cluster, they can reach Deck (the Spinnaker UI) on port 8080.\n\n### Give your users access to Spinnaker over the internet\n\nThe console includes a command that helps you create a secure endpoint from\nwhich to expose Spinnaker to your users, securely, over the internet.\n\n> **Note:** If you need to keep Spinnaker private, you can set up port\n> forwarding for your users. \n\n1. Navigate to step 2 of the installation flow in the Management console\n(\"Connect to Spinnaker\").\n\n1. Under \"Expose Spinnaker publicly,\" click the button to copy the command to\nthe command line, and press **Enter**.\n\n  The script creates a new endpoint from which to serve your Spinnaker instance.\n  After the script finishes, the guidance in the console changes to show\n  instructions for setting up OAuth so that your users can access this endpoint.\n\n1. Follow those on-screen instructions.\n\n  Make sure when you create your OAuth credentials that you copy the generated\n  client ID and secret. You'll need to provide them when prompted by the script.\n\n> **Note:** This process can take up to an hour, even if it appears that the\n> script has finished.\n\nYou now have a Spinnaker endpoint that you can share with your users, who\nauthenticate into it using OAuth2. A link to Spinnaker is displayed in the\nmanagement console. There is also a link on the GKE applications page for this\nSpinnaker instance.\n\n### Manage Spinnaker\n\nUse the management console to manage your spinnaker instance, including the\nfollowing actions:\n\n* Add administrators (operators)\n\n* Add cloud provider accounts\n\n  A provider is the cloud environment (for example, Google Compute Engine) where\n  you deploy your applications\n\n* Upgrade Spinnaker\n\n* Invoke Halyard commands to configure Spinnaker\n\n* Invoke spin commands to manage Spinnaker resources, like applications and\npipelines\n\n1. Access the management portion of this console.\n\n   Use one of the following options:\n\n   **If the console is already open:**\n\n   1. At the end of the installation flow, click **Next**.\n\n   1. Copy the command on the Next steps page and press **Enter**.\n\n      The instructions pane changes to start the management process.\n\n      ![Start managing Spinnaker from within the console](resources/start_management_pane.png)\n\n   **If the console is not already open:**\n\n   1. Go to the Google Kubernetes Engine applications page.\n\n   1. Open the Spinnaker application.\n\n      The application description includes a link: Open Management Environment\n      in Cloud Shell.\n\n   1. Click that link to open the management console, which now starts with the\n   management/admin functionality.\n\n      ![Start managing Spinnaker from the GKE Applications page](resources/open_tool_from_gke_application.png)\n\n   1. Select your GCP project, and click Start.\n\n#### Add administrators for your Spinnaker instance\n\nYou can give access to more operators, who can then use the management console.\n\n1. On the [IAM permissions page](https://console.developers.google.com/iam-admin/iam),\ngrant the person the 'Owner' role on the GCP project where you've installed Spinnaker.\n\n1. If you are serving Spinnaker on an IAP-secured endpoint, and if the person to \nwhom you're giving operator rights doesn't already have *user* access, use the\nfollowing command (which is also on step 5 of the management part of the\nconsole):\n\n   ```bash\n   ~/spinnaker-for-gcp/scripts/manage/grant_iap_access.sh\n   ```\n\n   ...and follow the instructions on the Cloud Shell command line.\n\n#### Add cloud provider accounts\n\nYou can use the management console to add accounts for as many cloud providers\n[as Spinnaker supports](https://www.spinnaker.io/setup/install/providers/).\nYou'll need one for each cloud on which your users intend to deploy\napplications. For example, if they will deploy applications to Google Compute\nEngine and AWS, you'll add a provider account for each.\n\nThe management console includes the following command, for adding a GKE account:\n\n`~/spinnaker-for-gcp/scripts/manage/add_gke_account.sh`\n\nAnd for Google Compute Engine:\n\n`~/spinnaker-for-gcp/scripts/manage/add_gce_account.sh`\n\nAnd for Google App Engine:\n\n`~/spinnaker-for-gcp/scripts/manage/add_gae_account.sh`\n\nYou can run these commands from the management console or enter them in Cloud\nShell against an existing Spinnaker instance.\n\n#### Run Halyard commands\n\nYou can invoke [any hal command](https://www.spinnaker.io/reference/halyard/commands)\nto configure and administer your Spinnaker installation.\n\nTo do so, just invoke the command from the Cloud Shell in the management\nconsole, *after* you've installed Spinnaker\n\n#### Upgrade Spinnaker\n\n1. Find out the version you want to upgrade to.\n\n   The [Versions page](https://www.spinnaker.io/community/releases/versions)\n   lists the stable versions available.\n\n1. In the console, navigate to the management flow:\n\n   `~/spinnaker-for-gcp/scripts/manage/update_console.sh`\n\n1. Click **Next** until you see the screen titled \"Scripts for Common Commands.\"\n\n1. Under \"Upgrade Spinnaker,\" copy the first command to the shell, and press\n**Enter**.\n\n   That command is...\n\n   ```bash\n   cloudshell edit \\\n    ~/spinnaker-for-gcp/scripts/install/properties\n    ```\n\n1. Edit the Spinnaker version in the `properties` file that is displayed.\n\n   ```bash\n   export SPINNAKER_VERSION=1.19.3\n   ```\n\n   The [Spinnaker Versions page](https://www.spinnaker.io/community/releases/versions)\n   shows the latest versions avaiable.\n\n1. Use the following command to invoke Halyard to apply the changes:\n\n   ```bash\n   ~/spinnaker-for-gcp/scripts/manage/update_spinnaker_version.sh\n   ```\n\n## Restart the management console\n\nIf you need to restart the console for any reason (for example, you closed the\ntab or window), you can restart it in the same way that you\n[started it](https://console.cloud.google.com/marketplace/details/google-cloud-platform/spinnaker).\nYou can also launch it from the [GKE Applications\npage](https://console.cloud.google.com/kubernetes/application) in the Google\nCloud Console, if you've previously installed Spinnaker for Google Cloud\nPlatform.\n\nWhen you restart the console, it prompts you to resume from where you left off, if you want.\n\n\n## Upgrade the management console\n\n1. In the management console, navigate to step 3, \"Scripts for Common Command,\"\nand scroll to the bottom of the page.\n\n1. Run the command shown under \"Upgrade Management Environment.\"\n\nThe management console is upgraded to include the latest changes.\n\n## Remove Spinnaker for Google Cloud Platform\n\n> **Warning:** If you installed Spinnaker on pre-existing infrastructure (GKE\n> cluster, Redis, service accounts), this script deletes those items. If you\n> want to keep them, edit the generated cleanup script\n> (`~/spinnaker-for-gcp/scripts/manage/generate_deletion_script.sh`) to comment\n> out the specific deletion commands for items you want to keep.\n\nIf you want to remove Spinnaker for any reason:\n\n1. Open the management console and click Next until you get to the \"Delete\nSpinnaker\" page.\n\n1. Copy the command to the Cloud Shell terminal, and press **Enter**.\n\nAll resources that were created for this Spinnaker instance, and any existing\nresources on which you might have deployed, are deleted.\n\n## Sample Applications\n\nThe Spinnaker for Google Cloud Platform solution comes with sample applications\nto help you get started with Spinnaker.\n\nTo install them:\n\n1. In the management console, click **Next** until you get to the step titled\n\"Use Spinnaker.\"\n\n1. Under **Install sample applications and pipelines**, click the button to\npaste the command, and press **Enter**.\n\n   Cloud Shell returns a list of available sample apps, numbered.\n\n1. Press the number corresponding to the application you want, or the number\ncorresponding to \"Quit\" to exit without installing any.\n\n1. Press Enter\n\n   The tutorial pane now displays guidance for the sample application.\n\n1. To exit the sample app and return to the management portion of the console,\nclick **Start** and then **Next**, then scroll to the bottom of the \"Start a new\nbuild\" page, and run the command under \"Return to Spinnaker console.\"\n\n## Other considerations\n\n### Spinnaker for GCP architecture\n\nSpinnaker and its microservices are installed on GKE using the following\narchitecture:\n\n![Architecture of Spinnaker on GCP](resources/spinnaker-k8s-app-architecture.png)\n\n### Install Spinnaker on an existing cluster\n\nYou can install your Spinnaker instance or instances on pre-existing\ninfrastructure, instead of having this solution create it new.\n\nThe cluster must have the following:\n\n* IP aliases enabled, because this uses a hosted Redis instance\n\n* Full Cloud Platform scope for its nodes if you're using the project default\nservice account\n\nBefore you run the installation script, do the following:\n\n1. Copy and run the following command (which is also available in step 1 of the\ninstallation flow):\n\n   ```bash\n   cloudshell edit \\\n       ~/spinnaker-for-gcp/scripts/install/properties\n   ```\n\n   The properties file is opened in the file editor.\n\n1. Edit this section of the `properties` file to identify the Kubernetes cluster\non which to install Spinnaker:\n\n   ```bash\n   # If cluster does not exist, it will be created.\n   export GKE_CLUSTER=$DEPLOYMENT_NAME\n   export ZONE=us-west1-b\n   export REGION=us-west1\n   ```\n\n1. Similarly, edit other properties to identify other existing infrastructure\nand accounts that you want to use, if applicable.\n\n   For example an existing Cloud Memorystore Redis instance, or a bucket or a\n   service account. In each case, if the infrastructure doesn't exist, the\n   installation script creates it for you.\n\n### Manage multiple Spinnaker installations\n\nIf you run multiple Spinnaker instances, they must be on separate clusters, and therefore in different Kubernetes contexts.\n\n> **Important:** If you're trying to install multiple Spinnaker instances, don't\nclone multiple copies of the spinnaker-for-gcp repo.\n\nTo manage one of those installations:\n\n1. Get your credentials.\n\n   ```bash\n   gcloud container get-credentials\n   ```\n\n1. Switch to the appropriate Kubernetes context.\n\n   ```bash\n   kubectl config use-context <CONTEXT_NAME>\n   ```\n\n1. Pull the configuration stored in that cluster.\n\n   ```bash\n   ~/spinnaker-for-gcp/scripts/manage/pull_config.sh\n   ```\n\nThe config now in `~/spinnaker-for-gcp/scripts/install/properties` is the one\nfor that Spinnaker instance. Perform the usual management tasks available to\nyou, including running `hal` commands. Spinnaker applies those commands to the\nSpinnaker instance in the chosen context.\n\n"
  },
  {
    "path": "apptest/tester/Dockerfile",
    "content": "FROM gcr.io/cloud-marketplace-tools/testrunner:0.1.2\n\nRUN apt-get update && apt-get install -y --no-install-recommends \\\n    ca-certificates \\\n    gettext \\\n    jq \\\n    uuid-runtime \\\n    wget \\\n    curl \\\n    && rm -rf /var/lib/apt/lists/*\n\nRUN wget -q -O /bin/kubectl \\\n    https://storage.googleapis.com/kubernetes-release/release/v1.12.0/bin/linux/amd64/kubectl \\\n      && chmod 755 /bin/kubectl\n\nCOPY tests/basic-suite.yaml /tests/basic-suite.yaml\nCOPY tester.sh /tester.sh\n\nWORKDIR /\nENTRYPOINT [\"/tester.sh\"]\n"
  },
  {
    "path": "apptest/tester/build-and-run-tests.sh",
    "content": "#!/bin/bash\n#\n# Would expect this to be deleted once these tests are properly integrated with GCP Marketplace Verification Pipeline.\n\nbold() {\n  echo \". $(tput bold)\" \"$*\" \"$(tput sgr0)\";\n}\n\nif [ -z \"$PROJECT_ID\" ]; then\n  PROJECT_ID=$(gcloud info --format='value(config.project)')\nfi\n\nif [ -z \"$PROJECT_ID\" ]; then\n  bold \"Please set PROJECT_ID env var.\"\n  exit 1\nfi\n\nexport PROJECT_ID\n\ndocker build -t gcr.io/$PROJECT_ID/spinnaker-c2d-tests .\n\ndocker push gcr.io/$PROJECT_ID/spinnaker-c2d-tests:latest\n\nkubectl delete job spinnaker-test-job\n\nenvsubst < spinnaker-test-job.yaml | kubectl apply -f -\n\nkubectl wait --for condition=complete job spinnaker-test-job\n\nkubectl logs -l job-name=spinnaker-test-job\n"
  },
  {
    "path": "apptest/tester/spinnaker-test-job.yaml",
    "content": "# Would expect this to be deleted once these tests are properly integrated with GCP Marketplace Verification Pipeline.\n\napiVersion: batch/v1\nkind: Job\nmetadata:\n  name: spinnaker-test-job\nspec:\n  template:\n    spec:\n      containers:\n      - name: spinnaker-tester-container\n        image: gcr.io/$PROJECT_ID/spinnaker-c2d-tests\n      restartPolicy: Never\n"
  },
  {
    "path": "apptest/tester/tester.sh",
    "content": "#!/bin/bash\n#\n# Copyright 2019 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nset -xeo pipefail\nshopt -s nullglob\n\nexport CLOUDDRIVER_ADDR=\"spin-clouddriver.spinnaker\"\nexport GATE_ADDR=\"spin-gate.spinnaker\"\n\nfor test in /tests/*; do\n  testrunner -logtostderr \"--test_spec=${test}\"\ndone\n"
  },
  {
    "path": "apptest/tester/tests/basic-suite.yaml",
    "content": "actions:\n- name: Clouddriver is up and healthy\n  bashTest:\n    script: curl -k \"http://{{ .Env.CLOUDDRIVER_ADDR }}:7002/health\" | jq -r .status\n    expect:\n      stdout:\n        equals: 'UP'\n      exitCode:\n        equals: 0\n- name: Gate returns credentials and they include default account\n  bashTest:\n    script: curl -k \"http://{{ .Env.GATE_ADDR }}:8084/credentials\" | jq [.[].name]\n    expect:\n      stdout:\n        contains: '\"spinnaker-install-account\"'\n      exitCode:\n        equals: 0\n"
  },
  {
    "path": "ci/CLOUD_BUILD.md",
    "content": "# Using Cloud Build to install Spinnaker for GCP\n\n## A note about Shared VPC support\n\nYou can't use Cloud Build to install Spinnaker for GCP with a shared VPC. For Shared VPC support, conduct the [setup in Cloud Shell](https://cloud.google.com/docs/ci-cd/spinnaker/spinnaker-for-gcp).\n\n## Service account\n\nTo install Spinnaker for GCP, you need to grant the [Cloud Build service account](https://console.cloud.google.com/cloud-build/settings) the following roles:\n\n- Cloud Functions Developer - roles/cloudfunctions.developer\n- Compute Network Viewer - roles/compute.networkViewer\n- Kubernetes Engine Admin - roles/container.admin \n- Create Service Accounts - roles/iam.serviceAccountCreator\n- Pub/Sub Editor - roles/pubsub.editor\n- Cloud Memorystore Redis Admin - roles/redis.admin\n- Service Usage Admin - roles/serviceusage.serviceUsageAdmin\n- Source Repository Administrator - roles/source.admin\n- Storage Admin - roles/storage.admin\n- Project IAM Admin - roles/resourcemanager.projectIamAdmin\n- Service Account User - roles/iam.serviceAccountUser\n\nYou can grant these roles using the IAM UI or with [gcloud](https://cloud.google.com/sdk/gcloud/reference/projects/add-iam-policy-binding).\n\n## Enable Cloud Resource Manager API\n\nFor Cloud Build to successfully retrieve IAM policies, you must enable the Cloud Resource Manager API. Visit this URL, substituting your project id.\n\nhttps://console.developers.google.com/apis/api/cloudresourcemanager.googleapis.com/overview?project=[PROJECT_ID]\n\n## Properties file\n\nTo get Cloud Build to install Spinnaker for GCP, you need to generate a properties file:\n\n 1. Run [setup_properties.sh](../scripts/install/setup_properties.sh).\n \n 1. Copy that file to the directory containing the Cloud Build YAML, so the installation script can access it while executing the Cloud Build job.\n\n## Submitting a Build\n\nCloud Builds can be triggered using [gcloud](https://cloud.google.com/cloud-build/docs/running-builds/start-build-manually), [build triggers](https://cloud.google.com/cloud-build/docs/running-builds/automate-builds), or [GitHub app triggers](https://cloud.google.com/cloud-build/docs/create-github-app-triggers). The solution in this repository installs Spinnaker for GCP using a gcloud-triggered build. Follow these steps to start a build:\n\n1. Create a new directory. The contents of this directory will be submitted to Cloud Build.\n2. Place the generated properties file into that directory.\n3. Copy the [cloudbuild.yaml](cloudbuild.yaml) file into the directory and edit the `user.name` and `user.email` used in the Git configuration steps.\n\n```yaml\n  - name: gcr.io/cloud-builders/git\n    args: ['config', '--global', 'user.name', '<example-user>']\n  - name: gcr.io/cloud-builders/git\n    args: ['config', '--global', 'user.email', '<example-user@example.com>']\n```\n\n4. Copy the [Dockerfile](Dockerfile) and [install.bash](install.bash) file into the directory.\n5. Submit the build to Cloud Build: `gcloud builds submit --timeout \"25m\"  --config cloudbuild.yaml --project PROJECT_ID .`\n\nCloud Build will execute the job, installing Spinnaker for GCP. If you make any changes to the properties file, re-run the job. Additional instructions for how to access or manage the deployed Spinnaker application are available [here](https://cloud.google.com/docs/ci-cd/spinnaker/spinnaker-for-gcp#access_spinnaker).\n"
  },
  {
    "path": "ci/Dockerfile",
    "content": "FROM gcr.io/cloud-builders/gcloud\n\nRUN apt-get -q update && apt-get install -qqy \\ \n  jq \\\n  gettext-base\n\nENTRYPOINT []"
  },
  {
    "path": "ci/JENKINS.md",
    "content": "# Using Jenkins to install Spinnaker for GCP\n\nYou can use Jekins to install Spinnaker for GCP. The Jenkins agent executing the job must be installed on a Unix-like operating system. \n\nThe following section assumes you have an existing Jenkins server. If not, consider one of the [Jenkins solutions on Google Cloud](https://cloud.google.com/jenkins/).\n\n## Jenkins on GCP\n\nIf your Jenkins server is running on GCP, follow [best practices](https://cloud.google.com/compute/docs/access/create-enable-service-accounts-for-instances#best_practices) for managing its service account. Your Jenkins server must have full access to all Google Cloud APIs to successfully install Spinnaker for GCP. See the [Compute Engine documentation](https://cloud.google.com/compute/docs/access/create-enable-service-accounts-for-instances#changeserviceaccountandscopes) for guidance on how to modify an instance's Google Cloud API access scopes.\n\nYou can't use Jenkins to install Spinnaker for GCP with a shared VPC. For Shared VPC support, conduct the [setup in Cloud Shell](https://cloud.google.com/docs/ci-cd/spinnaker/spinnaker-for-gcp).\n\n## Dependencies\n\nThere are several dependencies that must be available to the Jenkins server before it can be used to install Spinnaker for GCP.\n\n### Google Cloud SDK\n\nThe Google Cloud SDK is required to provision GCP resources. Install a [versioned archive](https://cloud.google.com/sdk/docs/downloads-versioned-archives) to the Jenkins server.\n\n### Git\n\nGit is required for backing up and restoring the Spinnaker for GCP configuration. Install Git on the Jenkins server by running `sudo apt-get install git-all`\n\n### `kubectl`\n\n`kubectl` is required to manage the cluster Spinnaker for GCP will be installed on. Install `kubectl` on the Jenkins server by running `sudo apt-get install kubectl`\n\n### jq\n\njq is required for processing JSON. Install jq to the Jenkins server by running `sudo apt-get install jq`\n\n### AnsiColor Plugin\n\nThe [AnsiColor Jenkins Plugin](https://plugins.jenkins.io/ansicolor) is required for properly rendering stdout while installing Spinnaker for GCP. Once the plugin has been installed, enable `Color ANSI Console Output` in the build configuration and set the `ANSI color map` to `xterm`.\n\n## Service account\n\nThe Jenkins server must be configured with a GCP service account with the following roles:\n\n- Cloud Functions Developer - roles/cloudfunctions.developer\n- Compute Network Viewer - roles/compute.networkViewer\n- Kubernetes Engine Admin - roles/container.admin \n- Create Service Accounts - roles/iam.serviceAccountCreator\n- Pub/Sub Editor - roles/pubsub.editor\n- Cloud Memorystore Redis Admin - roles/redis.admin\n- Service Usage Admin - roles/serviceusage.serviceUsageAdmin\n- Source Repository Administrator - roles/source.admin\n- Storage Admin - roles/storage.admin\n- Project IAM Admin - roles/resourcemanager.projectIamAdmin\n- Service Account User - roles/iam.serviceAccountUser\n\nThese roles can be enabled through the IAM UI or with [gcloud](https://cloud.google.com/sdk/gcloud/reference/projects/add-iam-policy-binding).\n\n## Enable Cloud Resource Manager API\n\nThe Cloud Resource Manager API must be enabled for Jenkins to successfully retrieve IAM policies. Enable it for your project by visiting the below URL and substituting your project id.\n\nhttps://console.developers.google.com/apis/api/cloudresourcemanager.googleapis.com/overview?project=[PROJECT_ID]\n\n## Properties file\n\nTo get Jenkins to install Spinnaker for GCP, you need to generate a properties file and make it available to Jenkins:\n\n1. Run [setup_properties.sh](../scripts/install/setup_properties.sh).\n \n1. Make the resulting properties file available to the installation script as it executes the Jenkins job. In this example, the properties file has been uploaded to the Jenkins server using the [Credentials plug-in](https://wiki.jenkins.io/display/JENKINS/Credentials+Plugin). It is made accessible to the job by binding the file to the PROPERTIES variable. Alternatively, you can use a secrets-management solution (like [Hashicorp Vault](https://www.vaultproject.io/)) and configure Jenkins to read it from there.\n\n### Configure the Jenkins job\n\nOnce the dependencies are fulfilled, follow these steps to configure a job to install Spinnaker for GCP. \n\n1. Create a `New Item` from the Jenkins menu and select a `Freestyle project`. \n1. From the configuration screen, enable `Color ANSI Console Output` and set the `ANSI color map` to `xterm`.\n1. Under `Build Environment`, enable `Delete workspace before build starts`.\n1. Add an `Execute Shell` build step.\n1. Configure the build step to retrieve and execute the `setup.sh` script.\n\n```shell\n#!/usr/bin/env bash\n\nset -e\n\ngit clone https://github.com/GoogleCloudPlatform/spinnaker-for-gcp.git\ngit config --global user.name \"jenkins-user\"\ngit config --global user.email \"jenkins-user@example.com\"\n\nPARENT_DIR=$WORKSPACE PROPERTIES_FILE=$PROPERTIES CI=true $WORKSPACE/spinnaker-for-gcp/scripts/install/setup.sh\n```\n\nIn the above example, the Git `user.name` and `user.email` must be configured before you run `setup.sh`. Git operations can also be managed using the [Jenkins Git plugin](https://plugins.jenkins.io/git).\n\n`setup.sh` requires several variables to be passed in:\n\n- `PARENT_DIR`: The absolute path for the Jenkins workspace. Jenkins makes this available via `$WORKSPACE`.\n- `PROPERTIES_FILE`: This is the absolute path to your generated Spinnaker for GCP properties file.\n- `CI`: This must be set to `true` when running `setup.sh` outside of Cloud Shell.\n\n1. Execute the job to install Spinnaker for GCP. \n\nIf you change the properties file, apply the change by re-running the job. \n\nAdditional instructions for how to access or manage the deployed Spinnaker application are available [here](https://cloud.google.com/docs/ci-cd/spinnaker/spinnaker-for-gcp#access_spinnaker).\n"
  },
  {
    "path": "ci/README.md",
    "content": "# Installing Spinnaker for GCP on a Continous Integration Server\n\nYou can install Spinnaker for GCP using a continous integration Server. A CI server can be used to conduct the initial installation and to apply updates when the Spinnaker for GCP properties file changes. Solutions for [Google Cloud Build](CLOUD_BUILD.md) and [Jenkins](JENKINS.md) are available.\n"
  },
  {
    "path": "ci/cloudbuild.yaml",
    "content": "steps:\n- name: 'gcr.io/cloud-builders/docker'\n  args: ['build', '-f', 'Dockerfile', '-t', 'installer', '.']\n- name: gcr.io/cloud-builders/git\n  args: ['clone', 'https://github.com/GoogleCloudPlatform/spinnaker-for-gcp.git']  \n- name: gcr.io/cloud-builders/git\n  args: ['config', '--global', 'user.name', '<example-user>']\n- name: gcr.io/cloud-builders/git\n  args: ['config', '--global', 'user.email', '<example-user@example.com>']\n- name: 'installer'\n  args: ['bash', './install.bash']\n  env:\n  - 'TERM=xterm'\n"
  },
  {
    "path": "ci/install.bash",
    "content": "#!/bin/bash\n\nset -e\n\nPARENT_DIR=/workspace PROPERTIES_FILE=/workspace/properties CI=true /workspace/spinnaker-for-gcp/scripts/install/setup.sh"
  },
  {
    "path": "samples/helloworldwebapp/cleanup_app_and_pipelines.sh",
    "content": "#!/usr/bin/env bash\n\nbold() {\n  echo \". $(tput bold)\" \"$*\" \"$(tput sgr0)\";\n}\n\ncd ~/cloudshell_open/spinnaker-for-gcp/\n\nsource scripts/install/properties\n\nscripts/manage/check_project_mismatch.sh\n\nread -p \". $(tput bold)You are about to delete all resources from the helloworldwebapp application and pipelines. This step is not reversible. Do you wish to continue (Y/n)? $(tput sgr0)\" yn\ncase $yn in\n  [Yy]* ) ;;\n  \"\" ) ;;\n  * ) exit;;\nesac\n\nbold \"Deleting Cloud Source Repository...\"\n\ngcloud source repos delete spinnaker-for-gcp-helloworldwebapp\nrm -rf ~/$PROJECT_ID/spinnaker-for-gcp-helloworldwebapp\n\nbold \"Deleting helloworldwebapp-prod and helloworldwebapp-staging Kubernetes resources...\"\n\nkubectl delete -f samples/helloworldwebapp/templates/repo/config/staging/namespace.yaml\nkubectl delete -f samples/helloworldwebapp/templates/repo/config/prod/namespace.yaml\n\nbold \"Deleting Cloud Build trigger...\"\n\nfor trigger in $(gcloud alpha builds triggers list --filter triggerTemplate.repoName=spinnaker-for-gcp-helloworldwebapp --format 'get(id)'); do\n  gcloud alpha builds triggers delete -q $trigger\ndone\n\nbold \"Deleting Kubernetes manifests...\"\n\ngsutil -m rm -r gs://$BUCKET_NAME/helloworldwebapp-manifests\n\nbold \"Deleting GCR images...\"\n\nfor digest in $(gcloud container images list-tags gcr.io/${PROJECT_ID}/spinnaker-for-gcp-helloworldwebapp --format='get(digest)'); do\n  gcloud container images delete -q --force-delete-tags \"gcr.io/${PROJECT_ID}/spinnaker-for-gcp-helloworldwebapp@${digest}\"\ndone\n\nbold \"Deleting Spinnaker helloworldwebapp application and pipelines...\"\n\nset -x\n~/spin pipeline delete -a helloworldwebapp -n \"Deploy to Staging\"\n~/spin pipeline delete -a helloworldwebapp -n \"Deploy to Production\"\n~/spin application delete helloworldwebapp\n{ set +x ;} 2> /dev/null\n\nbold \"Finished cleaning up helloworldwebapp resources.\"\n"
  },
  {
    "path": "samples/helloworldwebapp/create_app_and_pipelines.sh",
    "content": "#!/usr/bin/env bash\n\nbold() {\n  echo \". $(tput bold)\" \"$*\" \"$(tput sgr0)\";\n}\n\nsource ~/cloudshell_open/spinnaker-for-gcp/scripts/install/properties\n\n~/cloudshell_open/spinnaker-for-gcp/scripts/manage/check_project_mismatch.sh\n\npushd ~/cloudshell_open/spinnaker-for-gcp/samples/helloworldwebapp\n\nif ! ~/spin app list &> /dev/null ; then\n  bold \"Spinnaker instance is not reachable via the Spin CLI. Please make sure the Spinnaker \\\ninstance is reachable with port-forwarding or is exposed publicly.\n\nTo port-forward the Spinnaker UI, run this command:\n~/cloudshell_open/spinnaker-for-gcp/scripts/manage/connect_unsecured.sh\n\nIf you would instead like to expose the service with a domain behind Identity-Aware Proxy, \\\nrun this command:\n~/cloudshell_open/spinnaker-for-gcp/scripts/expose/configure_endpoint.sh\n\"\n  exit 1\nfi\n\nif [ ! -d ~/$PROJECT_ID/spinnaker-for-gcp-helloworldwebapp ]; then\n  bold 'Creating GCR repo \"spinnaker-for-gcp-helloworldwebapp\" in Spinnaker project...'\n  gcloud source repos create spinnaker-for-gcp-helloworldwebapp\n  mkdir -p ~/$PROJECT_ID\n  gcloud source repos clone spinnaker-for-gcp-helloworldwebapp ~/$PROJECT_ID/spinnaker-for-gcp-helloworldwebapp\nfi\n\nbold 'Adding/Updating Kubernetes config files, sample Go application, and cloud build files in sample repo...'\ncp -r templates/repo/config ~/$PROJECT_ID/spinnaker-for-gcp-helloworldwebapp/\ncp -r templates/repo/src ~/$PROJECT_ID/spinnaker-for-gcp-helloworldwebapp/\ncp templates/repo/Dockerfile ~/$PROJECT_ID/spinnaker-for-gcp-helloworldwebapp/\n\ncat templates/repo/cloudbuild_yaml.template | envsubst '$BUCKET_NAME' > ~/$PROJECT_ID/spinnaker-for-gcp-helloworldwebapp/cloudbuild.yaml\ncat ~/$PROJECT_ID/spinnaker-for-gcp-helloworldwebapp/config/staging/replicaset_yaml.template | envsubst > ~/$PROJECT_ID/spinnaker-for-gcp-helloworldwebapp/config/staging/replicaset.yaml\nrm ~/$PROJECT_ID/spinnaker-for-gcp-helloworldwebapp/config/staging/replicaset_yaml.template\ncat ~/$PROJECT_ID/spinnaker-for-gcp-helloworldwebapp/config/prod/replicaset_yaml.template | envsubst > ~/$PROJECT_ID/spinnaker-for-gcp-helloworldwebapp/config/prod/replicaset.yaml\nrm ~/$PROJECT_ID/spinnaker-for-gcp-helloworldwebapp/config/prod/replicaset_yaml.template\n\npushd ~/$PROJECT_ID/spinnaker-for-gcp-helloworldwebapp\n\ngit add *\ngit commit -m \"Add source, build, and manifest files.\"\ngit push\n\npopd\n\nif [ -z $(gcloud alpha builds triggers list --filter triggerTemplate.repoName=spinnaker-for-gcp-helloworldwebapp --format 'get(id)') ]; then\n  bold \"Creating Cloud Build build trigger for helloworld app...\"\n  gcloud alpha builds triggers create cloud-source-repositories \\\n    --repo spinnaker-for-gcp-helloworldwebapp \\\n    --branch-pattern master \\\n    --build-config cloudbuild.yaml \\\n    --included-files \"src/**,config/**\"\nfi\n\nbold \"Creating helloworldwebapp Spinnaker application...\"\n~/spin app save --application-name helloworldwebapp --cloud-providers kubernetes --owner-email $IAP_USER\n\nbold 'Creating \"Deploy to Staging\" Spinnaker pipeline...'\ncat templates/pipelines/deploystaging_json.template | envsubst  > templates/pipelines/deploystaging.json\n~/spin pi save -f templates/pipelines/deploystaging.json\n\nexport DEPLOY_STAGING_PIPELINE_ID=$(~/spin pi get -a helloworldwebapp -n 'Deploy to Staging' | jq -r '.id')\n\nbold 'Creating \"Deploy to Prod\" Spinnaker pipeline...'\ncat templates/pipelines/deployprod_json.template | envsubst  > templates/pipelines/deployprod.json\n~/spin pi save -f templates/pipelines/deployprod.json\n\npopd\n"
  },
  {
    "path": "samples/helloworldwebapp/install.md",
    "content": "# Install and run sample application and pipelines\n\n## Introduction\n\nTry out Spinnaker using the sample application provided with your Spinnaker instance. It comes with...\n\n* A sample \"hello world\" Go application\n* A Cloud Build trigger to build an image from source\n* Sample Spinnaker pipelines to deploy the image and validate the application in a progression from staging environment to production\n\nTo proceed, make sure the Spinnaker instance is reachable with port-forwarding or is exposed publicly.\n\nSelect the project containing your Spinnaker instance, then click **Start**, below.\n\n<walkthrough-project-billing-setup/>\n\n## Create application and pipelines\n\nRun this command to create the required resources:\n\n```bash\n~/cloudshell_open/spinnaker-for-gcp/samples/helloworldwebapp/create_app_and_pipelines.sh\n```\n\n### Resources created:\n\nThe source code is hosted in a repository in [Cloud Source Repository](https://source.cloud.google.com/{{project-id}}/spinnaker-for-gcp-helloworldwebapp)\nin the same project as your Spinnaker cluster.\n\nThis repository contains a few other items:\n\n* Kubernetes configs for the application\n\n  These are used to deploy the application and validate the service.\n\n* A [Cloud Build config](https://source.cloud.google.com/{{project-id}}/spinnaker-for-gcp-helloworldwebapp/+/master:cloudbuild.yaml)\n\n  This builds the image and copies the Kubernetes configs to the Spinnaker GCS bucket.\n\n* A [Cloud Build trigger](https://console.developers.google.com/cloud-build/triggers?project={{project-id}}) \n\n  This executes the Cloud Build config when any source code or manifest files are changed under\n  src/** or config/** in the repository.\n\nCloud Build creates an [image](https://gcr.io/{{project-id}}/spinnaker-for-gcp-helloworldwebapp)\nfrom source and tags that image with the short commit hash.\n\nThe script also creates two Kubernetes namespaces...\n* **helloworldwebapp-staging**\n* **helloworldwebapp-prod**\n\n...and the **helloworldwebapp-service** service in each of those namespaces, in the [Spinnaker Kubernetes cluster](https://console.developers.google.com/kubernetes/discovery?project={{project-id}}).\n\nThese services expose the Go application for staging and prod environments.\n\nThis process creates two Spinnaker pipelines under the **helloworldwebapp** Spinnaker application:\n\n* **Deploy to Staging**\n\n  This triggers on a newly completed GCB build, and deploys the image to the\n  **helloworldwebapp-staging** namespace. It then runs a validation job to check the health status of the service.\n\n* **Deploy to Production**\n\n  This starts on a successful **Deploy to Staging** run and Blue/Green deploys \n  the tested image to **helloworldwebapp-prod** namespace. It then runs the health validation job.\n \n  On success, the old replicaset is scaled down after a 5 minute wait period.\n\n  On failure, the old replicaset is re-enabled and the new replicaset is disabled. A Pub/Sub\n  notification of the failure is sent via the preconfigured Pub/Sub publisher.\n\nYou can navigate to your Spinnaker UI to see these pipelines.\n\n## Start a new build\n\nTo build and deploy an image, just change some [source code](https://source.cloud.google.com/{{project-id}}/spinnaker-for-gcp-helloworldwebapp/+/master:src/main.go)\nor [manifest files](https://source.cloud.google.com/{{project-id}}/spinnaker-for-gcp-helloworldwebapp/+/master:config/) and push the change to the master branch. \n\nThe repository is already cloned to your home directory. Make some changes to the source code...\n\n```bash\ncloudshell edit ~/{{project-id}}/spinnaker-for-gcp-helloworldwebapp/src/main.go\n```\n\n...and commit the changes:\n```bash\ncd ~/{{project-id}}/spinnaker-for-gcp-helloworldwebapp\n\ngit commit -am \"Cool new features\"\ngit push\n```\n\nThe new commit triggers the chain of events...\n1. Cloud Build builds the image.\n2. The **Deploy to Staging** pipeline deploys the image to staging and validates it.\n3. The **Deploy to Production** pipeline promotes the image to production and validates it.\n\nVisit the Spinnaker UI to verify that the pipelines complete successfully.\n\nAfter the pipelines finish, the [**helloworldwebapp-services**](https://console.developers.google.com/kubernetes/discovery?project={{project-id}})\nhosting the Go application will now be up and healthy. Click on the **endpoints**\nfor each service to see a \"Hello World\" page!\n\n### Clean-up\n\nRun this command to delete all the resources created above:\n\n```bash\n~/cloudshell_open/spinnaker-for-gcp/samples/helloworldwebapp/cleanup_app_and_pipelines.sh && cd ~/cloudshell_open/spinnaker-for-gcp\n```\n\n### Return to Spinnaker console\n\nRun this command to return to the management environment:\n\n```bash\n~/cloudshell_open/spinnaker-for-gcp/scripts/manage/update_console.sh\n```\n"
  },
  {
    "path": "samples/helloworldwebapp/templates/pipelines/deployprod_json.template",
    "content": "{\n  \"application\": \"helloworldwebapp\",\n  \"description\": \"When staging deployment and validation completes, Blue/Green deploy new image to production environment and validate.\",\n  \"expectedArtifacts\": [\n    {\n      \"displayName\": \"Prod Replicaset\",\n      \"id\": \"d9013e3f-e9cd-4f18-ace6-ef14369b7fec\",\n      \"matchArtifact\": {\n        \"id\": \"b4557686-0d7e-4163-8cb5-f7e7f1310fa8\",\n        \"name\": \"gs://$BUCKET_NAME/helloworldwebapp-manifests/.*/prod-replicaset.yaml\",\n        \"type\": \"gcs/object\"\n      },\n      \"useDefaultArtifact\": false,\n      \"usePriorArtifact\": true\n    },\n    {\n      \"displayName\": \"Hello World WebApp Image\",\n      \"id\": \"4f4d38de-80c3-4bc1-a807-c565bc4024ee\",\n      \"matchArtifact\": {\n        \"id\": \"9aa4d777-1d4e-44f9-8f62-baa33a6a8040\",\n        \"name\": \"gcr.io/$PROJECT_ID/spinnaker-for-gcp-helloworldwebapp\",\n        \"type\": \"docker/image\"\n      },\n      \"useDefaultArtifact\": false,\n      \"usePriorArtifact\": true\n    },\n    {\n      \"displayName\": \"Prod Namespace\",\n      \"id\": \"730af75f-50ad-49ab-b754-ec8ae75938f8\",\n      \"matchArtifact\": {\n        \"id\": \"1d82dc39-76e1-4a96-8f9c-7f68b3edfa67\",\n        \"name\": \"gs://$BUCKET_NAME/helloworldwebapp-manifests/.*/prod-namespace.yaml\",\n        \"type\": \"gcs/object\"\n      },\n      \"useDefaultArtifact\": false,\n      \"usePriorArtifact\": true\n    },\n    {\n      \"displayName\": \"Prod Service\",\n      \"id\": \"76641671-4d6b-4aee-94b4-dba73fbabfcd\",\n      \"matchArtifact\": {\n        \"id\": \"63b7838b-045b-4681-9a54-852cdb22efd0\",\n        \"name\": \"gs://$BUCKET_NAME/helloworldwebapp-manifests/.*/prod-service.yaml\",\n        \"type\": \"gcs/object\"\n      },\n      \"useDefaultArtifact\": false,\n      \"usePriorArtifact\": true\n    }\n  ],\n  \"keepWaitingPipelines\": false,\n  \"limitConcurrent\": true,\n  \"name\": \"Deploy to Production\",\n  \"notifications\": [\n    {\n      \"level\": \"pipeline\",\n      \"publisherName\": \"$PUBSUB_NOTIFICATION_PUBLISHER\",\n      \"type\": \"pubsub\",\n      \"when\": [\n        \"pipeline.failed\"\n      ]\n    }\n  ],\n  \"parameterConfig\": [],\n  \"stages\": [\n    {\n      \"account\": \"spinnaker-install-account\",\n      \"cloudProvider\": \"kubernetes\",\n      \"expectedArtifacts\": [],\n      \"manifestArtifactAccount\": \"gcs-install-account\",\n      \"manifestArtifactId\": \"d9013e3f-e9cd-4f18-ace6-ef14369b7fec\",\n      \"manifests\": [],\n      \"moniker\": {\n        \"app\": \"helloworldwebapp\"\n      },\n      \"name\": \"Blue/Green Deploy Replicaset to Production\",\n      \"refId\": \"1\",\n      \"relationships\": {\n        \"loadBalancers\": [],\n        \"securityGroups\": []\n      },\n      \"requiredArtifactIds\": [\n        \"4f4d38de-80c3-4bc1-a807-c565bc4024ee\"\n      ],\n      \"requisiteStageRefIds\": [\n        \"8\"\n      ],\n      \"skipExpressionEvaluation\": false,\n      \"source\": \"artifact\",\n      \"trafficManagement\": {\n        \"enabled\": true,\n        \"options\": {\n          \"enableTraffic\": true,\n          \"namespace\": \"helloworldwebapp-prod\",\n          \"services\": [\n            \"service helloworldwebapp-service\"\n          ],\n          \"strategy\": \"redblack\"\n        }\n      },\n      \"type\": \"deployManifest\"\n    },\n    {\n      \"failPipeline\": true,\n      \"instructions\": \"Continue with deployment?\",\n      \"judgmentInputs\": [],\n      \"name\": \"Manual Judgment\",\n      \"notifications\": [],\n      \"refId\": \"2\",\n      \"requisiteStageRefIds\": [],\n      \"type\": \"manualJudgment\"\n    },\n    {\n      \"account\": \"spinnaker-install-account\",\n      \"app\": \"helloworldwebapp\",\n      \"cloudProvider\": \"kubernetes\",\n      \"completeOtherBranchesThenFail\": false,\n      \"continuePipeline\": true,\n      \"failPipeline\": false,\n      \"location\": \"helloworldwebapp-prod\",\n      \"manifestName\": \"job validate-deployment\",\n      \"mode\": \"static\",\n      \"name\": \"Clean up Validation Job\",\n      \"options\": {\n        \"cascading\": true\n      },\n      \"refId\": \"4\",\n      \"requisiteStageRefIds\": [\n        \"5\"\n      ],\n      \"type\": \"deleteManifest\"\n    },\n    {\n      \"account\": \"spinnaker-install-account\",\n      \"cloudProvider\": \"kubernetes\",\n      \"completeOtherBranchesThenFail\": false,\n      \"continuePipeline\": true,\n      \"failPipeline\": false,\n      \"manifestArtifactAccount\": \"gcs-install-account\",\n      \"manifests\": [\n        {\n          \"apiVersion\": \"batch/v1\",\n          \"kind\": \"Job\",\n          \"metadata\": {\n            \"name\": \"validate-deployment\",\n            \"namespace\": \"helloworldwebapp-prod\"\n          },\n          \"spec\": {\n            \"backoffLimit\": 0,\n            \"template\": {\n              \"spec\": {\n                \"containers\": [\n                  {\n                    \"command\": [\n                      \"/bin/sh\",\n                      \"-c\",\n                      \"curl --max-time 120 helloworldwebapp-service\"\n                    ],\n                    \"image\": \"appropriate/curl:latest\",\n                    \"name\": \"validate-deployment\"\n                  }\n                ],\n                \"restartPolicy\": \"Never\"\n              }\n            }\n          }\n        }\n      ],\n      \"moniker\": {\n        \"app\": \"helloworldwebapp\"\n      },\n      \"name\": \"Validate New Prod\",\n      \"refId\": \"5\",\n      \"relationships\": {\n        \"loadBalancers\": [],\n        \"securityGroups\": []\n      },\n      \"requisiteStageRefIds\": [\n        \"1\"\n      ],\n      \"skipExpressionEvaluation\": false,\n      \"source\": \"text\",\n      \"trafficManagement\": {\n        \"enabled\": false,\n        \"options\": {\n          \"enableTraffic\": false,\n          \"services\": []\n        }\n      },\n      \"type\": \"deployManifest\"\n    },\n    {\n      \"account\": \"spinnaker-install-account\",\n      \"cloudProvider\": \"kubernetes\",\n      \"manifestArtifactAccount\": \"gcs-install-account\",\n      \"manifestArtifactId\": \"730af75f-50ad-49ab-b754-ec8ae75938f8\",\n      \"moniker\": {\n        \"app\": \"helloworldwebapp\"\n      },\n      \"name\": \"Deploy Namespace\",\n      \"refId\": \"7\",\n      \"relationships\": {\n        \"loadBalancers\": [],\n        \"securityGroups\": []\n      },\n      \"requiredArtifactIds\": [],\n      \"requisiteStageRefIds\": [\n        \"2\"\n      ],\n      \"skipExpressionEvaluation\": false,\n      \"source\": \"artifact\",\n      \"trafficManagement\": {\n        \"enabled\": false,\n        \"options\": {\n          \"enableTraffic\": false,\n          \"services\": []\n        }\n      },\n      \"type\": \"deployManifest\"\n    },\n    {\n      \"account\": \"spinnaker-install-account\",\n      \"cloudProvider\": \"kubernetes\",\n      \"manifestArtifactAccount\": \"gcs-install-account\",\n      \"manifestArtifactId\": \"76641671-4d6b-4aee-94b4-dba73fbabfcd\",\n      \"moniker\": {\n        \"app\": \"helloworldwebapp\"\n      },\n      \"name\": \"Deploy Service\",\n      \"refId\": \"8\",\n      \"relationships\": {\n        \"loadBalancers\": [],\n        \"securityGroups\": []\n      },\n      \"requiredArtifactIds\": [],\n      \"requisiteStageRefIds\": [\n        \"7\"\n      ],\n      \"skipExpressionEvaluation\": false,\n      \"source\": \"artifact\",\n      \"trafficManagement\": {\n        \"enabled\": false,\n        \"options\": {\n          \"enableTraffic\": false,\n          \"services\": []\n        }\n      },\n      \"type\": \"deployManifest\"\n    },\n    {\n      \"name\": \"Wait before Scale Down\",\n      \"refId\": \"9\",\n      \"requisiteStageRefIds\": [\n        \"17\"\n      ],\n      \"type\": \"wait\",\n      \"waitTime\": 300\n    },\n    {\n      \"account\": \"spinnaker-install-account\",\n      \"app\": \"helloworldwebapp\",\n      \"cloudProvider\": \"kubernetes\",\n      \"cluster\": \"replicaSet helloworldwebapp-frontend\",\n      \"criteria\": \"second_newest\",\n      \"kind\": \"replicaSet\",\n      \"location\": \"helloworldwebapp-prod\",\n      \"mode\": \"dynamic\",\n      \"name\": \"Scale Down Old Prod\",\n      \"refId\": \"10\",\n      \"replicas\": \"0\",\n      \"requisiteStageRefIds\": [\n        \"9\"\n      ],\n      \"type\": \"scaleManifest\"\n    },\n    {\n      \"failPipeline\": true,\n      \"instructions\": \"Validation Failed - Rollback to Old Prod?\",\n      \"judgmentInputs\": [],\n      \"name\": \"Rollback on Failure\",\n      \"notifications\": [],\n      \"refId\": \"11\",\n      \"requisiteStageRefIds\": [\n        \"16\"\n      ],\n      \"type\": \"manualJudgment\"\n    },\n    {\n      \"account\": \"spinnaker-install-account\",\n      \"app\": \"helloworldwebapp\",\n      \"cloudProvider\": \"kubernetes\",\n      \"cluster\": \"replicaSet helloworldwebapp-frontend\",\n      \"criteria\": \"second_newest\",\n      \"kind\": \"replicaSet\",\n      \"location\": \"helloworldwebapp-prod\",\n      \"mode\": \"dynamic\",\n      \"name\": \"Enable Old Prod\",\n      \"refId\": \"12\",\n      \"requisiteStageRefIds\": [\n        \"11\"\n      ],\n      \"type\": \"enableManifest\"\n    },\n    {\n      \"account\": \"spinnaker-install-account\",\n      \"app\": \"helloworldwebapp\",\n      \"cloudProvider\": \"kubernetes\",\n      \"cluster\": \"replicaSet helloworldwebapp-frontend\",\n      \"criteria\": \"newest\",\n      \"kind\": \"replicaSet\",\n      \"location\": \"helloworldwebapp-prod\",\n      \"mode\": \"dynamic\",\n      \"name\": \"Disable New Prod\",\n      \"refId\": \"13\",\n      \"requisiteStageRefIds\": [\n        \"12\"\n      ],\n      \"type\": \"disableManifest\"\n    },\n    {\n      \"name\": \"Validation Failed - Fail Pipeline\",\n      \"preconditions\": [\n        {\n          \"context\": {\n            \"expression\": \"${ #stage(\\\"Validate New Prod\\\")['status'].toString() == 'SUCCEEDED'}\"\n          },\n          \"failPipeline\": true,\n          \"type\": \"expression\"\n        }\n      ],\n      \"refId\": \"14\",\n      \"requisiteStageRefIds\": [\n        \"13\"\n      ],\n      \"stageEnabled\": {\n        \"expression\": \"${ #stage(\\\"Validate New Prod\\\")['status'].toString() != 'SUCCEEDED'}\",\n        \"type\": \"expression\"\n      },\n      \"type\": \"checkPreconditions\"\n    },\n    {\n      \"completeOtherBranchesThenFail\": false,\n      \"continuePipeline\": false,\n      \"failPipeline\": false,\n      \"name\": \"Validation Succeeded\",\n      \"preconditions\": [\n        {\n          \"context\": {\n            \"expression\": \"${ #stage(\\\"Validate New Prod\\\")['status'].toString() == 'SUCCEEDED'}\"\n          },\n          \"failPipeline\": false,\n          \"type\": \"expression\"\n        }\n      ],\n      \"refId\": \"15\",\n      \"requisiteStageRefIds\": [\n        \"4\"\n      ],\n      \"type\": \"checkPreconditions\"\n    },\n    {\n      \"completeOtherBranchesThenFail\": false,\n      \"continuePipeline\": false,\n      \"failPipeline\": false,\n      \"name\": \"Validation Failed\",\n      \"preconditions\": [\n        {\n          \"context\": {\n            \"expression\": \"${ #stage(\\\"Validate New Prod\\\")['status'].toString() != 'SUCCEEDED'}\"\n          },\n          \"failPipeline\": true,\n          \"type\": \"expression\"\n        }\n      ],\n      \"refId\": \"16\",\n      \"requisiteStageRefIds\": [\n        \"4\"\n      ],\n      \"type\": \"checkPreconditions\"\n    },\n    {\n      \"name\": \"Old Prod Version Present\",\n      \"preconditions\": [\n        {\n          \"cloudProvider\": \"kubernetes\",\n          \"context\": {\n            \"cluster\": \"replicaSet helloworldwebapp-frontend\",\n            \"comparison\": \">\",\n            \"credentials\": \"spinnaker-install-account\",\n            \"expected\": 1,\n            \"moniker\": {\n              \"app\": \"helloworldwebapp\",\n              \"cluster\": \"replicaSet helloworldwebapp-frontend\"\n            },\n            \"regions\": [\n              \"helloworldwebapp-prod\"\n            ]\n          },\n          \"failPipeline\": false,\n          \"type\": \"clusterSize\"\n        }\n      ],\n      \"refId\": \"17\",\n      \"requisiteStageRefIds\": [\n        \"15\"\n      ],\n      \"type\": \"checkPreconditions\"\n    }\n  ],\n  \"triggers\": [\n    {\n      \"application\": \"helloworldwebapp\",\n      \"enabled\": true,\n      \"pipeline\": \"$DEPLOY_STAGING_PIPELINE_ID\",\n      \"status\": [\n        \"successful\"\n      ],\n      \"type\": \"pipeline\"\n    }\n  ]\n}\n"
  },
  {
    "path": "samples/helloworldwebapp/templates/pipelines/deploystaging_json.template",
    "content": "{\n  \"application\": \"helloworldwebapp\",\n  \"description\": \"On GCB build completion, deploy new image to staging environment and validate.\",\n  \"expectedArtifacts\": [\n    {\n      \"displayName\": \"Staging Replicaset\",\n      \"id\": \"04429e2c-b48c-48e7-ac9b-6fdc4e3d7f59\",\n      \"matchArtifact\": {\n        \"id\": \"f52080c9-c9ce-4406-a869-e04af6c01389\",\n        \"name\": \"gs://$BUCKET_NAME/helloworldwebapp-manifests/.*/staging-replicaset.yaml\",\n        \"type\": \"gcs/object\"\n      },\n      \"useDefaultArtifact\": false,\n      \"usePriorArtifact\": true\n    },\n    {\n      \"displayName\": \"Hello World WebApp Image\",\n      \"id\": \"4f4d38de-80c3-4bc1-a807-c565bc4024ee\",\n      \"matchArtifact\": {\n        \"id\": \"9aa4d777-1d4e-44f9-8f62-baa33a6a8040\",\n        \"name\": \"gcr.io/$PROJECT_ID/spinnaker-for-gcp-helloworldwebapp\",\n        \"type\": \"docker/image\"\n      },\n      \"useDefaultArtifact\": false,\n      \"usePriorArtifact\": true\n    },\n    {\n      \"displayName\": \"Staging Namespace\",\n      \"id\": \"097c2e8e-295f-4020-85d9-18ed18f6133b\",\n      \"matchArtifact\": {\n        \"id\": \"4be2cb5b-bfee-43cc-a2e8-c544777f3436\",\n        \"name\": \"gs://$BUCKET_NAME/helloworldwebapp-manifests/.*/staging-namespace.yaml\",\n        \"type\": \"gcs/object\"\n      },\n      \"useDefaultArtifact\": false,\n      \"usePriorArtifact\": true\n    },\n    {\n      \"displayName\": \"Staging Service\",\n      \"id\": \"24f64da7-9eac-46df-8b0d-f7092b6a03e4\",\n      \"matchArtifact\": {\n        \"id\": \"09755b68-001a-4d61-bb17-985546618e5f\",\n        \"name\": \"gs://$BUCKET_NAME/helloworldwebapp-manifests/.*/staging-service.yaml\",\n        \"type\": \"gcs/object\"\n      },\n      \"useDefaultArtifact\": false,\n      \"usePriorArtifact\": true\n    }\n  ],\n  \"keepWaitingPipelines\": false,\n  \"limitConcurrent\": true,\n  \"name\": \"Deploy to Staging\",\n  \"parameterConfig\": [],\n  \"stages\": [\n    {\n      \"account\": \"spinnaker-install-account\",\n      \"cloudProvider\": \"kubernetes\",\n      \"expectedArtifacts\": [],\n      \"manifestArtifactAccount\": \"gcs-install-account\",\n      \"manifestArtifactId\": \"04429e2c-b48c-48e7-ac9b-6fdc4e3d7f59\",\n      \"manifests\": [],\n      \"moniker\": {\n        \"app\": \"helloworldwebapp\"\n      },\n      \"name\": \"Deploy Replicaset to Staging\",\n      \"refId\": \"1\",\n      \"relationships\": {\n        \"loadBalancers\": [],\n        \"securityGroups\": []\n      },\n      \"requiredArtifactIds\": [\n        \"4f4d38de-80c3-4bc1-a807-c565bc4024ee\"\n      ],\n      \"requisiteStageRefIds\": [\n        \"8\"\n      ],\n      \"skipExpressionEvaluation\": false,\n      \"source\": \"artifact\",\n      \"trafficManagement\": {\n        \"enabled\": true,\n        \"options\": {\n          \"enableTraffic\": true,\n          \"namespace\": \"helloworldwebapp-staging\",\n          \"services\": [\n            \"service helloworldwebapp-service\"\n          ],\n          \"strategy\": \"highlander\"\n        }\n      },\n      \"type\": \"deployManifest\"\n    },\n    {\n      \"account\": \"spinnaker-install-account\",\n      \"app\": \"helloworldwebapp\",\n      \"cloudProvider\": \"kubernetes\",\n      \"completeOtherBranchesThenFail\": false,\n      \"continuePipeline\": true,\n      \"failPipeline\": false,\n      \"location\": \"helloworldwebapp-staging\",\n      \"manifestName\": \"job validate-deployment\",\n      \"mode\": \"static\",\n      \"name\": \"Clean up Validation Job\",\n      \"options\": {\n        \"cascading\": true\n      },\n      \"refId\": \"2\",\n      \"requisiteStageRefIds\": [\n        \"5\"\n      ],\n      \"type\": \"deleteManifest\"\n    },\n    {\n      \"account\": \"spinnaker-install-account\",\n      \"cloudProvider\": \"kubernetes\",\n      \"completeOtherBranchesThenFail\": false,\n      \"continuePipeline\": true,\n      \"failPipeline\": false,\n      \"manifestArtifactAccount\": \"gcs-install-account\",\n      \"manifests\": [\n        {\n          \"apiVersion\": \"batch/v1\",\n          \"kind\": \"Job\",\n          \"metadata\": {\n            \"name\": \"validate-deployment\",\n            \"namespace\": \"helloworldwebapp-staging\"\n          },\n          \"spec\": {\n            \"backoffLimit\": 0,\n            \"template\": {\n              \"spec\": {\n                \"containers\": [\n                  {\n                    \"command\": [\n                      \"/bin/sh\",\n                      \"-c\",\n                      \"curl --max-time 120 helloworldwebapp-service\"\n                    ],\n                    \"image\": \"appropriate/curl:latest\",\n                    \"name\": \"validate-deployment\"\n                  }\n                ],\n                \"restartPolicy\": \"Never\"\n              }\n            }\n          }\n        }\n      ],\n      \"moniker\": {\n        \"app\": \"helloworldwebapp\"\n      },\n      \"name\": \"Validate Staging\",\n      \"refId\": \"5\",\n      \"relationships\": {\n        \"loadBalancers\": [],\n        \"securityGroups\": []\n      },\n      \"requisiteStageRefIds\": [\n        \"1\"\n      ],\n      \"skipExpressionEvaluation\": false,\n      \"source\": \"text\",\n      \"trafficManagement\": {\n        \"enabled\": false,\n        \"options\": {\n          \"enableTraffic\": false,\n          \"services\": []\n        }\n      },\n      \"type\": \"deployManifest\"\n    },\n    {\n      \"comments\": \"Fails the pipeline if the validation failed.\",\n      \"name\": \"Check Validation status\",\n      \"preconditions\": [\n        {\n          \"context\": {\n            \"expression\": \"${ #stage(\\\"Validate Staging\\\")['status'].toString() == 'SUCCEEDED'}\"\n          },\n          \"failPipeline\": true,\n          \"type\": \"expression\"\n        }\n      ],\n      \"refId\": \"6\",\n      \"requisiteStageRefIds\": [\n        \"2\"\n      ],\n      \"type\": \"checkPreconditions\"\n    },\n    {\n      \"account\": \"spinnaker-install-account\",\n      \"cloudProvider\": \"kubernetes\",\n      \"manifestArtifactAccount\": \"gcs-install-account\",\n      \"manifestArtifactId\": \"097c2e8e-295f-4020-85d9-18ed18f6133b\",\n      \"moniker\": {\n        \"app\": \"helloworldwebapp\"\n      },\n      \"name\": \"Deploy Namespace\",\n      \"refId\": \"7\",\n      \"relationships\": {\n        \"loadBalancers\": [],\n        \"securityGroups\": []\n      },\n      \"requiredArtifactIds\": [],\n      \"requisiteStageRefIds\": [],\n      \"skipExpressionEvaluation\": false,\n      \"source\": \"artifact\",\n      \"trafficManagement\": {\n        \"enabled\": false,\n        \"options\": {\n          \"enableTraffic\": false,\n          \"services\": []\n        }\n      },\n      \"type\": \"deployManifest\"\n    },\n    {\n      \"account\": \"spinnaker-install-account\",\n      \"cloudProvider\": \"kubernetes\",\n      \"manifestArtifactAccount\": \"gcs-install-account\",\n      \"manifestArtifactId\": \"24f64da7-9eac-46df-8b0d-f7092b6a03e4\",\n      \"moniker\": {\n        \"app\": \"helloworldwebapp\"\n      },\n      \"name\": \"Deploy Service\",\n      \"refId\": \"8\",\n      \"relationships\": {\n        \"loadBalancers\": [],\n        \"securityGroups\": []\n      },\n      \"requiredArtifactIds\": [],\n      \"requisiteStageRefIds\": [\n        \"7\"\n      ],\n      \"skipExpressionEvaluation\": false,\n      \"source\": \"artifact\",\n      \"trafficManagement\": {\n        \"enabled\": false,\n        \"options\": {\n          \"enableTraffic\": false,\n          \"services\": []\n        }\n      },\n      \"type\": \"deployManifest\"\n    }\n  ],\n  \"triggers\": [\n    {\n      \"attributeConstraints\": {\n        \"status\": \"SUCCESS\"\n      },\n      \"enabled\": true,\n      \"expectedArtifactIds\": [\n        \"4f4d38de-80c3-4bc1-a807-c565bc4024ee\"\n      ],\n      \"payloadConstraints\": {},\n      \"pubsubSystem\": \"google\",\n      \"subscriptionName\": \"gcb-account\",\n      \"type\": \"pubsub\"\n    }\n  ]\n}\n"
  },
  {
    "path": "samples/helloworldwebapp/templates/repo/Dockerfile",
    "content": "FROM alpine\n\nCOPY src/gopath/bin/helloworldwebapp /go/bin/helloworldwebapp\n\nENTRYPOINT /go/bin/helloworldwebapp\n"
  },
  {
    "path": "samples/helloworldwebapp/templates/repo/cloudbuild_yaml.template",
    "content": "steps:\n- name: 'gcr.io/cloud-builders/go'\n  args: [ 'install', '$PROJECT_ID/helloworldwebapp' ]\n  env: [ 'PROJECT_ROOT=$PROJECT_ID/helloworldwebapp' ]\n  dir: 'src'\n- name: 'ubuntu'\n  entrypoint: 'bash'\n  args:\n  - '-c'\n  - |\n    mkdir config-all\n\n    # rename config files to be appended with the environment, e.g. staging-service.yaml\n    for env in config/*; do\n      if [ -d $env ]; then\n        for file in $env/*; do\n          cp $file config-all/$(basename $env)-$(basename $file)\n        done\n      fi\n    done\n- name: 'gcr.io/cloud-builders/docker'\n  args: [ 'build', '-t', 'gcr.io/$PROJECT_ID/$REPO_NAME:$SHORT_SHA', '.' ]\nimages:\n  - 'gcr.io/$PROJECT_ID/$REPO_NAME:$SHORT_SHA'\nartifacts:\n  objects:\n    location: gs://$BUCKET_NAME/helloworldwebapp-manifests/$SHORT_SHA\n    paths: [ 'config-all/*' ]\n"
  },
  {
    "path": "samples/helloworldwebapp/templates/repo/config/prod/namespace.yaml",
    "content": "---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: helloworldwebapp-prod\n"
  },
  {
    "path": "samples/helloworldwebapp/templates/repo/config/prod/replicaset_yaml.template",
    "content": "---\napiVersion: apps/v1\nkind: ReplicaSet\nmetadata:\n  annotations:\n    traffic.spinnaker.io/load-balancers: '[\"service helloworldwebapp-service\"]'\n  labels:\n    app: helloworldwebapp\n  name: helloworldwebapp-frontend\n  namespace: helloworldwebapp-prod\nspec:\n  replicas: 3\n  selector:\n    matchLabels:\n      app: helloworldwebapp\n  template:\n    metadata:\n      labels:\n        app: helloworldwebapp\n    spec:\n      containers:\n        - image: gcr.io/$PROJECT_ID/spinnaker-for-gcp-helloworldwebapp # will be modified on deployment to point at a digest of an image\n          name: helloworldwebapp\n"
  },
  {
    "path": "samples/helloworldwebapp/templates/repo/config/prod/service.yaml",
    "content": "---\napiVersion: v1\nkind: Service\nmetadata:\n  name: helloworldwebapp-service\n  namespace: helloworldwebapp-prod\nspec:\n  ports:\n  - protocol: TCP\n    port: 80\n  selector:\n    frontedBy: helloworldwebapp-prod # will be applied to backends by Spinnaker\n  type: LoadBalancer\n  loadBalancerIP: \"\"\n"
  },
  {
    "path": "samples/helloworldwebapp/templates/repo/config/staging/namespace.yaml",
    "content": "---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: helloworldwebapp-staging\n"
  },
  {
    "path": "samples/helloworldwebapp/templates/repo/config/staging/replicaset_yaml.template",
    "content": "---\napiVersion: apps/v1\nkind: ReplicaSet\nmetadata:\n  annotations:\n    traffic.spinnaker.io/load-balancers: '[\"service helloworldwebapp-service\"]'\n  labels:\n    app: helloworldwebapp\n  name: helloworldwebapp-frontend\n  namespace: helloworldwebapp-staging\nspec:\n  replicas: 3\n  selector:\n    matchLabels:\n      app: helloworldwebapp\n  template:\n    metadata:\n      labels:\n        app: helloworldwebapp\n    spec:\n      containers:\n        - image: gcr.io/$PROJECT_ID/spinnaker-for-gcp-helloworldwebapp # will be modified on deployment to point at a digest of an image\n          name: helloworldwebapp\n"
  },
  {
    "path": "samples/helloworldwebapp/templates/repo/config/staging/service.yaml",
    "content": "---\napiVersion: v1\nkind: Service\nmetadata:\n  name: helloworldwebapp-service\n  namespace: helloworldwebapp-staging\nspec:\n  ports:\n  - protocol: TCP\n    port: 80\n  selector:\n    frontedBy: helloworldwebapp-staging # will be applied to backends by Spinnaker\n  type: LoadBalancer\n  loadBalancerIP: \"\"\n"
  },
  {
    "path": "samples/helloworldwebapp/templates/repo/src/main.go",
    "content": "package main\n\nimport (\n  \"io\"\n  \"net/http\"\n)\n\nfunc hello(w http.ResponseWriter, r *http.Request) {\n  io.WriteString(w, \"<body style='background-color: green'><h1>Hello World</h1></body>\")\n}\n\nfunc main() {\n  http.HandleFunc(\"/\", hello)\n  http.ListenAndServe(\":80\", nil)\n}\n"
  },
  {
    "path": "scripts/cli/install_hal.sh",
    "content": "#!/usr/bin/env bash\n\nHALYARD_DAEMON_PID_FILE=~/hal/halyard/pid\n\nfunction kill_daemon() {\n    pkill -F $HALYARD_DAEMON_PID_FILE\n}\n\nif [ -f \"$HALYARD_DAEMON_PID_FILE\" ]; then\n    HALYARD_DAEMON_PID=$(cat $HALYARD_DAEMON_PID_FILE)\n\n    set +e\n    ps $HALYARD_DAEMON_PID &> /dev/null\n    exit_code=$?\n    set -e\n\n    if [ \"$exit_code\" == \"0\" ]; then\n        kill_daemon\n    fi\nfi\n\n# Just in case the pid file doesn't match the daemon that's actually listening on the port.\npkill -f '/opt/halyard/lib/halyard-web' || true\npkill -f \"$HOME/hal/halyard/lib/halyard-web\" || true\n\ncurl -O https://raw.githubusercontent.com/spinnaker/halyard/master/install/debian/InstallHalyard.sh\nsudo bash InstallHalyard.sh --user $USER -y $@\n\nretVal=$?\nif [ $retVal == 13 ]; then\n  exit 13\nfi\n\nmkdir -p ~/hal/log\nsudo mv /etc/bash_completion.d/hal ~/hal/hal_completion\nsudo mv /usr/local/bin/hal ~/hal\nsudo mv /usr/local/bin/update-halyard ~/hal\nsudo rm -rf ~/hal/halyard/ && sudo mv /opt/halyard ~/hal\nsudo rm -rf ~/hal/spinnaker/ && sudo mv /opt/spinnaker ~/hal\n\nsed -i 's:^. /etc/bash_completion.d/hal:# . /etc/bash_completion.d/hal\\n. ~/hal/hal_completion\\nalias hal=~/hal/hal:' ~/.bashrc\nsed -i s:/opt/halyard:~/hal/halyard:g ~/hal/hal\nsed -i s:/var/log/spinnaker/halyard:~/hal/log:g ~/hal/hal\nsudo sed -i s:/opt/spinnaker:~/hal/spinnaker:g ~/hal/halyard/bin/halyard\nsed -i 's:rm -rf /opt/halyard:rm -rf ~/hal/halyard:g' ~/hal/update-halyard\nsed -i \"s:^  HAL_USER=.*$:  HAL_USER=$(cat ~/hal/spinnaker/config/halyard-user):g\" ~/hal/update-halyard\nsed -i s:/etc/bash_completion.d/hal:~/hal/hal_completion: ~/hal/update-halyard\n"
  },
  {
    "path": "scripts/cli/install_spin.sh",
    "content": "#!/usr/bin/env bash\n\ncurl -LO https://storage.googleapis.com/spinnaker-artifacts/spin/$(curl -s https://storage.googleapis.com/spinnaker-artifacts/spin/latest)/linux/amd64/spin\n\nchmod +x spin\nmv spin ~\n\ngrep -q '^alias spin=~/spin' ~/.bashrc || echo 'alias spin=~/spin' >> ~/.bashrc\n\nmkdir -p ~/.spin\n\n# If there is no properties file, generate a new ~/.spin/config relying on port-forwarding.\nif [ ! -f \"$HOME/cloudshell_open/spinnaker-for-gcp/scripts/install/properties\" ]; then\n  cat >~/.spin/config <<EOL\ngate:\n  endpoint: http://localhost:8080/gate\nEOL\n\n  exit 0\nfi\n\nsource ~/cloudshell_open/spinnaker-for-gcp/scripts/install/properties\n\n# Query for static ip address as a signal that the Spinnaker installation is exposed via a secured endpoint.\nexport IP_ADDR=$(gcloud compute addresses list --filter=\"name=$STATIC_IP_NAME\" \\\n  --format=\"value(address)\" --global --project $PROJECT_ID)\n\n# Only re-generate ~/.spin/config if Spinnaker installation in unsecured. Otherwise, leave whatever is there.\n# The ~/.spin/config will always be restored by pull_config.sh in any case.\nif [ -z \"$IP_ADDR\" ]; then\n  cat >~/.spin/config <<EOL\ngate:\n  endpoint: http://localhost:8080/gate\nEOL\nfi\n"
  },
  {
    "path": "scripts/cli/update_hal.sh",
    "content": "#!/usr/bin/env bash\n\nHALYARD_DAEMON_PID_FILE=~/hal/halyard/pid\n\nfunction kill_daemon() {\n    pkill -F $HALYARD_DAEMON_PID_FILE\n}\n\nif [ -f \"$HALYARD_DAEMON_PID_FILE\" ]; then\n    HALYARD_DAEMON_PID=$(cat $HALYARD_DAEMON_PID_FILE)\n\n    set +e\n    ps $HALYARD_DAEMON_PID &> /dev/null\n    exit_code=$?\n    set -e\n\n    if [ \"$exit_code\" == \"0\" ]; then\n        kill_daemon\n    fi\nfi\n\n# Just in case the pid file doesn't match the daemon that's actually listening on the port.\npkill -f '/opt/halyard/lib/halyard-web' || true\npkill -f \"$HOME/hal/halyard/lib/halyard-web\" || true\n\nHAL_USER=$(cat ~/hal/spinnaker/config/halyard-user)\n\nif [ -z \"$HAL_USER\" ]; then\n  echo >&2 \"Unable to derive halyard user, likely a corrupted install. Aborting.\"\n  exit 1\nfi\n\nsudo groupadd halyard || true\nsudo groupadd spinnaker || true\nsudo usermod -G halyard -a $HAL_USER || true\nsudo usermod -G spinnaker -a $HAL_USER || true\n\nsudo mkdir -p /var/log/spinnaker/halyard\nsudo chown $HAL_USER:halyard /var/log/spinnaker/halyard\nsudo chmod 755 /var/log/spinnaker /var/log/spinnaker/halyard\n\nsudo HAL_USER=$HAL_USER ~/hal/update-halyard $@\n\nretVal=$?\nif [ $retVal == 13 ]; then\n  exit 13\nfi\n\nmkdir -p ~/hal/log\nsudo mv /usr/local/bin/hal ~/hal\nsudo rm -rf ~/hal/halyard/ && sudo mv /opt/halyard ~/hal\nsudo mv /usr/local/bin/update-halyard ~/hal\n\nsed -i 's:^. /etc/bash_completion.d/hal:# . /etc/bash_completion.d/hal\\n. ~/hal/hal_completion\\nalias hal=~/hal/hal:' ~/.bashrc\nsed -i s:/opt/halyard:~/hal/halyard:g ~/hal/hal\nsed -i s:/var/log/spinnaker/halyard:~/hal/log:g ~/hal/hal\nsudo sed -i s:/opt/spinnaker:~/hal/spinnaker:g ~/hal/halyard/bin/halyard\nsed -i 's:rm -rf /opt/halyard:rm -rf ~/hal/halyard:g' ~/hal/update-halyard\nsed -i \"s:^  HAL_USER=.*$:  HAL_USER=$(cat ~/hal/spinnaker/config/halyard-user):g\" ~/hal/update-halyard\nsed -i s:/etc/bash_completion.d/hal:~/hal/hal_completion: ~/hal/update-halyard\n"
  },
  {
    "path": "scripts/experimental/configure_for_workload_identity.sh",
    "content": "#!/usr/bin/env bash\n\n# Prior to running this script, please ensure that you are running these versions or later:\n# export SPINNAKER_VERSION=release-1.17.x-latest-validated\n# export HALYARD_VERSION=1.26.0\n#\n# This script is intended to be run after the initial setup.sh script completes and Spinnaker is up\n# and running (without Workload Identity enabled).\n#\n# The expected workflow is as follows:\n#   - Generate the properties file by running the setup_properties.sh script\n#   - Modify the properties file to specify the above 2 Spinnaker/Halyard versions (or later versions)\n#   - Run setup.sh\n#   - Once Spinnaker is up and running, run this (configure_for_workload_identity.sh) script\n#\n# Note that this script results in each Spinnaker pod still using the default Kubernetes service account,\n#   and the default service account in the halyard and spinnaker namespaces being bound to one Google\n#   service account (spinnaker-wi-acct). If you want to specify a different Kubernetes service account\n#   for any service, you can do so via the `serviceAccountName` setting described here:\n#   https://www.spinnaker.io/reference/halyard/custom/#kubernetes\n#   You would also need to make the appropriate bindings between that Kubernetes service account and a\n#   Google service account.\n#\n#   The roles assigned are sufficient for deployment to GKE. If you intend to deploy to GCE or GAE, you\n#     will need to assign the appropriate roles to the spinnaker-wi-acct Google service account, similar\n#     to what we do in these helper scripts for the non-Workload Identity setup:\n#       https://github.com/GoogleCloudPlatform/spinnaker-for-gcp/blob/master/scripts/manage/add_gce_account.sh\n#       https://github.com/GoogleCloudPlatform/spinnaker-for-gcp/blob/master/scripts/manage/add_gae_account.sh\n\nbold() {\n  echo \". $(tput bold)\" \"$*\" \"$(tput sgr0)\";\n}\n\nPROPERTIES_FILE=\"$HOME/cloudshell_open/spinnaker-for-gcp/scripts/install/properties\"\n\nsource \"$PROPERTIES_FILE\"\n\nbold \"Enabling workload identity on cluster $GKE_CLUSTER in project $PROJECT_ID...\"\ngcloud beta container clusters update $GKE_CLUSTER \\\n  --zone=$ZONE \\\n  --identity-namespace=$PROJECT_ID.svc.id.goog \\\n  --project=$PROJECT_ID\n\nunset CLUSTER_STATUS\n\nwhile [ \"$CLUSTER_STATUS\" != \"RUNNING\" ]; do\n  CLUSTER_STATUS=$(gcloud container clusters describe $GKE_CLUSTER \\\n    --zone=$ZONE \\\n    --format=\"value(status)\" \\\n    --project=$PROJECT_ID)\n  sleep 5\n echo -n .\ndone\necho\n\nKSA_NAME=default\nGSA_NAME=spinnaker-wi-acct\nGSA_DISPLAY_NAME=\"Spinnaker Workload Identity service account\"\n\nbold \"Creating Google service account $GSA_NAME...\"\ngcloud iam service-accounts create $GSA_NAME \\\n  --display-name=\"$GSA_DISPLAY_NAME\" \\\n  --project=$PROJECT_ID\n\nGSA_EMAIL=$(gcloud iam service-accounts list \\\n  --filter=\"displayName:$GSA_DISPLAY_NAME\" \\\n  --format=\"value(email)\" \\\n  --project=$PROJECT_ID)\n\nwhile [ -z \"$GSA_EMAIL\" ]; do\n  GSA_EMAIL=$(gcloud iam service-accounts list \\\n    --filter=\"displayName:$GSA_DISPLAY_NAME\" \\\n    --format=\"value(email)\" \\\n    --project=$PROJECT_ID)\n  sleep 5\n  echo -n .\ndone\necho\n\nbold \"Assigning required roles to $GSA_DISPLAY_NAME...\"\n\nK8S_REQUIRED_ROLES=(cloudbuild.builds.editor container.admin logging.logWriter monitoring.admin pubsub.admin storage.admin)\nEXISTING_ROLES=$(gcloud projects get-iam-policy $PROJECT_ID \\\n  --filter=\"bindings.members:$GSA_EMAIL\" \\\n  --format=\"value(bindings.role)\" \\\n  --flatten=\"bindings[].members\")\n\nfor r in \"${K8S_REQUIRED_ROLES[@]}\"; do\n  if [ -z \"$(echo $EXISTING_ROLES | grep $r)\" ]; then\n    bold \"Assigning role $r...\"\n    gcloud projects add-iam-policy-binding $PROJECT_ID \\\n      --member=\"serviceAccount:$GSA_EMAIL\" \\\n      --role=\"roles/$r\" \\\n      --format=\"none\"\n  fi\ndone\n\nbold \"Creating Cloud IAM policy binding between Kubernetes service account halyard/$KSA_NAME and Google service account $GSA_NAME...\"\ngcloud iam service-accounts add-iam-policy-binding \\\n  --role=\"roles/iam.workloadIdentityUser\" \\\n  --member=\"serviceAccount:$PROJECT_ID.svc.id.goog[halyard/$KSA_NAME]\" \\\n  --project=$PROJECT_ID \\\n  $GSA_NAME@$PROJECT_ID.iam.gserviceaccount.com\n\nbold \"Creating Cloud IAM policy binding between Kubernetes service account spinnaker/$KSA_NAME and Google service account $GSA_NAME...\"\ngcloud iam service-accounts add-iam-policy-binding \\\n  --role=\"roles/iam.workloadIdentityUser\" \\\n  --member=\"serviceAccount:$PROJECT_ID.svc.id.goog[spinnaker/$KSA_NAME]\" \\\n  --project=$PROJECT_ID \\\n  $GSA_NAME@$PROJECT_ID.iam.gserviceaccount.com\n\nbold \"Annotating Kubernetes service account halyard/$KSA_NAME with Google service account to use ($GSA_NAME)...\"\nkubectl annotate serviceaccount \\\n  --namespace halyard \\\n  $KSA_NAME \\\n  iam.gke.io/gcp-service-account=$GSA_NAME@$PROJECT_ID.iam.gserviceaccount.com\n\nbold \"Annotating Kubernetes service account spinnaker/$KSA_NAME with Google service account to use ($GSA_NAME)...\"\nkubectl annotate serviceaccount \\\n  --namespace spinnaker \\\n  $KSA_NAME \\\n  iam.gke.io/gcp-service-account=$GSA_NAME@$PROJECT_ID.iam.gserviceaccount.com\n\nNODE_POOL_NAME=$(gcloud container clusters describe $GKE_CLUSTER \\\n  --zone=$ZONE \\\n  --format=\"value(nodePools[0].name)\" \\\n  --project=$PROJECT_ID)\n\nbold \"Enabling GKE_METADATA_SERVER on node pool $NODE_POOL_NAME...\"\ngcloud beta container node-pools update $NODE_POOL_NAME \\\n  --cluster=$GKE_CLUSTER \\\n  --zone=$ZONE \\\n  --workload-metadata-from-node=GKE_METADATA_SERVER \\\n  --project=$PROJECT_ID\n"
  },
  {
    "path": "scripts/expose/backend-config.yml",
    "content": "apiVersion: cloud.google.com/v1beta1\nkind: BackendConfig\nmetadata:\n  name: config-default\n  namespace: spinnaker\nspec:\n  iap:\n    enabled: true\n    oauthclientCredentials:\n      secretName: $SECRET_NAME\n"
  },
  {
    "path": "scripts/expose/configure_endpoint.sh",
    "content": "#!/usr/bin/env bash\n\nbold() {\n  echo \". $(tput bold)\" \"$*\" \"$(tput sgr0)\";\n}\n\npushd ~/cloudshell_open/spinnaker-for-gcp/scripts\n\nsource ./install/properties\n\n~/cloudshell_open/spinnaker-for-gcp/scripts/manage/check_project_mismatch.sh\n\nDOMAIN_NAME_LENGTH=$(echo -n $DOMAIN_NAME | wc -m)\n\nif [ \"$DOMAIN_NAME_LENGTH\" -gt \"63\" ]; then\n  echo \"Domain name $DOMAIN_NAME is greater than 63 characters. Please specify a \\\ndomain name not longer than 63 characters. The domain name is specified in the \\\n$HOME/cloudshell_open/spinnaker-for-gcp/scripts/install/properties file.\"\n  exit 1\nfi\n\nexport IP_ADDR=$(gcloud compute addresses list --filter=\"name=$STATIC_IP_NAME\" \\\n  --format=\"value(address)\" --global --project $PROJECT_ID)\n\nif [ -z \"$IP_ADDR\" ]; then\n  bold \"Creating static IP address $STATIC_IP_NAME...\"\n\n  gcloud compute addresses create $STATIC_IP_NAME --global --project $PROJECT_ID\n\n  export IP_ADDR=$(gcloud compute addresses list --filter=\"name=$STATIC_IP_NAME\" \\\n    --format=\"value(address)\" --global --project $PROJECT_ID)\nelse\n   bold \"Using existing static IP address $STATIC_IP_NAME ($IP_ADDR)...\"\nfi\n\nif [ $DOMAIN_NAME = \"$DEPLOYMENT_NAME.endpoints.$PROJECT_ID.cloud.goog\" ]; then\n  EXISTING_SERVICE_NAME=$(gcloud endpoints services list \\\n    --filter=\"serviceName=$DOMAIN_NAME\" --format=\"value(serviceName)\" \\\n    --project $PROJECT_ID)\n\n  if [ -z \"$EXISTING_SERVICE_NAME\" ]; then\n    gcurl() {\n      curl -s -H \"Authorization:Bearer $(gcloud auth print-access-token)\" \\\n        -H \"Content-Type: application/json\" -H \"Accept: application/json\" \\\n        -H \"X-Goog-User-Project: $PROJECT_ID\" $*\n    }\n\n    bold \"Creating service $DOMAIN_NAME...\"\n\n    gcurl -X POST -d \\\n      \"{\\\"serviceName\\\":\\\"$DOMAIN_NAME\\\",\\\"producerProjectId\\\":\\\"$PROJECT_ID\\\"}\" \\\n      https://servicemanagement.googleapis.com/v1/services/\n\n    while [ -z \"$SERVICE_NAME\" ]; do\n      SERVICE_NAME=$(gcloud endpoints services list \\\n        --filter=\"serviceName:$DOMAIN_NAME\" \\\n        --format=\"value(serviceName)\")\n      sleep 5\n     echo -n .\n    done\n    echo\n  else\n    bold \"Using existing service $EXISTING_SERVICE_NAME...\"\n  fi\n\n  # The service can exist without an endpoint configuration. The presence of the\n  # service configuration title is sufficient to indicate that we have configured\n  # the endpoint.\n  EXISTING_SERVICE_CONFIGURATION_NAME=$(gcloud endpoints services list \\\n    --filter=\"serviceName=$DOMAIN_NAME\" --format=\"value(serviceConfig.title)\" \\\n    --project $PROJECT_ID)\n\n  if [ -z \"$EXISTING_SERVICE_CONFIGURATION_NAME\" ]; then\n    bold \"Deploying service endpoint configuration for $DOMAIN_NAME...\"\n\n    cat expose/openapi.yml | envsubst > expose/openapi_expanded.yml\n\n    gcloud endpoints services deploy expose/openapi_expanded.yml --project $PROJECT_ID\n  else\n    bold \"Using existing service endpoint configuration for $DOMAIN_NAME...\"\n  fi\nelse\n  CURRENT_IP_ADDR=$(dig +short $DOMAIN_NAME)\n\n  if [ -z \"$CURRENT_IP_ADDR\" ]; then\n    CURRENT_IP_ADDR=\"UNRESOLVABLE\"\n  fi\n\n  bold \"Using existing domain $DOMAIN_NAME ($CURRENT_IP_ADDR)...\"\n\n  if [ $CURRENT_IP_ADDR != $IP_ADDR ]; then\n    bold \"** This domain currently resolves to $CURRENT_IP_ADDR\n   ** You must configure $DOMAIN_NAME's DNS settings such that it instead resolves to $IP_ADDR\"\n  fi\nfi\n\nEXISTING_MANAGED_CERT=$(gcloud beta compute ssl-certificates list \\\n  --filter=\"name=$MANAGED_CERT\" --format=\"value(name)\" --project $PROJECT_ID)\n\nif [ -z \"$EXISTING_MANAGED_CERT\" ]; then\n  bold \"Creating managed SSL certificate $MANAGED_CERT for domain $DOMAIN_NAME...\"\n\n  gcloud beta compute ssl-certificates create $MANAGED_CERT --domains $DOMAIN_NAME --global \\\n    --project $PROJECT_ID\nelse\n  bold \"Using existing managed SSL certificate $EXISTING_MANAGED_CERT...\"\nfi\n\n./expose/launch_configure_iap.sh\n\npopd\n"
  },
  {
    "path": "scripts/expose/configure_hal_security.sh",
    "content": "~/hal/hal config security api edit --override-base-url https://$DOMAIN_NAME/gate\n~/hal/hal config security ui edit --override-base-url https://$DOMAIN_NAME\n~/hal/hal config security authn iap edit --audience $AUD_CLAIM\n~/hal/hal config security authn iap enable\n"
  },
  {
    "path": "scripts/expose/configure_iap.md",
    "content": "# Expose Spinnaker\n\n### Configure OAuth consent screen\n\nGo to the [OAuth consent screen](https://console.developers.google.com/apis/credentials/consent?project=$PROJECT_ID).\n\nEnter an *Application name* (e.g. My Spinnaker) and your *Email address*, and click *Save*.\n\n### Create OAuth credentials\n\nGo to the [Credentials page](https://console.developers.google.com/apis/credentials/oauthclient?project=$PROJECT_ID) and create an *OAuth client ID*.\n\nSelect *Application type: Web application* and click *Create*.\n\nEnsure that you note the generated *Client ID* and *Client secret* for your new credentials, as you will need to provide them to the script in the next step.\n\n### Expose Spinnaker and allow for secure access via IAP\n\n```bash\n~/cloudshell_open/spinnaker-for-gcp/scripts/expose/configure_iap.sh\n```\n\nThere will be one final IAP configuration step described in the terminal.\n\nThis phase could take 30-60 minutes. **Spinnaker will be inaccessible during this time.**\n\n## Conclusion\n\nConnect to your Spinnaker installation [here](https://$DOMAIN_NAME).\n\n### View Spinnaker Audit Log\n\nView the who, what, when and where of your Spinnaker installation\n[here](https://console.developers.google.com/logs/viewer?project=$PROJECT_ID&resource=cloud_function&logName=projects%2F$PROJECT_ID%2Flogs%2F$CLOUD_FUNCTION_NAME&minLogLevel=200).\n"
  },
  {
    "path": "scripts/expose/configure_iap.sh",
    "content": "#!/usr/bin/env bash\n\nbold() {\n  echo \". $(tput bold)\" \"$*\" \"$(tput sgr0)\";\n}\n\npushd ~/cloudshell_open/spinnaker-for-gcp/scripts\n\nsource ./install/properties\n\n~/cloudshell_open/spinnaker-for-gcp/scripts/manage/check_project_mismatch.sh\n\nEXISTING_SECRET_NAME=$(kubectl get secret -n spinnaker \\\n  --field-selector metadata.name==\"$SECRET_NAME\" \\\n  -o json | jq .items[0].metadata.name)\n\nif [ $EXISTING_SECRET_NAME == 'null' ]; then\n  bold \"Creating Kubernetes secret $SECRET_NAME...\"\n\n  read -p 'Enter your OAuth credentials Client ID: ' CLIENT_ID\n  read -p 'Enter your OAuth credentials Client secret: ' CLIENT_SECRET\n\n  cat >~/.spin/config <<EOL\ngate:\n  endpoint: https://$DOMAIN_NAME/gate\n\nauth:\n  enabled: true\n  iap:\n    # check detailed config in https://cloud.google.com/iap/docs/authentication-howto#authenticating_from_a_desktop_app\n    iapClientId: $CLIENT_ID\n    serviceAccountKeyPath: \"$HOME/.spin/key.json\"\nEOL\n  SA_EMAIL=$(gcloud iam service-accounts --project $PROJECT_ID list \\\n    --filter=\"displayName:$SERVICE_ACCOUNT_NAME\" \\\n    --format='value(email)')\n\n  gcloud iam service-accounts keys create ~/.spin/key.json \\\n    --iam-account $SA_EMAIL \\\n    --project $PROJECT_ID\n\n  kubectl create secret generic $SECRET_NAME -n spinnaker --from-literal=client_id=$CLIENT_ID \\\n    --from-literal=client_secret=$CLIENT_SECRET\nelse\n  bold \"Using existing Kubernetes secret $SECRET_NAME...\"\nfi\n\nenvsubst < expose/backend-config.yml | kubectl apply -f -\n\n# Associate deck service with backend config.\nkubectl patch svc -n spinnaker spin-deck --patch \\\n  \"[{'op': 'add', 'path': '/metadata/annotations/beta.cloud.google.com~1backend-config', \\\n  'value':'{\\\"default\\\": \\\"config-default\\\"}'}]\" --type json\n\n# Change spin-deck service to NodePort:\nDECK_SERVICE_TYPE=$(kubectl get service -n spinnaker spin-deck \\\n  --output=jsonpath={.spec.type})\n\nif [ $DECK_SERVICE_TYPE != 'NodePort' ]; then\n  bold \"Patching spin-deck service to be NodePort instead of $DECK_SERVICE_TYPE...\"\n\n  kubectl patch service -n spinnaker spin-deck --patch \\\n    \"[{'op': 'replace', 'path': '/spec/type', \\\n    'value':'NodePort'}]\" --type json\nelse\n  bold \"Service spin-deck is already NodePort...\"\nfi\n\n# Create ingress:\nbold $(envsubst < expose/deck-ingress.yml | kubectl apply -f -)\n\nsource expose/set_iap_properties.sh\n\ngcurl() {\n  curl -s -H \"Authorization:Bearer $(gcloud auth print-access-token)\" \\\n    -H \"Content-Type: application/json\" -H \"Accept: application/json\" \\\n    -H \"X-Goog-User-Project: $PROJECT_ID\" $*\n}\n\nexport IAP_IAM_POLICY_ETAG=$(gcurl -X POST -d \"{\"options\":{\"requested_policy_version\":3}}\" \\\n  https://iap.googleapis.com/v1beta1/projects/$PROJECT_NUMBER/iap_web/compute/services/$BACKEND_SERVICE_ID:getIamPolicy | jq .etag)\n\ncat expose/iap_policy.json | envsubst | gcurl -X POST -d @- \\\n  https://iap.googleapis.com/v1beta1/projects/$PROJECT_NUMBER/iap_web/compute/services/$BACKEND_SERVICE_ID:setIamPolicy\n\nbold \"Configuring Spinnaker security settings...\"\n\ncat expose/configure_hal_security.sh | envsubst | bash\n\n~/cloudshell_open/spinnaker-for-gcp/scripts/manage/update_landing_page.sh\n~/cloudshell_open/spinnaker-for-gcp/scripts/manage/push_and_apply.sh\n\nbold \"ACTION REQUIRED:\"\nbold \"  - Navigate to: https://console.developers.google.com/apis/credentials/oauthclient/$CLIENT_ID?project=$PROJECT_ID\"\nbold \"  - Add https://iap.googleapis.com/v1/oauth/clientIds/$CLIENT_ID:handleRedirect to your Web client ID as an Authorized redirect URI.\"\n\n# # What about CORS?\n\n# # Wait for services to come online again (steal logic from setup.sh):\n\npopd\n"
  },
  {
    "path": "scripts/expose/deck-ingress.yml",
    "content": "apiVersion: extensions/v1beta1\nkind: Ingress\nmetadata:\n  name: deck-ingress\n  namespace: spinnaker\n  annotations:\n    ingress.gcp.kubernetes.io/pre-shared-cert: $MANAGED_CERT\n    kubernetes.io/ingress.global-static-ip-name: $STATIC_IP_NAME\nspec:\n  backend:\n    serviceName: spin-deck\n    servicePort: 9000\n"
  },
  {
    "path": "scripts/expose/iap_policy.json",
    "content": "{\n  \"policy\": {\n    \"etag\": $IAP_IAM_POLICY_ETAG,\n    \"bindings\": [\n      {\n        \"role\": \"roles/iap.httpsResourceAccessor\",\n        \"members\": [\n          \"serviceAccount:$SERVICE_ACCOUNT_NAME@$PROJECT_ID.iam.gserviceaccount.com\",\n          \"user:$IAP_USER\"\n        ]\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "scripts/expose/launch_configure_iap.sh",
    "content": "#!/usr/bin/env bash\n\npushd ~/cloudshell_open/spinnaker-for-gcp/scripts\n\nsource ./install/properties\n\ncat expose/configure_iap.md | envsubst > expose/configure_iap_expanded.md\n\ncloudshell launch-tutorial expose/configure_iap_expanded.md\n\npopd\n"
  },
  {
    "path": "scripts/expose/openapi.yml",
    "content": "swagger: \"2.0\"\ninfo: \n  title: Spinnaker for GCP - $PROJECT_ID\n  version: 1.0.0\nhost: $DOMAIN_NAME\nx-google-endpoints:\n  - name: $DOMAIN_NAME\n    target: $IP_ADDR\nx-google-allow: all\nbasePath: /\npaths: {}\n"
  },
  {
    "path": "scripts/expose/set_iap_properties.sh",
    "content": "#!/usr/bin/env bash\n\nif [ -z $CLIENT_ID ]; then\n  SECRET_JSON=$(kubectl get secret -n spinnaker $SECRET_NAME -o json)\n\n  export CLIENT_ID=$(echo $SECRET_JSON | jq -r .data.client_id | base64 -d)\n  export CLIENT_SECRET=$(echo $SECRET_JSON | jq -r .data.client_secret | base64 -d)\nfi\n\nexport PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format=\"value(projectNumber)\")\n\nbold \"Querying for backend service id...\"\n\nexport BACKEND_SERVICE_ID=$(gcloud compute backend-services list --project $PROJECT_ID \\\n  --filter=\"iap.oauth2ClientId:$CLIENT_ID AND description:spinnaker/spin-deck\" --format=\"value(id)\")\n\nwhile [ -z \"$BACKEND_SERVICE_ID\" ]; do\n  bold \"Waiting for backend service to be provisioned...\"\n  sleep 30\n\n  export BACKEND_SERVICE_ID=$(gcloud compute backend-services list --project $PROJECT_ID \\\n    --filter=\"iap.oauth2ClientId:$CLIENT_ID AND description:spinnaker/spin-deck\" --format=\"value(id)\")\ndone\n\nexport AUD_CLAIM=/projects/$PROJECT_NUMBER/global/backendServices/$BACKEND_SERVICE_ID\n"
  },
  {
    "path": "scripts/install/instructions.txt",
    "content": "\n+-------------------------------------------------------------------------------------------------------+\n|                                                                                                       |\n| To reopen the installation instructions in the right-hand pane at any time, enter:                    |\n|                                                                                                       |\n| cloudshell launch-tutorial ~/cloudshell_open/spinnaker-for-gcp/scripts/install/provision-spinnaker.md |\n|                                                                                                       |\n+-------------------------------------------------------------------------------------------------------+\n\n"
  },
  {
    "path": "scripts/install/provision-spinnaker.md",
    "content": "# Install Spinnaker\n\n## Select GCP project\n\nSelect the project in which you'll install Spinnaker, then click **Start**, below.\n\n<walkthrough-project-billing-setup>\n</walkthrough-project-billing-setup>\n\n## Spinnaker Installation\n\nClick the **Copy to Cloud Shell** button for each command below, then press **Enter**\nto run each commmand.\n\n### Configure Git\n\nIf you haven't already configured Git, use the commands below to do so now.\nReplace `[EMAIL_ADDRESS]` with your Git email address, and replace `[USERNAME]`\nwith your Git username.\n\n```bash\ngit config --global user.email \"[EMAIL_ADDRESS]\"\ngit config --global user.name \"[USERNAME]\"\n```\n\n### Configure the environment\n\nNow let's provision Spinnaker within your project {{project-id}}.\n\n```bash\nPROJECT_ID={{project-id}} ~/cloudshell_open/spinnaker-for-gcp/scripts/install/setup_properties.sh\n```\n\nAfter that script finishes, you can use the command below to open the properties file for your Spinnaker\ninstallation. This is optional.\n\n```bash\ncloudshell edit ~/cloudshell_open/spinnaker-for-gcp/scripts/install/properties\n```\n\n**Proceed with caution**. If you edit this file, the installation might not work\nas expected.\n\n### Begin the installation\n\n**This will take some time**\n\n```bash\n~/cloudshell_open/spinnaker-for-gcp/scripts/install/setup.sh\n```\n\nWatch the Cloud Shell command line to see when it completes, then click\n**Next** to continue to the next step.\n\n## Connect to Spinnaker\n\nYou'll now run commands to...\n* connect to Spinnaker \n* open the Spinnaker UI (Deck) in a browser window\n\nYou have two choices:\n* forward port 8080 to tunnel to Spinnaker from your Cloud Shell\n* expose Deck securely via a public IP\n\n### Forward the port to Deck, and connect\n\nDon't use the `hal deploy connect` command. Instead, use the following command\nonly.\n\n```bash\n~/cloudshell_open/spinnaker-for-gcp/scripts/manage/connect_unsecured.sh\n```\n\nTo connect to the Deck UI, click on the Preview button above and select \"Preview on port 8080\":\n\n![Image](https://github.com/GoogleCloudPlatform/spinnaker-for-gcp/raw/master/scripts/manage/preview_button.png)\n\n### Expose Spinnaker publicly\n\nIf you would like to connect to Spinnaker without relying on port forwarding, we can\nexpose it via a secure domain behind the [Identity-Aware Proxy](https://cloud.google.com/iap/).\n\nNote that this phase could take 30-60 minutes. **Spinnaker will be inaccessible during this time.**\n\n```bash\n~/cloudshell_open/spinnaker-for-gcp/scripts/expose/configure_endpoint.sh\n```\n\n## Next steps: manage Spinnaker\n\nNow that you've installed Spinnaker on Google Kubernetes Engine, and\naccessed it via port forwarding or made it available over the public\ninternet, you'll use this same console to manage your Spinnaker instance.\n\nYou can open this console by navigating to the Kubernetes Application on the\n[Applications](https://console.developers.google.com/kubernetes/application?project={{project-id}})\nview. The application's *Next Steps* section contains the relevant links and\noperator instructions.\n\nYou can...\n\n* Use [Halyard](https://www.spinnaker.io/reference/halyard/) to further\nconfigure Spinnaker\n* Add provider accounts\n* Upgrade Spinnaker\n* Add more operators\n\nTo start managing Spinnaker:\n\n```bash\n~/cloudshell_open/spinnaker-for-gcp/scripts/manage/update_console.sh\n```\n"
  },
  {
    "path": "scripts/install/quick-install.yml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  name: halyard\n\n---\n\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: spinnaker\n\n---\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: spinnaker-admin\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: cluster-admin\nsubjects:\n- kind: ServiceAccount\n  name: default\n  namespace: halyard\n- kind: ServiceAccount\n  name: default\n  namespace: spinnaker\n\n---\n\napiVersion: v1\nkind: PersistentVolumeClaim\nmetadata:\n  name: halyard-pv-claim\n  namespace: halyard\n  labels:\n    app: halyard-storage-claim\nspec:\n  accessModes:\n  - ReadWriteOnce\n  resources:\n    requests:\n      storage: 10Gi\n  storageClassName: standard\n\n---\n\napiVersion: apps/v1\nkind: StatefulSet\nmetadata:\n  name: spin-halyard\n  namespace: halyard\n  labels:\n    app: spin\n    stack: halyard\nspec:\n  serviceName: spin-halyard\n  replicas: 1\n  selector:\n    matchLabels:\n      app: spin\n      stack: halyard\n  template:\n    metadata:\n      labels:\n        app: spin\n        stack: halyard\n    spec:\n      securityContext:\n        runAsGroup: 1000\n        runAsUser: 1000\n        fsGroup: 1000\n      containers:\n      - name: halyard-daemon\n        image: us-docker.pkg.dev/spinnaker-community/docker/halyard:$HALYARD_VERSION\n        imagePullPolicy: Always\n        command:\n        - /bin/sh\n        args:\n        - -c\n        # We persist the files on a PersistentVolume. To have sane defaults,\n        # we initialise those files from a ConfigMap if they don't already exist.\n        - \"test -f /home/spinnaker/.hal/config || cp -R /home/spinnaker/staging/.hal/. /home/spinnaker/.hal/ && /opt/halyard/bin/halyard\"\n        readinessProbe:\n          exec:\n            command:\n            - wget\n            - -q\n            - --spider\n            - http://localhost:8064/health\n        resources:\n          requests:\n            cpu: 10m\n            memory: 256Mi\n        ports:\n        - containerPort: 8064\n        volumeMounts:\n        - name: persistentconfig\n          mountPath: /home/spinnaker/.hal\n        - name: halconfig\n          mountPath: /home/spinnaker/staging/.hal/config\n          subPath: config\n        - name: halconfig\n          mountPath: /home/spinnaker/staging/.hal/default/service-settings/deck.yml\n          subPath: deck.yml\n        - name: halconfig\n          mountPath: /home/spinnaker/staging/.hal/default/service-settings/gate.yml\n          subPath: gate.yml\n        - name: halconfig\n          mountPath: /home/spinnaker/staging/.hal/default/service-settings/fiat.yml\n          subPath: fiat.yml\n        - name: halconfig\n          mountPath: /home/spinnaker/staging/.hal/default/service-settings/redis.yml\n          subPath: redis.yml\n        - name: halconfig\n          mountPath: /home/spinnaker/staging/.hal/default/profiles/clouddriver-local.yml\n          subPath: clouddriver-local.yml\n        - name: halconfig\n          mountPath: /home/spinnaker/staging/.hal/default/profiles/echo-local.yml\n          subPath: echo-local.yml\n        - name: halconfig\n          mountPath: /home/spinnaker/staging/.hal/default/profiles/front50-local.yml\n          subPath: front50-local.yml\n        - name: halconfig\n          mountPath: /home/spinnaker/staging/.hal/default/profiles/gate-local.yml\n          subPath: gate-local.yml\n        - name: halconfig\n          mountPath: /home/spinnaker/staging/.hal/default/profiles/igor-local.yml\n          subPath: igor-local.yml\n      volumes:\n      - name: halconfig\n        configMap:\n          name: halconfig\n      - name: persistentconfig\n        persistentVolumeClaim:\n          claimName: halyard-pv-claim\n---\n\napiVersion: v1\nkind: Service\nmetadata:\n  name: spin-halyard\n  namespace: halyard\nspec:\n  ports:\n    - port: 8064\n      targetPort: 8064\n      protocol: TCP\n  selector:\n    app: spin\n    stack: halyard\n\n---\n\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: halconfig\n  namespace: halyard\ndata:\n  deck.yml: |\n    host: 0.0.0.0\n    env:\n      API_HOST: http://spin-gate.spinnaker:8084\n  fiat.yml: |\n    enabled: false\n    skipLifeCycleManagement: true\n  gate.yml: |\n    host: 0.0.0.0\n  clouddriver-local.yml: |\n    kubernetes.v2.managedBySuffix: for-gcp\n  echo-local.yml: |\n    rest:\n      enabled: true\n      endpoints:\n        - wrap: true\n          flatten: false\n          url: https://$REGION-$PROJECT_ID.cloudfunctions.net/$CLOUD_FUNCTION_NAME\n          username: $AUDIT_LOG_UNAME\n          password: $AUDIT_LOG_PW\n          eventName: spinnaker_events\n  front50-local.yml: |\n    spinnaker.s3.versioning: false\n  gate-local.yml: |\n    redis.configuration.secure: true\n  igor-local.yml: |\n    locking:\n      enabled: true\n  redis.yml: |\n    overrideBaseUrl: redis://$REDIS_INSTANCE_HOST:6379\n    skipLifeCycleManagement: true\n  config: |\n    currentDeployment: default\n    deploymentConfigurations:\n    - name: default\n      version: $SPINNAKER_VERSION\n      providers:\n        appengine:\n          enabled: false\n          accounts: []\n        aws:\n          enabled: false\n          accounts: []\n          bakeryDefaults:\n            baseImages: []\n          defaultKeyPairTemplate: '{{name}}-keypair'\n          defaultRegions:\n          - name: us-west-2\n          defaults:\n            iamRole: BaseIAMRole\n        ecs:\n          enabled: false\n          accounts: []\n        azure:\n          enabled: false\n          accounts: []\n          bakeryDefaults:\n            templateFile: azure-linux.json\n            baseImages: []\n        dcos:\n          enabled: false\n          accounts: []\n          clusters: []\n        dockerRegistry:\n          enabled: false\n          accounts: []\n        google:\n          enabled: false\n          accounts: []\n          bakeryDefaults:\n            templateFile: gce.json\n            baseImages: []\n            zone: us-central1-f\n            network: default\n            useInternalIp: false\n        kubernetes:\n          enabled: true\n          accounts:\n          - name: spinnaker-install-account\n            requiredGroupMembership: []\n            providerVersion: V2\n            permissions: {}\n            dockerRegistries: []\n            configureImagePullSecrets: true\n            serviceAccount: true\n            cacheThreads: 1\n            namespaces: []\n            omitNamespaces:\n            - halyard\n            - kube-public\n            - kube-system\n            - spinnaker\n            kinds: []\n            omitKinds: []\n            customResources: []\n            cachingPolicies: []\n            oAuthScopes: []\n          primaryAccount: spinnaker-install-account\n        oracle:\n          enabled: false\n          accounts: []\n          bakeryDefaults:\n            templateFile: oci.json\n            baseImages: []\n        cloudfoundry:\n          enabled: false\n          accounts: []\n      deploymentEnvironment:\n        size: SMALL\n        type: Distributed\n        accountName: spinnaker-install-account\n        imageVariant: SLIM\n        updateVersions: true\n        consul:\n          enabled: false\n        vault:\n          enabled: false\n        customSizing: {}\n        sidecars: {}\n        initContainers: {}\n        hostAliases: {}\n        affinity: {}\n        tolerations: {}\n        nodeSelectors: {}\n        gitConfig:\n          upstreamUser: spinnaker\n        livenessProbeConfig:\n          enabled: false\n        haServices:\n          clouddriver:\n            enabled: false\n            disableClouddriverRoDeck: false\n          echo:\n            enabled: false\n      persistentStorage:\n        persistentStoreType: gcs\n        azs: {}\n        gcs:\n          project: $PROJECT_ID\n          bucket: $BUCKET_NAME\n          rootFolder: front50\n        redis: {}\n        s3:\n          rootFolder: front50\n        oracle: {}\n      features:\n        auth: false\n        fiat: false\n        chaos: false\n        entityTags: false\n        artifacts: true\n      metricStores:\n        datadog:\n          enabled: false\n          tags: []\n        prometheus:\n          enabled: false\n          add_source_metalabels: true\n        stackdriver:\n          enabled: false\n        newrelic:\n          enabled: false\n          tags: []\n        period: 30\n        enabled: false\n      notifications:\n        slack:\n          enabled: false\n        twilio:\n          enabled: false\n          baseUrl: https://api.twilio.com/\n        github-status:\n          enabled: false\n      timezone: $TIMEZONE\n      ci:\n        jenkins:\n          enabled: false\n          masters: []\n        travis:\n          enabled: false\n          masters: []\n        wercker:\n          enabled: false\n          masters: []\n        concourse:\n          enabled: false\n          masters: []\n        gcb:\n          enabled: true\n          accounts:\n          - name: gcb-account\n            permissions: {}\n            project: $PROJECT_ID\n            subscriptionName: $GCB_PUBSUB_SUBSCRIPTION\n      repository:\n        artifactory:\n          enabled: false\n          searches: []\n      security:\n        apiSecurity:\n          ssl:\n            enabled: false\n          overrideBaseUrl: /gate\n        uiSecurity:\n          ssl:\n            enabled: false\n        authn:\n          oauth2:\n            enabled: false\n            client: {}\n            resource: {}\n            userInfoMapping: {}\n          saml:\n            enabled: false\n            userAttributeMapping: {}\n          ldap:\n            enabled: false\n          x509:\n            enabled: false\n          iap:\n            enabled: false\n          enabled: false\n        authz:\n          groupMembership:\n            service: EXTERNAL\n            google:\n              roleProviderType: GOOGLE\n            github:\n              roleProviderType: GITHUB\n            file:\n              roleProviderType: FILE\n            ldap:\n              roleProviderType: LDAP\n          enabled: false\n      artifacts:\n        bitbucket:\n          enabled: false\n          accounts: []\n        gcs:\n          enabled: true\n          accounts:\n          - name: gcs-install-account\n        oracle:\n          enabled: false\n          accounts: []\n        github:\n          enabled: false\n          accounts: []\n        gitlab:\n          enabled: false\n          accounts: []\n        http:\n          enabled: false\n          accounts: []\n        helm:\n          enabled: false\n          accounts: []\n        s3:\n          enabled: false\n          accounts: []\n        maven:\n          enabled: false\n          accounts: []\n        templates: []\n      pubsub:\n        enabled: true\n        google:\n          enabled: true\n          pubsubType: GOOGLE\n          subscriptions:\n          - name: gcr-pub-sub\n            project: $PROJECT_ID\n            subscriptionName: $GCR_PUBSUB_SUBSCRIPTION\n            ackDeadlineSeconds: 10\n            messageFormat: GCR\n          publishers:\n          - name: $PUBSUB_NOTIFICATION_PUBLISHER\n            project: $PROJECT_ID\n            topicName: $PUBSUB_NOTIFICATION_TOPIC\n            content: NOTIFICATIONS\n      canary:\n        enabled: true\n        serviceIntegrations:\n        - name: google\n          enabled: true\n          accounts:\n          - name: my-google-account\n            project: $PROJECT_ID\n            bucket: $BUCKET_NAME\n            rootFolder: kayenta\n            supportedTypes:\n            - METRICS_STORE\n            - CONFIGURATION_STORE\n            - OBJECT_STORE\n          gcsEnabled: true\n          stackdriverEnabled: true\n        - name: prometheus\n          enabled: false\n          accounts: []\n        - name: datadog\n          enabled: false\n          accounts: []\n        - name: signalfx\n          enabled: false\n          accounts: []\n        - name: aws\n          enabled: false\n          accounts: []\n          s3Enabled: false\n        reduxLoggerEnabled: true\n        defaultJudge: NetflixACAJudge-v1.0\n        stagesEnabled: true\n        templatesEnabled: true\n        showAllConfigsEnabled: true\n      plugins:\n        plugins: []\n        enabled: false\n        downloadingEnabled: false\n        pluginConfigurations:\n          plugins: {}\n      webhook:\n        trust:\n          enabled: false\n      telemetry:\n        enabled: false\n        endpoint: https://stats.spinnaker.io\n        instanceId:\n        connectionTimeoutMillis: 3000\n        readTimeoutMillis: 5000\n---\n\napiVersion: batch/v1\nkind: Job\nmetadata:\n  name: hal-deploy-apply\n  namespace: halyard\n  labels:\n    app: job\n    stack: hal-deploy\nspec:\n  template:\n    metadata:\n      labels:\n        app: job\n        stack: hal-deploy\n    spec:\n      restartPolicy: OnFailure\n      containers:\n      - name: hal-deploy-apply\n        # todo use a custom image\n        image: us-docker.pkg.dev/spinnaker-community/docker/halyard:$HALYARD_VERSION\n        command:\n        - /bin/sh\n        args:\n        - -c\n        - \"hal deploy apply --daemon-endpoint http://spin-halyard.halyard:8064\"\n"
  },
  {
    "path": "scripts/install/setup.sh",
    "content": "#!/usr/bin/env bash\n\nerr() {\n  echo \"$*\" >&2;\n}\n\n[ -z \"$PARENT_DIR\" ] && PARENT_DIR=$(dirname $(realpath $0) | rev | cut -d '/' -f 4- | rev)\n\nsource $PARENT_DIR/spinnaker-for-gcp/scripts/manage/service_utils.sh\n\ncheck_for_required_binaries\n\nPARENT_DIR=$PARENT_DIR $PARENT_DIR/spinnaker-for-gcp/scripts/manage/check_git_config.sh || exit 1\n\n[ -z \"$PROPERTIES_FILE\" ] && PROPERTIES_FILE=\"$PARENT_DIR/spinnaker-for-gcp/scripts/install/properties\"\n\nsource \"$PROPERTIES_FILE\"\n\ncheck_for_shared_vpc $CI\n\nPARENT_DIR=$PARENT_DIR PROPERTIES_FILE=$PROPERTIES_FILE $PARENT_DIR/spinnaker-for-gcp/scripts/manage/check_project_mismatch.sh\n\nOPERATOR_SA_EMAIL=$(gcloud config list account --format \"value(core.account)\" --project $PROJECT_ID)\nSETUP_EXISTING_ROLES=$(gcloud projects get-iam-policy --filter bindings.members:$OPERATOR_SA_EMAIL $PROJECT_ID \\\n  --flatten bindings[].members --format=\"value(bindings.role)\")\n\nif [ -z \"$SETUP_EXISTING_ROLES\" ]; then\n  bold \"Unable to verify that the service account \\\"$OPERATOR_SA_EMAIL\\\" has the required IAM roles.\"\n  bold \"\\\"$OPERATOR_SA_EMAIL\\\" requires the IAM role \\\"Project IAM Admin\\\" to proceed.\"\n  exit 1\nfi\n\nif [ -z \"$(echo $SETUP_EXISTING_ROLES | grep roles/owner)\" ]; then\n  SETUP_REQUIRED_ROLES=(cloudfunctions.developer compute.networkViewer container.admin iam.serviceAccountCreator iam.serviceAccountUser pubsub.editor redis.admin serviceusage.serviceUsageAdmin source.admin storage.admin)\n  \n  MISSING_ROLES=\"\"\n  for r in \"${SETUP_REQUIRED_ROLES[@]}\"; do\n    if [ -z \"$(echo $SETUP_EXISTING_ROLES | grep $r)\" ]; then\n      if [ -z \"$MISSING_ROLES\" ]; then\n        MISSING_ROLES=\"$r\"\n      else \n        MISSING_ROLES=\"$MISSING_ROLES, $r\"\n      fi\n    fi\n  done\n\n  if [ -n \"$MISSING_ROLES\" ]; then \n    bold \"The service account in use, \\\"$OPERATOR_SA_EMAIL\\\", is missing the following required role(s): $MISSING_ROLES.\"\n    bold \"Add the required role(s) and try re-running the script.\"\n    exit 1\n  fi\nfi\n\nREQUIRED_APIS=\"cloudbuild.googleapis.com cloudfunctions.googleapis.com container.googleapis.com endpoints.googleapis.com iap.googleapis.com monitoring.googleapis.com redis.googleapis.com sourcerepo.googleapis.com\"\nNUM_REQUIRED_APIS=$(wc -w <<< \"$REQUIRED_APIS\")\nNUM_ENABLED_APIS=$(gcloud services list --project $PROJECT_ID \\\n  --filter=\"config.name:($REQUIRED_APIS)\" \\\n  --format=\"value(config.name)\" | wc -l)\n\nif [ $NUM_ENABLED_APIS != $NUM_REQUIRED_APIS ]; then\n  bold \"Enabling required APIs ($REQUIRED_APIS) in $PROJECT_ID...\"\n  bold \"This phase will take a few minutes (progress will not be reported during this operation).\"\n  bold\n  bold \"Once the required APIs are enabled, the remaining components will be installed and configured. The entire installation may take 10 minutes or more.\"\n\n  gcloud services --project $PROJECT_ID enable $REQUIRED_APIS\nfi\n\nif [ \"$PROJECT_ID\" != \"$NETWORK_PROJECT\" ]; then\n  # Cloud Memorystore for Redis requires the Redis instance to be deployed in the Shared VPC\n  # host project: https://cloud.google.com/memorystore/docs/redis/networking#limited_and_unsupported_networks\n  if [ ! $(has_service_enabled $NETWORK_PROJECT redis.googleapis.com) ]; then\n    bold \"Enabling redis.googleapis.com in $NETWORK_PROJECT...\"\n\n    gcloud services --project $NETWORK_PROJECT enable redis.googleapis.com\n  fi\nfi\n\nsource $PARENT_DIR/spinnaker-for-gcp/scripts/manage/cluster_utils.sh\n\nCLUSTER_EXISTS=$(check_for_existing_cluster)\n\nif [ -n \"$CLUSTER_EXISTS\" ]; then\n  check_existing_cluster_location\n\n  bold \"Retrieving credentials for GKE cluster $GKE_CLUSTER...\"\n  gcloud container clusters get-credentials $GKE_CLUSTER --zone $ZONE --project $PROJECT_ID\n\n  bold \"Checking for Spinnaker application in cluster $GKE_CLUSTER...\"\n  SPINNAKER_APPLICATION_LIST_JSON=$(kubectl get applications -n spinnaker -l app.kubernetes.io/name=spinnaker --output json)\n  SPINNAKER_APPLICATION_COUNT=$(echo $SPINNAKER_APPLICATION_LIST_JSON | jq '.items | length')\n\n  if [ -n \"$SPINNAKER_APPLICATION_COUNT\" ] && [ \"$SPINNAKER_APPLICATION_COUNT\" != \"0\" ]; then\n    bold \"The GKE cluster $GKE_CLUSTER already contains an installed Spinnaker application.\"\n\n    if [ \"$SPINNAKER_APPLICATION_COUNT\" == \"1\" ]; then\n      EXISTING_SPINNAKER_APPLICATION_NAME=$(echo $SPINNAKER_APPLICATION_LIST_JSON | jq -r '.items[0].metadata.name')\n\n      if [ \"$EXISTING_SPINNAKER_APPLICATION_NAME\" == \"$DEPLOYMENT_NAME\" ]; then\n        bold \"Name of existing Spinnaker application matches name specified in properties file; carrying on with installation...\"\n      else\n        bold \"Please choose another cluster.\"\n        exit 1\n      fi\n    else\n      # Should never be more than 1 deployment in a cluster, but protect against it just in case.\n      bold \"Please choose another cluster.\"\n      exit 1\n    fi\n  fi\nfi\n\nNETWORK_SUBNET_MODE=$(gcloud compute networks list --project $NETWORK_PROJECT \\\n  --filter \"name=$NETWORK\" \\\n  --format \"value(x_gcloud_subnet_mode)\")\n\nif [ -z \"$NETWORK_SUBNET_MODE\" ]; then\n  bold \"Network $NETWORK was not found in project $NETWORK_PROJECT.\"\n  exit 1\nelif [ \"$NETWORK_SUBNET_MODE\" = \"LEGACY\" ]; then\n  bold \"Network $NETWORK is a legacy network. This installation requires a\" \\\n       \"non-legacy network. Please specify a non-legacy network in\" \\\n       \"$PROPERTIES_FILE and re-run this script.\"\n  exit 1\nfi\n\n# Verify that the subnet exists in the network.\nSUBNET_CHECK=$(gcloud compute networks subnets list --project=$NETWORK_PROJECT \\\n  --network=$NETWORK --filter \"region: ($REGION) AND name: ($SUBNET)\" \\\n  --format \"value(name)\")\n\nif [ -z \"$SUBNET_CHECK\" ]; then\n  bold \"Subnet $SUBNET was not found in network $NETWORK\" \\\n       \"in project $NETWORK_PROJECT. Please specify an existing subnet in\" \\\n       \"$PROPERTIES_FILE and re-run this script. You can verify\" \\\n       \"what subnetworks exist in this network by running:\"\n  bold \"  gcloud compute networks subnets list --project $NETWORK_PROJECT --network=$NETWORK --filter \\\"region: ($REGION)\\\"\"\n  exit 1\nfi\n\nSA_EMAIL=$(gcloud iam service-accounts --project $PROJECT_ID list \\\n  --filter=\"displayName:$SERVICE_ACCOUNT_NAME\" \\\n  --format='value(email)')\n\nif [ -z \"$SA_EMAIL\" ]; then\n  bold \"Creating service account $SERVICE_ACCOUNT_NAME...\"\n\n  gcloud iam service-accounts --project $PROJECT_ID create \\\n    $SERVICE_ACCOUNT_NAME \\\n    --display-name $SERVICE_ACCOUNT_NAME\n\n  while [ -z \"$SA_EMAIL\" ]; do\n    SA_EMAIL=$(gcloud iam service-accounts --project $PROJECT_ID list \\\n      --filter=\"displayName:$SERVICE_ACCOUNT_NAME\" \\\n      --format='value(email)')\n    sleep 5\n  done\nelse\n  bold \"Using existing service account $SERVICE_ACCOUNT_NAME...\"\nfi\n\nbold \"Assigning required roles to $SERVICE_ACCOUNT_NAME...\"\n\nK8S_REQUIRED_ROLES=(cloudbuild.builds.editor container.admin logging.logWriter monitoring.admin pubsub.admin storage.admin)\nEXISTING_ROLES=$(gcloud projects get-iam-policy --filter bindings.members:$SA_EMAIL $PROJECT_ID \\\n  --flatten bindings[].members --format=\"value(bindings.role)\")\n\nfor r in \"${K8S_REQUIRED_ROLES[@]}\"; do\n  if [ -z \"$(echo $EXISTING_ROLES | grep $r)\" ]; then\n    bold \"Assigning role $r...\"\n    gcloud projects add-iam-policy-binding $PROJECT_ID \\\n      --member serviceAccount:$SA_EMAIL \\\n      --role roles/$r \\\n      --format=none\n  fi\ndone\n\nexport REDIS_INSTANCE_HOST=$(gcloud redis instances list \\\n  --project $NETWORK_PROJECT --region $REGION \\\n  --filter=\"name=projects/$NETWORK_PROJECT/locations/$REGION/instances/$REDIS_INSTANCE\" \\\n  --format=\"value(host)\")\n\nif [ -z \"$REDIS_INSTANCE_HOST\" ]; then\n  bold \"Creating redis instance $REDIS_INSTANCE in project $NETWORK_PROJECT...\"\n\n  gcloud redis instances create $REDIS_INSTANCE --project $NETWORK_PROJECT \\\n    --region=$REGION --zone=$ZONE --network=$NETWORK_REFERENCE \\\n    --redis-config=notify-keyspace-events=gxE\n\n  export REDIS_INSTANCE_HOST=$(gcloud redis instances list \\\n    --project $NETWORK_PROJECT --region $REGION \\\n    --filter=\"name=projects/$NETWORK_PROJECT/locations/$REGION/instances/$REDIS_INSTANCE\" \\\n    --format=\"value(host)\")\nelse\n  bold \"Using existing redis instance $REDIS_INSTANCE ($REDIS_INSTANCE_HOST)...\"\nfi\n\n# TODO: Could verify ACLs here. In the meantime, error messages should suffice.\ngsutil ls $BUCKET_URI\n\nif [ $? != 0 ]; then\n  bold \"Creating bucket $BUCKET_URI...\"\n\n  gsutil mb -p $PROJECT_ID -l $REGION $BUCKET_URI\n  gsutil versioning set on $BUCKET_URI\nelse\n  bold \"Using existing bucket $BUCKET_URI...\"\nfi\n\nif [ -z \"$CLUSTER_EXISTS\" ]; then\n  bold \"Creating GKE cluster $GKE_CLUSTER...\"\n\n  # $GKE_RELEASE_CHANNEL is new as of 2021-08-13, so fall back to\n  # $GKE_CLUSTER_VERSION if it doesn't exist.\n  if [ -z \"$GKE_RELEASE_CHANNEL\" ]; then\n    CLUSTER_VERSION_SPEC=\"--cluster-version $GKE_CLUSTER_VERSION\"\n  else\n    CLUSTER_VERSION_SPEC=\"--release-channel $GKE_RELEASE_CHANNEL\"\n  fi\n\n  # TODO: Move some of these config settings to properties file.\n  # TODO: Should this be regional instead?\n  eval gcloud beta container clusters create $GKE_CLUSTER --project $PROJECT_ID \\\n    --zone $ZONE --network $NETWORK_REFERENCE --subnetwork $SUBNET_REFERENCE \\\n    $CLUSTER_VERSION_SPEC --machine-type $GKE_MACHINE_TYPE \\\n    --disk-type $GKE_DISK_TYPE --disk-size $GKE_DISK_SIZE --service-account $SA_EMAIL \\\n    --num-nodes $GKE_NUM_NODES --enable-stackdriver-kubernetes --enable-autoupgrade \\\n    --enable-autorepair --enable-ip-alias --addons HorizontalPodAutoscaling,HttpLoadBalancing \\\n    \"${CLUSTER_SECONDARY_RANGE_NAME:+'--cluster-secondary-range-name' $CLUSTER_SECONDARY_RANGE_NAME}\" \\\n    \"${SERVICES_SECONDARY_RANGE_NAME:+'--services-secondary-range-name' $SERVICES_SECONDARY_RANGE_NAME}\"\n\n  # If the cluster already exists, we already retrieved credentials way up at the top of the script.\n  bold \"Retrieving credentials for GKE cluster $GKE_CLUSTER...\"\n  gcloud container clusters get-credentials $GKE_CLUSTER --zone $ZONE --project $PROJECT_ID\nelse\n  bold \"Using existing GKE cluster $GKE_CLUSTER...\"\n  check_existing_cluster_prereqs\nfi\n\nGCR_PUBSUB_TOPIC_NAME=projects/$PROJECT_ID/topics/gcr\nEXISTING_GCR_PUBSUB_TOPIC_NAME=$(gcloud pubsub topics list --project $PROJECT_ID \\\n  --filter=\"name=$GCR_PUBSUB_TOPIC_NAME\" --format=\"value(name)\")\n\nif [ -z \"$EXISTING_GCR_PUBSUB_TOPIC_NAME\" ]; then\n  bold \"Creating pubsub topic $GCR_PUBSUB_TOPIC_NAME for GCR...\"\n  gcloud pubsub topics create --project $PROJECT_ID $GCR_PUBSUB_TOPIC_NAME\nelse\n  bold \"Using existing pubsub topic $EXISTING_GCR_PUBSUB_TOPIC_NAME for GCR...\"\nfi\n\nEXISTING_GCR_PUBSUB_SUBSCRIPTION_NAME=$(gcloud pubsub subscriptions list \\\n  --project $PROJECT_ID \\\n  --filter=\"name=projects/$PROJECT_ID/subscriptions/$GCR_PUBSUB_SUBSCRIPTION\" \\\n  --format=\"value(name)\")\n\nif [ -z \"$EXISTING_GCR_PUBSUB_SUBSCRIPTION_NAME\" ]; then\n  bold \"Creating pubsub subscription $GCR_PUBSUB_SUBSCRIPTION for GCR...\"\n  gcloud pubsub subscriptions create --project $PROJECT_ID $GCR_PUBSUB_SUBSCRIPTION \\\n    --topic=gcr\nelse\n  bold \"Using existing pubsub subscription $GCR_PUBSUB_SUBSCRIPTION for GCR...\"\nfi\n\nGCB_PUBSUB_TOPIC_NAME=projects/$PROJECT_ID/topics/cloud-builds\nEXISTING_GCB_PUBSUB_TOPIC_NAME=$(gcloud pubsub topics list --project $PROJECT_ID \\\n  --filter=\"name=$GCB_PUBSUB_TOPIC_NAME\" --format=\"value(name)\")\n\nif [ -z \"$EXISTING_GCB_PUBSUB_TOPIC_NAME\" ]; then\n  bold \"Creating pubsub topic $GCB_PUBSUB_TOPIC_NAME for GCB...\"\n  gcloud pubsub topics create --project $PROJECT_ID $GCB_PUBSUB_TOPIC_NAME\nelse\n  bold \"Using existing pubsub topic $EXISTING_GCB_PUBSUB_TOPIC_NAME for GCB...\"\nfi\n\nEXISTING_GCB_PUBSUB_SUBSCRIPTION_NAME=$(gcloud pubsub subscriptions list \\\n  --project $PROJECT_ID \\\n  --filter=\"name=projects/$PROJECT_ID/subscriptions/$GCB_PUBSUB_SUBSCRIPTION\" \\\n  --format=\"value(name)\")\n\nif [ -z \"$EXISTING_GCB_PUBSUB_SUBSCRIPTION_NAME\" ]; then\n  bold \"Creating pubsub subscription $GCB_PUBSUB_SUBSCRIPTION for GCB...\"\n  gcloud pubsub subscriptions create --project $PROJECT_ID $GCB_PUBSUB_SUBSCRIPTION \\\n    --topic=projects/$PROJECT_ID/topics/cloud-builds\nelse\n  bold \"Using existing pubsub subscription $GCB_PUBSUB_SUBSCRIPTION for GCB...\"\nfi\n\nNOTIFICATION_PUBSUB_TOPIC_NAME=projects/$PROJECT_ID/topics/$PUBSUB_NOTIFICATION_TOPIC\nEXISTING_NOTIFICATION_PUBSUB_TOPIC_NAME=$(gcloud pubsub topics list --project $PROJECT_ID \\\n  --filter=\"name=$NOTIFICATION_PUBSUB_TOPIC_NAME\" --format=\"value(name)\")\n\nif [ -z \"$EXISTING_NOTIFICATION_PUBSUB_TOPIC_NAME\" ]; then\n  bold \"Creating pubsub topic $NOTIFICATION_PUBSUB_TOPIC_NAME for notifications...\"\n  gcloud pubsub topics create --project $PROJECT_ID $NOTIFICATION_PUBSUB_TOPIC_NAME\nelse\n  bold \"Using existing pubsub topic $EXISTING_NOTIFICATION_PUBSUB_TOPIC_NAME for notifications...\"\nfi\n\nEXISTING_HAL_DEPLOY_APPLY_JOB_NAME=$(kubectl get job -n halyard \\\n  --field-selector metadata.name==\"hal-deploy-apply\" \\\n  -o json | jq -r .items[0].metadata.name)\n\nif [ $EXISTING_HAL_DEPLOY_APPLY_JOB_NAME != 'null' ]; then\n  bold \"Deleting earlier job $EXISTING_HAL_DEPLOY_APPLY_JOB_NAME...\"\n\n  kubectl delete job hal-deploy-apply -n halyard\nfi\n\nbold \"Provisioning Spinnaker resources...\"\n\nenvsubst < $PARENT_DIR/spinnaker-for-gcp/scripts/install/quick-install.yml | kubectl apply -f -\n\njob_ready() {\n  printf \"Waiting on job $1 to complete\"\n  while [[ \"$(kubectl get job $1 -n halyard -o \\\n            jsonpath=\"{.status.succeeded}\")\" != \"1\" ]]; do\n    printf \".\"\n    sleep 5\n  done\n  echo \"\"\n}\n\njob_ready hal-deploy-apply\n\n# Sourced to import $IP_ADDR. \n# Used at the end of setup to check if installation is exposed via a secured endpoint.\nsource $PARENT_DIR/spinnaker-for-gcp/scripts/manage/update_landing_page.sh\n\nPARENT_DIR=$PARENT_DIR PROPERTIES_FILE=$PROPERTIES_FILE $PARENT_DIR/spinnaker-for-gcp/scripts/manage/deploy_application_manifest.sh\n\n# Delete any existing deployment config secret.\n# It will be recreated with up-to-date contents during push_config.sh.\nEXISTING_DEPLOYMENT_SECRET_NAME=$(kubectl get secret -n halyard \\\n  --field-selector metadata.name==\"spinnaker-deployment\" \\\n  -o json | jq .items[0].metadata.name)\n\nif [ $EXISTING_DEPLOYMENT_SECRET_NAME != 'null' ]; then\n  bold \"Deleting Kubernetes secret spinnaker-deployment...\"\n  kubectl delete secret spinnaker-deployment -n halyard\nfi\n\nEXISTING_CLOUD_FUNCTION=$(gcloud functions list --project $PROJECT_ID \\\n  --format=\"value(name)\" --filter=\"entryPoint=$CLOUD_FUNCTION_NAME\")\n\nif [ -z \"$EXISTING_CLOUD_FUNCTION\" ]; then\n  bold \"Deploying audit log cloud function $CLOUD_FUNCTION_NAME...\"\n\n  cat $PARENT_DIR/spinnaker-for-gcp/scripts/install/spinnakerAuditLog/config_json.template | envsubst > $PARENT_DIR/spinnaker-for-gcp/scripts/install/spinnakerAuditLog/config.json\n  cat $PARENT_DIR/spinnaker-for-gcp/scripts/install/spinnakerAuditLog/index_js.template | envsubst > $PARENT_DIR/spinnaker-for-gcp/scripts/install/spinnakerAuditLog/index.js\n  gcloud functions deploy $CLOUD_FUNCTION_NAME --source $PARENT_DIR/spinnaker-for-gcp/scripts/install/spinnakerAuditLog \\\n    --trigger-http --memory 2048MB --runtime nodejs8 --allow-unauthenticated --project $PROJECT_ID --region $REGION\n  gcloud alpha functions add-iam-policy-binding $CLOUD_FUNCTION_NAME --project $PROJECT_ID --region $REGION --member allUsers --role roles/cloudfunctions.invoker\nelse\n  bold \"Using existing audit log cloud function $CLOUD_FUNCTION_NAME...\"\nfi\n\nif [ \"$USE_CLOUD_SHELL_HAL_CONFIG\" = true ]; then\n  # Not passing $CI since the guard makes it clear we are running from cloud shell.\n  $PARENT_DIR/spinnaker-for-gcp/scripts/manage/push_and_apply.sh\nelse\n  # We want the local hal config to match what was deployed.\n  CI=$CI PARENT_DIR=$PARENT_DIR PROPERTIES_FILE=$PROPERTIES_FILE $PARENT_DIR/spinnaker-for-gcp/scripts/manage/pull_config.sh\n  # We want a full backup stored in the bucket and the full deployment config stored in a secret.\n  CI=$CI PARENT_DIR=$PARENT_DIR PROPERTIES_FILE=$PROPERTIES_FILE $PARENT_DIR/spinnaker-for-gcp/scripts/manage/push_config.sh\nfi\n\ndeploy_ready() {\n  printf \"Waiting on $2 to come online\"\n  while [[ \"$(kubectl get deploy $1 -n spinnaker -o \\\n            jsonpath=\"{.status.readyReplicas}\")\" != \\\n           \"$(kubectl get deploy $1 -n spinnaker -o \\\n            jsonpath=\"{.status.replicas}\")\" ]]; do\n    printf \".\"\n    sleep 5\n  done\n  echo \"\"\n}\n\ndeploy_ready spin-gate \"API server\"\ndeploy_ready spin-front50 \"storage server\"\ndeploy_ready spin-orca \"orchestration engine\"\ndeploy_ready spin-kayenta \"canary analysis engine\"\ndeploy_ready spin-deck \"UI server\"\n\nif [ \"$CI\" != true ]; then\n  $PARENT_DIR/spinnaker-for-gcp/scripts/cli/install_hal.sh --version $HALYARD_VERSION\n  $PARENT_DIR/spinnaker-for-gcp/scripts/cli/install_spin.sh\n\n  # We want a backup containing the newly-created ~/.spin/* files as well.\n  # Not passing $CI since the guard already ensures it is not true.\n  $PARENT_DIR/spinnaker-for-gcp/scripts/manage/push_config.sh  \nfi\n\n# If restoring a secured endpoint, leave the user on the documentation for iap configuration.\nif [ \"$USE_CLOUD_SHELL_HAL_CONFIG\" = true -a -n \"$IP_ADDR\" -a \"$CI\" != true ]; then\n  $PARENT_DIR/spinnaker-for-gcp/scripts/expose/launch_configure_iap.sh\nfi\n\necho\nbold \"Installation complete.\"\necho\nbold \"Sign up for Spinnaker for GCP updates and announcements:\"\nbold \"  https://groups.google.com/forum/#!forum/spinnaker-for-gcp-announce\"\necho\n"
  },
  {
    "path": "scripts/install/setup_properties.sh",
    "content": "#!/usr/bin/env bash\n\n# PROJECT_ID should be set, but we will try to determine via gcloud config if not set.\n# DEPLOYMENT_NAME, GKE_CLUSTER and ZONE are optional.\n# If GKE_CLUSTER is set, ZONE is required. (This indicates that we should install in an existing cluster.)\n# If using a pre-existing cluster, that cluster must have:\n#   - IP aliases enabled (since we are using a hosted Redis instance)\n#   - Full Cloud Platform scope for its nodes (if using the default service account)\n# ZONE can be set and GKE_CLUSTER left unset. (This indicates we should create a new cluster in $ZONE.)\n\nbold() {\n  echo \". $(tput bold)\" \"$*\" \"$(tput sgr0)\";\n}\n\n~/cloudshell_open/spinnaker-for-gcp/scripts/manage/check_duplicate_dirs.sh || exit 1\n\nif [ -z \"$PROJECT_ID\" ]; then\n  PROJECT_ID=$(gcloud info --format='value(config.project)')\nfi\n\nif [ -z \"$PROJECT_ID\" ]; then\n  echo \"PROJECT_ID must be specified.\"\n  exit 1\nfi\n\nPROPERTIES_FILE=\"$HOME/cloudshell_open/spinnaker-for-gcp/scripts/install/properties\"\nif [ -f \"$PROPERTIES_FILE\" ]; then\n  bold \"The properties file already exists at $PROPERTIES_FILE. Please move it out of the way if you want to generate a new properties file.\"\n  exit 1\nfi\n\nif [ \"$GKE_CLUSTER\" ]; then\n  if [ -z \"$ZONE\" ]; then\n    echo \"If GKE_CLUSTER is specified, ZONE must also be specified.\"\n    exit 1\n  fi\n\n  # Since cluster already exists, must resolve service account from the cluster.\n  EXISTING_SA_EMAIL=$(gcloud beta container clusters describe --project $PROJECT_ID \\\n                        --zone $ZONE $GKE_CLUSTER --format=\"value(nodeConfig.serviceAccount)\")\n\n  if [ -z $EXISTING_SA_EMAIL ]; then\n    echo \"Unable to resolve service account from existing cluster $GKE_CLUSTER in zone $ZONE.\"\n    exit 1\n  fi\n\n  if [ \"$EXISTING_SA_EMAIL\" == \"default\" ]; then\n    SERVICE_ACCOUNT_NAME=\"Compute Engine default service account\"\n  else\n    SERVICE_ACCOUNT_NAME=$(echo $EXISTING_SA_EMAIL | cut -d @ -f 1)\n  fi\nfi\n\nNETWORK=\"default\"\nSUBNET=\"default\"\nZONE=${ZONE:-us-east1-c}\nREGION=$(echo $ZONE | cut -d - -f 1,2)\n\nsource ~/cloudshell_open/spinnaker-for-gcp/scripts/manage/service_utils.sh\n\nquery_redis_instance_names() {\n  if [ $(has_service_enabled $1 redis.googleapis.com) ]; then\n    # TODO: Should really query redis instances across _all_ regions to ensure no deployment naming collision.\n    # TODO: Alternatively, could incorporate region in generated deployment name.\n    EXISTING_REDIS_NAMES=$(gcloud redis instances list --region $REGION --project $1 \\\n                             --filter=\"name:spinnaker-\" \\\n                             --format=\"value(name)\")\n\n    echo \"$EXISTING_REDIS_NAMES\"\n  fi\n}\n\nEXISTING_REDIS_NAMES=$(query_redis_instance_names $PROJECT_ID)\n\n# Also avoid name collisions with potential Shared VPC host project.\nif [ $(has_service_enabled $PROJECT_ID compute.googleapis.com) ]; then\n  SHARED_VPC_HOST_PROJECT=$(gcloud compute shared-vpc get-host-project $PROJECT_ID --format=\"value(name)\")\nfi\n\nif [ \"$SHARED_VPC_HOST_PROJECT\" ]; then\n  SHARED_VPC_HOST_PROJECT_REDIS_NAMES=$(query_redis_instance_names $SHARED_VPC_HOST_PROJECT)\n\n  EXISTING_REDIS_NAMES=\"$EXISTING_REDIS_NAMES\"$'\\n'\"$SHARED_VPC_HOST_PROJECT_REDIS_NAMES\"\nfi\n\nEXISTING_DEPLOYMENT_COUNT=$(echo \"$EXISTING_REDIS_NAMES\" | sed '/^$/d' | wc -l)\nNEW_DEPLOYMENT_SUFFIX=$(($EXISTING_DEPLOYMENT_COUNT + 1))\nNEW_DEPLOYMENT_NAME=\"spinnaker-$NEW_DEPLOYMENT_SUFFIX\"\n\nwhile [[ \"$(echo \"$EXISTING_REDIS_NAMES\" | grep ^$NEW_DEPLOYMENT_NAME$ | wc -l)\" != \"0\" ]]; do\n  NEW_DEPLOYMENT_NAME=\"spinnaker-$((++NEW_DEPLOYMENT_SUFFIX))\"\ndone\n\ncat > ~/cloudshell_open/spinnaker-for-gcp/scripts/install/properties <<EOL\n#!/usr/bin/env bash\n\n# This file is generated just once per Spinnaker installation, prior to running setup.sh.\n# You can make changes to this file before running setup.sh for the first time.\n# If setup.sh is interrupted, you can run it again at any point and it will finish any incomplete steps.\n# Do not change this file once you have run setup.sh for the first time.\n# If you want to provision a new Spinnaker installation, whether in the same project or a different project,\n#   simply wait until setup.sh completes and delete this file (or the entire cloned repo) from your\n#   Cloud Shell home directory. Then you can relaunch the provision-spinnaker.md tutorial and generate a new\n#   properties file for use in provisioning a new Spinnaker installation.\n\nexport PROJECT_ID=$PROJECT_ID\nexport DEPLOYMENT_NAME=${DEPLOYMENT_NAME:-$NEW_DEPLOYMENT_NAME}\n\nexport SPINNAKER_VERSION=1.19.3\nexport HALYARD_VERSION=1.33.0\n\nexport ZONE=$ZONE\nexport REGION=$REGION\n\n# The specified network must exist, and it must not be a legacy network.\n# More info on legacy networks can be found here: https://cloud.google.com/vpc/docs/legacy\nexport NETWORK=$NETWORK\nexport SUBNET=$SUBNET\n\nEOL\n\nif [ \"$SHARED_VPC_HOST_PROJECT\" ]; then\n  cat >> ~/cloudshell_open/spinnaker-for-gcp/scripts/install/properties <<EOL\n# If you want to use a shared network/subnet from the Shared VPC host project, you'll need to perform\n# these steps prior to running the setup.sh script:\n#   1) Specify the name of the shared network in \\$NETWORK up above.\n#   2) Specify the name of the shared subnet in \\$SUBNET up above.\n#   3) Specify the Shared VPC host project id ($SHARED_VPC_HOST_PROJECT) in \\$NETWORK_PROJECT below.\n#   4) Ensure the subnet referenced by \\$SUBNET defines 2 named secondary ranges (one for\n#      pods, and one for services).\n#   5) Specify the names of the 2 secondary ranges in \\$CLUSTER_SECONDARY_RANGE_NAME and\n#      \\$SERVICES_SECONDARY_RANGE_NAME down below.\nEOL\nfi\n\ncat >> ~/cloudshell_open/spinnaker-for-gcp/scripts/install/properties <<EOL\nexport NETWORK_PROJECT=\\$PROJECT_ID\nexport NETWORK_REFERENCE=projects/\\$NETWORK_PROJECT/global/networks/\\$NETWORK\nexport SUBNET_REFERENCE=projects/\\$NETWORK_PROJECT/regions/\\$REGION/subnetworks/\\$SUBNET\nEOL\n\nif [ \"$SHARED_VPC_HOST_PROJECT\" ]; then\n  cat >> ~/cloudshell_open/spinnaker-for-gcp/scripts/install/properties <<EOL\nexport CLUSTER_SECONDARY_RANGE_NAME=\nexport SERVICES_SECONDARY_RANGE_NAME=\nEOL\nfi\n\ncat >> ~/cloudshell_open/spinnaker-for-gcp/scripts/install/properties <<EOL\n\n# If cluster does not exist, it will be created.\nexport GKE_CLUSTER=${GKE_CLUSTER:-\\$DEPLOYMENT_NAME}\n\n# These are only considered if a new GKE cluster is being created.\nexport GKE_RELEASE_CHANNEL=stable\nexport GKE_MACHINE_TYPE=n1-highmem-4\nexport GKE_DISK_TYPE=pd-standard\nexport GKE_DISK_SIZE=100\nexport GKE_NUM_NODES=3\n\n# See TZ column in https://en.wikipedia.org/wiki/List_of_tz_database_time_zones\nexport TIMEZONE=$(cat /etc/timezone)\n\n# If service account does not exist, it will be created.\nexport SERVICE_ACCOUNT_NAME=\"${SERVICE_ACCOUNT_NAME:-\"\\$DEPLOYMENT_NAME-acc-$(date +\"%s\")\"}\"\n\n# If Cloud Memorystore Redis instance does not exist, it will be created.\nexport REDIS_INSTANCE=\\$DEPLOYMENT_NAME\n\n# If bucket does not exist, it will be created.\nexport BUCKET_NAME=\"\\$DEPLOYMENT_NAME-$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 20 | head -n 1)-$(date +\"%s\")\"\nexport BUCKET_URI=\"gs://\\$BUCKET_NAME\"\n\n# If CSR repo does not exist, it will be created.\nexport CONFIG_CSR_REPO=\\$DEPLOYMENT_NAME-config\n\n# Used to authenticate calls to the audit log Cloud Function.\nexport AUDIT_LOG_UNAME=\"$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 20 | head -n 1)-$(date +\"%s\")\"\nexport AUDIT_LOG_PW=\"$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 20 | head -n 1)-$(date +\"%s\")\"\n\nexport CLOUD_FUNCTION_NAME=\"\\${DEPLOYMENT_NAME//-}AuditLog\"\n\nexport GCR_PUBSUB_SUBSCRIPTION=\\$DEPLOYMENT_NAME-gcr-pubsub-subscription\nexport GCB_PUBSUB_SUBSCRIPTION=\\$DEPLOYMENT_NAME-gcb-pubsub-subscription\n\nexport PUBSUB_NOTIFICATION_PUBLISHER=\\$DEPLOYMENT_NAME-publisher\nexport PUBSUB_NOTIFICATION_TOPIC=\\$DEPLOYMENT_NAME-notifications-topic\n\n# The properties following this line are only relevant if you intend to expose your new Spinnaker instance.\nexport STATIC_IP_NAME=\\$DEPLOYMENT_NAME-external-ip\nexport MANAGED_CERT=\\$DEPLOYMENT_NAME-managed-cert\nexport SECRET_NAME=\\$DEPLOYMENT_NAME-oauth-client-secret\n\n# If you own a domain name and want to use that instead of this automatically-assigned one,\n# specify it here (you must be able to configure the dns settings).\nexport DOMAIN_NAME=\\$DEPLOYMENT_NAME.endpoints.\\$PROJECT_ID.cloud.goog\n\n# This email address will be granted permissions as an IAP-Secured Web App User.\nexport IAP_USER=$(gcloud auth list --format=\"value(account)\" --filter=\"status=ACTIVE\")\nEOL\n\nif [ \"$SHARED_VPC_HOST_PROJECT\" ]; then\n  bold \"If you want to use a shared network/subnet from the Shared VPC host project ($SHARED_VPC_HOST_PROJECT),\" \\\n    \"there are additional instructions you must follow in the properties file. You must perform those steps\" \\\n    \"prior to running the setup.sh script:\"\n  bold \"  cloudshell edit ~/cloudshell_open/spinnaker-for-gcp/scripts/install/properties\"\nfi\n"
  },
  {
    "path": "scripts/install/spinnakerAuditLog/config_json.template",
    "content": "{\n  \"USERNAME\": \"$AUDIT_LOG_UNAME\",\n  \"PASSWORD\": \"$AUDIT_LOG_PW\",\n  \"TIMEZONE\": \"$TIMEZONE\",\n  \"PROJECT_ID\": \"$PROJECT_ID\",\n  \"AUDIT_LOG_NAME\": \"$CLOUD_FUNCTION_NAME\"\n}\n"
  },
  {
    "path": "scripts/install/spinnakerAuditLog/index_js.template",
    "content": "const config = require('./config.json');\nconst moment = require('moment-timezone');\nconst {Logging} = require('@google-cloud/logging');\n\nconst logging = new Logging({\n  projectId: config.PROJECT_ID,\n  keyFilename: config.CREDENTIALS_PATH\n});\n\n/**\n * Logs Spinnaker events to Stackdriver Logging.\n *\n * @param {!Object} req Cloud Function request context.\n * @param {!Object} res Cloud Function response context.\n */\nexports.$CLOUD_FUNCTION_NAME = function spinnakerAuditLog (req, res) {\n  log('req.body.payload=' + JSON.stringify(req.body.payload), null, null, 'DEBUG');\n\n  try {\n    verifyWebhook(req.get('authorization') || '');\n\n    if (req.body.eventName !== 'spinnaker_events' || req.body.payload === undefined) {\n      res.status(400).send('Spinnaker audit log request body is malformed.');\n    } else {\n      var content = req.body.payload.content;\n      var eventSource = req.body.payload.details.source;\n      var eventType = req.body.payload.details.type;\n      var execution = content.execution;\n      var context = content.context;\n      var stageDetails = (execution && execution.stages && execution.stages.length > 0) ? execution.stages.find(stage => stage.status === 'RUNNING') : {};\n      var user = execution && execution.authentication && execution.authentication.user ? execution.authentication.user : 'n/a';\n\n      if (execution && execution.trigger) {\n        if (execution.trigger.runAsUser) {\n          user = execution.trigger.runAsUser;\n        } else if (execution.trigger.user) {\n          user = execution.trigger.user;\n        }\n      }\n\n      var creationTimestamp = moment.tz(Number(req.body.payload.details.created), config.TIMEZONE).format('ddd, DD MMM YYYY HH:mm:ss z');\n\n      var reasonSegment;\n\n      if (eventSource === 'igor') {\n        if (eventType === 'build') {\n          var lastBuild = content.project.lastBuild;\n          var jenkinsTimestamp = moment.tz(Number(lastBuild.timestamp), config.TIMEZONE).format('ddd, DD MMM YYYY HH:mm:ss z');\n\n          if (lastBuild.result === 'SUCCESS') {\n            log('Jenkins project ' + content.project.name + ' successfully completed build #' + lastBuild.number + ' at ' + jenkinsTimestamp + '.', null, null);\n          } else {\n            log('Jenkins project ' + content.project.name + ' completed build #' + lastBuild.number + ' with status ' + lastBuild.result + ' at ' + jenkinsTimestamp + '.', null, null, 'ERROR');\n          }\n        } else if (eventType === 'docker') {\n          log('Docker tag ' + content.tag + ' was pushed to repository ' + content.repository + ' in registry ' + content.registry + ' at ' + creationTimestamp + '.', null, null);\n        }\n      } else if (eventType === 'git') {\n        log('Received webhook for project ' + content.slug + ' in org ' + content.repoProject + ' from ' + eventSource + ' at commit ' + content.hash + ' on branch ' + content.branch + ' at ' + creationTimestamp + '.', null, null);\n      } else if (eventType === 'orca:stage:starting' && !stageDetails.syntheticStageOwner) {\n        if (!content.standalone) {\n          log('User ' + user + ' executed operation ' + stageDetails.name + ' (of type ' + stageDetails.type + ') via pipeline ' + execution.name + ' of application ' + execution.application + ' at ' + creationTimestamp + '.', execution.application, execution.name);\n        } else if (stageDetails.type === 'savePipeline') {\n          log('User ' + user + ' executed operation (' + execution.description + ') at ' + creationTimestamp + '.', null, null);\n        } else {\n          reasonSegment = context.reason ? ' for reason \"' + context.reason + '\"' : '';\n\n          log('User ' + user + ' executed ad-hoc operation ' + execution.stages[0].type + ' (' + execution.description + ')' + reasonSegment + ' at ' + creationTimestamp + '.', null, null);\n        }\n      } else if (eventType === 'orca:pipeline:starting') {\n        var parametersSegment = execution.trigger.parameters ? ' (with parameters ' + JSON.stringify(execution.trigger.parameters) + ')' : '';\n\n        log('User ' + user + ' executed pipeline ' + execution.name + ' of application ' + execution.application + ' via ' + execution.trigger.type + ' trigger' + parametersSegment + ' at ' + creationTimestamp + '.', execution.application, execution.name);\n      } else if (eventType === 'orca:pipeline:failed' && execution.canceled) {\n        var cancellationUser = execution.canceledBy ? execution.canceledBy : null;\n\n        if (cancellationUser) {\n          reasonSegment = execution.cancellationReason ? ' for reason \"' + execution.cancellationReason + '\"' : '';\n\n          log('User ' + cancellationUser + ' canceled pipeline ' + execution.name + ' of application ' + execution.application + reasonSegment + ' at ' + creationTimestamp + '.', execution.application, execution.name, 'WARNING');\n        } else {\n          log('Pipeline ' + execution.name + ' of application ' + execution.application + ' failed at ' + creationTimestamp + '.', execution.application, execution.name, 'ERROR');\n        }\n      } else if (eventType === 'orca:pipeline:complete') {\n        log('Pipeline ' + execution.name + ' of application ' + execution.application + ' completed at ' + creationTimestamp + '.', execution.application, execution.name);\n      } else if (!content.standalone && context && stageDetails && stageDetails.type === 'manualJudgment' && eventType === 'orca:task:failed') {\n        var judgmentInputSegment = context.judgmentInput ? ' (judgment \"' + context.judgmentInput + '\" was selected)' : '';\n\n        log('User ' + context.lastModifiedBy + ' judged stage ' + stageDetails.name + ' of pipeline ' + execution.name + ' of application ' + execution.application + ' to stop' + judgmentInputSegment + ' at ' + creationTimestamp + '.', execution.application, execution.name, 'WARNING');\n      } else if (!content.standalone && context && stageDetails && stageDetails.type === 'manualJudgment' && eventType === 'orca:task:complete') {\n        var judgmentInputSegment = context.judgmentInput ? ' (judgment \"' + context.judgmentInput + '\" was selected)' : '';\n\n        log('User ' + context.lastModifiedBy + ' judged stage ' + stageDetails.name + ' of pipeline ' + execution.name + ' of application ' + execution.application + ' to continue' + judgmentInputSegment + ' at ' + creationTimestamp + '.');\n      } else if (eventType === 'orca:task:failed') {\n        var failureReasonSegment = context.exception && context.exception.details && context.exception.details.errors && context.exception.details.errors[0] ? ' due to ' + JSON.stringify(context.exception.details.errors) : '';\n\n        if (!content.standalone) {\n          log('Operation ' + stageDetails.name + ' (of type ' + stageDetails.type + ') of pipeline ' + execution.name + ' of application ' + execution.application + ' failed' + failureReasonSegment + ' at ' + creationTimestamp + '.', execution.application, execution.name, 'ERROR');\n        } else {\n          log('Ad-hoc operation ' + stageDetails.type + ' failed' + failureReasonSegment + ' at ' + creationTimestamp + '.', null, null, 'ERROR');\n        }\n      }\n\n      res.status(200).send('Success: ' + req.body.eventName);\n    }\n  } catch (err) {\n    log(err, 'ERROR');\n    res.status(err.code || 500).send(err);\n  }\n};\n\n/**\n * Verify that the webhook request came from spinnaker/echo.\n *\n * @param {string} authorization The authorization header of the request, e.g. \"Basic ZmdvOhJhcg==\"\n */\nfunction verifyWebhook (authorization) {\n  const basicAuth = new Buffer(authorization.replace('Basic ', ''), 'base64').toString();\n  const parts = basicAuth.split(':');\n\n  if (parts[0] !== config.USERNAME || parts[1] !== config.PASSWORD) {\n    const error = new Error('Invalid credentials');\n    error.code = 401;\n    throw error;\n  }\n}\n\n/**\n * Writes message to StackDriver with specified severity.\n * \n * @param {string} message - The message to log to StackDriver logging.\n * @param {('ALERT', 'CRITICAL', 'DEBUG', 'EMERGENCY', 'ERROR', 'INFO', 'NOTICE', 'WARNING', 'WRITE')} severity - The \n * severity of the logged message. Defaults to 'INFO'.\n */\nfunction log(message, application, pipeline, severity = 'INFO') {\n  var log = logging.log(config.AUDIT_LOG_NAME);\n  var metadata = {resource: {type: 'cloud_function'}, severity: severity};\n  var jsonPayload = {message: message};\n  if (application) {\n    jsonPayload.application = application;\n  }\n  if (pipeline) {\n    jsonPayload.pipeline = pipeline;\n  }\n  var entry = log.entry(metadata, jsonPayload);\n\n  log.write(entry);\n}\n"
  },
  {
    "path": "scripts/install/spinnakerAuditLog/package.json",
    "content": "{\n  \"dependencies\": {\n    \"@google-cloud/logging\": \"4.1.1\",\n    \"moment-timezone\": \"^0.5.11\"\n  }\n}\n"
  },
  {
    "path": "scripts/manage/add_gae_account.sh",
    "content": "#!/usr/bin/env bash\n\nbold() {\n  echo \". $(tput bold)\" \"$*\" \"$(tput sgr0)\";\n}\n\nsource ~/cloudshell_open/spinnaker-for-gcp/scripts/install/properties\n\nread -e -p \"Please enter the id of the project within which you wish to manage GAE resources: \" -i $PROJECT_ID MANAGED_PROJECT_ID\nread -e -p \"Please enter a name for the new Spinnaker account: \" -i \"$MANAGED_PROJECT_ID-acct\" GAE_ACCOUNT_NAME\n\nbold \"Assigning required roles to $SERVICE_ACCOUNT_NAME...\"\n\nSA_EMAIL=$(gcloud iam service-accounts --project $PROJECT_ID list \\\n  --filter=\"displayName:$SERVICE_ACCOUNT_NAME\" \\\n  --format='value(email)')\n\nGAE_REQUIRED_ROLES=(storage.admin appengine.appAdmin cloudscheduler.admin cloudbuild.serviceAgent cloudtasks.queueAdmin)\nEXISTING_ROLES=$(gcloud projects get-iam-policy --filter bindings.members:$SA_EMAIL $MANAGED_PROJECT_ID \\\n  --flatten bindings[].members --format=\"value(bindings.role)\")\n\nif [ \"$?\" != \"0\" ]; then\n    bold \"$USER does not have permission to query IAM policy on project $MANAGED_PROJECT_ID.\" \\\n         \"Please grant the necessary permissions and re-run this command.\"\n    exit 1\nfi\n\nfor r in \"${GAE_REQUIRED_ROLES[@]}\"; do\n  if [ -z \"$(echo $EXISTING_ROLES | grep $r)\" ]; then\n    bold \"Assigning role $r in project $MANAGED_PROJECT_ID to service account $SA_EMAIL...\"\n    gcloud projects add-iam-policy-binding $MANAGED_PROJECT_ID \\\n      --member serviceAccount:$SA_EMAIL \\\n      --role roles/$r \\\n      --format=none\n\n    if [ \"$?\" != \"0\" ]; then\n      bold \"$USER does not have permission to assign role $r on project $MANAGED_PROJECT_ID.\" \\\n           \"Please grant the necessary permissions and re-run this command.\"\n      exit 1\n    fi\n  fi\ndone\n\n~/hal/hal config provider appengine enable\n~/hal/hal config provider appengine account add $GAE_ACCOUNT_NAME --project $MANAGED_PROJECT_ID\n\nbold \"Remember that your configuration changes have only been made locally.\"\nbold \"They must be pushed and applied to your deployment to take effect:\"\nbold \"  ~/cloudshell_open/spinnaker-for-gcp/scripts/manage/push_and_apply.sh\"\n"
  },
  {
    "path": "scripts/manage/add_gce_account.sh",
    "content": "#!/usr/bin/env bash\n\nbold() {\n  echo \". $(tput bold)\" \"$*\" \"$(tput sgr0)\";\n}\n\nsource ~/cloudshell_open/spinnaker-for-gcp/scripts/install/properties\n\nread -e -p \"Please enter the id of the project within which you wish to manage GCE resources: \" -i $PROJECT_ID MANAGED_PROJECT_ID\nread -e -p \"Please enter a name for the new Spinnaker account: \" -i \"$MANAGED_PROJECT_ID-acct\" GCE_ACCOUNT_NAME\n\nbold \"Assigning required roles to $SERVICE_ACCOUNT_NAME...\"\n\nSA_EMAIL=$(gcloud iam service-accounts --project $PROJECT_ID list \\\n  --filter=\"displayName:$SERVICE_ACCOUNT_NAME\" \\\n  --format='value(email)')\n\nGCE_REQUIRED_ROLES=(compute.instanceAdmin compute.networkAdmin compute.securityAdmin compute.storageAdmin iam.serviceAccountUser)\nEXISTING_ROLES=$(gcloud projects get-iam-policy --filter bindings.members:$SA_EMAIL $MANAGED_PROJECT_ID \\\n  --flatten bindings[].members --format=\"value(bindings.role)\")\n\nif [ \"$?\" != \"0\" ]; then\n    bold \"$USER does not have permission to query IAM policy on project $MANAGED_PROJECT_ID.\" \\\n         \"Please grant the necessary permissions and re-run this command.\"\n    exit 1\nfi\n\nfor r in \"${GCE_REQUIRED_ROLES[@]}\"; do\n  if [ -z \"$(echo $EXISTING_ROLES | grep $r)\" ]; then\n    bold \"Assigning role $r in project $MANAGED_PROJECT_ID to service account $SA_EMAIL...\"\n    gcloud projects add-iam-policy-binding $MANAGED_PROJECT_ID \\\n      --member serviceAccount:$SA_EMAIL \\\n      --role roles/$r \\\n      --format=none\n\n    if [ \"$?\" != \"0\" ]; then\n      bold \"$USER does not have permission to assign role $r on project $MANAGED_PROJECT_ID.\" \\\n           \"Please grant the necessary permissions and re-run this command.\"\n      exit 1\n    fi\n  fi\ndone\n\n~/hal/hal config provider google account add $GCE_ACCOUNT_NAME --project $MANAGED_PROJECT_ID\n~/hal/hal config provider google enable\n\nbold \"Remember that your configuration changes have only been made locally.\"\nbold \"They must be pushed and applied to your deployment to take effect:\"\nbold \"  ~/cloudshell_open/spinnaker-for-gcp/scripts/manage/push_and_apply.sh\"\n"
  },
  {
    "path": "scripts/manage/add_gke_account.sh",
    "content": "#!/usr/bin/env bash\n\nbold() {\n  echo \". $(tput bold)\" \"$*\" \"$(tput sgr0)\";\n}\n\nsource ~/cloudshell_open/spinnaker-for-gcp/scripts/install/properties\n\nCURRENT_K8S_CONTEXT=$(kubectl config current-context)\nAVAILABLE_K8S_CONTEXTS=$(kubectl config get-contexts -o name)\n\necho \"Available contexts:\"\necho \"$AVAILABLE_K8S_CONTEXTS\"\necho\n\nif [ -z $CURRENT_K8S_CONTEXT ]; then\n  read -e -p \"Please enter the context you wish to use to manage your GKE resources: \" TARGET_K8S_CONTEXT\nelse\n  read -e -p \"Please enter the context you wish to use to manage your GKE resources: \" -i $CURRENT_K8S_CONTEXT TARGET_K8S_CONTEXT\nfi\n\nFOUND_CONTEXT=$(echo \"$AVAILABLE_K8S_CONTEXTS\" | grep \"^$TARGET_K8S_CONTEXT$\")\n\nif [ -z $FOUND_CONTEXT ]; then\n  bold \"$TARGET_K8S_CONTEXT not found in available contexts...\"\n  exit 1\nfi\n\nMANAGED_PROJECT_ID=$(echo $TARGET_K8S_CONTEXT | cut -d _ -f 2)\n\nread -e -p \"Please enter the id of the project within which the referenced cluster lives: \" -i $MANAGED_PROJECT_ID MANAGED_PROJECT_ID\nread -e -p \"Please enter a name for the new Spinnaker account: \" -i \"$(echo $TARGET_K8S_CONTEXT | cut -d _ -f 4)-acct\" GKE_ACCOUNT_NAME\n\nbold \"Assigning required roles to $SERVICE_ACCOUNT_NAME...\"\n\nSA_EMAIL=$(gcloud iam service-accounts --project $PROJECT_ID list \\\n  --filter=\"displayName:$SERVICE_ACCOUNT_NAME\" \\\n  --format='value(email)')\n\nGKE_REQUIRED_ROLES=(container.admin)\nEXISTING_ROLES=$(gcloud projects get-iam-policy --filter bindings.members:$SA_EMAIL $MANAGED_PROJECT_ID \\\n  --flatten bindings[].members --format=\"value(bindings.role)\")\n\nif [ \"$?\" != \"0\" ]; then\n    bold \"$USER does not have permission to query IAM policy on project $MANAGED_PROJECT_ID.\" \\\n         \"Please grant the necessary permissions and re-run this command.\"\n    exit 1\nfi\n\nfor r in \"${GKE_REQUIRED_ROLES[@]}\"; do\n  if [ -z \"$(echo $EXISTING_ROLES | grep $r)\" ]; then\n    bold \"Assigning role $r in project $MANAGED_PROJECT_ID to service account $SA_EMAIL...\"\n    gcloud projects add-iam-policy-binding $MANAGED_PROJECT_ID \\\n      --member serviceAccount:$SA_EMAIL \\\n      --role roles/$r \\\n      --format=none\n\n    if [ \"$?\" != \"0\" ]; then\n      bold \"$USER does not have permission to assign role $r on project $MANAGED_PROJECT_ID.\" \\\n           \"Please grant the necessary permissions and re-run this command.\"\n      exit 1\n    fi\n  fi\ndone\n\nmkdir -p ~/.hal/default/credentials\nKUBECONFIG_FILENAME=\"kubeconfig-$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 9 | head -n 1)\"\n\nbold \"Copying ~/.kube/config into ~/.hal/default/credentials/$KUBECONFIG_FILENAME so it can be pushed to your halyard daemon's pod...\"\n\ncp ~/.kube/config ~/.hal/default/credentials/$KUBECONFIG_FILENAME\n\n~/hal/hal config provider kubernetes account add $GKE_ACCOUNT_NAME \\\n  --provider-version v2 \\\n  --context $TARGET_K8S_CONTEXT \\\n  --kubeconfig-file ~/.hal/default/credentials/$KUBECONFIG_FILENAME\n\nbold \"Remember that your configuration changes have only been made locally.\"\nbold \"They must be pushed and applied to your deployment to take effect:\"\nbold \"  ~/cloudshell_open/spinnaker-for-gcp/scripts/manage/push_and_apply.sh\"\n"
  },
  {
    "path": "scripts/manage/add_missing_properties.sh",
    "content": "#!/usr/bin/env bash\n\nbold() {\n  echo \". $(tput bold)\" \"$*\" \"$(tput sgr0)\";\n}\n\n[ -z \"$PARENT_DIR\" ] && PARENT_DIR=$(dirname $(realpath $0) | rev | cut -d '/' -f 4- | rev)\n\nPROPERTIES_FILE=$PARENT_DIR/spinnaker-for-gcp/scripts/install/properties\n\nadd_property_if_missing() {\n  if [ -z \"$(grep \"export $1=\" $PROPERTIES_FILE)\" ]; then\n    bold \"Adding declaration of $1 to $PROPERTIES_FILE...\"\n\n    echo >> $PROPERTIES_FILE\n    echo \"$2\" >> $PROPERTIES_FILE\n  fi\n}\n\n\nread -r -d '' CSR_PROPERTY_DECLARATION  <<EOL\n# If CSR repo does not exist, it will be created.\nexport CONFIG_CSR_REPO=\\$DEPLOYMENT_NAME-config\nEOL\n\nadd_property_if_missing CONFIG_CSR_REPO \"$CSR_PROPERTY_DECLARATION\"\n\nadd_property_if_missing NETWORK_PROJECT \"export NETWORK_PROJECT=\\$PROJECT_ID\"\nadd_property_if_missing NETWORK_REFERENCE \"export NETWORK_REFERENCE=projects/\\$NETWORK_PROJECT/global/networks/\\$NETWORK\"\nadd_property_if_missing SUBNET_REFERENCE \"export SUBNET_REFERENCE=projects/\\$NETWORK_PROJECT/regions/\\$REGION/subnetworks/\\$SUBNET\"\n"
  },
  {
    "path": "scripts/manage/apply_config.sh",
    "content": "#!/usr/bin/env bash\n\nHALYARD_POD=spin-halyard-0\n\n# TODO(duftler): Use --wait-for-completion?\nkubectl exec $HALYARD_POD -n halyard -- bash -c 'hal deploy apply'\n\n~/cloudshell_open/spinnaker-for-gcp/scripts/manage/deploy_application_manifest.sh\n"
  },
  {
    "path": "scripts/manage/check_cluster_config.sh",
    "content": "#!/usr/bin/env bash\n\n# The logic is roughly as follows:\n#   Check each configured context that is in the specified project for a Spinnaker deployment.\n#   If there are no matching contexts that contain a deployment, retrieve credentials for all\n#     of the specified project's clusters and check each of the newly-configured contexts for\n#     a Spinnaker deployment.\n#   If there is exactly one matching context, and it contains a deployment, set that as the current context.\n#   Otherwise, generate a 'kubectl config use-context' command for each matching context that contains a\n#     deployment (and indicate if one of them is already set as the current context).\n\nbold() {\n  echo \". $(tput bold)\" \"$*\" \"$(tput sgr0)\";\n}\n\nif [ -z \"$PROJECT_ID\" ]; then\n  PROJECT_ID=$(gcloud info --format='value(config.project)')\nfi\n\nif [ -z \"$PROJECT_ID\" ]; then\n  echo \"PROJECT_ID must be specified.\"\n  exit 1\nfi\n\ncheck_for_spinnaker_deployment() {\n  if [ \"$1\" == \"$CURRENT_CONTEXT\" ]; then\n    CURRENT_CONTEXT_MATCH=\" (CURRENT CONTEXT)\"\n  else\n    unset CURRENT_CONTEXT_MATCH\n  fi\n\n  PROJECT_CONTAINING_CLUSTER=$(echo $1 | cut -d _ -f 2)\n\n  if [ \"$PROJECT_CONTAINING_CLUSTER\" == \"$PROJECT_ID\" ]; then\n    bold \"Checking for Spinnaker deployment in Kubernetes context $1...\"\n\n    SPINNAKER_APPLICATION_LIST_JSON=$(kubectl get applications -n spinnaker -l app.kubernetes.io/name=spinnaker --output json --context $1)\n    SPINNAKER_APPLICATION_COUNT=$(echo $SPINNAKER_APPLICATION_LIST_JSON | jq '.items | length')\n\n    if [ \"$SPINNAKER_APPLICATION_COUNT\" == \"0\" ] || [ -z $SPINNAKER_APPLICATION_COUNT ]; then\n      bold \"No Spinnaker deployment was found via context $1$CURRENT_CONTEXT_MATCH.\"\n    elif [ \"$SPINNAKER_APPLICATION_COUNT\" == \"1\" ]; then\n      bold \"Found Spinnaker deployment $(echo $SPINNAKER_APPLICATION_LIST_JSON | jq -r .items[0].metadata.name) via context $1$CURRENT_CONTEXT_MATCH.\"\n\n      FOUND_MATCH_IN_PROJECT=true\n\n      if [ $2 ] && [ -z \"$CURRENT_CONTEXT_MATCH\" ]; then\n        bold \"You can select this context using: kubectl config use-context $1\"\n      fi\n\n      if [ $3 ]; then\n        kubectl config use-context $1\n      fi\n    else\n      bold \"Multiple Spinnaker deployments were found in cluster $1. This should never be the case.\"\n\n      if [ \"$CURRENT_CONTEXT_MATCH\" ]; then\n        clear_current_context\n      fi\n\n      exit 1\n    fi\n  else\n    # The context is not from the specified project.\n\n    if [ \"$CURRENT_CONTEXT_MATCH\" ]; then\n      clear_current_context\n    fi\n  fi\n}\n\nclear_current_context() {\n  sed -i\"\" -e\"s/^current-context:.*$/current-context:/\" ~/.kube/config\n}\n\ncheck_all_contexts() {\n  if [ \"$CONTEXT_COUNT\" == \"1\" ]; then\n    # Since there is exactly one context configured, we'll set that as the current context (if a deployment is found).\n    check_for_spinnaker_deployment $CONTEXT_LIST \"\" \"select_context\"\n  else\n    CONTEXT_LIST=($CONTEXT_LIST)\n\n    # Since there are multiple contexts configured, we will just query for Spinnaker deployments and generate\n    # commands that can be used to select a current context.\n    for c in \"${CONTEXT_LIST[@]}\"; do\n      check_for_spinnaker_deployment $c \"generate_command\"\n    done\n  fi\n}\n\nquery_configured_contexts() {\n  CONTEXT_LIST=$(kubectl config get-contexts -o name)\n  CONTEXT_COUNT=$(echo \"$CONTEXT_LIST\" | sed '/^$/d' | wc -l)\n}\n\nget_all_project_cluster_credentials() {\n  bold \"Querying for GKE clusters in project $PROJECT_ID...\"\n\n  CLUSTER_LIST=$(gcloud beta container clusters list --format json --project $PROJECT_ID)\n  CLUSTER_COUNT=$(echo $CLUSTER_LIST | jq '. | length')\n\n  if [ \"$CLUSTER_COUNT\" == \"0\" ]; then\n    bold \"No GKE clusters were found in project $PROJECT_ID.\"\n    exit 1\n  fi\n\n  for (( i=0; i<$CLUSTER_COUNT; i++ )); do\n    # TODO: Determine implications of encountering non-zonal cluster here.\n    bold \"Retrieving credentials from project $PROJECT_ID for cluster\" \\\n      \"$(echo $CLUSTER_LIST | jq -r \".[$i].name\")\" \\\n      \"in zone $(echo $CLUSTER_LIST | jq -r \".[$i].zone\")...\"\n\n    gcloud container clusters get-credentials $(echo $CLUSTER_LIST | jq -r \".[$i].name\") \\\n      --zone $(echo $CLUSTER_LIST | jq -r \".[$i].zone\") --project $PROJECT_ID\n  done\n\n  # Removing current context since gcloud container clusters get-credentials implicitly sets it.\n  # We want to avoid this behavior since there may be multiple clusters in this project.\n  # If there is exactly one cluster, the context will be automatically selected in the next steps anyway.\n  clear_current_context\n}\n\nbold \"Querying for current Kubernetes context...\"\n\nCURRENT_CONTEXT=$(kubectl config current-context)\n\nquery_configured_contexts\n\nif [ \"$CONTEXT_COUNT\" != \"0\" ]; then\n  check_all_contexts\nfi\n\nif [ -z \"$FOUND_MATCH_IN_PROJECT\" ]; then\n  get_all_project_cluster_credentials\n  query_configured_contexts\n  check_all_contexts\nfi\n"
  },
  {
    "path": "scripts/manage/check_duplicate_dirs.sh",
    "content": "#!/usr/bin/env bash\n\nbold() {\n  echo \"$(tput bold)\"\"$*\" \"$(tput sgr0)\";\n}\n\nif [ -d \"$HOME/cloudshell_open\" ]; then\n  MATCHING_REPO_DIRS=$(find ~/cloudshell_open -maxdepth 1 -regex '.*/spinnaker-for-gcp-.+')\n\n  if [ \"$MATCHING_REPO_DIRS\" ]; then\n    NUM_EXTRANEOUS_DIRS=$(echo \"$MATCHING_REPO_DIRS\" | wc -l)\n\n    bold \"It looks like you might have cloned the spinnaker-for-gcp repository into\" \\\n         \"more than one directory. If you have any directories other than\" \\\n         \"$HOME/cloudshell_open/spinnaker-for-gcp that contain the repo, delete\" \\\n         \"them in order to avoid unwanted behavior.\"\n    bold \"If you have any directory that starts with spinnaker-for-gcp-*, even if\" \\\n         \"it doesn't contain a clone of the repo, you have to delete or move that\" \\\n         \"in order for this script to run.\"\n    bold \"Conflicting directories:\"\n    bold \"$MATCHING_REPO_DIRS\"\n    bold \"All Spinnaker for GCP commands are required to be run within the\" \\\n         \"~/cloudshell_open/spinnaker-for-gcp directory.\"\n\n    exit 1\n  fi\nfi\n\nif [ -d \"$HOME/spinnaker-for-gcp\" ]; then\n  bold \"It looks like the spinnaker-for-gcp repository was cloned into\" \\\n       \"~/spinnaker-for-gcp. The current target location for the cloned repo\" \\\n       \"is ~/cloudshell_open/spinnaker-for-gcp. If you have any directories other\" \\\n       \"than $HOME/cloudshell_open/spinnaker-for-gcp that contain the repo,\" \\\n       \"delete them in order to avoid unwanted behavior.\"\n  bold \"All Spinnaker for GCP commands are required to be run within the\" \\\n       \"~/cloudshell_open/spinnaker-for-gcp directory.\"\n  bold \"The easiest way to resolve this is to:\"\n  bold \"  - Delete the ~/spinnaker-for-gcp directory.\"\n  bold \"  - Exit out of Cloud Shell.\"\n  bold \"  - Click once more on the link you used to reach Cloud Shell (might be in\" \\\n       \"GCP Marketplace, might be in the Application details view in GKE).\"\n\n  exit 1\nfi\n"
  },
  {
    "path": "scripts/manage/check_git_config.sh",
    "content": "[ -z \"$PARENT_DIR\" ] && PARENT_DIR=$(dirname $(realpath $0) | rev | cut -d '/' -f 4- | rev)\n\nsource $PARENT_DIR/spinnaker-for-gcp/scripts/manage/service_utils.sh\n\nGIT_USERNAME=$(git config --global --get user.name)\nGIT_EMAIL=$(git config --global --get user.email)\n\nif [ -z \"$GIT_USERNAME\" ]; then\n  bold \"Your Git account username is not set. Run 'git config --global user.name \\\"Your Name\\\"' and try again.\"\n  exit 1\nfi\n\nif [ -z \"$GIT_EMAIL\" ]; then\n  bold \"Your Git account email is not set. Run 'git config --global user.email \\\"you@example.com\\\"' and try again.\"\n  exit 1\nfi\n"
  },
  {
    "path": "scripts/manage/check_project_mismatch.sh",
    "content": "#!/usr/bin/env bash\n\n[ -z \"$PARENT_DIR\" ] && PARENT_DIR=$(dirname $(realpath $0) | rev | cut -d '/' -f 4- | rev)\n\nsource $PARENT_DIR/spinnaker-for-gcp/scripts/manage/service_utils.sh\n\n[ -z \"$PROPERTIES_FILE\" ] && PROPERTIES_FILE=\"$PARENT_DIR/spinnaker-for-gcp/scripts/install/properties\"\n\nsource \"$PROPERTIES_FILE\"\n\nGCLOUD_PROJECT_ID=$(gcloud info --format='value(config.project)')\nGCLOUD_PROJECT_ID=${GCLOUD_PROJECT_ID:-'not set'}\n\nif [ \"$GCLOUD_PROJECT_ID\" != $PROJECT_ID ]; then\n  gcloud config set project $PROJECT_ID\n\n  bold \"Your Spinnaker config references GCP project id $PROJECT_ID, but your gcloud default project id was $GCLOUD_PROJECT_ID.\"\n  bold \"For safety when executing gcloud commands, 'gcloud config set project $PROJECT_ID' has been used to change the gcloud default.\"\nfi\n"
  },
  {
    "path": "scripts/manage/cluster_utils.sh",
    "content": "#!/usr/bin/env bash\n\n[ -z \"$PARENT_DIR\" ] && PARENT_DIR=$(dirname $(realpath $0) | rev | cut -d '/' -f 4- | rev)\n\nsource $PARENT_DIR/spinnaker-for-gcp/scripts/manage/service_utils.sh\n\ncheck_for_existing_cluster() {\n  bold \"Checking for existing cluster $GKE_CLUSTER...\" >&2\n\n  CLUSTER_EXISTS=$(gcloud container clusters list --project $PROJECT_ID \\\n    --filter=\"name=$GKE_CLUSTER\" \\\n    --format=\"value(name)\")\n\n  echo $CLUSTER_EXISTS\n}\n\ncheck_existing_cluster_location() {\n  bold \"Verifying location of existing cluster $GKE_CLUSTER...\"\n\n  # Query for cluster in specified zone, just in case there are multiple clusters with the same name.\n  CLUSTER_EXISTS_IN_SPECIFIED_ZONE=$(gcloud container clusters list --project $PROJECT_ID \\\n    --zone=$ZONE \\\n    --filter=\"name=$GKE_CLUSTER\" \\\n    --format=\"value(location)\")\n\n  # If it's not in the specified zone, figure out where exactly it is.\n  if [ -z \"$CLUSTER_EXISTS_IN_SPECIFIED_ZONE\" ]; then\n    EXISTING_CLUSTER_LOCATION=$(gcloud container clusters list --project $PROJECT_ID \\\n      --filter=\"name=$GKE_CLUSTER\" \\\n      --format=\"value(location)\")\n\n    LOCATION_IS_REGION=$(gcloud compute regions list --project $PROJECT_ID \\\n      --filter=\"name=$EXISTING_CLUSTER_LOCATION\" \\\n      --format=\"value(name)\")\n\n    if [ -n \"$LOCATION_IS_REGION\" ]; then\n      bold \"Your pre-existing cluster $GKE_CLUSTER is regional; we do not support regional clusters.\"\n      exit 1\n    fi\n  fi\n}\n\ncheck_existing_cluster_prereqs() {\n  EXISTING_CLUSTER_DESCRIPTION=$(gcloud container clusters describe $GKE_CLUSTER --zone $ZONE --format json)\n  IP_ALIASES_ENABLED=$(echo $EXISTING_CLUSTER_DESCRIPTION | jq .ipAllocationPolicy.useIpAliases)\n\n  if [ \"$IP_ALIASES_ENABLED\" != \"true\" ]; then\n    bold \"Your pre-existing cluster must have IP Aliases enabled.\"\n    exit 1\n  fi\n\n  NODE_CONFIG_SERVICE_ACCOUNT=$(echo $EXISTING_CLUSTER_DESCRIPTION | jq -r .nodeConfig.serviceAccount)\n\n  # If using the \"Compute Engine default service account\", Full Cloud Platform scope is required for its nodes.\n  if [ \"$NODE_CONFIG_SERVICE_ACCOUNT\" == \"default\" ]; then\n    NODES_HAVE_CLOUD_PLATFORM_SCOPE=$(echo $EXISTING_CLUSTER_DESCRIPTION | \\\n      jq '[.nodeConfig.oauthScopes[] == \"https://www.googleapis.com/auth/cloud-platform\"] | any')\n\n    if [ \"$NODES_HAVE_CLOUD_PLATFORM_SCOPE\" != \"true\" ]; then\n      bold \"Your pre-existing cluster is using the \\\"Compute Engine default service account\\\". As such,\" \\\n        \"your nodes must have Full Cloud Platform scope.\"\n      bold \"In general, we recommend using an IAM-backed service account instead. An IAM-backed service\" \\\n        \"account will be assigned the required roles during the Spinnaker for GCP setup process.\"\n      exit 1\n    fi\n  fi\n}\n"
  },
  {
    "path": "scripts/manage/connect_to_redis.sh",
    "content": "#!/usr/bin/env bash\n\nbold() {\n  echo \". $(tput bold)\" \"$*\" \"$(tput sgr0)\";\n}\n\nsource ~/cloudshell_open/spinnaker-for-gcp/scripts/install/properties\n\n~/cloudshell_open/spinnaker-for-gcp/scripts/manage/check_project_mismatch.sh\n\nbold \"Resolving redis host...\"\n\nexport REDIS_INSTANCE_HOST=$(gcloud redis instances list \\\n  --project $PROJECT_ID --region $REGION \\\n  --filter=\"name=projects/$PROJECT_ID/locations/$REGION/instances/$REDIS_INSTANCE\" \\\n  --format=\"value(host)\")\n\nbold \"Locating redis-cli deployment...\"\n\nREDIS_CLI_DEPLOYMENT=$(kubectl get deployments -n spinnaker --field-selector metadata.name=redisbox \\\n  --output name)\n\nif [ -z $REDIS_CLI_DEPLOYMENT ]; then\n  bold \"Deploying redis-cli...\"\n\n  kubectl run redisbox --image=gcr.io/google_containers/redis:v1 -n spinnaker\nfi\n\nbold \"Waiting for redis-cli deployment to become available...\"\n\nkubectl wait --for condition=available deployment redisbox -n spinnaker\n\nbold \"Locating redis-cli pod...\"\n\nREDIS_CLI_POD=$(kubectl get pods -n spinnaker -l run=redisbox \\\n  -o=jsonpath='{.items[0].metadata.name}')\n\nbold \"Connecting to redis-cli pod and specifying redis host $REDIS_INSTANCE_HOST...\"\n\nkubectl exec -it $REDIS_CLI_POD -n spinnaker -- redis-cli -h $REDIS_INSTANCE_HOST\n\nbold \"Deleting redis-cli deployment...\"\n\nkubectl delete deployment redisbox -n spinnaker\n"
  },
  {
    "path": "scripts/manage/connect_unsecured.sh",
    "content": "#!/usr/bin/env bash\n\nbold() {\n  echo \". $(tput bold)\" \"$*\" \"$(tput sgr0)\";\n}\n\nsource ~/cloudshell_open/spinnaker-for-gcp/scripts/install/properties\n\n~/cloudshell_open/spinnaker-for-gcp/scripts/manage/check_project_mismatch.sh\n\nbold \"Locating Deck pod...\"\n\nDECK_POD=$(kubectl -n spinnaker get pods -l cluster=spin-deck,app=spin \\\n  -o=jsonpath='{.items[0].metadata.name}')\n\nbold \"Forwarding localhost port 8080 to 9000 on $DECK_POD...\"\n\npkill -f 'kubectl -n spinnaker port-forward'\nkubectl -n spinnaker port-forward $DECK_POD 8080:9000 > /dev/null 2>&1 &\n\n# Query for static ip address as a signal that the Spinnaker installation is exposed via a secured endpoint.\nexport IP_ADDR=$(gcloud compute addresses list --filter=\"name=$STATIC_IP_NAME\" \\\n  --format=\"value(address)\" --global --project $PROJECT_ID)\n\nif [ \"$IP_ADDR\" ]; then\n  bold \"Are you sure you aren't intending to connect via the domain name instead? Asking since you have a static ip configured...\"\nfi\n"
  },
  {
    "path": "scripts/manage/deploy_application_manifest.sh",
    "content": "#!/usr/bin/env bash\n\n[ -z \"$PARENT_DIR\" ] && PARENT_DIR=$(dirname $(realpath $0) | rev | cut -d '/' -f 4- | rev)\n\nsource $PARENT_DIR/spinnaker-for-gcp/scripts/manage/service_utils.sh\n\n[ -z \"$PROPERTIES_FILE\" ] && PROPERTIES_FILE=\"$PARENT_DIR/spinnaker-for-gcp/scripts/install/properties\"\n\nif [ ! -f \"$PROPERTIES_FILE\" ]; then\n  bold \"No properties file was found. Not updating GKE Application details view.\"\n  git checkout -- $PARENT_DIR/spinnaker-for-gcp/scripts/manage/landing_page_expanded.md\n  exit 0\nfi\n\nsource \"$PROPERTIES_FILE\"\n\n# Query for static ip address as a signal that the Spinnaker installation is exposed via a secured endpoint.\nexport IP_ADDR=$(gcloud compute addresses list --filter=\"name=$STATIC_IP_NAME\" \\\n  --format=\"value(address)\" --global --project $PROJECT_ID)\n\nif [ -z \"$IP_ADDR\" ]; then\n  APP_MANIFEST_MIDDLE=spinnaker_application_manifest_middle_unsecured.yaml\nelse\n  APP_MANIFEST_MIDDLE=spinnaker_application_manifest_middle_secured.yaml\nfi\n\nkubectl apply -f \"https://raw.githubusercontent.com/GoogleCloudPlatform/marketplace-k8s-app-tools/master/crd/app-crd.yaml\"\ncat $PARENT_DIR/spinnaker-for-gcp/templates/spinnaker_application_manifest_top.yaml \\\n  $PARENT_DIR/spinnaker-for-gcp/templates/$APP_MANIFEST_MIDDLE \\\n  $PARENT_DIR/spinnaker-for-gcp/templates/spinnaker_application_manifest_bottom.yaml \\\n  | envsubst | kubectl apply -f -\n\nbold \"Labeling resources as components of application $DEPLOYMENT_NAME...\"\nkubectl label service --overwrite -n spinnaker spin-clouddriver app.kubernetes.io/name=$DEPLOYMENT_NAME -o name\nkubectl label service --overwrite -n spinnaker spin-deck app.kubernetes.io/name=$DEPLOYMENT_NAME -o name\nkubectl label service --overwrite -n spinnaker spin-echo app.kubernetes.io/name=$DEPLOYMENT_NAME -o name\nkubectl label service --overwrite -n spinnaker spin-front50 app.kubernetes.io/name=$DEPLOYMENT_NAME -o name\nkubectl label service --overwrite -n spinnaker spin-gate app.kubernetes.io/name=$DEPLOYMENT_NAME -o name\nkubectl label service --overwrite -n spinnaker spin-igor app.kubernetes.io/name=$DEPLOYMENT_NAME -o name\nkubectl label service --overwrite -n spinnaker spin-kayenta app.kubernetes.io/name=$DEPLOYMENT_NAME -o name\nkubectl label service --overwrite -n spinnaker spin-orca app.kubernetes.io/name=$DEPLOYMENT_NAME -o name\nkubectl label service --overwrite -n spinnaker spin-rosco app.kubernetes.io/name=$DEPLOYMENT_NAME -o name\n\nkubectl label deployment --overwrite -n spinnaker spin-clouddriver app.kubernetes.io/name=$DEPLOYMENT_NAME -o name\nkubectl label deployment --overwrite -n spinnaker spin-deck app.kubernetes.io/name=$DEPLOYMENT_NAME -o name\nkubectl label deployment --overwrite -n spinnaker spin-echo app.kubernetes.io/name=$DEPLOYMENT_NAME -o name\nkubectl label deployment --overwrite -n spinnaker spin-front50 app.kubernetes.io/name=$DEPLOYMENT_NAME -o name\nkubectl label deployment --overwrite -n spinnaker spin-gate app.kubernetes.io/name=$DEPLOYMENT_NAME -o name\nkubectl label deployment --overwrite -n spinnaker spin-igor app.kubernetes.io/name=$DEPLOYMENT_NAME -o name\nkubectl label deployment --overwrite -n spinnaker spin-kayenta app.kubernetes.io/name=$DEPLOYMENT_NAME -o name\nkubectl label deployment --overwrite -n spinnaker spin-orca app.kubernetes.io/name=$DEPLOYMENT_NAME -o name\nkubectl label deployment --overwrite -n spinnaker spin-rosco app.kubernetes.io/name=$DEPLOYMENT_NAME -o name\n"
  },
  {
    "path": "scripts/manage/generate_deletion_script.sh",
    "content": "#!/usr/bin/env bash\n\nbold() {\n  echo \". $(tput bold)\" \"$*\" \"$(tput sgr0)\";\n}\n\nsource ~/cloudshell_open/spinnaker-for-gcp/scripts/install/properties\n\nbold \"Generating deletion script for $DEPLOYMENT_NAME in cluster $GKE_CLUSTER of project $PROJECT_ID...\"\n\n~/cloudshell_open/spinnaker-for-gcp/scripts/manage/check_project_mismatch.sh\n\nDELETION_SCRIPT_FILENAME=\"$HOME/cloudshell_open/spinnaker-for-gcp/scripts/manage/delete-all_${PROJECT_ID}_${GKE_CLUSTER}_${DEPLOYMENT_NAME}.sh\"\n\nSA_EMAIL=$(gcloud iam service-accounts --project $PROJECT_ID list \\\n  --filter=\"displayName:$SERVICE_ACCOUNT_NAME\" \\\n  --format='value(email)')\n\ncat > $DELETION_SCRIPT_FILENAME <<EOL\n#!/usr/bin/env bash\n\n# Ensure that you comment out the deletion commands for resources you'd rather not delete.\n\nbold() {\n  echo \". \\$(tput bold)\" \"\\$*\" \"\\$(tput sgr0)\";\n}\n\nbold \"Deleting cluster $GKE_CLUSTER in $PROJECT_ID...\"\ngcloud container clusters delete $GKE_CLUSTER --zone $ZONE --project $PROJECT_ID\n\nbold \"Deleting bucket $BUCKET_URI...\"\ngsutil rm -r $BUCKET_URI\n\nbold \"Deleting Cloud Source Repository $CONFIG_CSR_REPO...\"\ngcloud source repos delete $CONFIG_CSR_REPO --project=$PROJECT_ID\n\nbold \"Deleting subscription $GCR_PUBSUB_SUBSCRIPTION in $PROJECT_ID...\"\ngcloud pubsub subscriptions delete $GCR_PUBSUB_SUBSCRIPTION --project $PROJECT_ID\n\nbold \"Deleting subscription $GCB_PUBSUB_SUBSCRIPTION in $PROJECT_ID...\"\ngcloud pubsub subscriptions delete $GCB_PUBSUB_SUBSCRIPTION --project $PROJECT_ID\n\nbold \"Deleting cloud function $CLOUD_FUNCTION_NAME in $PROJECT_ID...\"\ngcloud functions delete $CLOUD_FUNCTION_NAME --region $REGION --project $PROJECT_ID\n\nbold \"Deleting redis instance $REDIS_INSTANCE in $NETWORK_PROJECT...\"\ngcloud redis instances delete $REDIS_INSTANCE --region $REGION --project $NETWORK_PROJECT\nEOL\n\nif [ \"$SA_EMAIL\" ]; then\n  EXISTING_ROLES=($(gcloud projects get-iam-policy --filter bindings.members:$SA_EMAIL $PROJECT_ID \\\n    --flatten bindings[].members --format=\"value(bindings.role)\"))\n\n  for r in \"${EXISTING_ROLES[@]}\"; do\n    cat >> $DELETION_SCRIPT_FILENAME <<EOL\n\nbold \"Deleting IAM policy binding for role $r from $SA_EMAIL in $PROJECT_ID...\"\ngcloud projects remove-iam-policy-binding $PROJECT_ID --member serviceAccount:$SA_EMAIL --role $r\nEOL\n  done\n\n  cat >> $DELETION_SCRIPT_FILENAME <<EOL\n\nbold \"Deleting service account $SA_EMAIL in $PROJECT_ID...\"\ngcloud iam service-accounts delete $SA_EMAIL --project $PROJECT_ID\nEOL\nfi\n\n# Query for static ip address as a signal that the Spinnaker installation is exposed via a secured endpoint.\nexport IP_ADDR=$(gcloud compute addresses list --filter=\"name=$STATIC_IP_NAME\" \\\n  --format=\"value(address)\" --global --project $PROJECT_ID)\n\nif [ \"$IP_ADDR\" ]; then\n\n  cat >> $DELETION_SCRIPT_FILENAME <<EOL\n\nbold \"Deleting static IP address $STATIC_IP_NAME in $PROJECT_ID...\"\ngcloud compute addresses delete $STATIC_IP_NAME --global --project $PROJECT_ID\n\nbold \"Deleting managed SSL certificate $MANAGED_CERT in project $PROJECT_ID...\"\ngcloud beta compute ssl-certificates delete $MANAGED_CERT --global --project $PROJECT_ID\n\nbold \"Deleting service endpoint $DOMAIN_NAME in project $PROJECT_ID...\"\ngcloud endpoints services delete $DOMAIN_NAME --project $PROJECT_ID\n\nbold \"Ensure that you manually delete your OAuth Client ID here: https://console.developers.google.com/apis/credentials?project=$PROJECT_ID\"\nEOL\nfi\n\nchmod +x $DELETION_SCRIPT_FILENAME\n\necho\nbold \"Use this command to delete all the resources that were provisioned as part of your Spinnaker installation:\"\nbold \"  $DELETION_SCRIPT_FILENAME\"\n\necho\nbold \"Warning: If you installed Spinnaker on pre-existing infrastructure (GKE cluster, Redis, service accounts, ...),\" \\\n     \"this script deletes them. If you want to keep them, edit the generated cleanup script $DELETION_SCRIPT_FILENAME\" \\\n     \"to comment out the specific deletion commands for items you want to keep:\"\nbold \"  cloudshell edit $DELETION_SCRIPT_FILENAME\"\n"
  },
  {
    "path": "scripts/manage/grant_iap_access.sh",
    "content": "#!/usr/bin/env bash\n\nbold() {\n  echo \". $(tput bold)\" \"$*\" \"$(tput sgr0)\";\n}\n\necho \"Please enter the member you wish to grant the 'IAP-secured Web App User' role.\"\necho \"Note that you must include the correct prefix depending on the type of member.\"\necho \"These are the supported types: \"\necho \"  user:some-user@somedomain.net, serviceAccount:some-service-account@some-project.iam.gserviceaccount.com, group:some-group@somedomain.net, domain:somedomain.net\"\nread -p \"Member to add: \" MEMBER_TO_ADD\necho\n\npushd ~/cloudshell_open/spinnaker-for-gcp/scripts/install\n\nsource ./properties\n\n~/cloudshell_open/spinnaker-for-gcp/scripts/manage/check_project_mismatch.sh\n\nsource ~/cloudshell_open/spinnaker-for-gcp/scripts/expose/set_iap_properties.sh\n\ngcurl() {\n  curl -s -H \"Authorization:Bearer $(gcloud auth print-access-token)\" \\\n    -H \"Content-Type: application/json\" -H \"Accept: application/json\" \\\n    -H \"X-Goog-User-Project: $PROJECT_ID\" $*\n}\n\nbold \"Querying for existing IAM policy...\"\n\nexport EXISTING_IAM_POLICY=$(gcurl -X POST -d \"{\"options\":{\"requested_policy_version\":3}}\" \\\n  https://iap.googleapis.com/v1beta1/projects/$PROJECT_NUMBER/iap_web/compute/services/$BACKEND_SERVICE_ID:getIamPolicy)\n\nif [ \"$(echo $EXISTING_IAM_POLICY | grep \"\\\"$MEMBER_TO_ADD\\\"\")\" ]; then\n  bold \"Member $MEMBER_TO_ADD already has the 'IAP-secured Web App User' role.\"\n  exit 1\nfi\n\nUPDATED_IAM_POLICY=$(echo \"{}\" \\\n  | jq --argjson existing_policy \"$EXISTING_IAM_POLICY\" '. += {\"policy\":$existing_policy}' \\\n  | jq \".policy.bindings[0].members += [\\\"$MEMBER_TO_ADD\\\"]\")\n\nbold \"Granting member $MEMBER_TO_ADD the 'IAP-secured Web App User' role...\"\n\necho $UPDATED_IAM_POLICY | gcurl -X POST -d @- \\\n  https://iap.googleapis.com/v1beta1/projects/$PROJECT_NUMBER/iap_web/compute/services/$BACKEND_SERVICE_ID:setIamPolicy\n\npopd\n"
  },
  {
    "path": "scripts/manage/instructions.txt",
    "content": "\n+------------------------------------------------------------------------------------------+\n|                                                                                          |\n| To reopen the ongoing management instructions in the right-hand pane at any time, enter: |\n|                                                                                          |\n| ~/cloudshell_open/spinnaker-for-gcp/scripts/manage/update_console.sh                     |\n|                                                                                          |\n+------------------------------------------------------------------------------------------+\n\n"
  },
  {
    "path": "scripts/manage/landing_page_base.md",
    "content": "# Manage Spinnaker\n\nUse this section to manage your Spinnaker deployment going forward.\n\n## Select GCP project\n\nSelect the project in which your Spinnaker is installed, then click **Start**.\n\n<walkthrough-project-billing-setup>\n</walkthrough-project-billing-setup>\n\n## Manage Spinnaker via Halyard from Cloud Shell\n\nThis management environment lets you run [Halyard\ncommands](https://www.spinnaker.io/reference/halyard/) to configure and manage\nyour Spinnaker installation.\n\n### Ensure you are connected to the correct Kubernetes context\n\n```bash\nPROJECT_ID={{project-id}} ~/cloudshell_open/spinnaker-for-gcp/scripts/manage/check_cluster_config.sh\n```\n\n### Pull Spinnaker config\n\nPaste and run this command to pull the configuration from your Spinnaker\ndeployment into your Cloud Shell.\n\n```bash\n~/cloudshell_open/spinnaker-for-gcp/scripts/manage/pull_config.sh\n```\n\n### Update this console\n\n**This is required if you've just pulled config from a different Spinnaker deployment.**\n\nThis command refreshes the contents of the right-hand pane, including details on how\nto connect to Spinnaker.\n\n```bash\n~/cloudshell_open/spinnaker-for-gcp/scripts/manage/update_console.sh\n```\n\n### Configure Spinnaker via Halyard\n\nAll [halyard](https://www.spinnaker.io/reference/halyard/commands/) commands are available.\n\n```bash\nhal config\n```\n\nAs with provisioning Spinnaker, don't use `hal deploy connect` when managing\nSpinnaker. Also, don't use `hal deploy apply`. Instead, use the `push_and_apply.sh`\ncommand shown below.\n\n### Notes on Halyard commands that reference local files\n\nIf you add a Kubernetes account that references a kubeconfig file, that file must live within\nthe '`~/.hal/default/credentials`' directory on your Cloud Shell VM. The\nkubeconfig is specified using the `--kubeconfig-file` argument to the\n`hal config provider kubernetes account add` and ...`edit` commands.\n\nA similar requirement applies for any other local file referenced from your halyard config,\nincluding Google JSON key files specified via the `--json-path` argument to various commands.\nThese files must live within '`~/.hal/default/credentials`' or '`~/.hal/default/profiles`'.\n\n### Push and apply updated config to Spinnaker deployment\n\nIf you change any of the configuration, paste and run this command to push\nand apply those changes to your Spinnaker deployment.\n\n```bash\n~/cloudshell_open/spinnaker-for-gcp/scripts/manage/push_and_apply.sh\n```\n\n## Included command-line tools\n\n### Halyard CLI\n\nThe [Halyard CLI](https://www.spinnaker.io/reference/halyard/) (`hal`) and\ndaemon are installed in your Cloud Shell.\n\nIf you want to use a specific version of Halyard, use:\n\n```bash\n~/cloudshell_open/spinnaker-for-gcp/scripts/cli/install_hal.sh --version $HALYARD_VERSION\n```\n\nIf you want to upgrade to the latest version of Halyard, use:\n\n```bash\n~/cloudshell_open/spinnaker-for-gcp/scripts/cli/update_hal.sh\n```\n\n### Spinnaker CLI\n\nThe [Spinnaker CLI](https://www.spinnaker.io/guides/spin/app/) \n(`spin`) is installed in your Cloud Shell.\n\nIf you want to upgrade to the latest version, use:\n\n```bash\n~/cloudshell_open/spinnaker-for-gcp/scripts/cli/install_spin.sh\n```\n\n## Scripts for Common Commands\n\nRemember that any configuration changes you make locally (e.g. adding\naccounts) must be pushed and applied to your deployment to take effect:\n\n```bash\n~/cloudshell_open/spinnaker-for-gcp/scripts/manage/push_and_apply.sh\n```\n\n### Add Spinnaker account for GKE\n\nThis script grants the required\n[IAM roles](https://cloud.google.com/kubernetes-engine/docs/how-to/iam) to the\nSpinnaker instance's service account, in the GCP project containing the referenced\ncluster.\n\nBefore you run this command, make sure you've configured the context you intend\nto use to manage your GKE resources.\n\nThe public Spinnaker documentation contains details on [configuring GKE\nclusters](https://www.spinnaker.io/setup/install/providers/kubernetes-v2/gke/).\n\n```bash\n~/cloudshell_open/spinnaker-for-gcp/scripts/manage/add_gke_account.sh\n```\n\n### Add Spinnaker account for GCE\n\nThis script grants the required\n[IAM roles](https://cloud.google.com/compute/docs/access/) to the Spinnaker\ninstance's service account, in the GCP project within which you wish to manage\nGCE resources.\n\n```bash\n~/cloudshell_open/spinnaker-for-gcp/scripts/manage/add_gce_account.sh\n```\n\n### Add Spinnaker account for GAE\n\nThis script grants the required\n[IAM roles](https://cloud.google.com/appengine/docs/admin-api/access-control)\nto the Spinnaker instance's service account, in the GCP project within which you\nwish to manage GAE resources.\n\n```bash\n~/cloudshell_open/spinnaker-for-gcp/scripts/manage/add_gae_account.sh\n```\n\n### Upgrade Spinnaker\n\nFirst, modify `SPINNAKER_VERSION` in your `properties` file to reflect the desired version of Spinnaker:\n\n```bash\ncloudshell edit ~/cloudshell_open/spinnaker-for-gcp/scripts/install/properties\n```\n\nNext, use Halyard to apply the changes:\n\n```bash\n~/cloudshell_open/spinnaker-for-gcp/scripts/manage/update_spinnaker_version.sh\n```\n\n### Upgrade Halyard daemon running in cluster\n\nFirst, modify `HALYARD_VERSION` in your `properties` file to reflect the desired version of Halyard:\n\n```bash\ncloudshell edit ~/cloudshell_open/spinnaker-for-gcp/scripts/install/properties\n```\n\nNext, apply this change to the Statefulset managing the Halyard daemon:\n\n```bash\n~/cloudshell_open/spinnaker-for-gcp/scripts/manage/update_halyard_daemon.sh\n```\n\n### Upgrade Management Environment\n\nUpdate the commands and documentation in your management environment to the latest available version.\n\n```bash\n~/cloudshell_open/spinnaker-for-gcp/scripts/manage/update_management_environment.sh\n```\n\n### Sign up for Spinnaker for GCP updates and announcements\n\nJoin the [mailing list](https://groups.google.com/forum/#!forum/spinnaker-for-gcp-announce) to keep informed about updates and other announcements.\n\n### Connect to Redis\n\n```bash\n~/cloudshell_open/spinnaker-for-gcp/scripts/manage/connect_to_redis.sh\n```\n\n### Restore a backup to Cloud Shell\n\nRestore a backup of the halyard configuration and deployment configuration from Cloud Source Repositories to your Cloud Shell. \n\n```bash\n~/cloudshell_open/spinnaker-for-gcp/scripts/manage/restore_backup_to_cloud_shell.sh -p $PROJECT_ID -r $CONFIG_CSR_REPO -h GIT_HASH\n```\n\nAll backups can be viewed in this [Cloud Source Repository](https://source.cloud.google.com/$PROJECT_ID/$CONFIG_CSR_REPO).\n\n## Configure Operator Access\n\nTo add additional operators, grant them the `Owner` role on GCP Project {{project-id}}: [IAM Permissions](https://console.developers.google.com/iam-admin/iam?project={{project-id}})\n\nOnce they have been added to the project, they can locate Spinnaker by navigating to the newly-registered [Kubernetes Application](https://console.developers.google.com/kubernetes/application/$ZONE/$DEPLOYMENT_NAME/spinnaker/$DEPLOYMENT_NAME?project={{project-id}}).\n\nThe application's *Next Steps* section contains the relevant links and operator instructions.\n\n### If you have secured Spinnaker via IAP\n\nGranting someone the `Owner` role does not implicitly grant them access as a user. For configuring user access, please continue on to the *Configure User Access (IAP)* section.\n\n"
  },
  {
    "path": "scripts/manage/landing_page_secured.md",
    "content": "## Configure User Access (IAP)\n\n```bash\n~/cloudshell_open/spinnaker-for-gcp/scripts/manage/grant_iap_access.sh\n```\n\nAlternatively, you can manually grant the `IAP-secured Web App User` role on the `spinnaker/spin-deck` resource to the user you'd like to grant access to [here](https://console.developers.google.com/security/iap?project={{project-id}}).\n\n## Use Spinnaker\n\n### Connect to Spinnaker\n\nConnect to your Spinnaker installation [here](https://$DOMAIN_NAME).\n\n### View Spinnaker Audit Log\n\nView the who, what, when and where of your Spinnaker installation\n[here](https://console.developers.google.com/logs/viewer?project={{project-id}}&resource=cloud_function&logName=projects%2F{{project-id}}%2Flogs%2F$CLOUD_FUNCTION_NAME&minLogLevel=200).\n\n### View Spinnaker Container Logs\n\nView the logging output of the individual components of your Spinnaker installation\n[here](https://console.developers.google.com/logs/viewer?project={{project-id}}&resource=k8s_container%2Fcluster_name%2F$GKE_CLUSTER%2Fnamespace_name%2Fspinnaker).\n\n### Install sample applications and pipelines\n\nThere are sample applications with example pipelines available to install and try out.\nView and install the samples by running this command:\n\n```bash\n~/cloudshell_open/spinnaker-for-gcp/scripts/manage/list_samples.sh\n```\n\n## Delete Spinnaker\n\n### Generate a cleanup script\n\nThis command generates a script that deletes all the resources that were provisioned as part of your Spinnaker installation.\n\n```bash\n~/cloudshell_open/spinnaker-for-gcp/scripts/manage/generate_deletion_script.sh\n```\n"
  },
  {
    "path": "scripts/manage/landing_page_unsecured.md",
    "content": "## Use Spinnaker\n\n### Forward the port to Deck, and connect\n\nDon't use the `hal deploy connect` command. Instead, use the following command\nonly.\n\n```bash\n~/cloudshell_open/spinnaker-for-gcp/scripts/manage/connect_unsecured.sh\n```\n\nTo connect to the Deck UI, click on the Preview button above and select \"Preview on port 8080\":\n\n![Image](https://github.com/GoogleCloudPlatform/spinnaker-for-gcp/raw/master/scripts/manage/preview_button.png)\n\n### View Spinnaker Audit Log\n\nView the who, what, when and where of your Spinnaker installation\n[here](https://console.developers.google.com/logs/viewer?project={{project-id}}&resource=cloud_function&logName=projects%2F{{project-id}}%2Flogs%2F$CLOUD_FUNCTION_NAME&minLogLevel=200).\n\n### View Spinnaker Container Logs\n\nView the logging output of the individual components of your Spinnaker installation\n[here](https://console.developers.google.com/logs/viewer?project={{project-id}}&resource=k8s_container%2Fcluster_name%2F$GKE_CLUSTER%2Fnamespace_name%2Fspinnaker).\n\n### Expose Spinnaker\n\nIf you would like to connect to Spinnaker without relying on port forwarding, we can\nexpose it via a secure domain behind the [Identity-Aware Proxy](https://cloud.google.com/iap/).\n\nNote that this phase could take 30-60 minutes. **Spinnaker will be inaccessible during this time.**\n\n```bash\n~/cloudshell_open/spinnaker-for-gcp/scripts/expose/configure_endpoint.sh\n```\n\n### Install sample applications and pipelines\n\nThere are sample applications with example pipelines available to install and try out.\nView and install the samples by running this command:\n\n```bash\n~/cloudshell_open/spinnaker-for-gcp/scripts/manage/list_samples.sh\n```\n\n## Delete Spinnaker\n\n### Generate a cleanup script\n\nThis command generates a script that deletes all the resources that were provisioned as part of your Spinnaker installation.\n\n```bash\n~/cloudshell_open/spinnaker-for-gcp/scripts/manage/generate_deletion_script.sh\n```\n"
  },
  {
    "path": "scripts/manage/list_samples.sh",
    "content": "#!/usr/bin/env bash\n\nbold() {\n  echo \". $(tput bold)\" \"$*\" \"$(tput sgr0)\";\n}\n\nbold \"Here is a list of sample applications available to install. Selecting one will launch\" \\\n      \"a tutorial to install it.\"\n\nPS3='Please enter your choice: '\n\ntutorials=($(ls -d ~/cloudshell_open/spinnaker-for-gcp/samples/*/ | xargs -n 1 basename) \"Quit\")\n\nselect tutorial in \"${tutorials[@]}\"\ndo\n  case $tutorial in\n    \"Quit\")\n      break\n      ;;\n    \"\")\n      bold \"Please choose a valid entry (1-${#tutorials[@]})\";;\n    *)\n      bold \"Launching $tutorial tutorial...\"\n      cloudshell launch-tutorial ~/cloudshell_open/spinnaker-for-gcp/samples/$tutorial/install.md\n      break\n      ;;\n  esac\ndone\n"
  },
  {
    "path": "scripts/manage/pull_config.sh",
    "content": "#!/usr/bin/env bash\n\n[ -z \"$PARENT_DIR\" ] && PARENT_DIR=$(dirname $(realpath $0) | rev | cut -d '/' -f 4- | rev)\n\nif [ \"$CI\" == true ]; then\n  HAL_PARENT_DIR=$PARENT_DIR\nelse\n  HAL_PARENT_DIR=$HOME\nfi\n\nsource $PARENT_DIR/spinnaker-for-gcp/scripts/manage/service_utils.sh\n\n[ -z \"$PROPERTIES_FILE\" ] && PROPERTIES_FILE=\"$PARENT_DIR/spinnaker-for-gcp/scripts/install/properties\"\n\n$PARENT_DIR/spinnaker-for-gcp/scripts/manage/check_duplicate_dirs.sh || exit 1\n\nCURRENT_CONTEXT=$(kubectl config current-context)\n\nif [ \"$?\" != \"0\" ]; then\n  bold \"No current Kubernetes context is configured.\"\n  exit 1\nfi\n\nHALYARD_POD=spin-halyard-0\n\nTEMP_DIR=$(mktemp -d -t halyard.XXXXX)\npushd $TEMP_DIR\n\nmkdir .hal\n\n# Remove local config so persistent config from Halyard Daemon pod can be copied into place.\nbold \"Removing $HAL_PARENT_DIR/.hal...\"\nrm -rf $HAL_PARENT_DIR/.hal\n\n# Copy persistent config into place.\nbold \"Copying halyard/$HALYARD_POD:/home/spinnaker/.hal into $HAL_PARENT_DIR/.hal...\"\n\nkubectl cp halyard/$HALYARD_POD:/home/spinnaker/.hal .hal\n\nsource $PARENT_DIR/spinnaker-for-gcp/scripts/manage/restore_config_utils.sh\nrewrite_hal_key_paths\n\n# We want just these subdirs from the Halyard Daemon pod to be copied into place in $HAL_PARENT_DIR/.hal.\ncopy_hal_subdirs\ncp .hal/config $HAL_PARENT_DIR/.hal\n\nEXISTING_DEPLOYMENT_SECRET_NAME=$(kubectl get secret -n halyard \\\n  --field-selector metadata.name==\"spinnaker-deployment\" \\\n  -o json | jq .items[0].metadata.name)\n\nif [ $EXISTING_DEPLOYMENT_SECRET_NAME != 'null' ]; then\n  bold \"Restoring Spinnaker deployment config files from Kubernetes secret spinnaker-deployment...\"\n  DEPLOYMENT_SECRET_DATA=$(kubectl get secret spinnaker-deployment -n halyard -o json)\n\n  extract_to_file_if_defined() {\n    DATA_ITEM_VALUE=$(echo $DEPLOYMENT_SECRET_DATA | jq -r \".data.\\\"$1\\\"\")\n\n    if [ $DATA_ITEM_VALUE != 'null' ]; then\n      echo $DATA_ITEM_VALUE | base64 -d > $2\n    fi\n  }\n\n  extract_to_file_if_defined properties \"$PROPERTIES_FILE\"\n  extract_to_file_if_defined config.json $PARENT_DIR/spinnaker-for-gcp/scripts/install/spinnakerAuditLog/config.json\n  extract_to_file_if_defined index.js $PARENT_DIR/spinnaker-for-gcp/scripts/install/spinnakerAuditLog/index.js\n  extract_to_file_if_defined configure_iap_expanded.md $PARENT_DIR/spinnaker-for-gcp/scripts/expose/configure_iap_expanded.md\n  extract_to_file_if_defined openapi_expanded.yml $PARENT_DIR/spinnaker-for-gcp/scripts/expose/openapi_expanded.yml\n  mkdir -p ~/.spin\n  extract_to_file_if_defined config ~/.spin/config\n  extract_to_file_if_defined key.json ~/.spin/key.json\n\n  rewrite_spin_key_path\nfi\n\npopd\nrm -rf $TEMP_DIR\n\nif [ \"$CI\" != true ]; then\n  # Update the generated markdown pages.\n  ~/cloudshell_open/spinnaker-for-gcp/scripts/manage/update_landing_page.sh\nfi\n"
  },
  {
    "path": "scripts/manage/push_and_apply.sh",
    "content": "#!/usr/bin/env bash\n\n~/cloudshell_open/spinnaker-for-gcp/scripts/manage/push_config.sh || exit 1\n~/cloudshell_open/spinnaker-for-gcp/scripts/manage/apply_config.sh\n"
  },
  {
    "path": "scripts/manage/push_config.sh",
    "content": "#!/usr/bin/env bash\n\n[ -z \"$PARENT_DIR\" ] && PARENT_DIR=$(dirname $(realpath $0) | rev | cut -d '/' -f 4- | rev)\n\nif [ \"$CI\" == true ]; then\n  HAL_PARENT_DIR=$PARENT_DIR\nelse\n  HAL_PARENT_DIR=$HOME\nfi\n\nsource $PARENT_DIR/spinnaker-for-gcp/scripts/manage/service_utils.sh\n\n[ -z \"$PROPERTIES_FILE\" ] && PROPERTIES_FILE=\"$PARENT_DIR/spinnaker-for-gcp/scripts/install/properties\"\n\n$PARENT_DIR/spinnaker-for-gcp/scripts/manage/check_duplicate_dirs.sh || exit 1\n$PARENT_DIR/spinnaker-for-gcp/scripts/manage/check_git_config.sh || exit 1\n\nsource \"$PROPERTIES_FILE\"\n\n# TODO(duftler): Add check to ensure that we are not overriding with older or empty config.\n\nCURRENT_CONTEXT=$(kubectl config current-context)\n\nif [ \"$?\" != \"0\" ]; then\n  bold \"No current Kubernetes context is configured.\"\n  exit 1\nfi\n\nCURRENT_CONTEXT_PROJECT=$(echo $CURRENT_CONTEXT | cut -d '_' -f 2)\nCURRENT_CONTEXT_ZONE=$(echo $CURRENT_CONTEXT | cut -d '_' -f 3)\nCURRENT_CONTEXT_CLUSTER=$(echo $CURRENT_CONTEXT | cut -d '_' -f 4)\n\nif [ $CURRENT_CONTEXT_PROJECT != $PROJECT_ID ]; then\n  bold \"Your Spinnaker config references project $PROJECT_ID, but you are connected to a cluster in project $CURRENT_CONTEXT_PROJECT.\"\n  bold \"Use 'kubectl config use-context' to connect to the correct cluster before pushing the config.\"\n  exit 1\nfi\n\nif [ $CURRENT_CONTEXT_ZONE != $ZONE ]; then\n  bold \"Your Spinnaker config references zone $ZONE, but you are connected to a cluster in zone $CURRENT_CONTEXT_ZONE.\"\n  bold \"Use 'kubectl config use-context' to connect to the correct cluster before pushing the config.\"\n  exit 1\nfi\n\nif [ $CURRENT_CONTEXT_CLUSTER != $GKE_CLUSTER ]; then\n  bold \"Your Spinnaker config references cluster $GKE_CLUSTER, but you are connected to cluster $CURRENT_CONTEXT_CLUSTER.\"\n  bold \"Use 'kubectl config use-context' to connect to the correct cluster before pushing the config.\"\n  exit 1\nfi\n\nsource $PARENT_DIR/spinnaker-for-gcp/scripts/manage/cluster_utils.sh\n\nCLUSTER_EXISTS=$(check_for_existing_cluster)\n\nif [ -z \"$CLUSTER_EXISTS\" ]; then\n  bold \"Cluster $GKE_CLUSTER cannot be found. It may not exist.\"\n  bold \"To recreate your installation with this config, run:\"\n  bold \"USE_CLOUD_SHELL_HAL_CONFIG=true $PARENT_DIR/spinnaker-for-gcp/scripts/install/setup.sh\"\n  exit 1\nfi\n\nif [ -z \"$CONFIG_CSR_REPO\" ]; then\n  bold \"CONFIG_CSR_REPO was not set. Please run the $PARENT_DIR/spinnaker-for-gcp/scripts/manage/update_management_environment.sh\" \\\n       \"command to ensure you have all the necessary properties declared.\"\n  exit 1\nfi\n\nHALYARD_POD=spin-halyard-0\n\nTEMP_DIR=$(mktemp -d -t halyard.XXXXX)\npushd $TEMP_DIR\n\nEXISTING_CSR_REPO=$(gcloud source repos list --format=\"value(name)\" --filter=\"name=projects/$PROJECT_ID/repos/$CONFIG_CSR_REPO\" --project=$PROJECT_ID)\n\nif [ -z \"$EXISTING_CSR_REPO\" ]; then\n  bold \"Creating Cloud Source Repository $CONFIG_CSR_REPO...\"\n\n  gcloud source repos create $CONFIG_CSR_REPO --project=$PROJECT_ID\nfi\n\ngcloud source repos clone $CONFIG_CSR_REPO --project=$PROJECT_ID\ncd $CONFIG_CSR_REPO\n\nbold \"Backing up $HAL_PARENT_DIR/.hal...\"\n\nrm -rf .hal\nmkdir .hal\n\n# We want just these subdirs within $HAL_PARENT_DIR/.hal to be copied into place on the Halyard Daemon pod.\nDIRS=(credentials profiles service-settings)\n\nfor p in \"${DIRS[@]}\"; do\n  for f in $(find $HAL_PARENT_DIR/.hal/*/$p -prune 2> /dev/null); do\n    SUB_PATH=$(echo $f | rev | cut -d '/' -f 1,2 | rev)\n    mkdir -p .hal/$SUB_PATH\n    cp -RT $HAL_PARENT_DIR/.hal/$SUB_PATH .hal/$SUB_PATH\n  done\ndone\n\ncp $HAL_PARENT_DIR/.hal/config .hal\n\n# Please note, rewritable key paths are in both push_config.sh and restore_config_utils.sh\nREWRITABLE_KEYS=(kubeconfigFile jsonPath jsonKey passwordFile path templatePath tokenFile \\\n                 usernamePasswordFile sshPrivateKeyFilePath sshKnownHostsFilePath trustStore credentialPath)\nfor k in \"${REWRITABLE_KEYS[@]}\"; do\n  grep $k .hal/config &> /dev/null\n  FOUND_TOKEN=$?\n\n  if [ \"$FOUND_TOKEN\" == \"0\" ]; then\n    bold \"Rewriting $k path to reflect user 'spinnaker' on Halyard Daemon pod...\"\n    sed -i \"s/$k: \\/home\\/$USER/$k: \\/home\\/spinnaker/\" .hal/config\n  fi\ndone\n\nbold \"Backing up Spinnaker deployment config files...\"\n\nrm -rf deployment_config_files\nmkdir deployment_config_files\n\ncopy_if_exists() {\n  if [ -e $1 ]; then\n    # If a filter token was passed, only copy the file if the token is present in the source file.\n    if [ $3 ]; then\n      if [ \"$(grep $3 $1)\" ]; then\n        cp $1 $2\n      fi\n    else\n      cp $1 $2\n    fi\n  fi\n}\n\ncopy_if_exists \"$PROPERTIES_FILE\" deployment_config_files\ncopy_if_exists $PARENT_DIR/spinnaker-for-gcp/scripts/install/spinnakerAuditLog/config.json deployment_config_files\ncopy_if_exists $PARENT_DIR/spinnaker-for-gcp/scripts/install/spinnakerAuditLog/index.js deployment_config_files\n\n# These files are generated when Spinnaker is exposed via IAP.\n# If the operator is managing more than one installation we don't want to inadvertently backup files from the wrong installation.\ncopy_if_exists $PARENT_DIR/spinnaker-for-gcp/scripts/expose/configure_iap_expanded.md deployment_config_files \"$PROJECT_ID\\.\"\ncopy_if_exists $PARENT_DIR/spinnaker-for-gcp/scripts/expose/openapi_expanded.yml deployment_config_files \"$PROJECT_ID\\.\"\ncopy_if_exists ~/.spin/config deployment_config_files \"$PROJECT_ID\\.\"\ncopy_if_exists ~/.spin/config deployment_config_files \"localhost\\:\"\ncopy_if_exists ~/.spin/key.json deployment_config_files \"$PROJECT_ID\\.\"\n\n# Remove old persistent config so new config can be copied into place.\nbold \"Removing halyard/$HALYARD_POD:/home/spinnaker/.hal...\"\nkubectl -n halyard exec $HALYARD_POD -- bash -c \"rm -rf ~/.hal/*\"\n\n# Copy new config into place.\nbold \"Copying $HAL_PARENT_DIR/.hal into halyard/$HALYARD_POD:/home/spinnaker/.hal...\"\n\nkubectl -n halyard cp $TEMP_DIR/$CONFIG_CSR_REPO/.hal spin-halyard-0:/home/spinnaker\n\nEXISTING_DEPLOYMENT_SECRET_NAME=$(kubectl get secret -n halyard \\\n  --field-selector metadata.name==\"spinnaker-deployment\" \\\n  -o json | jq .items[0].metadata.name)\n\nif [ $EXISTING_DEPLOYMENT_SECRET_NAME != 'null' ]; then\n  bold \"Deleting Kubernetes secret spinnaker-deployment...\"\n  kubectl delete secret spinnaker-deployment -n halyard\nfi\n\nbold \"Creating Kubernetes secret spinnaker-deployment containing Spinnaker deployment config files...\"\nkubectl create secret generic spinnaker-deployment -n halyard \\\n  --from-file deployment_config_files\n\ngit add .\ngit commit -m 'Automated backup.'\ngit push\n\npopd\nrm -rf $TEMP_DIR\n"
  },
  {
    "path": "scripts/manage/restore_backup_to_cloud_shell.sh",
    "content": "#!/usr/bin/env bash\n\nbold() {\n  echo \". $(tput bold)\" \"$*\" \"$(tput sgr0)\";\n}\n\n~/cloudshell_open/spinnaker-for-gcp/scripts/manage/check_git_config.sh || exit 1\n\nwhile getopts \":p:r:h:\" options; do\n  case $options in\n    p ) PROJECT_ID=$OPTARG ;;\n    r ) CONFIG_CSR_REPO=$OPTARG ;;\n    h ) GIT_HASH=$OPTARG ;;\n    \\? ) bold \"Invalid option supplied: -$OPTARG\"\n  esac\ndone\n\nEXAMPLE_COMMAND=\"'restore_backup_to_cloud_shell.sh -p PROJECT -r REPOSITORY_NAME -h GIT_HASH'\"\n\nif [ -z \"$PROJECT_ID\" ]; then\n  bold \"Project id is required. $EXAMPLE_COMMAND\"\n  exit 1\nfi\n\nif [ -z \"$CONFIG_CSR_REPO\" ]; then\n  bold \"Cloud Source Repository name is required. $EXAMPLE_COMMAND\"\n  exit 1\nfi\n\nif [ -z \"$GIT_HASH\" ]; then\n  bold \"Git commit hash is required. $EXAMPLE_COMMAND\"\n  exit 1\nfi\n\nTEMP_DIR=$(mktemp -d -t halyard.XXXXX)\npushd $TEMP_DIR\n\nEXISTING_CSR_REPO=$(gcloud source repos list --format=\"value(name)\" --filter=\"name=projects/$PROJECT_ID/repos/$CONFIG_CSR_REPO\" --project=$PROJECT_ID)\n\nif [ -n \"$EXISTING_CSR_REPO\" ]; then\n  gcloud source repos clone $CONFIG_CSR_REPO --project=$PROJECT_ID\nelse\n  bold \"Cloud Source Repository $CONFIG_CSR_REPO not found\"\n  popd\n  rm -rf $TEMP_DIR\n  exit 1\nfi\n\ncd $CONFIG_CSR_REPO\nHASH_CHECKOUT_ERROR=$(git branch --contains $GIT_HASH 2>&1 > /dev/null)\n\nif [ -n \"$HASH_CHECKOUT_ERROR\" ]; then\n  bold \"Git commit hash: $GIT_HASH not found. Please enter a valid commit hash.\"\n  popd\n  rm -rf $TEMP_DIR\n  exit 1\nfi\n\nHASH_PREVIEW_LINK=\"https://source.cloud.google.com/$PROJECT_ID/$EXISTING_CSR_REPO/+/$GIT_HASH\"\n\nread -p \". $(tput bold)You are about to replace the configuration files in your Cloud Shell with the configuration at:\n. $HASH_PREVIEW_LINK\n. This step is not reversible. Do you wish to continue (Y/n)? $(tput sgr0)\" yn\ncase $yn in\n  [Yy]* ) ;;\n  \"\" ) ;;\n  * ) \n    popd\n    rm -rf $TEMP_DIR\n    exit\n  ;;\nesac\n\ngit checkout $GIT_HASH &> /dev/null\n\n# Remove local hal config so persistent config from backup can be copied into place.\nbold \"Removing $HOME/.hal...\"\nrm -rf ~/.hal\n\n# Copy persistent config into place.\nbold \"Copying $CONFIG_CSR_REPO/.hal into $HOME/.hal...\"\n\nsource ~/cloudshell_open/spinnaker-for-gcp/scripts/manage/restore_config_utils.sh\nrewrite_hal_key_paths\n\n# We want just these subdirs from the backup to be copied into place in ~/.hal.\ncopy_hal_subdirs\ncp .hal/config ~/.hal\n\nremove_and_copy() {\n  if [ -e $1 ]; then\n    cp $1 $2\n  elif [ -e $2 ]; then\n    rm $2\n  fi\n}\n\ncd deployment_config_files\nbold \"Restoring deployment config... from $CONFIG_CSR_REPO\"\nremove_and_copy properties ~/cloudshell_open/spinnaker-for-gcp/scripts/install/properties \nremove_and_copy config.json ~/cloudshell_open/spinnaker-for-gcp/scripts/install/spinnakerAuditLog/config.json\nremove_and_copy index.js ~/cloudshell_open/spinnaker-for-gcp/scripts/install/spinnakerAuditLog/index.js\n\nremove_and_copy configure_iap_expanded.md ~/cloudshell_open/spinnaker-for-gcp/scripts/expose/configure_iap_expanded.md\nremove_and_copy openapi_expanded.yml ~/cloudshell_open/spinnaker-for-gcp/scripts/expose/openapi_expanded.yml\nmkdir -p ~/.spin\nremove_and_copy config ~/.spin/config\nremove_and_copy key.json ~/.spin/key.json\n\nif [ -e ~/.spin/config ]; then\n  rewrite_spin_key_path\nfi\n\npopd\nrm -rf $TEMP_DIR\n\nbold \"Configuration applied. To diff this config with what was last deployed, go to:\"\nbold \"https://source.cloud.google.com/$PROJECT_ID/$EXISTING_CSR_REPO/+/$GIT_HASH...master\"\nbold \"Note: If secure access via IAP is already configured, that access is left unchanged and remains secure.\"\nbold \"To apply the halyard config changes to the cluster, run:\"\nbold \"~/cloudshell_open/spinnaker-for-gcp/scripts/manage/push_and_apply.sh\"\n"
  },
  {
    "path": "scripts/manage/restore_config_utils.sh",
    "content": "#!/usr/bin/env bash\n\n[ -z \"$PARENT_DIR\" ] && PARENT_DIR=$(dirname $(realpath $0) | rev | cut -d '/' -f 4- | rev)\n\nif [ \"$CI\" == true ]; then\n  HAL_PARENT_DIR=$PARENT_DIR\nelse\n  HAL_PARENT_DIR=$HOME\nfi\n\nsource $PARENT_DIR/spinnaker-for-gcp/scripts/manage/service_utils.sh\n\n# Please note, rewritable key paths are in both push_config.sh and restore_config_utils.sh\nrewrite_hal_key_paths() {\n  REWRITABLE_KEYS=(kubeconfigFile jsonPath jsonKey passwordFile path templatePath tokenFile \\\n                   usernamePasswordFile sshPrivateKeyFilePath sshKnownHostsFilePath trustStore credentialPath)\n  for k in \"${REWRITABLE_KEYS[@]}\"; do\n    grep $k .hal/config &> /dev/null\n    FOUND_TOKEN=$?\n\n    if [ \"$FOUND_TOKEN\" == \"0\" ]; then\n      bold \"Rewriting $k path to reflect local user '$USER' on Cloud Shell VM...\"\n      sed -i \"s/$k: \\/home\\/spinnaker/$k: \\/home\\/$USER/\" .hal/config\n    fi\n  done\n}\n\ncopy_hal_subdirs() {\n  DIRS=(credentials profiles service-settings)\n\n  for p in \"${DIRS[@]}\"; do\n    for f in $(find .hal/*/$p -prune 2> /dev/null); do\n      SUB_PATH=$(echo $f | rev | cut -d '/' -f 1,2 | rev)\n      mkdir -p $HAL_PARENT_DIR/.hal/$SUB_PATH\n      cp -RT .hal/$SUB_PATH $HAL_PARENT_DIR/.hal/$SUB_PATH\n    done\n  done  \n}\n\nrewrite_spin_key_path() {\n  bold \"Rewriting key path in $HOME/.spin/config to reflect local user '$USER' on Cloud Shell VM...\"\n  sed -i \"s/^    serviceAccountKeyPath: .*/    serviceAccountKeyPath: \\\"\\/home\\/$USER\\/.spin\\/key.json\\\"/\" $HOME/.spin/config\n}\n"
  },
  {
    "path": "scripts/manage/service_utils.sh",
    "content": "#!/usr/bin/env bash\n\n[ -z \"$PARENT_DIR\" ] && PARENT_DIR=$(dirname $(realpath $0) | rev | cut -d '/' -f 4- | rev)\n\n[ -z \"$PROPERTIES_FILE\" ] && PROPERTIES_FILE=\"$PARENT_DIR/spinnaker-for-gcp/scripts/install/properties\"\n\nif [ -f \"$PROPERTIES_FILE\" ]; then\n    source \"$PROPERTIES_FILE\"\nfi\n\nbold() {\n  echo \". $(tput bold)\" \"$*\" \"$(tput sgr0)\";\n}\n\nhas_service_enabled() {\n  gcloud services list --project $1 \\\n    --filter=\"config.name:$2\" \\\n    --format=\"value(config.name)\"\n}\n\ncheck_for_command() {\n  COMMAND_PRESENT=$(command -v $1)\n  echo $COMMAND_PRESENT\n}\n\ncheck_for_required_binaries() {\n  REQUIRED_BINARIES=(git gcloud jq kubectl)\n\n  MISSING_BINARIES=\"\"\n  for b in \"${REQUIRED_BINARIES[@]}\"; do\n    BINARY_PATH=$(check_for_command $b)\n    if [ -z \"$BINARY_PATH\" ]; then\n      if [ -z $MISSING_BINARIES ]; then\n        MISSING_BINARIES=\"$b\"\n      else \n        MISSING_BINARIES=\"$MISSING_BINARIES, $b\"\n      fi\n    fi\n  done\n\n  if [ -n \"$MISSING_BINARIES\" ]; then \n    bold \"The following command(s) are required for setup but were not found: $MISSING_BINARIES\"\n    exit 1\n  fi\n}\n\ncheck_for_shared_vpc() {\n  if [ \"$PROJECT_ID\" != \"$NETWORK_PROJECT\" -a \"$1\" = true ]; then\n    bold \"Automated setup of Spinnaker for GCP with a Shared VPC host project is currently unsupported. To proceed, continue the setup in Cloud Shell.\"\n    exit 1\n  fi\n}"
  },
  {
    "path": "scripts/manage/update_console.sh",
    "content": "#!/usr/bin/env bash\n\n[ -z \"$PARENT_DIR\" ] && PARENT_DIR=$(dirname $(realpath $0) | rev | cut -d '/' -f 4- | rev)\n\n$PARENT_DIR/spinnaker-for-gcp/scripts/manage/check_duplicate_dirs.sh || exit 1\n\ncloudshell launch-tutorial ~/cloudshell_open/spinnaker-for-gcp/scripts/manage/landing_page_expanded.md\n"
  },
  {
    "path": "scripts/manage/update_halyard_daemon.sh",
    "content": "#!/usr/bin/env bash\n\nbold() {\n  echo \". $(tput bold)\" \"$*\" \"$(tput sgr0)\";\n}\n\nsource ~/cloudshell_open/spinnaker-for-gcp/scripts/install/properties\n\nbold \"Updating halyard daemon...\"\n\nif [ -z \"$HALYARD_VERSION\" ]; then\n\tbold \"HALYARD_VERSION not set...\"\n\texit 1\nfi\n\nkubectl set image statefulset spin-halyard -n halyard halyard-daemon=us-docker.pkg.dev/spinnaker-community/docker/halyard:$HALYARD_VERSION\n"
  },
  {
    "path": "scripts/manage/update_landing_page.sh",
    "content": "#!/usr/bin/env bash\n\n[ -z \"$PARENT_DIR\" ] && PARENT_DIR=$(dirname $(realpath $0) | rev | cut -d '/' -f 4- | rev)\n\nsource $PARENT_DIR/spinnaker-for-gcp/scripts/manage/service_utils.sh\n\n[ -z \"$PROPERTIES_FILE\" ] && PROPERTIES_FILE=\"$PARENT_DIR/spinnaker-for-gcp/scripts/install/properties\"\n\nif [ ! -f \"$PROPERTIES_FILE\" ]; then\n  bold \"No properties file was found. Resetting the management environment.\"\n  git checkout -- $PARENT_DIR/spinnaker-for-gcp/scripts/manage/landing_page_expanded.md\n  exit 0\nfi\n\nsource \"$PROPERTIES_FILE\"\n\n# Query for static ip address as a signal that the Spinnaker installation is exposed via a secured endpoint.\nexport IP_ADDR=$(gcloud compute addresses list --filter=\"name=$STATIC_IP_NAME\" \\\n  --format=\"value(address)\" --global --project $PROJECT_ID)\n\nif [ -z \"$IP_ADDR\" ]; then\n  bold \"Updating Cloud Shell landing page for unsecured Spinnaker...\"\n  cat $PARENT_DIR/spinnaker-for-gcp/scripts/manage/landing_page_base.md $PARENT_DIR/spinnaker-for-gcp/scripts/manage/landing_page_unsecured.md \\\n    | envsubst > $PARENT_DIR/spinnaker-for-gcp/scripts/manage/landing_page_expanded.md\nelse\n  bold \"Updating Cloud Shell landing page for secured Spinnaker...\"\n  cat $PARENT_DIR/spinnaker-for-gcp/scripts/manage/landing_page_base.md $PARENT_DIR/spinnaker-for-gcp/scripts/manage/landing_page_secured.md \\\n    | envsubst > $PARENT_DIR/spinnaker-for-gcp/scripts/manage/landing_page_expanded.md\nfi\n"
  },
  {
    "path": "scripts/manage/update_management_environment.sh",
    "content": "#!/usr/bin/env bash\n\nbold() {\n  echo \". $(tput bold)\" \"$*\" \"$(tput sgr0)\";\n}\n\npushd ~/cloudshell_open/spinnaker-for-gcp/scripts/manage\n\n# We re-generate landing_page_expanded.md all the time; we should not stash those changes.\ngit checkout -- landing_page_expanded.md\n\nGIT_STASH_COUNT_BEFORE=$(git stash list | wc -l)\n\nbold \"Stashing local changes...\"\ngit stash save \"Stashed by update_management_environment.sh\"\n\nGIT_STASH_COUNT_AFTER=$(git stash list | wc -l)\n\nif [ \"$GIT_STASH_COUNT_AFTER\" != $GIT_STASH_COUNT_BEFORE ]; then\n  bold \"Changes were stashed. You will need to manually reapply any stashed changes after the update.\"\nfi\n\ngit checkout master\ngit pull origin master\n\n# New properties have been added over time and we want to ensure these are declared in the properties file.\n~/cloudshell_open/spinnaker-for-gcp/scripts/manage/add_missing_properties.sh\n\n# Update the GKE Application details view.\n~/cloudshell_open/spinnaker-for-gcp/scripts/manage/deploy_application_manifest.sh\n\n# Update the generated markdown pages.\n./update_landing_page.sh\n\n# Refresh the tutorial view.\n./update_console.sh\n\npopd"
  },
  {
    "path": "scripts/manage/update_spinnaker_version.sh",
    "content": "#!/usr/bin/env bash\n\nbold() {\n  echo \". $(tput bold)\" \"$*\" \"$(tput sgr0)\";\n}\n\nsource ~/cloudshell_open/spinnaker-for-gcp/scripts/install/properties\n\nbold \"Updating Spinnaker to version $SPINNAKER_VERSION...\"\n\n~/hal/hal config version edit --version $SPINNAKER_VERSION\n~/cloudshell_open/spinnaker-for-gcp/scripts/manage/push_and_apply.sh\n~/cloudshell_open/spinnaker-for-gcp/scripts/manage/update_landing_page.sh\n"
  },
  {
    "path": "templates/spinnaker_application_manifest_bottom.yaml",
    "content": "  info:\n  - name: Application Namespace\n    value: spinnaker\n  selector:\n    matchLabels:\n      app.kubernetes.io/name: $DEPLOYMENT_NAME\n  componentKinds:\n  - group: v1\n    kind: ConfigMap\n  - group: extensions/v1beta1\n    kind: Deployment\n  - group: v1\n    kind: PersistentVolumeClaim\n  - group: v1\n    kind: Secret\n  - group: v1\n    kind: Service\n  - group: apps/v1beta2\n    kind: StatefulSet\n"
  },
  {
    "path": "templates/spinnaker_application_manifest_middle_secured.yaml",
    "content": "    notes: |-\n      # Manage your Spinnaker\n      [Open management console in Cloud Shell](https://console.cloud.google.com/cloudshell/editor?shellonly=true&cloudshell_git_repo=https://github.com/GoogleCloudPlatform/spinnaker-for-gcp.git&cloudshell_working_dir=scripts/manage&cloudshell_tutorial=landing_page_expanded.md&cloudshell_print=instructions.txt)\n\n      Ensure you're connected to the correct GKE Cluster from within Cloud Shell. Follow the instructions in the management console to check the Kubernetes context you are using and update it if necessary.\n\n      # Connect to your Spinnaker\n      [https://$DOMAIN_NAME](https://$DOMAIN_NAME)\n\n      # View the who, what, when and where of your Spinnaker installation\n      [Spinnaker audit log](https://console.developers.google.com/logs/viewer?project=$PROJECT_ID&resource=cloud_function&logName=projects%2F$PROJECT_ID%2Flogs%2F$CLOUD_FUNCTION_NAME&minLogLevel=200)\n\n      # View Spinnaker container logs\n      [Spinnaker container logs](https://console.developers.google.com/logs/viewer?project=$PROJECT_ID&resource=k8s_container%2Fcluster_name%2F$GKE_CLUSTER%2Fnamespace_name%2Fspinnaker)\n\n      # Configuration backups\n      Full backups are stored in [this repo](https://source.cloud.google.com/$PROJECT_ID/$CONFIG_CSR_REPO).\n\n      # Stay up to date\n      Join the [Spinnaker for GCP Announce](https://groups.google.com/forum/#!forum/spinnaker-for-gcp-announce) Google Group.\n"
  },
  {
    "path": "templates/spinnaker_application_manifest_middle_unsecured.yaml",
    "content": "    notes: |-\n      # Manage your Spinnaker\n      [Open management console in Cloud Shell](https://console.cloud.google.com/cloudshell/editor?shellonly=true&cloudshell_git_repo=https://github.com/GoogleCloudPlatform/spinnaker-for-gcp.git&cloudshell_working_dir=scripts/manage&cloudshell_tutorial=landing_page_expanded.md&cloudshell_print=instructions.txt)\n\n      Ensure you're connected to the correct GKE Cluster from within Cloud Shell. Follow the instructions in the management console to check the Kubernetes context you are using and update it if necessary.\n\n      # Connect to your Spinnaker\n      Follow the link above to establish port forwarding via Cloud Shell. Note that you can securely expose your Spinnaker via that link as well so it can be directly accessed in the future.\n\n      # View the who, what, when and where of your Spinnaker installation\n      [Spinnaker audit log](https://console.developers.google.com/logs/viewer?project=$PROJECT_ID&resource=cloud_function&logName=projects%2F$PROJECT_ID%2Flogs%2F$CLOUD_FUNCTION_NAME&minLogLevel=200)\n\n      # View Spinnaker container logs\n      [Spinnaker container logs](https://console.developers.google.com/logs/viewer?project=$PROJECT_ID&resource=k8s_container%2Fcluster_name%2F$GKE_CLUSTER%2Fnamespace_name%2Fspinnaker)\n\n      # Configuration backups\n      Full backups are stored in [this repo](https://source.cloud.google.com/$PROJECT_ID/$CONFIG_CSR_REPO).\n\n      # Stay up to date\n      Join the [Spinnaker for GCP Announce](https://groups.google.com/forum/#!forum/spinnaker-for-gcp-announce) Google Group.\n"
  },
  {
    "path": "templates/spinnaker_application_manifest_top.yaml",
    "content": "---\napiVersion: app.k8s.io/v1beta1\nkind: Application\nmetadata:\n  name: $DEPLOYMENT_NAME\n  namespace: spinnaker\n  annotations:\n    kubernetes-engine.cloud.google.com/icon: >-\n      data:image/png;base64, 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\n    marketplace.cloud.google.com/deploy-info: '{\"partner_id\": \"google-cloud-platform\", \"product_id\": \"spinnaker\", \"partner_name\": \"Google\"}'\n  labels:\n    app.kubernetes.io/name: spinnaker\n    app.kubernetes.io/instance: $DEPLOYMENT_NAME\n    app.kubernetes.io/version: $SPINNAKER_VERSION\n    app.kubernetes.io/managed-by: Marketplace\nspec:\n  descriptor:\n    type: Spinnaker\n    version: $SPINNAKER_VERSION\n    description: |-\n      Spinnaker is an open source, multi-cloud continuous\n      delivery platform for releasing software changes with\n      high velocity and confidence.\n\n      If you would like to learn more about Spinnaker, please\n      visit the [Spinnaker website](https://spinnaker.io/).\n\n      # Support\n\n      This image is built by Google. It is your responsibility to keep\n      container images you run or store in your own repositories\n      up to date with security patches.\n\n      Community support for Spinnaker is available on:\n\n      [GitHub](https://github.com/spinnaker/spinnaker/issues)\n\n      [Slack](http://join.spinnaker.io/)\n\n      [Stack Overflow](http://stackoverflow.com/questions/tagged/spinnaker/)\n    maintainers:\n    - name: Google Click to Deploy\n      url: https://cloud.google.com/solutions/#click-to-deploy\n    links:\n    - description: 'User Guide: Install and Manage Spinnaker on Google Cloud Platform'\n      url: https://cloud.google.com/docs/ci-cd/spinnaker/spinnaker-for-gcp\n    - description: 'Spinnaker Documentation'\n      url: https://spinnaker.io\n"
  }
]