diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index fb93c3845a..0000000000 --- a/Dockerfile +++ /dev/null @@ -1,42 +0,0 @@ -FROM openjdk:8 as build - -LABEL maintainer="delivery-engineering@netflix.com" - -COPY . workdir/ - -WORKDIR workdir - -RUN GRADLE_USER_HOME=cache ./gradlew installDist -x test -Prelease.useLastTag=true - -FROM openjdk:8 - -COPY --from=build /workdir/halyard-web/build/install/halyard /opt/halyard - -RUN echo '#!/usr/bin/env bash' | tee /usr/local/bin/hal > /dev/null && \ - echo '/opt/halyard/bin/hal "$@"' | tee /usr/local/bin/hal > /dev/null - -RUN chmod +x /usr/local/bin/hal - -ENV KUBECTL_RELEASE=1.12.7 -ENV AWS_BINARY_RELEASE_DATE=2019-03-27 - -RUN curl -LO https://storage.googleapis.com/kubernetes-release/release/v${KUBECTL_RELEASE}/bin/linux/amd64/kubectl && \ - chmod +x ./kubectl && \ - mv ./kubectl /usr/local/bin/kubectl && \ - /usr/local/bin/kubectl version --client - -RUN curl -o aws-iam-authenticator https://amazon-eks.s3-us-west-2.amazonaws.com/${KUBECTL_RELEASE}/${AWS_BINARY_RELEASE_DATE}/bin/linux/amd64/aws-iam-authenticator && \ - chmod +x ./aws-iam-authenticator && \ - mv ./aws-iam-authenticator /usr/local/bin/aws-iam-authenticator - -ENV PATH "$PATH:/usr/local/bin/aws-iam-authenticator" - -RUN wget -O /tmp/get-pip.py https://bootstrap.pypa.io/get-pip.py && \ - python /tmp/get-pip.py && \ - pip install awscli --upgrade - -RUN useradd -m spinnaker - -USER spinnaker - -CMD "/opt/halyard/bin/halyard" diff --git a/Dockerfile.compile b/Dockerfile.compile new file mode 100644 index 0000000000..64dbcdc538 --- /dev/null +++ b/Dockerfile.compile @@ -0,0 +1,5 @@ +FROM gcr.io/spinnaker-marketplace/gradle_cache +ENV GRADLE_USER_HOME /gradle_cache/.gradle +COPY . compiled_sources +WORKDIR compiled_sources +RUN ./gradlew installDist -x test -Prelease.useLastTag=true diff --git a/Dockerfile.local b/Dockerfile.local index 77a3d9b4a2..ff4cf03490 100644 --- a/Dockerfile.local +++ b/Dockerfile.local @@ -1,17 +1,10 @@ FROM openjdk:8 - -MAINTAINER delivery-engineering@netflix.com +LABEL maintainer="delivery-engineering@netflix.com" +COPY --from=halyard.compile /compiled_sources/halyard-web/build/install/halyard /opt/halyard ENV KUBECTL_RELEASE=1.12.7 ENV AWS_BINARY_RELEASE_DATE=2019-03-27 -COPY . workdir/ - -WORKDIR workdir - -RUN cp -r ./halyard-web/build/install/halyard /opt && \ - cd .. && \ - rm -rf workdir RUN echo '#!/usr/bin/env bash' | tee /usr/local/bin/hal > /dev/null && \ echo '/opt/halyard/bin/hal "$@"' | tee /usr/local/bin/hal > /dev/null @@ -28,6 +21,10 @@ RUN curl -o aws-iam-authenticator https://amazon-eks.s3-us-west-2.amazonaws.com/ ENV PATH "$PATH:/usr/local/bin/aws-iam-authenticator" +RUN wget -O /tmp/get-pip.py https://bootstrap.pypa.io/get-pip.py && \ + python /tmp/get-pip.py && \ + pip install --upgrade awscli==1.16.208 + RUN useradd -m spinnaker USER spinnaker diff --git a/Dockerfile.slim b/Dockerfile.slim deleted file mode 100644 index e7dd7b628f..0000000000 --- a/Dockerfile.slim +++ /dev/null @@ -1,9 +0,0 @@ -FROM openjdk:8-jre-alpine - -MAINTAINER delivery-engineering@netflix.com - -COPY ./halyard-web/build/install/halyard /opt/halyard - -RUN apk add --update bash && rm -rf /var/cache/apk/* - -CMD ["/opt/halyard/bin/halyard"] diff --git a/Dockerfile.ubuntu b/Dockerfile.ubuntu new file mode 100644 index 0000000000..d1e6146526 --- /dev/null +++ b/Dockerfile.ubuntu @@ -0,0 +1,29 @@ +FROM ubuntu:bionic +LABEL maintainer="delivery-engineering@netflix.com" +COPY --from=halyard.compile /compiled_sources/halyard-web/build/install/halyard /opt/halyard + +ENV KUBECTL_RELEASE=1.12.7 +ENV AWS_BINARY_RELEASE_DATE=2019-03-27 +ENV AWS_CLI_VERSION=1.16.208 + +RUN apt-get update && \ + apt-get upgrade -y && \ + apt-get install -y \ + curl \ + python-pip && \ + pip install awscli==${AWS_CLI_VERSION} --upgrade + +RUN echo '#!/usr/bin/env bash' | tee /usr/local/bin/hal > /dev/null && \ + echo '/opt/halyard/bin/hal "$@"' | tee /usr/local/bin/hal > /dev/null +RUN chmod +x /usr/local/bin/hal + +RUN curl -LO https://storage.googleapis.com/kubernetes-release/release/v${KUBECTL_RELEASE}/bin/linux/amd64/kubectl && \ + chmod +x ./kubectl && \ + mv ./kubectl /usr/local/bin/kubectl + +RUN curl -o /usr/local/bin/aws-iam-authenticator https://amazon-eks.s3-us-west-2.amazonaws.com/${KUBECTL_RELEASE}/${AWS_BINARY_RELEASE_DATE}/bin/linux/amd64/aws-iam-authenticator && \ + chmod +x /usr/local/bin/aws-iam-authenticator + +RUN adduser --disabled-login --system spinnaker +USER spinnaker +CMD "/opt/halyard/bin/halyard" diff --git a/README.md b/README.md index 5dbbe71d56..8cf446c584 100644 --- a/README.md +++ b/README.md @@ -57,3 +57,7 @@ halyard. To change these, check out the relevant bits in halyard-web/halyard-web __hal__ is a CLI for making changes to your __halconfig__ via the __daemon__. Read the command reference [here](docs/commands.md). + +# Container Images +## Building locally +Execute `./release/docker.sh` to build both the default `openjdk8` and `ubuntu` based images. \ No newline at end of file diff --git a/build.gradle b/build.gradle index 25aa561178..05f555a0ce 100644 --- a/build.gradle +++ b/build.gradle @@ -40,9 +40,6 @@ allprojects { apply plugin: 'java-library' apply plugin: 'groovy' - sourceSets.main.java.srcDirs = [] - sourceSets.main.groovy.srcDirs += ["src/main/java"] - test { testLogging { showStandardStreams = false diff --git a/cloudbuild-tagged.yaml b/cloudbuild-tagged.yaml deleted file mode 100644 index 33f2520c60..0000000000 --- a/cloudbuild-tagged.yaml +++ /dev/null @@ -1,14 +0,0 @@ -steps: -- name: 'spinnakerrelease/gradle_cache' - env: ["GRADLE_USER_HOME=/gradle_cache/.gradle"] - entrypoint: "bash" - args: [ "-c", "./gradlew halyard-web:installDist -x test -Prelease.version=$TAG_NAME"] -- name: 'gcr.io/cloud-builders/docker' - args: ["build", - "-t", "gcr.io/$PROJECT_ID/$REPO_NAME:$COMMIT_SHA", - "-t", "gcr.io/$PROJECT_ID/$REPO_NAME:$TAG_NAME", - "-f", "Dockerfile.slim", - "."] -images: -- 'gcr.io/$PROJECT_ID/$REPO_NAME:$COMMIT_SHA' -- 'gcr.io/$PROJECT_ID/$REPO_NAME:$TAG_NAME' diff --git a/cloudbuild.yaml b/cloudbuild.yaml deleted file mode 100644 index eada2f94ee..0000000000 --- a/cloudbuild.yaml +++ /dev/null @@ -1,11 +0,0 @@ -steps: -- name: 'spinnakerrelease/gradle_cache' - env: ["GRADLE_USER_HOME=/gradle_cache/.gradle"] - entrypoint: "bash" - args: [ "-c", "./gradlew halyard-web:installDist -x test"] -- name: 'gcr.io/cloud-builders/docker' - args: ["build", "-t", "gcr.io/$PROJECT_ID/halyard:$COMMIT_SHA", "-t", "gcr.io/$PROJECT_ID/halyard:latest", "-f", "Dockerfile.slim", "."] -images: -- 'gcr.io/$PROJECT_ID/halyard:$COMMIT_SHA' -- 'gcr.io/$PROJECT_ID/halyard:latest' -timeout: 25m diff --git a/docs/commands.md b/docs/commands.md index 72eca87f12..3990f4cb88 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -55,6 +55,15 @@ * [**hal config artifact gitlab account list**](#hal-config-artifact-gitlab-account-list) * [**hal config artifact gitlab disable**](#hal-config-artifact-gitlab-disable) * [**hal config artifact gitlab enable**](#hal-config-artifact-gitlab-enable) + * [**hal config artifact gitrepo**](#hal-config-artifact-gitrepo) + * [**hal config artifact gitrepo account**](#hal-config-artifact-gitrepo-account) + * [**hal config artifact gitrepo account add**](#hal-config-artifact-gitrepo-account-add) + * [**hal config artifact gitrepo account delete**](#hal-config-artifact-gitrepo-account-delete) + * [**hal config artifact gitrepo account edit**](#hal-config-artifact-gitrepo-account-edit) + * [**hal config artifact gitrepo account get**](#hal-config-artifact-gitrepo-account-get) + * [**hal config artifact gitrepo account list**](#hal-config-artifact-gitrepo-account-list) + * [**hal config artifact gitrepo disable**](#hal-config-artifact-gitrepo-disable) + * [**hal config artifact gitrepo enable**](#hal-config-artifact-gitrepo-enable) * [**hal config artifact helm**](#hal-config-artifact-helm) * [**hal config artifact helm account**](#hal-config-artifact-helm-account) * [**hal config artifact helm account add**](#hal-config-artifact-helm-account-add) @@ -138,6 +147,15 @@ * [**hal config canary google disable**](#hal-config-canary-google-disable) * [**hal config canary google edit**](#hal-config-canary-google-edit) * [**hal config canary google enable**](#hal-config-canary-google-enable) + * [**hal config canary newrelic**](#hal-config-canary-newrelic) + * [**hal config canary newrelic account**](#hal-config-canary-newrelic-account) + * [**hal config canary newrelic account add**](#hal-config-canary-newrelic-account-add) + * [**hal config canary newrelic account delete**](#hal-config-canary-newrelic-account-delete) + * [**hal config canary newrelic account edit**](#hal-config-canary-newrelic-account-edit) + * [**hal config canary newrelic account get**](#hal-config-canary-newrelic-account-get) + * [**hal config canary newrelic account list**](#hal-config-canary-newrelic-account-list) + * [**hal config canary newrelic disable**](#hal-config-canary-newrelic-disable) + * [**hal config canary newrelic enable**](#hal-config-canary-newrelic-enable) * [**hal config canary prometheus**](#hal-config-canary-prometheus) * [**hal config canary prometheus account**](#hal-config-canary-prometheus-account) * [**hal config canary prometheus account add**](#hal-config-canary-prometheus-account-add) @@ -300,6 +318,10 @@ * [**hal config metric-stores datadog edit**](#hal-config-metric-stores-datadog-edit) * [**hal config metric-stores datadog enable**](#hal-config-metric-stores-datadog-enable) * [**hal config metric-stores edit**](#hal-config-metric-stores-edit) + * [**hal config metric-stores newrelic**](#hal-config-metric-stores-newrelic) + * [**hal config metric-stores newrelic disable**](#hal-config-metric-stores-newrelic-disable) + * [**hal config metric-stores newrelic edit**](#hal-config-metric-stores-newrelic-edit) + * [**hal config metric-stores newrelic enable**](#hal-config-metric-stores-newrelic-enable) * [**hal config metric-stores prometheus**](#hal-config-metric-stores-prometheus) * [**hal config metric-stores prometheus disable**](#hal-config-metric-stores-prometheus-disable) * [**hal config metric-stores prometheus edit**](#hal-config-metric-stores-prometheus-edit) @@ -533,6 +555,10 @@ * [**hal config storage oracle edit**](#hal-config-storage-oracle-edit) * [**hal config storage s3**](#hal-config-storage-s3) * [**hal config storage s3 edit**](#hal-config-storage-s3-edit) + * [**hal config telemetry**](#hal-config-telemetry) + * [**hal config telemetry disable**](#hal-config-telemetry-disable) + * [**hal config telemetry edit**](#hal-config-telemetry-edit) + * [**hal config telemetry enable**](#hal-config-telemetry-enable) * [**hal config version**](#hal-config-version) * [**hal config version edit**](#hal-config-version-edit) * [**hal config webhook**](#hal-config-webhook) @@ -817,6 +843,7 @@ hal config [parameters] [subcommands] * `repository`: Configure, validate, and view the specified repository. * `security`: Configure Spinnaker's security. This includes external SSL, authentication mechanisms, and authorization policies. * `storage`: Show Spinnaker's persistent storage configuration. + * `telemetry`: Show Spinnaker's telemetry settings. * `version`: Configure & view the current deployment of Spinnaker's version. * `webhook`: Show Spinnaker's webhook configuration. @@ -835,6 +862,7 @@ hal config artifact [subcommands] * `gcs`: Manage and view Spinnaker configuration for the gcs provider * `github`: Manage and view Spinnaker configuration for the github provider * `gitlab`: Manage and view Spinnaker configuration for the gitlab provider + * `gitrepo`: Manage and view Spinnaker configuration for the gitrepo provider * `helm`: Manage and view Spinnaker configuration for the helm provider * `http`: Manage and view Spinnaker configuration for the http provider * `maven`: Manage and view Spinnaker configuration for the maven provider @@ -1464,6 +1492,174 @@ hal config artifact gitlab enable [parameters] * `--no-validate`: (*Default*: `false`) Skip validation. +--- +## hal config artifact gitrepo + +Manage and view Spinnaker configuration for the gitrepo provider + +#### Usage +``` +hal config artifact gitrepo [parameters] [subcommands] +``` + +#### Parameters + * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. + * `--no-validate`: (*Default*: `false`) Skip validation. + +#### Subcommands + * `account`: Manage and view Spinnaker configuration for the gitrepo artifact provider's account + * `disable`: Set the gitrepo artifact provider as disabled + * `enable`: Set the gitrepo artifact provider as enabled + +--- +## hal config artifact gitrepo account + +Manage and view Spinnaker configuration for the gitrepo artifact provider's account + +#### Usage +``` +hal config artifact gitrepo account ACCOUNT [parameters] [subcommands] +``` + +#### Parameters +`ACCOUNT`: The name of the account to operate on. + * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. + * `--no-validate`: (*Default*: `false`) Skip validation. + +#### Subcommands + * `add`: Add an artifact account to the gitrepo artifact provider. + * `delete`: Delete a specific gitrepo artifact account by name. + * `edit`: Edit an artifact account in the gitrepo artifact provider. + * `get`: Get the specified account details for the gitrepo provider. + * `list`: List the artifact account names for the gitrepo artifact provider. + +--- +## hal config artifact gitrepo account add + +Add an artifact account to the gitrepo artifact provider. + +#### Usage +``` +hal config artifact gitrepo account add ACCOUNT [parameters] +``` + +#### Parameters +`ACCOUNT`: The name of the account to operate on. + * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. + * `--no-validate`: (*Default*: `false`) Skip validation. + * `--password`: (*Sensitive data* - user will be prompted on standard input) Git password + * `--ssh-known-hosts-file-path`: File containing the known and trusted SSH hosts. + * `--ssh-private-key-file-path`: Path to the ssh private key in PEM format + * `--ssh-private-key-passphrase`: (*Sensitive data* - user will be prompted on standard input) Passphrase for encrypted private key + * `--ssh-trust-unknown-hosts`: Setting this to true allows Spinnaker to authenticate with unknown hosts + * `--token`: (*Sensitive data* - user will be prompted on standard input) Git token + * `--token-file`: File containing a Git authentication token + * `--username`: Git username + * `--username-password-file`: File containing "username:password" to use for Git authentication + + +--- +## hal config artifact gitrepo account delete + +Delete a specific gitrepo artifact account by name. + +#### Usage +``` +hal config artifact gitrepo account delete ACCOUNT [parameters] +``` + +#### Parameters +`ACCOUNT`: The name of the account to operate on. + * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. + * `--no-validate`: (*Default*: `false`) Skip validation. + + +--- +## hal config artifact gitrepo account edit + +Edit an artifact account in the gitrepo artifact provider. + +#### Usage +``` +hal config artifact gitrepo account edit ACCOUNT [parameters] +``` + +#### Parameters +`ACCOUNT`: The name of the account to operate on. + * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. + * `--no-validate`: (*Default*: `false`) Skip validation. + * `--password`: (*Sensitive data* - user will be prompted on standard input) Git password + * `--ssh-known-hosts-file-path`: File containing the known and trusted SSH hosts + * `--ssh-private-key-file-path`: Path to the ssh private key in PEM format + * `--ssh-private-key-passphrase`: (*Sensitive data* - user will be prompted on standard input) Passphrase for encrypted private key + * `--ssh-trust-unknown-hosts`: Setting this to true allows Spinnaker to authenticate with unknown hosts + * `--token`: (*Sensitive data* - user will be prompted on standard input) Git token + * `--token-file`: File containing a Git authentication token + * `--username`: Git username + * `--username-password-file`: File containing "username:password" to use for Git authentication + + +--- +## hal config artifact gitrepo account get + +Get the specified account details for the gitrepo provider. + +#### Usage +``` +hal config artifact gitrepo account get ACCOUNT [parameters] +``` + +#### Parameters +`ACCOUNT`: The name of the account to operate on. + * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. + * `--no-validate`: (*Default*: `false`) Skip validation. + + +--- +## hal config artifact gitrepo account list + +List the artifact account names for the gitrepo artifact provider. + +#### Usage +``` +hal config artifact gitrepo account list [parameters] +``` + +#### Parameters + * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. + * `--no-validate`: (*Default*: `false`) Skip validation. + + +--- +## hal config artifact gitrepo disable + +Set the gitrepo artifact provider as disabled + +#### Usage +``` +hal config artifact gitrepo disable [parameters] +``` + +#### Parameters + * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. + * `--no-validate`: (*Default*: `false`) Skip validation. + + +--- +## hal config artifact gitrepo enable + +Set the gitrepo artifact provider as enabled + +#### Usage +``` +hal config artifact gitrepo enable [parameters] +``` + +#### Parameters + * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. + * `--no-validate`: (*Default*: `false`) Skip validation. + + --- ## hal config artifact helm @@ -2360,6 +2556,7 @@ hal config canary [parameters] [subcommands] * `edit`: Edit Spinnaker's canary analysis settings. * `enable`: Set Spinnaker's canary analysis to enabled. * `google`: Configure your canary analysis Google service integration settings for Spinnaker. + * `newrelic`: Configure your canary analysis New Relic service integration settings for Spinnaker. * `prometheus`: Configure your canary analysis Prometheus service integration settings for Spinnaker. * `signalfx`: Configure your canary analysis SignalFx service integration settings for Spinnaker. @@ -2933,6 +3130,162 @@ hal config canary google enable [parameters] * `--no-validate`: (*Default*: `false`) Skip validation. +--- +## hal config canary newrelic + +Configure your canary analysis New Relic service integration settings for Spinnaker. + +#### Usage +``` +hal config canary newrelic [parameters] [subcommands] +``` + +#### Parameters + * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. + * `--no-validate`: (*Default*: `false`) Skip validation. + +#### Subcommands + * `account`: Manage and view Spinnaker configuration for the newrelic service integration's canary accounts. + * `disable`: Set Spinnaker's canary analysis newrelic service integration to disabled. + * `enable`: Set Spinnaker's canary analysis newrelic service integration to enabled. + +--- +## hal config canary newrelic account + +Manage and view Spinnaker configuration for the newrelic service integration's canary accounts. + +#### Usage +``` +hal config canary newrelic account ACCOUNT [parameters] [subcommands] +``` + +#### Parameters +`ACCOUNT`: The name of the canary account to operate on. + * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. + * `--no-validate`: (*Default*: `false`) Skip validation. + +#### Subcommands + * `add`: Add a canary account to the NewRelic service integration. + * `delete`: Delete a specific newrelic canary account by name. + * `edit`: Edit a canary account in the newrelic service integration. + * `get`: Get the specified canary account details for the newrelic service integration. + * `list`: List the canary account names for the newrelic service integration. + +--- +## hal config canary newrelic account add + +Add a canary account to the NewRelic service integration. + +#### Usage +``` +hal config canary newrelic account add ACCOUNT [parameters] +``` + +#### Parameters +`ACCOUNT`: The name of the canary account to operate on. + * `--api-key`: (*Required*) (*Sensitive data* - user will be prompted on standard input) Your account's unique New Relic Insights API key. See [https://docs.newrelic.com/docs/insights/insights-api/get-data/query-insights-event-data-api](https://docs.newrelic.com/docs/insights/insights-api/get-data/query-insights-event-data-api). + * `--application-key`: (*Required*) Your New Relic account id. See [https://docs.newrelic.com/docs/accounts/install-new-relic/account-setup/account-id](https://docs.newrelic.com/docs/accounts/install-new-relic/account-setup/account-id). + * `--base-url`: (*Required*) The base URL to the New Relic Insights server. + * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. + * `--no-validate`: (*Default*: `false`) Skip validation. + + +--- +## hal config canary newrelic account delete + +Delete a specific newrelic canary account by name. + +#### Usage +``` +hal config canary newrelic account delete ACCOUNT [parameters] +``` + +#### Parameters +`ACCOUNT`: The name of the canary account to operate on. + * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. + * `--no-validate`: (*Default*: `false`) Skip validation. + + +--- +## hal config canary newrelic account edit + +Edit a canary account in the newrelic service integration. + +#### Usage +``` +hal config canary newrelic account edit ACCOUNT [parameters] +``` + +#### Parameters +`ACCOUNT`: The name of the canary account to operate on. + * `--api-key`: (*Sensitive data* - user will be prompted on standard input) Your account's unique New Relic Insights API key. See [https://docs.newrelic.com/docs/insights/insights-api/get-data/query-insights-event-data-api](https://docs.newrelic.com/docs/insights/insights-api/get-data/query-insights-event-data-api). + * `--application-key`: Your New Relic account id. See [https://docs.newrelic.com/docs/accounts/install-new-relic/account-setup/account-id](https://docs.newrelic.com/docs/accounts/install-new-relic/account-setup/account-id). + * `--base-url`: The base URL to the New Relic Insights server. + * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. + * `--no-validate`: (*Default*: `false`) Skip validation. + + +--- +## hal config canary newrelic account get + +Get the specified canary account details for the newrelic service integration. + +#### Usage +``` +hal config canary newrelic account get ACCOUNT [parameters] +``` + +#### Parameters +`ACCOUNT`: The name of the canary account to operate on. + * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. + * `--no-validate`: (*Default*: `false`) Skip validation. + + +--- +## hal config canary newrelic account list + +List the canary account names for the newrelic service integration. + +#### Usage +``` +hal config canary newrelic account list [parameters] +``` + +#### Parameters + * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. + * `--no-validate`: (*Default*: `false`) Skip validation. + + +--- +## hal config canary newrelic disable + +Set Spinnaker's canary analysis newrelic service integration to disabled. + +#### Usage +``` +hal config canary newrelic disable [parameters] +``` + +#### Parameters + * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. + * `--no-validate`: (*Default*: `false`) Skip validation. + + +--- +## hal config canary newrelic enable + +Set Spinnaker's canary analysis newrelic service integration to enabled. + +#### Usage +``` +hal config canary newrelic enable [parameters] +``` + +#### Parameters + * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. + * `--no-validate`: (*Default*: `false`) Skip validation. + + --- ## hal config canary prometheus @@ -3162,6 +3515,9 @@ hal config canary signalfx account add ACCOUNT [parameters] #### Parameters `ACCOUNT`: The name of the canary account to operate on. * `--access-token`: (*Required*) (*Sensitive data* - user will be prompted on standard input) The SignalFx access token. + * `--base-url`: The base URL to the SignalFx server. Defaults to [https://stream.signalfx.com](https://stream.signalfx.com) + * `--default-location-key`: Location key is used to filter by deployment region. If omitted requests must supply the _location_key if it is needed. + * `--default-scope-key`: Scope key is used to distinguish between base and canary deployments. If omitted every request must supply the _scope_key param in extended scope params * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. * `--no-validate`: (*Default*: `false`) Skip validation. @@ -3195,6 +3551,9 @@ hal config canary signalfx account edit ACCOUNT [parameters] #### Parameters `ACCOUNT`: The name of the canary account to operate on. * `--access-token`: (*Sensitive data* - user will be prompted on standard input) The SignalFx access token. + * `--base-url`: The base URL to the SignalFx server. Defaults to [https://stream.signalfx.com](https://stream.signalfx.com) + * `--default-location-key`: Location key is used to filter by deployment region. If omitted requests must supply the _location_key if it is needed. + * `--default-scope-key`: Scope key is used to distinguish between base and canary deployments. If omitted every request must supply the _scope_key param in extended scope params * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. * `--no-validate`: (*Default*: `false`) Skip validation. @@ -5775,7 +6134,6 @@ hal config features edit [parameters] * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. * `--gremlin`: Enable Gremlin fault-injection support. * `--infrastructure-stages`: Enable infrastructure stages. Allows for creating Load Balancers as part of pipelines. - * `--jobs`: Allow Spinnaker to run containers in Kubernetes and Titus as Job stages in pipelines. * `--managed-pipeline-templates-v2-ui`: Enable managed pipeline templates v2 UI support. * `--mine-canary`: Enable canary support. For this to work, you'll need a canary judge configured. Currently, Halyard does not configure canary judge for you. * `--no-validate`: (*Default*: `false`) Skip validation. @@ -5848,6 +6206,7 @@ hal config metric-stores [parameters] [subcommands] #### Subcommands * `datadog`: Configure your datadog metric store. * `edit`: Configure global metric stores properties. + * `newrelic`: Configure your newrelic metric store. * `prometheus`: Configure your prometheus metric store. * `stackdriver`: Configure your stackdriver metric store. @@ -5867,7 +6226,7 @@ hal config metric-stores datadog [parameters] [subcommands] #### Subcommands * `disable`: Set the datadog method as disabled - * `edit`: Edit the datadog authentication method. + * `edit`: Edit the datadog metric store. * `enable`: Set the datadog method as enabled --- @@ -5888,7 +6247,7 @@ hal config metric-stores datadog disable [parameters] --- ## hal config metric-stores datadog edit -Edit the datadog authentication method. +Edit the datadog metric store. #### Usage ``` @@ -5936,6 +6295,75 @@ hal config metric-stores edit [parameters] * `--period`: (*Required*) Set the polling period for the monitoring daemon. +--- +## hal config metric-stores newrelic + +Configure your newrelic metric store. + +#### Usage +``` +hal config metric-stores newrelic [parameters] [subcommands] +``` + +#### Parameters + * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. + * `--no-validate`: (*Default*: `false`) Skip validation. + +#### Subcommands + * `disable`: Set the newrelic method as disabled + * `edit`: Edit the newrelic metric store. + * `enable`: Set the newrelic method as enabled + +--- +## hal config metric-stores newrelic disable + +Set the newrelic method as disabled + +#### Usage +``` +hal config metric-stores newrelic disable [parameters] +``` + +#### Parameters + * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. + * `--no-validate`: (*Default*: `false`) Skip validation. + + +--- +## hal config metric-stores newrelic edit + +Edit the newrelic metric store. + +#### Usage +``` +hal config metric-stores newrelic edit [parameters] +``` + +#### Parameters + * `--add-tag`: Add this tag to the list of tags. Use the format key:value i.e. --add-tag app:test + * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. + * `--host`: The URL to post metric data to. In almost all cases, this is set correctly by default and should not be used. + * `--insert-key`: Your New Relic Insights insert key + * `--no-validate`: (*Default*: `false`) Skip validation. + * `--remove-tag`: Remove this tag from the list of tags. Use the name of the tag you want to remove i.e. --remove-tag app + * `--tags`: (*Default*: `[]`) Your custom tags. Please delimit the KVP with colons i.e. --tags app:test env:dev + + +--- +## hal config metric-stores newrelic enable + +Set the newrelic method as enabled + +#### Usage +``` +hal config metric-stores newrelic enable [parameters] +``` + +#### Parameters + * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. + * `--no-validate`: (*Default*: `false`) Skip validation. + + --- ## hal config metric-stores prometheus @@ -5952,7 +6380,7 @@ hal config metric-stores prometheus [parameters] [subcommands] #### Subcommands * `disable`: Set the prometheus method as disabled - * `edit`: Edit the prometheus authentication method. + * `edit`: Edit the prometheus metric store. * `enable`: Set the prometheus method as enabled --- @@ -5973,7 +6401,7 @@ hal config metric-stores prometheus disable [parameters] --- ## hal config metric-stores prometheus edit -Edit the prometheus authentication method. +Edit the prometheus metric store. #### Usage ``` @@ -6017,7 +6445,7 @@ hal config metric-stores stackdriver [parameters] [subcommands] #### Subcommands * `disable`: Set the stackdriver method as disabled - * `edit`: Edit the stackdriver authentication method. + * `edit`: Edit the stackdriver metric store. * `enable`: Set the stackdriver method as enabled --- @@ -6038,7 +6466,7 @@ hal config metric-stores stackdriver disable [parameters] --- ## hal config metric-stores stackdriver edit -Edit the stackdriver authentication method. +Edit the stackdriver metric store. #### Usage ``` @@ -6349,8 +6777,10 @@ hal config notification slack edit [parameters] ``` #### Parameters + * `--base-url`: Slack endpoint. Optional, only set if using a compatible API. * `--bot-name`: The name of your slack bot. * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. + * `--force-use-incoming-webhook`: Force usage of incoming webhooks endpoint for slack. Optional, only set if using a compatible API. * `--no-validate`: (*Default*: `false`) Skip validation. * `--token`: (*Sensitive data* - user will be prompted on standard input) Your slack bot token. @@ -6742,11 +7172,23 @@ Example: [http://{{region}}.eureka.url.to.use:8080/eureka-server/v2](http://{{re Using {{region}} will make Spinnaker use AWS regions in the hostname to access discovery so that you can have discovery for multiple regions. * `--edda`: The endpoint Edda is reachable at. Edda is not a hard dependency of Spinnaker, but is helpful for reducing the request volume against AWS. See [https://github.com/Netflix/edda](https://github.com/Netflix/edda) for more information. * `--environment`: The environment name for the account. Many accounts can share the same environment (e.g. dev, test, prod) + * `--launching-lifecycle-hook-default-result`: (*Default*: `ABANDON`) Defines the action the Auto Scaling group should take when the lifecycle hook timeout elapses or if an unexpected failure occurs. This parameter can be either CONTINUE or ABANDON. The default value is ABANDON. + * `--launching-lifecycle-hook-heartbeat-timeout-seconds`: (*Default*: `3600`) Set the heartbeat timeout for the lifecycle hook. Instances can " + + "remain in a wait state for a finite period of time. The default is one hour (3600 seconds). + * `--launching-lifecycle-hook-notification-target-arn`: The ARN of the notification target that Amazon EC2 Auto Scaling uses to notify you when an instance is in the transition state for the lifecycle hook. This target can be either an SQS queue or an SNS topic. + + * `--launching-lifecycle-hook-role-arn`: The ARN of the IAM role that allows the Auto Scaling group to publish to the specified notification target, for example, an Amazon SNS topic or an Amazon SQS queue. * `--no-validate`: (*Default*: `false`) Skip validation. * `--provider-version`: Some providers support multiple versions/release tracks. This allows you to pick the version of the provider (not the resources it manages) to run within Spinnaker. * `--read-permissions`: (*Default*: `[]`) A user must have at least one of these roles in order to view this account's cloud resources. * `--regions`: (*Default*: `[]`) The AWS regions this Spinnaker account will manage. * `--required-group-membership`: (*Default*: `[]`) A user must be a member of at least one specified group in order to make changes to this account's cloud resources. + * `--terminating-lifecycle-hook-default-result`: (*Default*: `ABANDON`) Defines the action the Auto Scaling group should take when the lifecycle hook timeout elapses or if an unexpected failure occurs. This parameter can be either CONTINUE or ABANDON. The default value is ABANDON. + * `--terminating-lifecycle-hook-heartbeat-timeout-seconds`: (*Default*: `3600`) Set the heartbeat timeout for the lifecycle hook. Instances can " + + "remain in a wait state for a finite period of time. The default is one hour (3600 seconds). + * `--terminating-lifecycle-hook-notification-target-arn`: The ARN of the notification target that Amazon EC2 Auto Scaling uses to notify you when an instance is in the transition state for the lifecycle hook. This target can be either an SQS queue or an SNS topic. + + * `--terminating-lifecycle-hook-role-arn`: The ARN of the IAM role that allows the Auto Scaling group to publish to the specified notification target, for example, an Amazon SNS topic or an Amazon SQS queue. * `--write-permissions`: (*Default*: `[]`) A user must have at least one of these roles in order to make changes to this account's cloud resources. @@ -6795,6 +7237,12 @@ Example: [http://{{region}}.eureka.url.to.use:8080/eureka-server/v2](http://{{re Using {{region}} will make Spinnaker use AWS regions in the hostname to access discovery so that you can have discovery for multiple regions. * `--edda`: The endpoint Edda is reachable at. Edda is not a hard dependency of Spinnaker, but is helpful for reducing the request volume against AWS. See [https://github.com/Netflix/edda](https://github.com/Netflix/edda) for more information. * `--environment`: The environment name for the account. Many accounts can share the same environment (e.g. dev, test, prod) + * `--launching-lifecycle-hook-default-result`: (*Default*: `ABANDON`) Defines the action the Auto Scaling group should take when the lifecycle hook timeout elapses or if an unexpected failure occurs. This parameter can be either CONTINUE or ABANDON. The default value is ABANDON. + * `--launching-lifecycle-hook-heartbeat-timeout-seconds`: (*Default*: `3600`) Set the heartbeat timeout for the lifecycle hook. Instances can " + + "remain in a wait state for a finite period of time. The default is one hour (3600 seconds). + * `--launching-lifecycle-hook-notification-target-arn`: The ARN of the notification target that Amazon EC2 Auto Scaling uses to notify you when an instance is in the transition state for the lifecycle hook. This target can be either an SQS queue or an SNS topic. + + * `--launching-lifecycle-hook-role-arn`: The ARN of the IAM role that allows the Auto Scaling group to publish to the specified notification target, for example, an Amazon SNS topic or an Amazon SQS queue. * `--no-validate`: (*Default*: `false`) Skip validation. * `--provider-version`: Some providers support multiple versions/release tracks. This allows you to pick the version of the provider (not the resources it manages) to run within Spinnaker. * `--read-permissions`: A user must have at least one of these roles in order to view this account's cloud resources. @@ -6804,6 +7252,12 @@ Using {{region}} will make Spinnaker use AWS regions in the hostname to access d * `--remove-required-group-membership`: Remove this group from the list of required group memberships. * `--remove-write-permission`: Remove this permission to from list of write permissions. * `--required-group-membership`: A user must be a member of at least one specified group in order to make changes to this account's cloud resources. + * `--terminating-lifecycle-hook-default-result`: (*Default*: `ABANDON`) Defines the action the Auto Scaling group should take when the lifecycle hook timeout elapses or if an unexpected failure occurs. This parameter can be either CONTINUE or ABANDON. The default value is ABANDON. + * `--terminating-lifecycle-hook-heartbeat-timeout-seconds`: (*Default*: `3600`) Set the heartbeat timeout for the lifecycle hook. Instances can " + + "remain in a wait state for a finite period of time. The default is one hour (3600 seconds). + * `--terminating-lifecycle-hook-notification-target-arn`: The ARN of the notification target that Amazon EC2 Auto Scaling uses to notify you when an instance is in the transition state for the lifecycle hook. This target can be either an SQS queue or an SNS topic. + + * `--terminating-lifecycle-hook-role-arn`: The ARN of the IAM role that allows the Auto Scaling group to publish to the specified notification target, for example, an Amazon SNS topic or an Amazon SQS queue. * `--write-permissions`: A user must have at least one of these roles in order to make changes to this account's cloud resources. @@ -8580,6 +9034,7 @@ hal config provider kubernetes account edit ACCOUNT [parameters] #### Parameters `ACCOUNT`: The name of the account to operate on. + * `--add-custom-resource`: (V2 Only) Add Kubernetes custom resource to the list of custom resources to managed by clouddriver and made available for use in patch and delete manifest stages. Fields besides the Kubernetes Kind (resource name) can be set using the flags "--spinnaker-kind" and "--versioned" * `--add-docker-registry`: Add this docker registry to the list of docker registries to use as a source of images. * `--add-kind`: Add this kind to the list of kinds to manage. * `--add-namespace`: Add this namespace to the list of namespaces to manage. @@ -8616,6 +9071,7 @@ This can only be set when --namespaces is empty or not set. created by Spinnaker; as opposed to attempting to configure applications for resources already present in Kubernetes. * `--provider-version`: Some providers support multiple versions/release tracks. This allows you to pick the version of the provider (not the resources it manages) to run within Spinnaker. * `--read-permissions`: A user must have at least one of these roles in order to view this account's cloud resources. + * `--remove-custom-resource`: Remove this Kubernetes custom resource by name from the list of custom resources to manage. * `--remove-docker-registry`: Remove this docker registry from the list of docker registries to use as a source of images. * `--remove-kind`: Remove this kind to the list of kinds to manage. * `--remove-namespace`: Remove this namespace to the list of namespaces to manage. @@ -8626,6 +9082,8 @@ created by Spinnaker; as opposed to attempting to configure applications for res * `--remove-write-permission`: Remove this permission to from list of write permissions. * `--required-group-membership`: A user must be a member of at least one specified group in order to make changes to this account's cloud resources. * `--service-account`: When true, Spinnaker attempt to authenticate against Kubernetes using a Kubernetes service account. This only works when Halyard & Spinnaker are deployed in Kubernetes. Read more about service accounts here: [https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/). + * `--spinnaker-kind`: Set the Spinnaker kind for custom resource being added. + * `--versioned`: Configure whether the custom resource being added is versioned by Spinnaker. * `--write-permissions`: A user must have at least one of these roles in order to make changes to this account's cloud resources. @@ -9814,6 +10272,7 @@ hal config security authn saml edit [parameters] * `--metadata`: The address to your identity provider's metadata XML file. This can be a URL or the path of a local file. * `--no-validate`: (*Default*: `false`) Skip validation. * `--service-address-url`: The address of the Gate server that will be accesible by the SAML identity provider. This should be the full URL, including port, e.g. [https://gate.org.com:8084/](https://gate.org.com:8084/). If deployed behind a load balancer, this would be the laod balancer's address. + * `--user-attribute-mapping-email`: The email field returned from your SAML provider. * `--user-attribute-mapping-first-name`: The first name field returned from your SAML provider. * `--user-attribute-mapping-last-name`: The last name field returned from your SAML provider. * `--user-attribute-mapping-roles`: The roles field returned from your SAML provider. @@ -10410,6 +10869,71 @@ Example: "user/spinnaker" or "role/spinnakerManaged" * `--server-side-encryption`: Use Amazon Server-Side Encryption ('x-amz-server-side-encryption' header). Supports 'AES256' (for Amazon S3-managed encryption keys, equivalent to a header value of 'AES256') and 'AWSKMS' (for AWS KMS-managed encryption keys, equivalent to a header value of 'aws:kms'. +--- +## hal config telemetry + +Show Spinnaker's telemetry settings. + +#### Usage +``` +hal config telemetry [parameters] [subcommands] +``` + +#### Parameters + * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. + * `--no-validate`: (*Default*: `false`) Skip validation. + +#### Subcommands + * `disable`: Set Spinnaker's telemetry settings to disabled. + * `edit`: Edit Spinnaker's telemetry settings. + * `enable`: Set Spinnaker's telemetry settings to enabled. + +--- +## hal config telemetry disable + +Set Spinnaker's telemetry settings to disabled. + +#### Usage +``` +hal config telemetry disable [parameters] +``` + +#### Parameters + * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. + * `--no-validate`: (*Default*: `false`) Skip validation. + + +--- +## hal config telemetry edit + +Edit Spinnaker's telemetry settings. + +#### Usage +``` +hal config telemetry edit [parameters] +``` + +#### Parameters + * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. + * `--endpoint`: Set the endpoint for telemetry metrics. + * `--no-validate`: (*Default*: `false`) Skip validation. + + +--- +## hal config telemetry enable + +Set Spinnaker's telemetry settings to enabled. + +#### Usage +``` +hal config telemetry enable [parameters] +``` + +#### Parameters + * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. + * `--no-validate`: (*Default*: `false`) Skip validation. + + --- ## hal config version diff --git a/gradle.properties b/gradle.properties index 18d245439c..6f9eca9f11 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -#Thu Sep 05 14:11:55 UTC 2019 +#Mon Oct 28 20:24:06 UTC 2019 enablePublishing=false -korkVersion=6.6.0 +korkVersion=6.15.1 org.gradle.parallel=true diff --git a/halyard-backup/halyard-backup.gradle b/halyard-backup/halyard-backup.gradle index 8e80b2e9aa..98a63328a7 100644 --- a/halyard-backup/halyard-backup.gradle +++ b/halyard-backup/halyard-backup.gradle @@ -7,6 +7,7 @@ dependencies { // TODO(lwander) Move to spinnaker-dependencies implementation 'com.google.apis:google-api-services-cloudkms:v1-rev8-1.22.0' implementation 'com.google.apis:google-api-services-storage' + implementation 'com.google.auth:google-auth-library-oauth2-http' implementation 'org.apache.commons:commons-lang3' implementation 'commons-io:commons-io:2.6' implementation 'org.apache.commons:commons-compress:1.14' diff --git a/halyard-backup/src/main/java/com/netflix/spinnaker/halyard/backup/kms/v1/google/GoogleKms.java b/halyard-backup/src/main/java/com/netflix/spinnaker/halyard/backup/kms/v1/google/GoogleKms.java index 13e93d9a17..08ac01569f 100644 --- a/halyard-backup/src/main/java/com/netflix/spinnaker/halyard/backup/kms/v1/google/GoogleKms.java +++ b/halyard-backup/src/main/java/com/netflix/spinnaker/halyard/backup/kms/v1/google/GoogleKms.java @@ -18,7 +18,6 @@ package com.netflix.spinnaker.halyard.backup.kms.v1.google; -import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; import com.google.api.client.googleapis.json.GoogleJsonResponseException; import com.google.api.client.http.HttpTransport; import com.google.api.client.http.javanet.NetHttpTransport; @@ -27,6 +26,9 @@ import com.google.api.services.cloudkms.v1.CloudKMS; import com.google.api.services.cloudkms.v1.CloudKMSScopes; import com.google.api.services.cloudkms.v1.model.*; +import com.google.auth.http.HttpCredentialsAdapter; +import com.google.auth.oauth2.GoogleCredentials; +import com.google.auth.oauth2.ServiceAccountCredentials; import com.netflix.spinnaker.halyard.core.error.v1.HalException; import com.netflix.spinnaker.halyard.core.problem.v1.Problem; import java.io.FileInputStream; @@ -46,7 +48,7 @@ class GoogleKms { private final KeyRing keyRing; private final CryptoKey cryptoKey; - private GoogleCredential credential; + private GoogleCredentials credentials; private static final String KEY_PURPOSE = "ENCRYPT_DECRYPT"; @@ -73,7 +75,7 @@ class GoogleKms { : properties.getCryptoKeyName()); keyRing = ensureKeyRingExists(cloudKms, locationId, keyRingId); - cryptoKey = ensureCryptoKeyExists(cloudKms, credential, keyRingId, cryptoKeyId); + cryptoKey = ensureCryptoKeyExists(cloudKms, credentials, keyRingId, cryptoKeyId); } byte[] encryptContents(String plaintext) { @@ -101,12 +103,12 @@ private CloudKMS buildCredentials(GoogleSecureStorageProperties properties) { HttpTransport transport = new NetHttpTransport(); JsonFactory jsonFactory = new JacksonFactory(); try { - credential = loadKmsCredential(transport, jsonFactory, properties.getJsonPath()); + credentials = loadKmsCredential(properties.getJsonPath()); } catch (IOException e) { throw new RuntimeException("Unable to load KMS credentials: " + e.getMessage(), e); } - return new CloudKMS.Builder(transport, jsonFactory, credential) + return new CloudKMS.Builder(transport, jsonFactory, new HttpCredentialsAdapter(credentials)) .setApplicationName("halyard") .build(); } @@ -124,7 +126,7 @@ private static String cryptoKeyId(String keyRingId, String cryptoKeyId) { } private static CryptoKey ensureCryptoKeyExists( - CloudKMS cloudKms, GoogleCredential credential, String keyRingId, String cryptoKeyId) { + CloudKMS cloudKms, GoogleCredentials credentials, String keyRingId, String cryptoKeyId) { CryptoKey cryptoKey; try { cryptoKey = @@ -144,8 +146,15 @@ private static CryptoKey ensureCryptoKeyExists( if (cryptoKey == null) { String cryptoKeyName = cryptoKeyId.substring(cryptoKeyId.lastIndexOf('/') + 1); log.info("Creating a new crypto key " + cryptoKeyName); - String user = "serviceAccount:" + credential.getServiceAccountId(); - cryptoKey = createCryptoKey(cloudKms, keyRingId, cryptoKeyName, user); + if (credentials instanceof ServiceAccountCredentials) { + String user = + "serviceAccount:" + ((ServiceAccountCredentials) credentials).getClientEmail(); + cryptoKey = createCryptoKey(cloudKms, keyRingId, cryptoKeyName, user); + } else { + throw new HalException( + Problem.Severity.FATAL, + "Credentials are not an instance of ServiceAccountCredentials: " + credentials); + } } return cryptoKey; @@ -255,22 +264,21 @@ private static KeyRing createKeyRing(CloudKMS cloudKms, String locationId, Strin } } - private static GoogleCredential loadKmsCredential( - HttpTransport transport, JsonFactory factory, String jsonPath) throws IOException { - GoogleCredential credential; + private static GoogleCredentials loadKmsCredential(String jsonPath) throws IOException { + GoogleCredentials credentials; if (!jsonPath.isEmpty()) { FileInputStream stream = new FileInputStream(jsonPath); - credential = GoogleCredential.fromStream(stream, transport, factory); + credentials = GoogleCredentials.fromStream(stream); log.info("Loaded kms credentials from " + jsonPath); } else { log.info("Using kms default application credentials."); - credential = GoogleCredential.getApplicationDefault(); + credentials = GoogleCredentials.getApplicationDefault(); } - if (credential.createScopedRequired()) { - credential = credential.createScoped(CloudKMSScopes.all()); + if (credentials.createScopedRequired()) { + credentials = credentials.createScoped(CloudKMSScopes.all()); } - return credential; + return credentials; } } diff --git a/halyard-backup/src/main/java/com/netflix/spinnaker/halyard/backup/kms/v1/google/GoogleStorage.java b/halyard-backup/src/main/java/com/netflix/spinnaker/halyard/backup/kms/v1/google/GoogleStorage.java index b29766e156..2ad09bc4dd 100644 --- a/halyard-backup/src/main/java/com/netflix/spinnaker/halyard/backup/kms/v1/google/GoogleStorage.java +++ b/halyard-backup/src/main/java/com/netflix/spinnaker/halyard/backup/kms/v1/google/GoogleStorage.java @@ -18,7 +18,6 @@ package com.netflix.spinnaker.halyard.backup.kms.v1.google; -import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; import com.google.api.client.googleapis.json.GoogleJsonResponseException; import com.google.api.client.http.ByteArrayContent; import com.google.api.client.http.HttpTransport; @@ -29,6 +28,8 @@ import com.google.api.services.storage.StorageScopes; import com.google.api.services.storage.model.Bucket; import com.google.api.services.storage.model.StorageObject; +import com.google.auth.http.HttpCredentialsAdapter; +import com.google.auth.oauth2.GoogleCredentials; import com.netflix.spinnaker.halyard.core.error.v1.HalException; import com.netflix.spinnaker.halyard.core.problem.v1.Problem; import java.io.FileInputStream; @@ -73,14 +74,14 @@ void writeBytes(String name, byte[] contents) { private Storage buildCredentials(GoogleSecureStorageProperties properties) { HttpTransport transport = new NetHttpTransport(); JsonFactory jsonFactory = new JacksonFactory(); - GoogleCredential credential; + GoogleCredentials credentials; try { - credential = loadStorageCredential(transport, jsonFactory, properties.getJsonPath()); + credentials = loadStorageCredential(properties.getJsonPath()); } catch (IOException e) { throw new RuntimeException("Unable to load KMS credentials: " + e.getMessage(), e); } - return new Storage.Builder(transport, jsonFactory, credential) + return new Storage.Builder(transport, jsonFactory, new HttpCredentialsAdapter(credentials)) .setApplicationName("halyard") .build(); } @@ -124,22 +125,21 @@ private static Bucket createBucket( } } - private static GoogleCredential loadStorageCredential( - HttpTransport transport, JsonFactory factory, String jsonPath) throws IOException { - GoogleCredential credential; + private static GoogleCredentials loadStorageCredential(String jsonPath) throws IOException { + GoogleCredentials credentials; if (!jsonPath.isEmpty()) { FileInputStream stream = new FileInputStream(jsonPath); - credential = GoogleCredential.fromStream(stream, transport, factory); + credentials = GoogleCredentials.fromStream(stream); log.info("Loaded storage credentials from " + jsonPath); } else { log.info("Using storage default application credentials."); - credential = GoogleCredential.getApplicationDefault(); + credentials = GoogleCredentials.getApplicationDefault(); } - if (credential.createScopedRequired()) { - credential = credential.createScoped(StorageScopes.all()); + if (credentials.createScopedRequired()) { + credentials = credentials.createScoped(StorageScopes.all()); } - return credential; + return credentials; } } diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/ConfigCommand.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/ConfigCommand.java index 37b97d48f7..6becb93394 100644 --- a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/ConfigCommand.java +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/ConfigCommand.java @@ -25,6 +25,7 @@ import com.netflix.spinnaker.halyard.cli.command.v1.config.providers.ProviderCommand; import com.netflix.spinnaker.halyard.cli.command.v1.config.pubsubs.PubsubCommand; import com.netflix.spinnaker.halyard.cli.command.v1.config.repository.RepositoryCommand; +import com.netflix.spinnaker.halyard.cli.command.v1.config.telemetry.TelemetryCommand; import com.netflix.spinnaker.halyard.cli.command.v1.config.webhook.WebhookCommand; import com.netflix.spinnaker.halyard.cli.services.v1.Daemon; import com.netflix.spinnaker.halyard.cli.services.v1.OperationHandler; @@ -71,6 +72,7 @@ public class ConfigCommand extends AbstractConfigCommand { registerSubcommand(new ListCommand()); registerSubcommand(new RepositoryCommand()); registerSubcommand(new ManifestCommand()); + registerSubcommand(new TelemetryCommand()); } @Override diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/EditFeaturesCommand.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/EditFeaturesCommand.java index 33337d7287..b496da5d96 100644 --- a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/EditFeaturesCommand.java +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/EditFeaturesCommand.java @@ -42,13 +42,6 @@ public class EditFeaturesCommand extends AbstractConfigCommand { arity = 1) private Boolean chaos = null; - @Parameter( - names = "--jobs", - description = - "Allow Spinnaker to run containers in Kubernetes and Titus as Job stages in pipelines.", - arity = 1) - private Boolean jobs = null; - @Parameter( names = "--pipeline-templates", description = @@ -122,7 +115,6 @@ protected void executeThis() { int originalHash = features.hashCode(); features.setChaos(chaos != null ? chaos : features.isChaos()); - features.setJobs(jobs != null ? jobs : features.isJobs()); features.setPipelineTemplates( pipelineTemplates != null ? pipelineTemplates : features.getPipelineTemplates()); features.setArtifacts(artifacts != null ? artifacts : features.getArtifacts()); diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/MetricStoresCommand.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/MetricStoresCommand.java index 48d68034ec..9933808552 100644 --- a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/MetricStoresCommand.java +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/MetricStoresCommand.java @@ -20,6 +20,7 @@ import com.beust.jcommander.Parameters; import com.netflix.spinnaker.halyard.cli.command.v1.config.metricStores.EditMetricStoresCommand; import com.netflix.spinnaker.halyard.cli.command.v1.config.metricStores.datadog.DatadogCommand; +import com.netflix.spinnaker.halyard.cli.command.v1.config.metricStores.newrelic.NewrelicCommand; import com.netflix.spinnaker.halyard.cli.command.v1.config.metricStores.prometheus.PrometheusCommand; import com.netflix.spinnaker.halyard.cli.command.v1.config.metricStores.stackdriver.StackdriverCommand; import com.netflix.spinnaker.halyard.cli.services.v1.Daemon; @@ -45,6 +46,7 @@ public class MetricStoresCommand extends AbstractConfigCommand { public MetricStoresCommand() { registerSubcommand(new EditMetricStoresCommand()); registerSubcommand(new DatadogCommand()); + registerSubcommand(new NewrelicCommand()); registerSubcommand(new PrometheusCommand()); registerSubcommand(new StackdriverCommand()); } diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/artifacts/ArtifactProviderCommand.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/artifacts/ArtifactProviderCommand.java index 3bc4fb7573..99940e7c85 100644 --- a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/artifacts/ArtifactProviderCommand.java +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/artifacts/ArtifactProviderCommand.java @@ -24,6 +24,7 @@ import com.netflix.spinnaker.halyard.cli.command.v1.config.artifacts.gcs.GcsArtifactProviderCommand; import com.netflix.spinnaker.halyard.cli.command.v1.config.artifacts.github.GitHubArtifactProviderCommand; import com.netflix.spinnaker.halyard.cli.command.v1.config.artifacts.gitlab.GitlabArtifactProviderCommand; +import com.netflix.spinnaker.halyard.cli.command.v1.config.artifacts.gitrepo.GitRepoArtifactProviderCommand; import com.netflix.spinnaker.halyard.cli.command.v1.config.artifacts.helm.HelmArtifactProviderCommand; import com.netflix.spinnaker.halyard.cli.command.v1.config.artifacts.http.HttpArtifactProviderCommand; import com.netflix.spinnaker.halyard.cli.command.v1.config.artifacts.maven.MavenArtifactProviderCommand; @@ -53,6 +54,7 @@ public ArtifactProviderCommand() { registerSubcommand(new OracleArtifactProviderCommand()); registerSubcommand(new GitHubArtifactProviderCommand()); registerSubcommand(new GitlabArtifactProviderCommand()); + registerSubcommand(new GitRepoArtifactProviderCommand()); registerSubcommand(new HttpArtifactProviderCommand()); registerSubcommand(new HelmArtifactProviderCommand()); registerSubcommand(new S3ArtifactProviderCommand()); diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/artifacts/gitrepo/GitRepoAddArtifactAccountCommand.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/artifacts/gitrepo/GitRepoAddArtifactAccountCommand.java new file mode 100644 index 0000000000..cc4652312a --- /dev/null +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/artifacts/gitrepo/GitRepoAddArtifactAccountCommand.java @@ -0,0 +1,99 @@ +/* + * Copyright 2019 Armory, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License") + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + */ + +package com.netflix.spinnaker.halyard.cli.command.v1.config.artifacts.gitrepo; + +import com.beust.jcommander.Parameter; +import com.beust.jcommander.Parameters; +import com.netflix.spinnaker.halyard.cli.command.v1.config.artifacts.account.AbstractAddArtifactAccountCommand; +import com.netflix.spinnaker.halyard.cli.command.v1.converter.LocalFileConverter; +import com.netflix.spinnaker.halyard.config.model.v1.artifacts.gitrepo.GitRepoArtifactAccount; +import com.netflix.spinnaker.halyard.config.model.v1.node.ArtifactAccount; + +@Parameters(separators = "=") +public class GitRepoAddArtifactAccountCommand extends AbstractAddArtifactAccountCommand { + @Parameter(names = "--username", description = "Git username") + private String username; + + @Parameter(names = "--password", password = true, description = "Git password") + private String password; + + @Parameter( + names = "--username-password-file", + converter = LocalFileConverter.class, + description = "File containing \"username:password\" to use for Git authentication") + private String usernamePasswordFile; + + @Parameter(names = "--token", password = true, description = "Git token") + private String token; + + @Parameter( + names = "--token-file", + converter = LocalFileConverter.class, + description = "File containing a Git authentication token") + private String tokenFile; + + @Parameter( + names = "--ssh-private-key-file-path", + converter = LocalFileConverter.class, + description = "Path to the ssh private key in PEM format") + private String sshPrivateKeyFilePath; + + @Parameter( + names = "--ssh-private-key-passphrase", + password = true, + description = "Passphrase for encrypted private key") + private String sshPrivateKeyPassphrase; + + @Parameter( + names = "--ssh-known-hosts-file-path", + converter = LocalFileConverter.class, + description = "File containing the known and trusted SSH hosts.") + private String sshKnownHostsFilePath; + + @Parameter( + names = "--ssh-trust-unknown-hosts", + description = "Setting this to true allows Spinnaker to authenticate with unknown hosts") + private Boolean sshTrustUnknownHosts = null; + + @Override + protected ArtifactAccount buildArtifactAccount(String accountName) { + GitRepoArtifactAccount artifactAccount = new GitRepoArtifactAccount().setName(accountName); + artifactAccount + .setUsername(username) + .setPassword(password) + .setUsernamePasswordFile(usernamePasswordFile) + .setToken(token) + .setTokenFile(tokenFile) + .setSshPrivateKeyFilePath(sshPrivateKeyFilePath) + .setSshPrivateKeyPassphrase(sshPrivateKeyPassphrase) + .setSshKnownHostsFilePath(sshKnownHostsFilePath) + .setSshTrustUnknownHosts(sshTrustUnknownHosts); + return artifactAccount; + } + + @Override + protected ArtifactAccount emptyArtifactAccount() { + return new GitRepoArtifactAccount(); + } + + @Override + protected String getArtifactProviderName() { + return "gitrepo"; + } +} diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/artifacts/gitrepo/GitRepoArtifactAccountCommand.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/artifacts/gitrepo/GitRepoArtifactAccountCommand.java new file mode 100644 index 0000000000..23efe325fe --- /dev/null +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/artifacts/gitrepo/GitRepoArtifactAccountCommand.java @@ -0,0 +1,35 @@ +/* + * Copyright 2019 Armory, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License") + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + */ + +package com.netflix.spinnaker.halyard.cli.command.v1.config.artifacts.gitrepo; + +import com.beust.jcommander.Parameters; +import com.netflix.spinnaker.halyard.cli.command.v1.config.artifacts.AbstractArtifactAccountCommand; + +@Parameters(separators = "=") +public class GitRepoArtifactAccountCommand extends AbstractArtifactAccountCommand { + @Override + protected String getArtifactProviderName() { + return "gitrepo"; + } + + public GitRepoArtifactAccountCommand() { + registerSubcommand(new GitRepoAddArtifactAccountCommand()); + registerSubcommand(new GitRepoEditArtifactAccountCommand()); + } +} diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/artifacts/gitrepo/GitRepoArtifactProviderCommand.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/artifacts/gitrepo/GitRepoArtifactProviderCommand.java new file mode 100644 index 0000000000..d2dd4324e4 --- /dev/null +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/artifacts/gitrepo/GitRepoArtifactProviderCommand.java @@ -0,0 +1,34 @@ +/* + * Copyright 2019 Armory, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License") + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + */ + +package com.netflix.spinnaker.halyard.cli.command.v1.config.artifacts.gitrepo; + +import com.beust.jcommander.Parameters; +import com.netflix.spinnaker.halyard.cli.command.v1.config.artifacts.AbstractNamedArtifactProviderCommand; + +@Parameters(separators = "=") +public class GitRepoArtifactProviderCommand extends AbstractNamedArtifactProviderCommand { + @Override + protected String getArtifactProviderName() { + return "gitrepo"; + } + + public GitRepoArtifactProviderCommand() { + registerSubcommand(new GitRepoArtifactAccountCommand()); + } +} diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/artifacts/gitrepo/GitRepoEditArtifactAccountCommand.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/artifacts/gitrepo/GitRepoEditArtifactAccountCommand.java new file mode 100644 index 0000000000..8e91e88f92 --- /dev/null +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/artifacts/gitrepo/GitRepoEditArtifactAccountCommand.java @@ -0,0 +1,100 @@ +/* + * Copyright 2019 Armory, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License") + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + */ + +package com.netflix.spinnaker.halyard.cli.command.v1.config.artifacts.gitrepo; + +import com.beust.jcommander.Parameter; +import com.beust.jcommander.Parameters; +import com.netflix.spinnaker.halyard.cli.command.v1.config.artifacts.account.AbstractArtifactEditAccountCommand; +import com.netflix.spinnaker.halyard.cli.command.v1.converter.LocalFileConverter; +import com.netflix.spinnaker.halyard.config.model.v1.artifacts.gitrepo.GitRepoArtifactAccount; +import com.netflix.spinnaker.halyard.config.model.v1.node.ArtifactAccount; + +@Parameters(separators = "=") +public class GitRepoEditArtifactAccountCommand + extends AbstractArtifactEditAccountCommand { + @Parameter(names = "--username", description = "Git username") + private String username; + + @Parameter(names = "--password", password = true, description = "Git password") + private String password; + + @Parameter( + names = "--username-password-file", + converter = LocalFileConverter.class, + description = "File containing \"username:password\" to use for Git authentication") + private String usernamePasswordFile; + + @Parameter(names = "--token", password = true, description = "Git token") + private String token; + + @Parameter( + names = "--token-file", + converter = LocalFileConverter.class, + description = "File containing a Git authentication token") + private String tokenFile; + + @Parameter( + names = "--ssh-private-key-file-path", + converter = LocalFileConverter.class, + description = "Path to the ssh private key in PEM format") + private String sshPrivateKeyFilePath; + + @Parameter( + names = "--ssh-private-key-passphrase", + password = true, + description = "Passphrase for encrypted private key") + private String sshPrivateKeyPassphrase; + + @Parameter( + names = "--ssh-known-hosts-file-path", + converter = LocalFileConverter.class, + description = "File containing the known and trusted SSH hosts") + private String sshKnownHostsFilePath; + + @Parameter( + names = "--ssh-trust-unknown-hosts", + description = "Setting this to true allows Spinnaker to authenticate with unknown hosts") + private Boolean sshTrustUnknownHosts = null; + + @Override + protected ArtifactAccount editArtifactAccount(GitRepoArtifactAccount account) { + account.setUsername(isSet(username) ? username : account.getUsername()); + account.setPassword(isSet(password) ? password : account.getPassword()); + account.setUsernamePasswordFile( + isSet(usernamePasswordFile) ? usernamePasswordFile : account.getUsernamePasswordFile()); + account.setToken(isSet(token) ? token : account.getToken()); + account.setTokenFile(isSet(tokenFile) ? tokenFile : account.getTokenFile()); + account.setSshPrivateKeyFilePath( + isSet(sshPrivateKeyFilePath) ? sshPrivateKeyFilePath : account.getSshPrivateKeyFilePath()); + account.setSshPrivateKeyPassphrase( + isSet(sshPrivateKeyPassphrase) + ? sshPrivateKeyPassphrase + : account.getSshPrivateKeyPassphrase()); + account.setSshKnownHostsFilePath( + isSet(sshKnownHostsFilePath) ? sshKnownHostsFilePath : account.getSshKnownHostsFilePath()); + account.setSshTrustUnknownHosts( + isSet(sshTrustUnknownHosts) ? sshTrustUnknownHosts : account.isSshTrustUnknownHosts()); + return account; + } + + @Override + protected String getArtifactProviderName() { + return "gitrepo"; + } +} diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/canary/CanaryCommand.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/canary/CanaryCommand.java index 7d5b688fa9..312a3a1db1 100644 --- a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/canary/CanaryCommand.java +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/canary/CanaryCommand.java @@ -21,6 +21,7 @@ import com.netflix.spinnaker.halyard.cli.command.v1.config.canary.aws.CanaryAwsCommand; import com.netflix.spinnaker.halyard.cli.command.v1.config.canary.datadog.CanaryDatadogCommand; import com.netflix.spinnaker.halyard.cli.command.v1.config.canary.google.CanaryGoogleCommand; +import com.netflix.spinnaker.halyard.cli.command.v1.config.canary.newrelic.CanaryNewRelicCommand; import com.netflix.spinnaker.halyard.cli.command.v1.config.canary.prometheus.CanaryPrometheusCommand; import com.netflix.spinnaker.halyard.cli.command.v1.config.canary.signalfx.CanarySignalfxCommand; import com.netflix.spinnaker.halyard.cli.services.v1.Daemon; @@ -48,6 +49,7 @@ public CanaryCommand() { registerSubcommand(new CanaryDatadogCommand()); registerSubcommand(new CanarySignalfxCommand()); registerSubcommand(new CanaryAwsCommand()); + registerSubcommand(new CanaryNewRelicCommand()); } @Override diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/canary/newrelic/CanaryNewRelicCommand.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/canary/newrelic/CanaryNewRelicCommand.java new file mode 100644 index 0000000000..40e908478b --- /dev/null +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/canary/newrelic/CanaryNewRelicCommand.java @@ -0,0 +1,67 @@ +/* + * Copyright 2019 New Relic Corporation. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License") + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.halyard.cli.command.v1.config.canary.newrelic; + +import com.beust.jcommander.Parameters; +import com.netflix.spinnaker.halyard.cli.command.v1.config.AbstractConfigCommand; +import com.netflix.spinnaker.halyard.cli.command.v1.config.canary.EnableDisableCanaryServiceIntegrationCommandBuilder; +import com.netflix.spinnaker.halyard.cli.command.v1.config.canary.newrelic.account.NewRelicCanaryAccountCommand; +import com.netflix.spinnaker.halyard.cli.services.v1.Daemon; +import com.netflix.spinnaker.halyard.cli.services.v1.OperationHandler; +import com.netflix.spinnaker.halyard.cli.ui.v1.AnsiFormatUtils; +import com.netflix.spinnaker.halyard.config.model.v1.canary.Canary; +import com.netflix.spinnaker.halyard.config.model.v1.canary.newrelic.NewRelicCanaryServiceIntegration; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Parameters(separators = "=") +@Data +@EqualsAndHashCode(callSuper = false) +public class CanaryNewRelicCommand extends AbstractConfigCommand { + + String commandName = NewRelicCanaryServiceIntegration.NAME; + + String shortDescription = + "Configure your canary analysis New Relic service integration settings for Spinnaker."; + + public CanaryNewRelicCommand() { + registerSubcommand( + new EnableDisableCanaryServiceIntegrationCommandBuilder() + .setName("newrelic") + .setEnable(true) + .build()); + registerSubcommand( + new EnableDisableCanaryServiceIntegrationCommandBuilder() + .setName("newrelic") + .setEnable(false) + .build()); + registerSubcommand(new NewRelicCanaryAccountCommand()); + } + + @Override + protected void executeThis() { + String currentDeployment = getCurrentDeployment(); + + new OperationHandler() + .setOperation(Daemon.getCanary(currentDeployment, !noValidate)) + .setFailureMesssage("Failed to load canary settings.") + .setSuccessMessage("Configured canary settings: ") + .setFormat(AnsiFormatUtils.Format.STRING) + .setUserFormatted(true) + .get(); + } +} diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/canary/newrelic/account/NewRelicAddCanaryAccountCommand.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/canary/newrelic/account/NewRelicAddCanaryAccountCommand.java new file mode 100644 index 0000000000..6b73210541 --- /dev/null +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/canary/newrelic/account/NewRelicAddCanaryAccountCommand.java @@ -0,0 +1,72 @@ +/* + * Copyright 2019 New Relic Corporation. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License") + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.halyard.cli.command.v1.config.canary.newrelic.account; + +import com.beust.jcommander.Parameter; +import com.beust.jcommander.Parameters; +import com.netflix.spinnaker.halyard.cli.command.v1.config.canary.account.AbstractAddCanaryAccountCommand; +import com.netflix.spinnaker.halyard.config.model.v1.canary.AbstractCanaryAccount; +import com.netflix.spinnaker.halyard.config.model.v1.canary.Canary; +import com.netflix.spinnaker.halyard.config.model.v1.canary.newrelic.NewRelicCanaryAccount; + +@Parameters(separators = "=") +public class NewRelicAddCanaryAccountCommand extends AbstractAddCanaryAccountCommand { + + @Override + protected String getServiceIntegration() { + return "NewRelic"; + } + + @Parameter( + names = "--base-url", + required = true, + description = "The base URL to the New Relic Insights server.") + private String baseUrl; + + @Parameter( + names = "--api-key", + required = true, + password = true, + description = + "Your account's unique New Relic Insights API key. See https://docs.newrelic.com/docs/insights/insights-api/get-data/query-insights-event-data-api.") + private String apiKey; + + @Parameter( + names = "--application-key", + required = true, + description = + "Your New Relic account id. See https://docs.newrelic.com/docs/accounts/install-new-relic/account-setup/account-id.") + private String applicationKey; + + @Override + protected AbstractCanaryAccount buildAccount(Canary canary, String accountName) { + NewRelicCanaryAccount account = + (NewRelicCanaryAccount) new NewRelicCanaryAccount().setName(accountName); + + account + .setEndpoint(new NewRelicCanaryAccount.Endpoint().setBaseUrl(baseUrl)) + .setApiKey(apiKey) + .setApplicationKey(applicationKey); + + return account; + } + + @Override + protected AbstractCanaryAccount emptyAccount() { + return new NewRelicCanaryAccount(); + } +} diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/canary/newrelic/account/NewRelicCanaryAccountCommand.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/canary/newrelic/account/NewRelicCanaryAccountCommand.java new file mode 100644 index 0000000000..e6fa9e52db --- /dev/null +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/canary/newrelic/account/NewRelicCanaryAccountCommand.java @@ -0,0 +1,35 @@ +/* + * Copyright 2019 New Relic Corporation. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License") + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.halyard.cli.command.v1.config.canary.newrelic.account; + +import com.beust.jcommander.Parameters; +import com.netflix.spinnaker.halyard.cli.command.v1.config.canary.account.AbstractCanaryAccountCommand; + +/** Interact with the New Relic service integration */ +@Parameters(separators = "=") +public class NewRelicCanaryAccountCommand extends AbstractCanaryAccountCommand { + + @Override + protected String getServiceIntegration() { + return "newrelic"; + } + + public NewRelicCanaryAccountCommand() { + registerSubcommand(new NewRelicAddCanaryAccountCommand()); + registerSubcommand(new NewRelicEditCanaryAccountCommand()); + } +} diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/canary/newrelic/account/NewRelicEditCanaryAccountCommand.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/canary/newrelic/account/NewRelicEditCanaryAccountCommand.java new file mode 100644 index 0000000000..911ff5459c --- /dev/null +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/canary/newrelic/account/NewRelicEditCanaryAccountCommand.java @@ -0,0 +1,61 @@ +/* + * Copyright 2019 New Relic Corporation. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License") + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.halyard.cli.command.v1.config.canary.newrelic.account; + +import com.beust.jcommander.Parameter; +import com.beust.jcommander.Parameters; +import com.netflix.spinnaker.halyard.cli.command.v1.config.canary.account.AbstractEditCanaryAccountCommand; +import com.netflix.spinnaker.halyard.config.model.v1.canary.AbstractCanaryAccount; +import com.netflix.spinnaker.halyard.config.model.v1.canary.newrelic.NewRelicCanaryAccount; + +@Parameters(separators = "=") +public class NewRelicEditCanaryAccountCommand + extends AbstractEditCanaryAccountCommand { + + @Override + protected String getServiceIntegration() { + return "newrelic"; + } + + @Parameter(names = "--base-url", description = "The base URL to the New Relic Insights server.") + private String baseUrl; + + @Parameter( + names = "--api-key", + password = true, + description = + "Your account's unique New Relic Insights API key. See https://docs.newrelic.com/docs/insights/insights-api/get-data/query-insights-event-data-api.") + private String apiKey; + + @Parameter( + names = "--application-key", + description = + "Your New Relic account id. See https://docs.newrelic.com/docs/accounts/install-new-relic/account-setup/account-id.") + private String applicationKey; + + @Override + protected AbstractCanaryAccount editAccount(NewRelicCanaryAccount account) { + account.setEndpoint( + isSet(baseUrl) + ? new NewRelicCanaryAccount.Endpoint().setBaseUrl(baseUrl) + : account.getEndpoint()); + account.setApiKey(isSet(apiKey) ? apiKey : account.getApiKey()); + account.setApplicationKey(isSet(applicationKey) ? applicationKey : account.getApplicationKey()); + + return account; + } +} diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/canary/signalfx/account/SignalfxAddCanaryAccountCommand.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/canary/signalfx/account/SignalfxAddCanaryAccountCommand.java index 68164245cd..01b103056a 100644 --- a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/canary/signalfx/account/SignalfxAddCanaryAccountCommand.java +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/canary/signalfx/account/SignalfxAddCanaryAccountCommand.java @@ -38,6 +38,26 @@ protected String getServiceIntegration() { description = "The SignalFx access token.") private String accessToken; + @Parameter( + names = "--base-url", + required = false, + description = "The base URL to the SignalFx server. Defaults to https://stream.signalfx.com") + private String baseUrl; + + @Parameter( + names = "--default-scope-key", + required = false, + description = + "Scope key is used to distinguish between base and canary deployments. If omitted every request must supply the _scope_key param in extended scope params") + private String defaultScopeKey; + + @Parameter( + names = "--default-location-key", + required = false, + description = + "Location key is used to filter by deployment region. If omitted requests must supply the _location_key if it is needed.") + private String defaultLocationKey; + @Override protected AbstractCanaryAccount buildAccount(Canary canary, String accountName) { SignalfxCanaryAccount account = @@ -45,6 +65,18 @@ protected AbstractCanaryAccount buildAccount(Canary canary, String accountName) account.setAccessToken(accessToken); + if (isSet(defaultScopeKey)) { + account.setDefaultScopeKey(defaultScopeKey); + } + + if (isSet(defaultLocationKey)) { + account.setDefaultLocationKey(defaultLocationKey); + } + + if (isSet(baseUrl)) { + account.setEndpoint(new SignalfxCanaryAccount.Endpoint().setBaseUrl(baseUrl)); + } + return account; } diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/canary/signalfx/account/SignalfxEditCanaryAccountCommand.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/canary/signalfx/account/SignalfxEditCanaryAccountCommand.java index 5af07d800c..f63949092b 100644 --- a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/canary/signalfx/account/SignalfxEditCanaryAccountCommand.java +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/canary/signalfx/account/SignalfxEditCanaryAccountCommand.java @@ -34,10 +34,42 @@ protected String getServiceIntegration() { @Parameter(names = "--access-token", password = true, description = "The SignalFx access token.") private String accessToken; + @Parameter( + names = "--base-url", + required = false, + description = "The base URL to the SignalFx server. Defaults to https://stream.signalfx.com") + private String baseUrl; + + @Parameter( + names = "--default-scope-key", + required = false, + description = + "Scope key is used to distinguish between base and canary deployments. If omitted every request must supply the _scope_key param in extended scope params") + private String defaultScopeKey; + + @Parameter( + names = "--default-location-key", + required = false, + description = + "Location key is used to filter by deployment region. If omitted requests must supply the _location_key if it is needed.") + private String defaultLocationKey; + @Override protected AbstractCanaryAccount editAccount(SignalfxCanaryAccount account) { account.setAccessToken(isSet(accessToken) ? accessToken : account.getAccessToken()); + if (isSet(defaultScopeKey)) { + account.setDefaultScopeKey(defaultScopeKey); + } + + if (isSet(defaultLocationKey)) { + account.setDefaultLocationKey(defaultLocationKey); + } + + if (isSet(baseUrl)) { + account.setEndpoint(new SignalfxCanaryAccount.Endpoint().setBaseUrl(baseUrl)); + } + return account; } } diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/metricStores/AbstractEditMetricStoreCommand.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/metricStores/AbstractEditMetricStoreCommand.java index bb6461f7f7..f491533815 100644 --- a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/metricStores/AbstractEditMetricStoreCommand.java +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/metricStores/AbstractEditMetricStoreCommand.java @@ -40,7 +40,7 @@ public abstract class AbstractEditMetricStoreCommand protected abstract MetricStore editMetricStore(T metricStore); public String getShortDescription() { - return "Edit the " + getMetricStoreType().getId() + " authentication method."; + return "Edit the " + getMetricStoreType().getId() + " metric store."; } @Override diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/metricStores/newrelic/EditNewrelicCommand.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/metricStores/newrelic/EditNewrelicCommand.java new file mode 100644 index 0000000000..02b250a554 --- /dev/null +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/metricStores/newrelic/EditNewrelicCommand.java @@ -0,0 +1,76 @@ +/* + * Copyright 2019 New Relic Corporation. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License") + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.netflix.spinnaker.halyard.cli.command.v1.config.metricStores.newrelic; + +import com.beust.jcommander.Parameter; +import com.beust.jcommander.Parameters; +import com.netflix.spinnaker.halyard.cli.command.v1.config.metricStores.AbstractEditMetricStoreCommand; +import com.netflix.spinnaker.halyard.config.model.v1.metricStores.newrelic.NewrelicStore; +import com.netflix.spinnaker.halyard.config.model.v1.node.MetricStore; +import com.netflix.spinnaker.halyard.config.model.v1.node.MetricStores; +import java.util.ArrayList; +import java.util.List; + +@Parameters(separators = "=") +public class EditNewrelicCommand extends AbstractEditMetricStoreCommand { + public MetricStores.MetricStoreType getMetricStoreType() { + return MetricStores.MetricStoreType.NEWRELIC; + } + + @Parameter(names = "--insert-key", description = "Your New Relic Insights insert key") + private String insertKey; + + @Parameter( + names = "--host", + description = + "The URL to post metric data to. In almost all cases, this is set correctly by default and should not be used.") + private String host; + + @Parameter( + names = "--tags", + variableArity = true, + description = + "Your custom tags. Please delimit the KVP with colons i.e. --tags app:test env:dev") + private List tags = new ArrayList<>(); + + @Parameter( + names = "--add-tag", + description = + "Add this tag to the list of tags. Use the format key:value i.e. --add-tag app:test") + private String addTag; + + @Parameter( + names = "--remove-tag", + description = + "Remove this tag from the list of tags. Use the name of the tag you want to remove i.e. --remove-tag app") + private String removeTag; + + @Override + protected MetricStore editMetricStore(NewrelicStore newrelicStore) { + newrelicStore.setInsertKey(isSet(insertKey) ? insertKey : newrelicStore.getInsertKey()); + newrelicStore.setHost(isSet(host) ? host : newrelicStore.getHost()); + + try { + newrelicStore.setTags(updateStringList(newrelicStore.getTags(), tags, addTag, removeTag)); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Set either --tags or --[add/remove]-tag"); + } + + return newrelicStore; + } +} diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/metricStores/newrelic/NewrelicCommand.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/metricStores/newrelic/NewrelicCommand.java new file mode 100644 index 0000000000..4f6106da93 --- /dev/null +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/metricStores/newrelic/NewrelicCommand.java @@ -0,0 +1,34 @@ +/* + * Copyright 2019 New Relic Corporation. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License") + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.netflix.spinnaker.halyard.cli.command.v1.config.metricStores.newrelic; + +import com.beust.jcommander.Parameters; +import com.netflix.spinnaker.halyard.cli.command.v1.config.metricStores.MetricStoreCommand; +import com.netflix.spinnaker.halyard.config.model.v1.node.MetricStores; + +@Parameters(separators = "=") +public class NewrelicCommand extends MetricStoreCommand { + public MetricStores.MetricStoreType getMetricStoreType() { + return MetricStores.MetricStoreType.NEWRELIC; + } + + public NewrelicCommand() { + super(); + registerSubcommand(new EditNewrelicCommand()); + } +} diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/notifications/slack/EditSlackCommand.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/notifications/slack/EditSlackCommand.java index 9f19784c7a..edc401949d 100644 --- a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/notifications/slack/EditSlackCommand.java +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/notifications/slack/EditSlackCommand.java @@ -37,10 +37,26 @@ protected String getNotificationName() { @Parameter(names = "--token", password = true, description = "Your slack bot token.") private String token; + @Parameter( + names = "--base-url", + description = "Slack endpoint. Optional, only set if using a compatible API.") + private String baseUrl; + + @Parameter( + names = "--force-use-incoming-webhook", + description = + "Force usage of incoming webhooks endpoint for slack. Optional, only set if using a compatible API.") + private Boolean forceUseIncomingWebhook; + @Override protected Notification editNotification(SlackNotification notification) { + notification.setBaseUrl(isSet(baseUrl) ? baseUrl : notification.getBaseUrl()); notification.setBotName(isSet(botName) ? botName : notification.getBotName()); notification.setToken(isSet(token) ? token : notification.getToken()); + notification.setForceUseIncomingWebhook( + isSet(forceUseIncomingWebhook) + ? forceUseIncomingWebhook + : notification.getForceUseIncomingWebhook()); return notification; } } diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/providers/aws/AwsAddAccountCommand.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/providers/aws/AwsAddAccountCommand.java index 15bb3db998..510fdb002d 100644 --- a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/providers/aws/AwsAddAccountCommand.java +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/providers/aws/AwsAddAccountCommand.java @@ -17,15 +17,20 @@ package com.netflix.spinnaker.halyard.cli.command.v1.config.providers.aws; +import static com.netflix.spinnaker.halyard.cli.command.v1.config.providers.aws.AwsLifecycleHookUtil.getLifecycleHook; + import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameters; import com.netflix.spinnaker.halyard.cli.command.v1.config.providers.account.AbstractAddAccountCommand; import com.netflix.spinnaker.halyard.config.model.v1.node.Account; import com.netflix.spinnaker.halyard.config.model.v1.providers.aws.AwsAccount; import com.netflix.spinnaker.halyard.config.model.v1.providers.aws.AwsProvider; +import com.netflix.spinnaker.halyard.config.model.v1.providers.aws.AwsProvider.AwsLifecycleHook; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; +import java.util.stream.Stream; @Parameters(separators = "=") public class AwsAddAccountCommand extends AbstractAddAccountCommand { @@ -62,6 +67,46 @@ protected String getProviderName() { required = true) private String assumeRole; + @Parameter( + names = "--launching-lifecycle-hook-default-result", + description = AwsCommandProperties.HOOK_DEFAULT_VALUE_DESCRIPTION) + private String launchingHookDefaultResult = "ABANDON"; + + @Parameter( + names = "--launching-lifecycle-hook-heartbeat-timeout-seconds", + description = AwsCommandProperties.HOOK_HEARTBEAT_TIMEOUT_DESCRIPTION) + private Integer launchingHookHeartbeatTimeoutSeconds = 3600; + + @Parameter( + names = "--launching-lifecycle-hook-notification-target-arn", + description = AwsCommandProperties.HOOK_NOTIFICATION_TARGET_ARN) + private String launchingHookNotificationTargetArn; + + @Parameter( + names = "--launching-lifecycle-hook-role-arn", + description = AwsCommandProperties.HOOK_ROLE_ARN_DESCRIPTION) + private String launchingHookRoleArn; + + @Parameter( + names = "--terminating-lifecycle-hook-default-result", + description = AwsCommandProperties.HOOK_DEFAULT_VALUE_DESCRIPTION) + private String terminatingHookDefaultResult = "ABANDON"; + + @Parameter( + names = "--terminating-lifecycle-hook-heartbeat-timeout-seconds", + description = AwsCommandProperties.HOOK_HEARTBEAT_TIMEOUT_DESCRIPTION) + private Integer terminatingHookHeartbeatTimeoutSeconds = 3600; + + @Parameter( + names = "--terminating-lifecycle-hook-notification-target-arn", + description = AwsCommandProperties.HOOK_NOTIFICATION_TARGET_ARN) + private String terminatingHookNotificationTargetArn; + + @Parameter( + names = "--terminating-lifecycle-hook-role-arn", + description = AwsCommandProperties.HOOK_ROLE_ARN_DESCRIPTION) + private String terminatingHookRoleArn; + @Override protected Account buildAccount(String accountName) { AwsAccount account = (AwsAccount) new AwsAccount().setName(accountName); @@ -74,11 +119,35 @@ protected Account buildAccount(String accountName) { regions.stream() .map(r -> new AwsProvider.AwsRegion().setName(r)) .collect(Collectors.toList())) - .setAssumeRole(assumeRole); + .setAssumeRole(assumeRole) + .setLifecycleHooks(getLifecycleHooks()); return account; } + private List getLifecycleHooks() { + Optional initHook = + getLifecycleHook( + "autoscaling:EC2_INSTANCE_LAUNCHING", + launchingHookDefaultResult, + launchingHookNotificationTargetArn, + launchingHookRoleArn, + launchingHookHeartbeatTimeoutSeconds); + + Optional terminatingHook = + getLifecycleHook( + "autoscaling:EC2_INSTANCE_TERMINATING", + terminatingHookDefaultResult, + terminatingHookNotificationTargetArn, + terminatingHookRoleArn, + terminatingHookHeartbeatTimeoutSeconds); + + return Stream.of(initHook, terminatingHook) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toList()); + } + @Override protected Account emptyAccount() { return new AwsAccount(); diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/providers/aws/AwsCommandProperties.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/providers/aws/AwsCommandProperties.java index 66c799c69c..bbed80085b 100644 --- a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/providers/aws/AwsCommandProperties.java +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/providers/aws/AwsCommandProperties.java @@ -50,4 +50,22 @@ public class AwsCommandProperties { + "as described at http://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html#credentials-default"; public static final String SECRET_KEY_DESCRIPTION = "Your AWS Secret Key."; + + public static final String HOOK_DEFAULT_VALUE_DESCRIPTION = + "Defines the action the Auto Scaling group should take when " + + "the lifecycle hook timeout elapses or if an unexpected failure occurs. This parameter can be either CONTINUE or ABANDON. " + + "The default value is ABANDON."; + + public static final String HOOK_HEARTBEAT_TIMEOUT_DESCRIPTION = + "Set the heartbeat timeout for the lifecycle hook. Instances can \" +\n" + + " \"remain in a wait state for a finite period of time. The default is one hour (3600 seconds)."; + + public static final String HOOK_NOTIFICATION_TARGET_ARN = + "The ARN of the notification target that Amazon EC2 Auto Scaling " + + "uses to notify you when an instance is in the transition state for the lifecycle hook. This target can be either " + + "an SQS queue or an SNS topic.\n"; + + public static final String HOOK_ROLE_ARN_DESCRIPTION = + "The ARN of the IAM role that allows the Auto Scaling group " + + "to publish to the specified notification target, for example, an Amazon SNS topic or an Amazon SQS queue."; } diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/providers/aws/AwsEditAccountCommand.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/providers/aws/AwsEditAccountCommand.java index bfe920ea87..fc0d2281d6 100644 --- a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/providers/aws/AwsEditAccountCommand.java +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/providers/aws/AwsEditAccountCommand.java @@ -17,14 +17,19 @@ package com.netflix.spinnaker.halyard.cli.command.v1.config.providers.aws; +import static com.netflix.spinnaker.halyard.cli.command.v1.config.providers.aws.AwsLifecycleHookUtil.getLifecycleHook; + import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameters; import com.netflix.spinnaker.halyard.cli.command.v1.config.providers.account.AbstractEditAccountCommand; import com.netflix.spinnaker.halyard.config.model.v1.node.Account; import com.netflix.spinnaker.halyard.config.model.v1.providers.aws.AwsAccount; import com.netflix.spinnaker.halyard.config.model.v1.providers.aws.AwsProvider; +import com.netflix.spinnaker.halyard.config.model.v1.providers.aws.AwsProvider.AwsLifecycleHook; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; +import java.util.stream.Stream; @Parameters(separators = "=") public class AwsEditAccountCommand extends AbstractEditAccountCommand { @@ -65,6 +70,46 @@ protected String getProviderName() { @Parameter(names = "--assume-role", description = AwsCommandProperties.ASSUME_ROLE_DESCRIPTION) private String assumeRole; + @Parameter( + names = "--launching-lifecycle-hook-default-result", + description = AwsCommandProperties.HOOK_DEFAULT_VALUE_DESCRIPTION) + private String launchingHookDefaultResult = "ABANDON"; + + @Parameter( + names = "--launching-lifecycle-hook-heartbeat-timeout-seconds", + description = AwsCommandProperties.HOOK_HEARTBEAT_TIMEOUT_DESCRIPTION) + private Integer launchingHookHeartbeatTimeoutSeconds = 3600; + + @Parameter( + names = "--launching-lifecycle-hook-notification-target-arn", + description = AwsCommandProperties.HOOK_NOTIFICATION_TARGET_ARN) + private String launchingHookNotificationTargetArn; + + @Parameter( + names = "--launching-lifecycle-hook-role-arn", + description = AwsCommandProperties.HOOK_ROLE_ARN_DESCRIPTION) + private String launchingHookRoleArn; + + @Parameter( + names = "--terminating-lifecycle-hook-default-result", + description = AwsCommandProperties.HOOK_DEFAULT_VALUE_DESCRIPTION) + private String terminatingHookDefaultResult = "ABANDON"; + + @Parameter( + names = "--terminating-lifecycle-hook-heartbeat-timeout-seconds", + description = AwsCommandProperties.HOOK_HEARTBEAT_TIMEOUT_DESCRIPTION) + private Integer terminatingHookHeartbeatTimeoutSeconds = 3600; + + @Parameter( + names = "--terminating-lifecycle-hook-notification-target-arn", + description = AwsCommandProperties.HOOK_NOTIFICATION_TARGET_ARN) + private String terminatingHookNotificationTargetArn; + + @Parameter( + names = "--terminating-lifecycle-hook-role-arn", + description = AwsCommandProperties.HOOK_ROLE_ARN_DESCRIPTION) + private String terminatingHookRoleArn; + @Override protected Account editAccount(AwsAccount account) { account.setDefaultKeyPair(isSet(defaultKeyPair) ? defaultKeyPair : account.getDefaultKeyPair()); @@ -73,6 +118,9 @@ protected Account editAccount(AwsAccount account) { account.setAccountId(isSet(accountId) ? accountId : account.getAccountId()); account.setAssumeRole(isSet(assumeRole) ? assumeRole : account.getAssumeRole()); + List hooks = getLifecycleHooks(); + account.setLifecycleHooks(!hooks.isEmpty() ? hooks : account.getLifecycleHooks()); + try { List existingRegions = account.getRegions().stream() @@ -89,4 +137,27 @@ protected Account editAccount(AwsAccount account) { return account; } + + private List getLifecycleHooks() { + Optional initHook = + getLifecycleHook( + "autoscaling:EC2_INSTANCE_LAUNCHING", + launchingHookDefaultResult, + launchingHookNotificationTargetArn, + launchingHookRoleArn, + launchingHookHeartbeatTimeoutSeconds); + + Optional terminatingHook = + getLifecycleHook( + "autoscaling:EC2_INSTANCE_TERMINATING", + terminatingHookDefaultResult, + terminatingHookNotificationTargetArn, + terminatingHookRoleArn, + terminatingHookHeartbeatTimeoutSeconds); + + return Stream.of(initHook, terminatingHook) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toList()); + } } diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/providers/aws/AwsLifecycleHookUtil.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/providers/aws/AwsLifecycleHookUtil.java new file mode 100644 index 0000000000..6222c10f2b --- /dev/null +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/providers/aws/AwsLifecycleHookUtil.java @@ -0,0 +1,37 @@ +package com.netflix.spinnaker.halyard.cli.command.v1.config.providers.aws; + +import static org.apache.commons.lang3.StringUtils.isBlank; + +import com.netflix.spinnaker.halyard.config.model.v1.providers.aws.AwsProvider.AwsLifecycleHook; +import java.util.Optional; + +public class AwsLifecycleHookUtil { + + public static Optional getLifecycleHook( + String transition, + String defaultResult, + String notificationTargetArn, + String roleArn, + Integer heartbeatTimeoutSeconds) { + + if (isBlank(notificationTargetArn) || isBlank(roleArn) || isBlank(defaultResult)) { + return Optional.empty(); + } + + if (!notificationTargetArn.contains(":")) { + notificationTargetArn = "arn:aws:sns:{{region}}:{{accountId}}:" + notificationTargetArn; + } + + if (!roleArn.contains(":")) { + roleArn = "arn:aws:iam::{{accountId}}:" + roleArn; + } + + return Optional.of( + new AwsLifecycleHook() + .setLifecycleTransition(transition) + .setDefaultResult(defaultResult.trim()) + .setHeartbeatTimeout(heartbeatTimeoutSeconds) + .setNotificationTargetARN(notificationTargetArn.trim()) + .setRoleARN(roleArn.trim())); + } +} diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/providers/kubernetes/KubernetesAddAccountCommand.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/providers/kubernetes/KubernetesAddAccountCommand.java index 6f2e67b991..f05b2812bc 100644 --- a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/providers/kubernetes/KubernetesAddAccountCommand.java +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/providers/kubernetes/KubernetesAddAccountCommand.java @@ -143,6 +143,7 @@ protected Account buildAccount(String accountName) { account.setCheckPermissionsOnStartup(checkPermissionsOnStartup); account.setLiveManifestCalls(liveManifestCalls); account.setCacheThreads(cacheThreads); + return account; } diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/providers/kubernetes/KubernetesCommandProperties.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/providers/kubernetes/KubernetesCommandProperties.java index bc5209aa7b..cf6ed38617 100644 --- a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/providers/kubernetes/KubernetesCommandProperties.java +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/providers/kubernetes/KubernetesCommandProperties.java @@ -69,4 +69,7 @@ public class KubernetesCommandProperties { static final String CACHE_THREADS = "Number of caching agents for this kubernetes account. Each agent handles a subset of the namespaces available to this account. " + "By default, only 1 agent caches all kinds for all namespaces in the account."; + static final String CUSTOM_RESOURCES = + "(V2 Only) Add Kubernetes custom resource to the list of custom resources to managed by clouddriver and made available for use in patch and delete manifest stages. " + + "Fields besides the Kubernetes Kind (resource name) can be set using the flags \"--spinnaker-kind\" and \"--versioned\""; } diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/providers/kubernetes/KubernetesEditAccountCommand.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/providers/kubernetes/KubernetesEditAccountCommand.java index bad35b6615..ce7cddf5cf 100644 --- a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/providers/kubernetes/KubernetesEditAccountCommand.java +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/providers/kubernetes/KubernetesEditAccountCommand.java @@ -154,6 +154,31 @@ protected String getProviderName() { @Parameter(names = "--naming-strategy", hidden = true) public String namingStrategy; + @Parameter( + names = "--add-custom-resource", + arity = 1, + description = KubernetesCommandProperties.CUSTOM_RESOURCES) + public String addCustomResourceName; + + @Parameter( + names = "--spinnaker-kind", + arity = 1, + description = "Set the Spinnaker kind for custom resource being added.") + public String addCustomResourceSpinnakerKind; + + @Parameter( + names = "--versioned", + arity = 1, + description = "Configure whether the custom resource being added is versioned by Spinnaker.") + public Boolean addCustomResourceVersioned; + + @Parameter( + names = "--remove-custom-resource", + arity = 1, + description = + "Remove this Kubernetes custom resource by name from the list of custom resources to manage.") + public String removeCustomResource; + @Parameter( names = "--service-account", arity = 1, @@ -244,6 +269,36 @@ protected Account editAccount(KubernetesAccount account) { throw new IllegalArgumentException("Set either --omit-kinds or --[add/remove]-omit-kind"); } + if (isSet(addCustomResourceName)) { + KubernetesAccount.CustomKubernetesResource customKubernetesResource = + new KubernetesAccount.CustomKubernetesResource(); + customKubernetesResource.setKubernetesKind(addCustomResourceName); + customKubernetesResource.setSpinnakerKind( + isSet(addCustomResourceSpinnakerKind) + ? addCustomResourceSpinnakerKind + : customKubernetesResource.getSpinnakerKind()); + customKubernetesResource.setVersioned( + isSet(addCustomResourceVersioned) + ? addCustomResourceVersioned + : customKubernetesResource.isVersioned()); + account.getCustomResources().add(customKubernetesResource); + } else { + if (isSet(addCustomResourceSpinnakerKind) || isSet(addCustomResourceVersioned)) { + throw new IllegalArgumentException( + "\"--spinnaker-kind\" and \"--versioned\" can only be used with \"--add-custom-resource\" " + + "to set the name for the custom resource."); + } + } + + if (isSet(removeCustomResource)) { + List newCustomResources = + account.getCustomResources().stream() + .filter(entry -> !entry.getKubernetesKind().equals(removeCustomResource)) + .collect(Collectors.toList()); + + account.setCustomResources(newCustomResources); + } + try { List oldRegistries = account.getDockerRegistries().stream() diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/security/authn/saml/EditSamlCommand.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/security/authn/saml/EditSamlCommand.java index 72fa086bb9..e7837bc154 100644 --- a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/security/authn/saml/EditSamlCommand.java +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/security/authn/saml/EditSamlCommand.java @@ -106,6 +106,11 @@ public class EditSamlCommand extends AbstractEditAuthnMethodCommand { description = "The roles delimiter field returned from your SAML provider.") private String userAttributeMappingRolesDelimiter; + @Parameter( + names = "--user-attribute-mapping-email", + description = "The email field returned from your SAML provider.") + private String userAttributeMappingEmail; + @Override protected AuthnMethod editAuthnMethod(Saml s) { s.setIssuerId(isSet(issuerId) ? issuerId : s.getIssuerId()); @@ -145,6 +150,10 @@ protected AuthnMethod editAuthnMethod(Saml s) { isSet(userAttributeMappingUsername) ? userAttributeMappingUsername : userAttributeMapping.getUsername()); + userAttributeMapping.setEmail( + isSet(userAttributeMappingEmail) + ? userAttributeMappingEmail + : userAttributeMapping.getEmail()); return s; } diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/telemetry/AbstractEnableDisableTelemetryCommand.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/telemetry/AbstractEnableDisableTelemetryCommand.java new file mode 100644 index 0000000000..5bd33822af --- /dev/null +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/telemetry/AbstractEnableDisableTelemetryCommand.java @@ -0,0 +1,64 @@ +/* + * Copyright 2019 Armory, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License") + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.halyard.cli.command.v1.config.telemetry; + +import com.beust.jcommander.Parameters; +import com.netflix.spinnaker.halyard.cli.command.v1.NestableCommand; +import com.netflix.spinnaker.halyard.cli.command.v1.config.AbstractConfigCommand; +import com.netflix.spinnaker.halyard.cli.services.v1.Daemon; +import com.netflix.spinnaker.halyard.cli.services.v1.OperationHandler; +import java.util.HashMap; +import java.util.Map; +import lombok.AccessLevel; +import lombok.Getter; + +@Parameters(separators = "=") +public abstract class AbstractEnableDisableTelemetryCommand extends AbstractConfigCommand { + @Override + public String getCommandName() { + return isEnable() ? "enable" : "disable"; + } + + private String subjunctivePerfectAction() { + return isEnable() ? "enabled" : "disabled"; + } + + private String indicativePastPerfectAction() { + return isEnable() ? "enabled" : "disabled"; + } + + protected abstract boolean isEnable(); + + @Getter(AccessLevel.PROTECTED) + private Map subcommands = new HashMap<>(); + + @Override + public String getShortDescription() { + return "Set Spinnaker's telemetry settings to " + subjunctivePerfectAction() + "."; + } + + @Override + protected void executeThis() { + String currentDeployment = getCurrentDeployment(); + boolean enable = isEnable(); + new OperationHandler() + .setOperation(Daemon.setTelemetryEnableDisable(currentDeployment, !noValidate, enable)) + .setFailureMesssage("Failed to " + getCommandName() + " telemetry settings.") + .setSuccessMessage("Successfully " + indicativePastPerfectAction() + " telemetry settings.") + .get(); + } +} diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/telemetry/TelemetryCommand.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/telemetry/TelemetryCommand.java new file mode 100644 index 0000000000..3a588913bc --- /dev/null +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/telemetry/TelemetryCommand.java @@ -0,0 +1,54 @@ +/* + * Copyright 2019 Armory, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License") + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.halyard.cli.command.v1.config.telemetry; + +import com.beust.jcommander.Parameters; +import com.netflix.spinnaker.halyard.cli.command.v1.config.AbstractConfigCommand; +import com.netflix.spinnaker.halyard.cli.services.v1.Daemon; +import com.netflix.spinnaker.halyard.cli.services.v1.OperationHandler; +import com.netflix.spinnaker.halyard.cli.ui.v1.AnsiFormatUtils; +import com.netflix.spinnaker.halyard.config.model.v1.node.Telemetry; +import lombok.AccessLevel; +import lombok.Getter; + +@Parameters(separators = "=") +public class TelemetryCommand extends AbstractConfigCommand { + @Getter(AccessLevel.PUBLIC) + private String commandName = "telemetry"; + + @Getter(AccessLevel.PUBLIC) + private String shortDescription = "Show Spinnaker's telemetry settings."; + + public TelemetryCommand() { + registerSubcommand(new TelemetryEditCommand()); + registerSubcommand(new TelemetryEnableDisableCommandBuilder().setEnable(true).build()); + registerSubcommand(new TelemetryEnableDisableCommandBuilder().setEnable(false).build()); + } + + @Override + protected void executeThis() { + String currentDeployment = getCurrentDeployment(); + + new OperationHandler() + .setOperation(Daemon.getTelemetry(currentDeployment, !noValidate)) + .setFailureMesssage("Failed to load telemetry.") + .setSuccessMessage("Configured telemetry: ") + .setFormat(AnsiFormatUtils.Format.STRING) + .setUserFormatted(true) + .get(); + } +} diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/telemetry/TelemetryEditCommand.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/telemetry/TelemetryEditCommand.java new file mode 100644 index 0000000000..055e870a36 --- /dev/null +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/telemetry/TelemetryEditCommand.java @@ -0,0 +1,58 @@ +/* + * Copyright 2019 Armory, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License") + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.halyard.cli.command.v1.config.telemetry; + +import com.beust.jcommander.Parameter; +import com.beust.jcommander.Parameters; +import com.netflix.spinnaker.halyard.cli.command.v1.config.AbstractConfigCommand; +import com.netflix.spinnaker.halyard.cli.services.v1.Daemon; +import com.netflix.spinnaker.halyard.cli.services.v1.OperationHandler; +import com.netflix.spinnaker.halyard.config.model.v1.node.Telemetry; +import lombok.AccessLevel; +import lombok.Getter; + +@Parameters(separators = "=") +public class TelemetryEditCommand extends AbstractConfigCommand { + @Getter(AccessLevel.PUBLIC) + private String commandName = "edit"; + + @Getter(AccessLevel.PUBLIC) + private String shortDescription = "Edit Spinnaker's telemetry settings."; + + @Parameter(names = "--endpoint", description = "Set the endpoint for telemetry metrics.") + private String endpoint; + + @Override + protected void executeThis() { + String currentDeployment = getCurrentDeployment(); + Telemetry telemetry = + new OperationHandler() + .setOperation(Daemon.getTelemetry(currentDeployment, false)) + .setFailureMesssage("Failed to load telemetry settings.") + .get(); + + if (isSet(endpoint)) { + telemetry.setEndpoint(endpoint); + } + + new OperationHandler() + .setOperation(Daemon.setTelemetry(currentDeployment, !noValidate, telemetry)) + .setFailureMesssage("Failed to edit telemetry settings.") + .setSuccessMessage("Successfully edited telemetry settings.") + .get(); + } +} diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/telemetry/TelemetryEnableDisableCommandBuilder.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/telemetry/TelemetryEnableDisableCommandBuilder.java new file mode 100644 index 0000000000..b7a24521ee --- /dev/null +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/telemetry/TelemetryEnableDisableCommandBuilder.java @@ -0,0 +1,43 @@ +/* + * Copyright 2019 Armory, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License") + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.halyard.cli.command.v1.config.telemetry; + +import com.beust.jcommander.Parameters; +import com.netflix.spinnaker.halyard.cli.command.v1.CommandBuilder; +import com.netflix.spinnaker.halyard.cli.command.v1.NestableCommand; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; + +public class TelemetryEnableDisableCommandBuilder implements CommandBuilder { + @Setter boolean enable; + + @Override + public NestableCommand build() { + return new TelemetryEnableDisableCommand(enable); + } + + @Parameters(separators = "=") + private static class TelemetryEnableDisableCommand extends AbstractEnableDisableTelemetryCommand { + private TelemetryEnableDisableCommand(boolean enable) { + this.enable = enable; + } + + @Getter(AccessLevel.PROTECTED) + boolean enable; + } +} diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/services/v1/Daemon.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/services/v1/Daemon.java index 0923a34c52..3f754ea244 100644 --- a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/services/v1/Daemon.java +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/services/v1/Daemon.java @@ -1376,6 +1376,30 @@ public static Supplier getManifest( getService().getManifest(deploymentName, serviceName, apiGroupVersion)); } + public static Supplier getTelemetry(String deploymentName, boolean validate) { + return () -> { + Object rawTelemetry = + ResponseUnwrapper.get(getService().getTelemetry(deploymentName, validate)); + return getObjectMapper().convertValue(rawTelemetry, new TypeReference() {}); + }; + } + + public static Supplier setTelemetryEnableDisable( + String deploymentName, boolean validate, boolean enable) { + return () -> { + ResponseUnwrapper.get(getService().setTelemetryEnabled(deploymentName, validate, enable)); + return null; + }; + } + + public static Supplier setTelemetry( + String deploymentName, boolean validate, Telemetry telemetry) { + return () -> { + ResponseUnwrapper.get(getService().setTelemetry(deploymentName, validate, telemetry)); + return null; + }; + } + private static DaemonService service; private static ObjectMapper objectMapper; diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/services/v1/DaemonService.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/services/v1/DaemonService.java index ecd2fe7fb4..353c37e63d 100644 --- a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/services/v1/DaemonService.java +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/services/v1/DaemonService.java @@ -927,6 +927,22 @@ DaemonTask deletePlugin( @Path("pluginName") String pluginName, @Query("validate") boolean validate); + @GET("/v1/config/deployments/{deploymentName}/telemetry/") + DaemonTask getTelemetry( + @Path("deploymentName") String deploymentName, @Query("validate") boolean validate); + + @PUT("/v1/config/deployments/{deploymentName}/telemetry/") + DaemonTask setTelemetry( + @Path("deploymentName") String deploymentName, + @Query("validate") boolean validate, + @Body Telemetry telemetry); + + @PUT("/v1/config/deployments/{deploymentName}/telemetry/enabled/") + DaemonTask setTelemetryEnabled( + @Path("deploymentName") String deploymentName, + @Query("validate") boolean validate, + @Body boolean enabled); + @GET("/v1/spin/install/latest") DaemonTask installSpin(); diff --git a/halyard-cli/src/test/groovy/com/netflix/spinnaker/halyard/cli/command/v1/CommandTreeSpec.groovy b/halyard-cli/src/test/groovy/com/netflix/spinnaker/halyard/cli/command/v1/CommandTreeSpec.groovy index 6175df9f61..5affbf6463 100644 --- a/halyard-cli/src/test/groovy/com/netflix/spinnaker/halyard/cli/command/v1/CommandTreeSpec.groovy +++ b/halyard-cli/src/test/groovy/com/netflix/spinnaker/halyard/cli/command/v1/CommandTreeSpec.groovy @@ -37,6 +37,8 @@ import com.netflix.spinnaker.halyard.cli.command.v1.config.security.authn.saml.S import com.netflix.spinnaker.halyard.cli.command.v1.config.security.authn.x509.X509Command import com.netflix.spinnaker.halyard.cli.command.v1.config.security.authz.AuthzCommand import com.netflix.spinnaker.halyard.cli.command.v1.config.security.ui.UiSecurityCommand +import com.netflix.spinnaker.halyard.cli.command.v1.config.telemetry.TelemetryCommand +import com.netflix.spinnaker.halyard.cli.command.v1.config.telemetry.TelemetryEnableDisableCommandBuilder import com.netflix.spinnaker.halyard.cli.command.v1.plugins.AddPluginCommand import com.netflix.spinnaker.halyard.cli.command.v1.plugins.DeletePluginCommand import com.netflix.spinnaker.halyard.cli.command.v1.plugins.EditPluginCommand @@ -99,6 +101,7 @@ class CommandTreeSpec extends Specification { ConfigCommand | "security" | SecurityCommand ConfigCommand | "version" | VersionConfigCommand ConfigCommand | "ci" | CiCommand + ConfigCommand | "telemetry" | TelemetryCommand SecurityCommand | "api" | ApiSecurityCommand SecurityCommand | "authn" | AuthnCommand diff --git a/halyard-config/halyard-config.gradle b/halyard-config/halyard-config.gradle index d7602b0d52..64ced41982 100644 --- a/halyard-config/halyard-config.gradle +++ b/halyard-config/halyard-config.gradle @@ -15,6 +15,7 @@ dependencies { implementation "com.netflix.spinnaker.front50:front50-s3:$front50Version" implementation 'com.netflix.spinnaker.kork:kork-secrets' implementation 'com.netflix.spinnaker.kork:kork-secrets-aws' + implementation 'com.netflix.spinnaker.kork:kork-secrets-gcp' implementation "com.netflix.spinnaker.kork:kork-web" implementation 'com.amazonaws:aws-java-sdk-core:1.11.534' implementation 'com.amazonaws:aws-java-sdk-s3:1.11.534' @@ -28,6 +29,7 @@ dependencies { implementation 'io.fabric8:kubernetes-client:4.1.1' implementation 'com.squareup.retrofit:retrofit' implementation 'com.jcraft:jsch' + implementation 'de.huxhorn.sulky:de.huxhorn.sulky.ulid' // TODO: add clouddriverDCOS once that's merged implementation project(':halyard-core') diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/config/v1/HalconfigDirectoryStructure.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/config/v1/HalconfigDirectoryStructure.java index b1b750fc90..7334c34a7f 100644 --- a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/config/v1/HalconfigDirectoryStructure.java +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/config/v1/HalconfigDirectoryStructure.java @@ -130,7 +130,7 @@ public Path getCachePath() { } public Path getSpinInstallScriptPath() { - return ensureDirectory((Paths.get("/opt/spin/install/install-spin.sh"))); + return ensureDirectory(Paths.get("/opt/spin/install/install-spin.sh")); } public Path getBackupConfigPath() { diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/config/v1/HalconfigParser.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/config/v1/HalconfigParser.java index c52bd56b8b..5194ad505c 100644 --- a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/config/v1/HalconfigParser.java +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/config/v1/HalconfigParser.java @@ -36,7 +36,6 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -61,8 +60,6 @@ @Slf4j @Component public class HalconfigParser { - @Autowired String halyardVersion; - @Autowired StrictObjectMapper objectMapper; @Autowired HalconfigDirectoryStructure halconfigDirectoryStructure; @@ -105,8 +102,8 @@ private InputStream getHalconfigStream() throws FileNotFoundException { String baseDirectory = useBackup ? halconfigDirectoryStructure.getBackupConfigPath().toString() - : halconfigDirectoryStructure.getHalconfigDirectory(); - return new FileInputStream(new File(baseDirectory, "config")); + : halconfigDirectoryStructure.getHalconfigPath(); + return new FileInputStream(new File(baseDirectory)); } /** @@ -167,7 +164,7 @@ public void cleanLocalFiles(Path stagingDirectoryPath) { return; } Halconfig halconfig = getHalconfig(); - Set referencedFiles = new HashSet(); + Set referencedFiles = new HashSet<>(); Consumer fileFinder = n -> referencedFiles.addAll( @@ -189,12 +186,11 @@ public void cleanLocalFiles(Path stagingDirectoryPath) { halconfig.recursiveConsume(fileFinder); Set existingStagingFiles = - ((List) - FileUtils.listFiles( - stagingDirectoryPath.toFile(), - TrueFileFilter.INSTANCE, - TrueFileFilter.INSTANCE)) - .stream().map(f -> f.getAbsolutePath()).collect(Collectors.toSet()); + FileUtils.listFiles( + stagingDirectoryPath.toFile(), TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE) + .stream() + .map(f -> f.getAbsolutePath()) + .collect(Collectors.toSet()); existingStagingFiles.removeAll(referencedFiles); diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/config/v1/ResourceConfig.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/config/v1/ResourceConfig.java index 3d9bad2791..9d9a9276f6 100644 --- a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/config/v1/ResourceConfig.java +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/config/v1/ResourceConfig.java @@ -32,6 +32,9 @@ @Component public class ResourceConfig { + + public static final String DEFAULT_HALCONFIG_BUCKET = "halconfig"; + /** * Directory containing the halconfig. * @@ -50,6 +53,7 @@ TaskScheduler taskScheduler() { @Bean String localBomPath(@Value("${halyard.halconfig.directory:~/.hal}") String path) { + return normalizePath(Paths.get(path, ".boms").toString()); } @@ -73,6 +77,11 @@ String spinconfigBucket( return spinconfigBucket; } + @Bean + Boolean gcsEnabled(@Value("${spinnaker.config.input.gcs.enabled:true}") boolean gcsEnabled) { + return gcsEnabled; + } + @Bean String gitRoot(@Value("${git.root:~/dev/spinnaker}") String gitRoot) { return normalizePath(gitRoot); diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/artifacts/gitrepo/GitRepoArtifactAccount.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/artifacts/gitrepo/GitRepoArtifactAccount.java new file mode 100644 index 0000000000..902abbf1d8 --- /dev/null +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/artifacts/gitrepo/GitRepoArtifactAccount.java @@ -0,0 +1,41 @@ +/* + * Copyright 2019 Armory, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License") + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + */ + +package com.netflix.spinnaker.halyard.config.model.v1.artifacts.gitrepo; + +import com.netflix.spinnaker.halyard.config.model.v1.node.ArtifactAccount; +import com.netflix.spinnaker.halyard.config.model.v1.node.LocalFile; +import com.netflix.spinnaker.halyard.config.model.v1.node.Secret; +import com.netflix.spinnaker.halyard.config.model.v1.node.SecretFile; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class GitRepoArtifactAccount extends ArtifactAccount { + String name; + String username; + @Secret String password; + @LocalFile @SecretFile String usernamePasswordFile; + @Secret String token; + @LocalFile @SecretFile String tokenFile; + @LocalFile @SecretFile String sshPrivateKeyFilePath; + @Secret String sshPrivateKeyPassphrase; + @LocalFile @SecretFile String sshKnownHostsFilePath; + boolean sshTrustUnknownHosts; +} diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/artifacts/gitrepo/GitRepoArtifactProvider.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/artifacts/gitrepo/GitRepoArtifactProvider.java new file mode 100644 index 0000000000..beb291d831 --- /dev/null +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/artifacts/gitrepo/GitRepoArtifactProvider.java @@ -0,0 +1,32 @@ +/* + * Copyright 2019 Armory, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License") + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + */ + +package com.netflix.spinnaker.halyard.config.model.v1.artifacts.gitrepo; + +import com.netflix.spinnaker.halyard.config.model.v1.node.ArtifactProvider; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +public class GitRepoArtifactProvider extends ArtifactProvider { + @Override + public ProviderType providerType() { + return ProviderType.GITREPO; + } +} diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/canary/AbstractCanaryServiceIntegration.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/canary/AbstractCanaryServiceIntegration.java index 2442cf8050..81d8aae58c 100644 --- a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/canary/AbstractCanaryServiceIntegration.java +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/canary/AbstractCanaryServiceIntegration.java @@ -21,6 +21,7 @@ import com.netflix.spinnaker.halyard.config.model.v1.canary.aws.AwsCanaryServiceIntegration; import com.netflix.spinnaker.halyard.config.model.v1.canary.datadog.DatadogCanaryServiceIntegration; import com.netflix.spinnaker.halyard.config.model.v1.canary.google.GoogleCanaryServiceIntegration; +import com.netflix.spinnaker.halyard.config.model.v1.canary.newrelic.NewRelicCanaryServiceIntegration; import com.netflix.spinnaker.halyard.config.model.v1.canary.prometheus.PrometheusCanaryServiceIntegration; import com.netflix.spinnaker.halyard.config.model.v1.canary.signalfx.SignalfxCanaryServiceIntegration; import com.netflix.spinnaker.halyard.config.model.v1.node.Node; @@ -48,7 +49,10 @@ name = SignalfxCanaryServiceIntegration.NAME), @JsonSubTypes.Type( value = AwsCanaryServiceIntegration.class, - name = AwsCanaryServiceIntegration.NAME) + name = AwsCanaryServiceIntegration.NAME), + @JsonSubTypes.Type( + value = NewRelicCanaryServiceIntegration.class, + name = NewRelicCanaryServiceIntegration.NAME) }) @Data @EqualsAndHashCode(callSuper = false) diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/canary/Canary.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/canary/Canary.java index e8226b7579..6d8c3c5c5e 100644 --- a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/canary/Canary.java +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/canary/Canary.java @@ -23,6 +23,8 @@ import com.netflix.spinnaker.halyard.config.model.v1.canary.datadog.DatadogCanaryServiceIntegration; import com.netflix.spinnaker.halyard.config.model.v1.canary.google.GoogleCanaryAccount; import com.netflix.spinnaker.halyard.config.model.v1.canary.google.GoogleCanaryServiceIntegration; +import com.netflix.spinnaker.halyard.config.model.v1.canary.newrelic.NewRelicCanaryAccount; +import com.netflix.spinnaker.halyard.config.model.v1.canary.newrelic.NewRelicCanaryServiceIntegration; import com.netflix.spinnaker.halyard.config.model.v1.canary.prometheus.PrometheusCanaryAccount; import com.netflix.spinnaker.halyard.config.model.v1.canary.prometheus.PrometheusCanaryServiceIntegration; import com.netflix.spinnaker.halyard.config.model.v1.canary.signalfx.SignalfxCanaryAccount; @@ -45,7 +47,9 @@ public class Canary extends Node implements Cloneable { new PrometheusCanaryServiceIntegration(), new DatadogCanaryServiceIntegration(), new SignalfxCanaryServiceIntegration(), - new AwsCanaryServiceIntegration()); + new AwsCanaryServiceIntegration(), + new NewRelicCanaryServiceIntegration()); + boolean reduxLoggerEnabled = true; String defaultMetricsAccount; String defaultStorageAccount; @@ -80,6 +84,8 @@ public static Class translateCanaryAccountType( return SignalfxCanaryAccount.class; case AwsCanaryServiceIntegration.NAME: return AwsCanaryAccount.class; + case NewRelicCanaryServiceIntegration.NAME: + return NewRelicCanaryAccount.class; default: throw new IllegalArgumentException( "No account type for canary service integration " + serviceIntegrationName + "."); diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/canary/newrelic/NewRelicCanaryAccount.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/canary/newrelic/NewRelicCanaryAccount.java new file mode 100644 index 0000000000..365a0a6079 --- /dev/null +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/canary/newrelic/NewRelicCanaryAccount.java @@ -0,0 +1,44 @@ +/* + * Copyright 2019 New Relic Corporation. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License") + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.halyard.config.model.v1.canary.newrelic; + +import com.netflix.spinnaker.halyard.config.model.v1.canary.AbstractCanaryAccount; +import com.netflix.spinnaker.halyard.config.model.v1.canary.AbstractCanaryServiceIntegration; +import com.netflix.spinnaker.halyard.config.model.v1.node.Secret; +import java.util.Collections; +import java.util.Set; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import lombok.extern.slf4j.Slf4j; + +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Slf4j +public class NewRelicCanaryAccount extends AbstractCanaryAccount implements Cloneable { + private Endpoint endpoint; + @Secret private String apiKey; + @Secret private String applicationKey; + private Set supportedTypes = + Collections.singleton(AbstractCanaryServiceIntegration.SupportedTypes.METRICS_STORE); + + @Data + public static class Endpoint { + private String baseUrl; + } +} diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/canary/newrelic/NewRelicCanaryServiceIntegration.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/canary/newrelic/NewRelicCanaryServiceIntegration.java new file mode 100644 index 0000000000..58c2148bed --- /dev/null +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/canary/newrelic/NewRelicCanaryServiceIntegration.java @@ -0,0 +1,34 @@ +/* + * Copyright 2019 New Relic Corporation. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License") + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.halyard.config.model.v1.canary.newrelic; + +import com.netflix.spinnaker.halyard.config.model.v1.canary.AbstractCanaryServiceIntegration; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import lombok.extern.slf4j.Slf4j; + +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Slf4j +public class NewRelicCanaryServiceIntegration + extends AbstractCanaryServiceIntegration implements Cloneable { + public static final String NAME = "newrelic"; + + String name = NAME; +} diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/canary/signalfx/SignalfxCanaryAccount.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/canary/signalfx/SignalfxCanaryAccount.java index 425ceebc84..a702a224ec 100644 --- a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/canary/signalfx/SignalfxCanaryAccount.java +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/canary/signalfx/SignalfxCanaryAccount.java @@ -32,6 +32,15 @@ @Slf4j public class SignalfxCanaryAccount extends AbstractCanaryAccount implements Cloneable { @Secret private String accessToken; + private Endpoint endpoint; + private String defaultScopeKey; + private String defaultLocationKey; + private Set supportedTypes = Collections.singleton(AbstractCanaryServiceIntegration.SupportedTypes.METRICS_STORE); + + @Data + public static class Endpoint { + private String baseUrl; + } } diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/metricStores/newrelic/NewrelicStore.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/metricStores/newrelic/NewrelicStore.java new file mode 100644 index 0000000000..49347fc28d --- /dev/null +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/metricStores/newrelic/NewrelicStore.java @@ -0,0 +1,50 @@ +/* + * Copyright 2019 New Relic Corporation. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License") + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.netflix.spinnaker.halyard.config.model.v1.metricStores.newrelic; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.netflix.spinnaker.halyard.config.model.v1.node.MetricStore; +import com.netflix.spinnaker.halyard.config.model.v1.node.MetricStores; +import com.netflix.spinnaker.halyard.config.model.v1.node.Secret; +import java.util.ArrayList; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +public class NewrelicStore extends MetricStore { + @Override + public String getNodeName() { + return "newrelic"; + } + + @JsonIgnore + private MetricStores.MetricStoreType metricStoreType = MetricStores.MetricStoreType.NEWRELIC; + + @Secret(alwaysDecrypt = true) + @JsonProperty("insert_key") + private String insertKey; + + @JsonProperty("host") + private String host; + + @JsonProperty("tags") + private List tags = new ArrayList<>(); +} diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/ArtifactProvider.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/ArtifactProvider.java index 1a9c08be24..1b493900fa 100644 --- a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/ArtifactProvider.java +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/ArtifactProvider.java @@ -54,6 +54,7 @@ public enum ProviderType { ORACLE("oracle"), GITHUB("github"), GITLAB("gitlab"), + GITREPO("gitrepo"), HELM("helm"), HTTP("http"), S3("s3"), diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/Artifacts.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/Artifacts.java index cfe205726c..b95d96123a 100644 --- a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/Artifacts.java +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/Artifacts.java @@ -23,6 +23,7 @@ import com.netflix.spinnaker.halyard.config.model.v1.artifacts.gcs.GcsArtifactProvider; import com.netflix.spinnaker.halyard.config.model.v1.artifacts.github.GitHubArtifactProvider; import com.netflix.spinnaker.halyard.config.model.v1.artifacts.gitlab.GitlabArtifactProvider; +import com.netflix.spinnaker.halyard.config.model.v1.artifacts.gitrepo.GitRepoArtifactProvider; import com.netflix.spinnaker.halyard.config.model.v1.artifacts.helm.HelmArtifactProvider; import com.netflix.spinnaker.halyard.config.model.v1.artifacts.http.HttpArtifactProvider; import com.netflix.spinnaker.halyard.config.model.v1.artifacts.maven.MavenArtifactProvider; @@ -45,6 +46,7 @@ public class Artifacts extends Node { OracleArtifactProvider oracle = new OracleArtifactProvider(); GitHubArtifactProvider github = new GitHubArtifactProvider(); GitlabArtifactProvider gitlab = new GitlabArtifactProvider(); + GitRepoArtifactProvider gitrepo = new GitRepoArtifactProvider(); HttpArtifactProvider http = new HttpArtifactProvider(); HelmArtifactProvider helm = new HelmArtifactProvider(); S3ArtifactProvider s3 = new S3ArtifactProvider(); diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/DeploymentConfiguration.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/DeploymentConfiguration.java index 671df86399..4c29fefd94 100644 --- a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/DeploymentConfiguration.java +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/DeploymentConfiguration.java @@ -90,6 +90,8 @@ public class DeploymentConfiguration extends Node { Webhook webhook = new Webhook(); + Telemetry telemetry = new Telemetry(); + @Override public String getNodeName() { return name; diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/Features.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/Features.java index 9c340a6c5a..8b4a4e17f7 100644 --- a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/Features.java +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/Features.java @@ -16,11 +16,13 @@ package com.netflix.spinnaker.halyard.config.model.v1.node; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.Data; import lombok.EqualsAndHashCode; @Data @EqualsAndHashCode(callSuper = false) +@JsonIgnoreProperties({"jobs"}) public class Features extends Node { @Override public String getNodeName() { @@ -36,7 +38,6 @@ public NodeIterator getChildren() { private boolean fiat; private boolean chaos; private boolean entityTags; - private boolean jobs; @ValidForSpinnakerVersion( lowerBound = "1.2.0", diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/MetricStores.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/MetricStores.java index 4bd5cf48db..621049ab7c 100644 --- a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/MetricStores.java +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/MetricStores.java @@ -18,6 +18,7 @@ package com.netflix.spinnaker.halyard.config.model.v1.node; import com.netflix.spinnaker.halyard.config.model.v1.metricStores.datadog.DatadogStore; +import com.netflix.spinnaker.halyard.config.model.v1.metricStores.newrelic.NewrelicStore; import com.netflix.spinnaker.halyard.config.model.v1.metricStores.prometheus.PrometheusStore; import com.netflix.spinnaker.halyard.config.model.v1.metricStores.stackdriver.StackdriverStore; import java.lang.reflect.Field; @@ -33,10 +34,14 @@ public class MetricStores extends Node { private DatadogStore datadog = new DatadogStore(); private PrometheusStore prometheus = new PrometheusStore(); private StackdriverStore stackdriver = new StackdriverStore(); + private NewrelicStore newrelic = new NewrelicStore(); private int period = 30; public boolean isEnabled() { - return datadog.isEnabled() || prometheus.isEnabled() || stackdriver.isEnabled(); + return datadog.isEnabled() + || prometheus.isEnabled() + || stackdriver.isEnabled() + || newrelic.isEnabled(); } public void setEnabled(boolean ignored) {} @@ -64,7 +69,8 @@ public static Class translateMetricStoreType(String metri public enum MetricStoreType { DATADOG("datadog"), PROMETHEUS("prometheus"), - STACKDRIVER("stackdriver"); + STACKDRIVER("stackdriver"), + NEWRELIC("newrelic"); @Getter private final String id; diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/NodeFilter.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/NodeFilter.java index 92c66c8609..7f8667f992 100644 --- a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/NodeFilter.java +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/NodeFilter.java @@ -349,6 +349,11 @@ public NodeFilter withAnyPlugin() { return this; } + public NodeFilter setTelemetry() { + matchers.add(Node.thisNodeAcceptor(Telemetry.class)); + return this; + } + public NodeFilter() { withAnyHalconfigFile(); } diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/Plugins.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/Plugins.java index 81616e295f..c9321893fb 100644 --- a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/Plugins.java +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/Plugins.java @@ -18,7 +18,9 @@ import com.netflix.spinnaker.halyard.config.model.v1.plugins.Plugin; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import lombok.Data; import lombok.EqualsAndHashCode; @@ -26,6 +28,9 @@ @Data @EqualsAndHashCode(callSuper = false) public class Plugins extends Node { + private List plugins = new ArrayList<>(); + private boolean enabled; + private boolean downloadingEnabled; @Override public String getNodeName() { @@ -38,7 +43,16 @@ public NodeIterator getChildren() { plugins.stream().map(a -> (Node) a).collect(Collectors.toList())); } - private List plugins = new ArrayList<>(); - private boolean enabled; - private boolean downloadingEnabled; + public Map getPluginConfigurations() { + Map fullyRenderedYaml = new LinkedHashMap<>(); + Map pluginMetadata = + plugins.stream() + .filter(p -> p.getEnabled()) + .filter(p -> !p.getManifestLocation().isEmpty()) + .collect( + Collectors.toMap(p -> p.generateManifest().getName(), p -> p.getCombinedOptions())); + + fullyRenderedYaml.put("plugins", pluginMetadata); + return fullyRenderedYaml; + } } diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/Telemetry.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/Telemetry.java new file mode 100644 index 0000000000..5d25fe71a8 --- /dev/null +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/Telemetry.java @@ -0,0 +1,40 @@ +/* + * Copyright 2019 Armory, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License") + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.halyard.config.model.v1.node; + +import de.huxhorn.sulky.ulid.ULID; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = false) +public class Telemetry extends Node { + + public static String DEFAULT_TELEMETRY_ENDPOINT = "https://stats.spinnaker.io"; + + @Override + public String getNodeName() { + return "telemetry"; + } + + private Boolean enabled = false; + private String endpoint = DEFAULT_TELEMETRY_ENDPOINT; + private String instanceId = new ULID().nextULID(); + private String spinnakerVersion; + private int connectionTimeoutMillis = 3000; + private int readTimeoutMillis = 5000; +} diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/notifications/SlackNotification.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/notifications/SlackNotification.java index dc27746e93..3c890a96fd 100644 --- a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/notifications/SlackNotification.java +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/notifications/SlackNotification.java @@ -29,6 +29,8 @@ public class SlackNotification extends Notification { String botName; @Secret String token; + String baseUrl; + Boolean forceUseIncomingWebhook; @Override @JsonIgnore diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/plugins/Plugin.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/plugins/Plugin.java index 978885699b..24de3d49bc 100644 --- a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/plugins/Plugin.java +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/plugins/Plugin.java @@ -24,6 +24,9 @@ import java.io.IOException; import java.io.InputStream; import java.net.URL; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import lombok.Data; import lombok.EqualsAndHashCode; import org.yaml.snakeyaml.Yaml; @@ -33,9 +36,10 @@ @Data @EqualsAndHashCode(callSuper = true) public class Plugin extends Node { - public String name; - public Boolean enabled; - public String manifestLocation; + private String name; + private Boolean enabled; + private String manifestLocation; + private Map options = new HashMap<>(); @Override public String getNodeName() { @@ -72,4 +76,43 @@ public InputStream downloadManifest() { .build()); } } + + /** + * Used to merge plugin options passed in through the hal config to overwrite the default plugin + * options, as necessary. Since options can be a nested map, if a hal config overwrites a + * particular piece of the default plugin options, we want to preserve the other options. + * + *

If a key is present on both the original and new options, the types match, and are of type + * Map or List, then we merge the two values. Otherwise, we overwrite the keys with the values in + * the newMap. + * + * @param original + * @param newMap + * @return + */ + public static Map merge( + Map original, Map newMap) { + for (String key : newMap.keySet()) { + if (newMap.get(key) instanceof Map && original.get(key) instanceof Map) { + Map originalChild = (Map) original.get(key); + Map newChild = (Map) newMap.get(key); + original.put(key, Plugin.merge(originalChild, newChild)); + } else if (newMap.get(key) instanceof List && original.get(key) instanceof List) { + List originalChild = (List) original.get(key); + List newChild = (List) newMap.get(key); + for (Object each : newChild) { + if (!originalChild.contains(each)) { + originalChild.add(each); + } + } + } else { + original.put(key, newMap.get(key)); + } + } + return original; + } + + public Map getCombinedOptions() { + return Plugin.merge(generateManifest().getOptions(), getOptions()); + } } diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/providers/aws/AwsAccount.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/providers/aws/AwsAccount.java index fa93a0edb2..623c70bbd7 100644 --- a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/providers/aws/AwsAccount.java +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/providers/aws/AwsAccount.java @@ -33,4 +33,5 @@ public class AwsAccount extends Account { private List regions = new ArrayList<>(); private String assumeRole; private String sessionName; + private List lifecycleHooks = new ArrayList<>(); } diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/providers/aws/AwsProvider.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/providers/aws/AwsProvider.java index 803380d4b5..b7f70dabfe 100644 --- a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/providers/aws/AwsProvider.java +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/providers/aws/AwsProvider.java @@ -43,6 +43,15 @@ public static class AwsRegion { String name; } + @Data + public static class AwsLifecycleHook { + String defaultResult; + Integer heartbeatTimeout; + String lifecycleTransition; + String notificationTargetARN; + String roleARN; + } + @Data public static class AwsDefaults { String iamRole = "BaseIAMRole"; diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/security/Saml.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/security/Saml.java index 8523738ade..0007165852 100644 --- a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/security/Saml.java +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/security/Saml.java @@ -30,7 +30,7 @@ public class Saml extends AuthnMethod { private final Method method = Method.SAML; private final String nodeName = "saml"; - @LocalFile private String metadataLocal; + @LocalFile @SecretFile private String metadataLocal; private String metadataRemote; private String issuerId; @@ -49,5 +49,6 @@ public static class UserAttributeMapping { private String roles; private String rolesDelimiter; private String username; + private String email; } } diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/services/v1/ArtifactProviderService.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/services/v1/ArtifactProviderService.java index 7c2afe6fa2..7cd06d0517 100644 --- a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/services/v1/ArtifactProviderService.java +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/services/v1/ArtifactProviderService.java @@ -24,6 +24,7 @@ import com.netflix.spinnaker.halyard.config.model.v1.artifacts.gcs.GcsArtifactProvider; import com.netflix.spinnaker.halyard.config.model.v1.artifacts.github.GitHubArtifactProvider; import com.netflix.spinnaker.halyard.config.model.v1.artifacts.gitlab.GitlabArtifactProvider; +import com.netflix.spinnaker.halyard.config.model.v1.artifacts.gitrepo.GitRepoArtifactProvider; import com.netflix.spinnaker.halyard.config.model.v1.artifacts.helm.HelmArtifactProvider; import com.netflix.spinnaker.halyard.config.model.v1.artifacts.http.HttpArtifactProvider; import com.netflix.spinnaker.halyard.config.model.v1.artifacts.oracle.OracleArtifactProvider; @@ -114,6 +115,9 @@ public void setArtifactProvider(String deploymentName, ArtifactProvider provider case GITLAB: artifacts.setGitlab((GitlabArtifactProvider) provider); break; + case GITREPO: + artifacts.setGitrepo((GitRepoArtifactProvider) provider); + break; case HTTP: artifacts.setHttp((HttpArtifactProvider) provider); break; diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/services/v1/MetricStoresService.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/services/v1/MetricStoresService.java index 9ff1396c7c..5b670bb1ee 100644 --- a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/services/v1/MetricStoresService.java +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/services/v1/MetricStoresService.java @@ -18,6 +18,7 @@ package com.netflix.spinnaker.halyard.config.services.v1; import com.netflix.spinnaker.halyard.config.model.v1.metricStores.datadog.DatadogStore; +import com.netflix.spinnaker.halyard.config.model.v1.metricStores.newrelic.NewrelicStore; import com.netflix.spinnaker.halyard.config.model.v1.metricStores.prometheus.PrometheusStore; import com.netflix.spinnaker.halyard.config.model.v1.metricStores.stackdriver.StackdriverStore; import com.netflix.spinnaker.halyard.config.model.v1.node.DeploymentConfiguration; @@ -111,6 +112,9 @@ public void setMetricStore(String deploymentName, MetricStore metricStore) { case DATADOG: metricStores.setDatadog((DatadogStore) metricStore); break; + case NEWRELIC: + metricStores.setNewrelic((NewrelicStore) metricStore); + break; case PROMETHEUS: metricStores.setPrometheus((PrometheusStore) metricStore); break; diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/services/v1/TelemetryService.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/services/v1/TelemetryService.java new file mode 100644 index 0000000000..0121716753 --- /dev/null +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/services/v1/TelemetryService.java @@ -0,0 +1,57 @@ +/* + * Copyright 2019 Armory, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License") + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.halyard.config.services.v1; + +import com.netflix.spinnaker.halyard.config.model.v1.node.DeploymentConfiguration; +import com.netflix.spinnaker.halyard.config.model.v1.node.NodeFilter; +import com.netflix.spinnaker.halyard.config.model.v1.node.Telemetry; +import com.netflix.spinnaker.halyard.core.problem.v1.ProblemSet; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class TelemetryService { + private final LookupService lookupService; + private final ValidateService validateService; + private final DeploymentService deploymentService; + + public Telemetry getTelemetry(String deploymentName) { + NodeFilter filter = new NodeFilter().setDeployment(deploymentName).setTelemetry(); + + return lookupService.getSingularNodeOrDefault( + filter, Telemetry.class, Telemetry::new, n -> setTelemetry(deploymentName, n)); + } + + public void setTelemetry(String deploymentName, Telemetry telemetry) { + DeploymentConfiguration deploymentConfiguration = + deploymentService.getDeploymentConfiguration(deploymentName); + deploymentConfiguration.setTelemetry(telemetry); + } + + public void setTelemetryEnabled(String deploymentName, boolean validate, boolean enable) { + DeploymentConfiguration deploymentConfiguration = + deploymentService.getDeploymentConfiguration(deploymentName); + Telemetry telemetry = deploymentConfiguration.getTelemetry(); + telemetry.setEnabled(enable); + } + + public ProblemSet validateTelemetry(String deploymentName) { + NodeFilter filter = new NodeFilter().setDeployment(deploymentName).setTelemetry(); + return validateService.validateMatchingFilter(filter); + } +} diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/validate/v1/HaEchoValidator.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/validate/v1/HaEchoValidator.java index 8c808eaab1..eab221c8ba 100644 --- a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/validate/v1/HaEchoValidator.java +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/validate/v1/HaEchoValidator.java @@ -38,7 +38,7 @@ public void validate(ConfigProblemSetBuilder p, DeploymentEnvironment n) { p.addProblem( Problem.Severity.WARNING, "High Availability (HA) is enabled for echo, but found custom sizing for the main service (this setting will be ignored). " - + "With HA enabled, the service is split into multiple sub-services (echo-scheduler, echo-scheduler). You need to update the component sizing for each sub-service, individually."); + + "With HA enabled, the service is split into multiple sub-services (spin-echo-scheduler, spin-echo-worker). You need to update the component sizing for each sub-service, individually."); } if (!haEchoEnabled diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/validate/v1/providers/aws/AwsAccountValidator.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/validate/v1/providers/aws/AwsAccountValidator.java index 5464d82334..953d0f17a0 100644 --- a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/validate/v1/providers/aws/AwsAccountValidator.java +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/validate/v1/providers/aws/AwsAccountValidator.java @@ -31,11 +31,14 @@ @Component public class AwsAccountValidator extends Validator { + @Autowired ProviderService providerService; @Override - public void validate(ConfigProblemSetBuilder p, AwsAccount n) { - p.addProblem(Severity.WARNING, "No validation for the AWS provider has been implemented."); + public void validate(ConfigProblemSetBuilder p, AwsAccount awsAccount) { + awsAccount.getLifecycleHooks().stream() + .flatMap(AwsLifecycleHookValidation::getValidationErrors) + .forEach(error -> p.addProblem(Severity.FATAL, error)); } public static AWSCredentialsProvider getAwsCredentialsProvider( diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/validate/v1/providers/aws/AwsLifecycleHookValidation.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/validate/v1/providers/aws/AwsLifecycleHookValidation.java new file mode 100644 index 0000000000..4c76d5b3f6 --- /dev/null +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/validate/v1/providers/aws/AwsLifecycleHookValidation.java @@ -0,0 +1,52 @@ +package com.netflix.spinnaker.halyard.config.validate.v1.providers.aws; + +import com.google.api.client.util.Lists; +import com.google.common.collect.Sets; +import com.netflix.spinnaker.halyard.config.model.v1.providers.aws.AwsProvider.AwsLifecycleHook; +import java.util.Collection; +import java.util.List; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +public class AwsLifecycleHookValidation { + private static final Pattern SNS_PATTERN = Pattern.compile("^arn:aws:sns:[^:]+:[^:]+:[^:]+$"); + private static final Pattern IAM_ROLE_PATTERN = Pattern.compile("^arn:aws:iam::[^:]+:[^:]+$"); + private static final Collection VALID_LIFECYCLE_HOOK_RESULTS = + Sets.newHashSet("ABANDON", "CONTINUE"); + + public static Stream getValidationErrors(AwsLifecycleHook hook) { + List errors = Lists.newArrayList(); + String snsArn = hook.getNotificationTargetARN(); + if (!isValidSnsArn(snsArn)) { + errors.add("Invalid SNS notification ARN: " + snsArn); + } + + if (!isValidRoleArn(hook.getRoleARN())) { + errors.add("Invalid IAM role ARN: " + hook.getRoleARN()); + } + + if (!VALID_LIFECYCLE_HOOK_RESULTS.contains(hook.getDefaultResult())) { + errors.add("Invalid lifecycle default result: " + hook.getDefaultResult()); + } + + if (!isValidHeartbeatTimeout(hook.getHeartbeatTimeout())) { + errors.add( + "Lifecycle heartbeat timeout must be between 30 and 7200. Provided value was: " + + hook.getHeartbeatTimeout()); + } + + return errors.stream(); + } + + private static boolean isValidSnsArn(String arn) { + return SNS_PATTERN.matcher(arn).matches(); + } + + private static boolean isValidRoleArn(String arn) { + return IAM_ROLE_PATTERN.matcher(arn).matches(); + } + + private static boolean isValidHeartbeatTimeout(Integer timeout) { + return timeout != null && timeout >= 30 && timeout <= 7200; + } +} diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/validate/v1/providers/kubernetes/KubernetesAccountValidator.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/validate/v1/providers/kubernetes/KubernetesAccountValidator.java index 2bc4386e3e..5966500596 100644 --- a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/validate/v1/providers/kubernetes/KubernetesAccountValidator.java +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/validate/v1/providers/kubernetes/KubernetesAccountValidator.java @@ -111,6 +111,17 @@ private void validateKindConfig(ConfigProblemSetBuilder psBuilder, KubernetesAcc psBuilder.addProblem(ERROR, "At most one of \"kinds\" and \"omitKinds\" may be specified."); } + if (CollectionUtils.isNotEmpty(customResources)) { + List kubernetesKindNotSet = + customResources.stream() + .map(KubernetesAccount.CustomKubernetesResource::getKubernetesKind) + .filter(cr -> (cr == null || cr.isEmpty())) + .collect(Collectors.toList()); + if (CollectionUtils.isNotEmpty(kubernetesKindNotSet)) { + psBuilder.addProblem(ERROR, "Missing custom resource name (Kubernetes Kind)."); + } + } + if (CollectionUtils.isNotEmpty(kinds) && CollectionUtils.isNotEmpty(customResources)) { List unmatchedKinds = customResources.stream() diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/validate/v1/security/SamlValidator.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/validate/v1/security/SamlValidator.java index 72d5c16992..90a47a89e0 100644 --- a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/validate/v1/security/SamlValidator.java +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/validate/v1/security/SamlValidator.java @@ -47,7 +47,7 @@ public void validate(ConfigProblemSetBuilder p, Saml saml) { if (StringUtils.isNotEmpty(saml.getMetadataLocal())) { try { - new File(new URI("file:" + saml.getMetadataLocal())); + new File(new URI("file:" + validatingFileDecrypt(p, saml.getMetadataLocal()))); } catch (Exception f) { p.addProblem(Problem.Severity.ERROR, f.getMessage()); } diff --git a/halyard-config/src/test/groovy/com/netflix/spinnaker/halyard/config/model/v1/plugins/ManifestSpec.groovy b/halyard-config/src/test/groovy/com/netflix/spinnaker/halyard/config/model/v1/plugins/ManifestSpec.groovy index 091e72b381..c6389d273f 100644 --- a/halyard-config/src/test/groovy/com/netflix/spinnaker/halyard/config/model/v1/plugins/ManifestSpec.groovy +++ b/halyard-config/src/test/groovy/com/netflix/spinnaker/halyard/config/model/v1/plugins/ManifestSpec.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2018 Bol.com + * Copyright 2019 Armory, Inc * * Licensed under the Apache License, Version 2.0 (the "License") * you may not use this file except in compliance with the License. diff --git a/halyard-config/src/test/groovy/com/netflix/spinnaker/halyard/config/model/v1/plugins/PluginSpec.groovy b/halyard-config/src/test/groovy/com/netflix/spinnaker/halyard/config/model/v1/plugins/PluginSpec.groovy new file mode 100644 index 0000000000..4497094210 --- /dev/null +++ b/halyard-config/src/test/groovy/com/netflix/spinnaker/halyard/config/model/v1/plugins/PluginSpec.groovy @@ -0,0 +1,90 @@ +/* + * Copyright 2019 Armory, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License") + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.halyard.config.model.v1.plugins + +import com.netflix.spinnaker.halyard.config.error.v1.IllegalConfigException +import org.springframework.boot.liquibase.SpringPackageScanClassResolver +import org.springframework.core.SpringProperties +import spock.lang.Specification + +class PluginSpec extends Specification { + + void "plugin merge works correctly"() { + setup: + def originalOptions = [ + example: [ + url: 'host', + port: 1000, + nested: [ + foo: 'bar', + key: 'value' + ], + list: [ + 'first', + 'second' + ], + changeMe: [ + 'change', + 'to', + 'integer' + ], + doNot: 'change' + ] + ] + + def newOptions = [ + example: [ + url: 'new.host', + port: 2000, + nested: [ + foo: 'baz', + cat: 'dog' + ], + list: [ + 'new', + 'list' + ], + changeMe: 200 + ] + ] + + when: + def subject = Plugin.merge(originalOptions, newOptions) + + then: + def expectedOptions = [ + example: [ + url: 'new.host', + port: 2000, + nested: [ + foo: 'baz', + cat: 'dog', + key: 'value' + ], + list: [ + 'first', + 'second', + 'new', + 'list' + ], + changeMe: 200, + doNot: 'change' + ] + ] + subject == expectedOptions + } +} diff --git a/halyard-config/src/test/groovy/com/netflix/spinnaker/halyard/config/model/v1/plugins/PluginsSpec.groovy b/halyard-config/src/test/groovy/com/netflix/spinnaker/halyard/config/model/v1/plugins/PluginsSpec.groovy new file mode 100644 index 0000000000..1c536099ee --- /dev/null +++ b/halyard-config/src/test/groovy/com/netflix/spinnaker/halyard/config/model/v1/plugins/PluginsSpec.groovy @@ -0,0 +1,53 @@ +/* + * Copyright 2019 Armory, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License") + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.halyard.config.model.v1.plugins + +import com.netflix.spinnaker.halyard.config.model.v1.node.Plugins +import spock.lang.Specification + +class PluginsSpec extends Specification { + + void "getPluginConfigurations works correctly"() { + setup: + def plugins = new Plugins() + def plugin = Spy(Plugin) + def manifest = Stub(Manifest) + manifest.name >> 'namespace/name' + manifest.options >> [foo: 'bar'] + plugin.manifestLocation >> 'manifest-location' + plugin.enabled >> true + plugin.options >> [cat: 'dog'] + plugin.name >> 'should-not-be-present' + plugins.setPlugins([plugin]) + + plugin.generateManifest() >> manifest + + when: + def subject = plugins.getPluginConfigurations() + + then: + def expectedOptions = [ + plugins: [ + 'namespace/name': [ + foo: 'bar', + cat: 'dog' + ] + ] + ] + subject == expectedOptions + } +} diff --git a/halyard-config/src/test/groovy/com/netflix/spinnaker/halyard/config/services/v1/HalconfigParserMocker.groovy b/halyard-config/src/test/groovy/com/netflix/spinnaker/halyard/config/services/v1/HalconfigParserMocker.groovy index e4344605dc..1accfd8e83 100644 --- a/halyard-config/src/test/groovy/com/netflix/spinnaker/halyard/config/services/v1/HalconfigParserMocker.groovy +++ b/halyard-config/src/test/groovy/com/netflix/spinnaker/halyard/config/services/v1/HalconfigParserMocker.groovy @@ -31,9 +31,7 @@ class HalconfigParserMocker extends Specification { def parserStub = new HalconfigParser() parserStub.objectMapper = new StrictObjectMapper() parserStub.yamlParser = new Yaml(new SafeConstructor()) - def hcDirStructure = new HalconfigDirectoryStructure() - hcDirStructure.halconfigDirectory = "/home/spinnaker/.hal" - parserStub.halconfigDirectoryStructure = hcDirStructure + parserStub.halconfigDirectoryStructure = new HalconfigDirectoryStructure(); def stream = new ByteArrayInputStream(config.getBytes(StandardCharsets.UTF_8)) Halconfig halconfig = parserStub.parseHalconfig(stream) diff --git a/halyard-config/src/test/groovy/com/netflix/spinnaker/halyard/config/services/v1/PluginServiceSpec.groovy b/halyard-config/src/test/groovy/com/netflix/spinnaker/halyard/config/services/v1/PluginServiceSpec.groovy index 735805ca35..977865f32a 100644 --- a/halyard-config/src/test/groovy/com/netflix/spinnaker/halyard/config/services/v1/PluginServiceSpec.groovy +++ b/halyard-config/src/test/groovy/com/netflix/spinnaker/halyard/config/services/v1/PluginServiceSpec.groovy @@ -49,6 +49,10 @@ deploymentConfigurations: plugins: - name: test-plugin manifestLocation: /home/user/test-plugin.yaml + options: + foo: bar + nested: + key: value """ def pluginService = makePluginService(config) @@ -60,6 +64,8 @@ deploymentConfigurations: result.size() == 1 result[0].getName() == "test-plugin" result[0].getManifestLocation() == "/home/user/test-plugin.yaml" + result[0].getOptions().get("foo") == "bar" + result[0].getOptions().get("nested").get("key") == "value" when: result = pluginService.getPlugin(DEPLOYMENT, "test-plugin") diff --git a/halyard-core/halyard-core.gradle b/halyard-core/halyard-core.gradle index 243b003435..c9aaf40711 100644 --- a/halyard-core/halyard-core.gradle +++ b/halyard-core/halyard-core.gradle @@ -8,8 +8,10 @@ dependencies { implementation "com.netflix.spinnaker.clouddriver:clouddriver-aws:$clouddriverVersion" implementation 'com.netflix.spinnaker.kork:kork-secrets' implementation 'com.netflix.spinnaker.kork:kork-secrets-aws' + implementation 'com.netflix.spinnaker.kork:kork-secrets-gcp' implementation 'com.google.apis:google-api-services-storage' implementation 'com.google.api.grpc:grpc-google-common-protos:1.0.5' + implementation 'com.google.auth:google-auth-library-oauth2-http' implementation 'org.apache.commons:commons-exec' implementation 'org.apache.commons:commons-compress:1.14' implementation 'commons-io:commons-io:2.6' diff --git a/halyard-core/src/main/java/com/netflix/spinnaker/halyard/core/provider/v1/google/GoogleCredentials.java b/halyard-core/src/main/java/com/netflix/spinnaker/halyard/core/provider/v1/google/GoogleCredentials.java index 7a9526d284..e9859434c9 100644 --- a/halyard-core/src/main/java/com/netflix/spinnaker/halyard/core/provider/v1/google/GoogleCredentials.java +++ b/halyard-core/src/main/java/com/netflix/spinnaker/halyard/core/provider/v1/google/GoogleCredentials.java @@ -20,9 +20,11 @@ import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport; import com.google.api.client.http.HttpBackOffUnsuccessfulResponseHandler; +import com.google.api.client.http.HttpRequest; import com.google.api.client.http.HttpRequestInitializer; import com.google.api.client.http.HttpTransport; import com.google.api.client.util.ExponentialBackOff; +import com.google.auth.http.HttpCredentialsAdapter; import java.io.IOException; import java.security.GeneralSecurityException; import java.util.concurrent.TimeUnit; @@ -47,13 +49,15 @@ public static HttpRequestInitializer retryRequestInitializer() { } public static HttpRequestInitializer setHttpTimeout( - final HttpRequestInitializer requestInitializer) { - return request -> { - requestInitializer.initialize(request); - request.setConnectTimeout((int) TimeUnit.MINUTES.toMillis(2)); - request.setReadTimeout((int) TimeUnit.MINUTES.toMillis(2)); - request.setUnsuccessfulResponseHandler( - new HttpBackOffUnsuccessfulResponseHandler(new ExponentialBackOff())); + final com.google.auth.oauth2.GoogleCredentials credentials) { + return new HttpCredentialsAdapter(credentials) { + public void initialize(HttpRequest request) throws IOException { + super.initialize(request); + request.setConnectTimeout((int) TimeUnit.MINUTES.toMillis(2)); + request.setReadTimeout((int) TimeUnit.MINUTES.toMillis(2)); + request.setUnsuccessfulResponseHandler( + new HttpBackOffUnsuccessfulResponseHandler(new ExponentialBackOff())); + } }; } } diff --git a/halyard-core/src/main/java/com/netflix/spinnaker/halyard/core/registry/v1/GoogleProfileReader.java b/halyard-core/src/main/java/com/netflix/spinnaker/halyard/core/registry/v1/GoogleProfileReader.java index 06afa4543e..f21e3611cb 100644 --- a/halyard-core/src/main/java/com/netflix/spinnaker/halyard/core/registry/v1/GoogleProfileReader.java +++ b/halyard-core/src/main/java/com/netflix/spinnaker/halyard/core/registry/v1/GoogleProfileReader.java @@ -19,7 +19,6 @@ package com.netflix.spinnaker.halyard.core.registry.v1; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; import com.google.api.client.http.HttpRequestInitializer; import com.google.api.client.json.JsonFactory; import com.google.api.client.json.jackson2.JacksonFactory; @@ -121,15 +120,15 @@ private Storage createGoogleStorage(boolean useApplicationDefaultCreds) { HttpRequestInitializer requestInitializer; try { - GoogleCredential credential = + com.google.auth.oauth2.GoogleCredentials credentials = useApplicationDefaultCreds - ? GoogleCredential.getApplicationDefault() - : new GoogleCredential(); - if (credential.createScopedRequired()) { - credential = - credential.createScoped(Collections.singleton(StorageScopes.DEVSTORAGE_FULL_CONTROL)); + ? com.google.auth.oauth2.GoogleCredentials.getApplicationDefault() + : com.google.auth.oauth2.GoogleCredentials.newBuilder().build(); + if (credentials.createScopedRequired()) { + credentials = + credentials.createScoped(Collections.singleton(StorageScopes.DEVSTORAGE_FULL_CONTROL)); } - requestInitializer = GoogleCredentials.setHttpTimeout(credential); + requestInitializer = GoogleCredentials.setHttpTimeout(credentials); log.info("Loaded application default credential for reading BOMs & profiles."); } catch (Exception e) { diff --git a/halyard-core/src/main/java/com/netflix/spinnaker/halyard/core/registry/v1/GoogleWriteableProfileRegistry.java b/halyard-core/src/main/java/com/netflix/spinnaker/halyard/core/registry/v1/GoogleWriteableProfileRegistry.java index 60afc5c718..c36561b6ed 100644 --- a/halyard-core/src/main/java/com/netflix/spinnaker/halyard/core/registry/v1/GoogleWriteableProfileRegistry.java +++ b/halyard-core/src/main/java/com/netflix/spinnaker/halyard/core/registry/v1/GoogleWriteableProfileRegistry.java @@ -17,7 +17,6 @@ package com.netflix.spinnaker.halyard.core.registry.v1; -import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; import com.google.api.client.http.ByteArrayContent; import com.google.api.client.http.HttpTransport; import com.google.api.client.json.JsonFactory; @@ -47,35 +46,35 @@ public class GoogleWriteableProfileRegistry { GoogleWriteableProfileRegistry(WriteableProfileRegistryProperties properties) { HttpTransport httpTransport = GoogleCredentials.buildHttpTransport(); JsonFactory jsonFactory = JacksonFactory.getDefaultInstance(); - GoogleCredential credential; + com.google.auth.oauth2.GoogleCredentials credentials; try { - credential = loadCredential(httpTransport, jsonFactory, properties.getJsonPath()); + credentials = loadCredentials(properties.getJsonPath()); } catch (IOException e) { throw new RuntimeException("Failed to load json credential", e); } this.storage = new Storage.Builder( - httpTransport, jsonFactory, GoogleCredentials.setHttpTimeout(credential)) + httpTransport, jsonFactory, GoogleCredentials.setHttpTimeout(credentials)) .setApplicationName("halyard") .build(); this.properties = properties; } - private GoogleCredential loadCredential( - HttpTransport transport, JsonFactory factory, String jsonPath) throws IOException { - GoogleCredential credential; + private com.google.auth.oauth2.GoogleCredentials loadCredentials(String jsonPath) + throws IOException { + com.google.auth.oauth2.GoogleCredentials credentials; if (!jsonPath.isEmpty()) { FileInputStream stream = new FileInputStream(jsonPath); - credential = - GoogleCredential.fromStream(stream, transport, factory) + credentials = + com.google.auth.oauth2.GoogleCredentials.fromStream(stream) .createScoped(Collections.singleton(StorageScopes.DEVSTORAGE_FULL_CONTROL)); log.info("Loaded credentials from " + jsonPath); } else { log.info("Using default application credentials."); - credential = GoogleCredential.getApplicationDefault(); + credentials = com.google.auth.oauth2.GoogleCredentials.getApplicationDefault(); } - return credential; + return credentials; } public void writeBom(String version, String contents) { diff --git a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/config/v1/secrets/DecryptingObjectMapper.java b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/config/v1/secrets/DecryptingObjectMapper.java index df2ef4c4e9..7efed9a316 100644 --- a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/config/v1/secrets/DecryptingObjectMapper.java +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/config/v1/secrets/DecryptingObjectMapper.java @@ -33,6 +33,7 @@ import com.netflix.spinnaker.halyard.deploy.spinnaker.v1.profile.Profile; import com.netflix.spinnaker.kork.secrets.EncryptedSecret; import java.io.IOException; +import java.net.URL; import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; @@ -119,9 +120,11 @@ public void serialize(Object value, JsonGenerator gen, SerializerProvider provid throws IOException { if (value != null) { String sValue = value.toString(); - if (!EncryptedSecret.isEncryptedSecret(sValue)) { + if (!EncryptedSecret.isEncryptedSecret(sValue) && !isURL(sValue)) { + // metadataUrl is either a URL or a filepath, so only add prefix if it's a path sValue = annotation.prefix() + sValue; - } else if (shouldDecrypt) { + } + if (EncryptedSecret.isEncryptedSecret(sValue) && shouldDecrypt) { // Decrypt the content of the file and store on the profile under a random // generated file name String name = newRandomFilePath(beanPropertyWriter.getName()); @@ -148,4 +151,14 @@ protected String newRandomFilePath(String fieldName) { protected String getCompleteFilePath(String filename) { return Paths.get(decryptedOutputDirectory.toString(), filename).toString(); } + + private boolean isURL(String property) { + try { + URL url = new URL(property); + url.toURI(); + return true; + } catch (Exception exception) { + return false; + } + } } diff --git a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/deployment/v1/KubectlDeployer.java b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/deployment/v1/KubectlDeployer.java index e0fbe2f61d..cd702c0e8c 100644 --- a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/deployment/v1/KubectlDeployer.java +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/deployment/v1/KubectlDeployer.java @@ -139,7 +139,8 @@ public void rollback( AccountDeploymentDetails deploymentDetails, SpinnakerRuntimeSettings runtimeSettings, List serviceTypes) { - throw new UnsupportedOperationException("todo(lwander)"); + throw new UnsupportedOperationException( + "Please file an issue: https://github.com/spinnaker/spinnaker/issues/new"); } @Override @@ -148,7 +149,8 @@ public void collectLogs( AccountDeploymentDetails deploymentDetails, SpinnakerRuntimeSettings runtimeSettings, List serviceTypes) { - throw new UnsupportedOperationException("todo(lwander)"); + throw new UnsupportedOperationException( + "Please file an issue: https://github.com/spinnaker/spinnaker/issues/new"); } @Override @@ -181,7 +183,8 @@ public void flushInfrastructureCaches( KubectlServiceProvider serviceProvider, AccountDeploymentDetails deploymentDetails, SpinnakerRuntimeSettings runtimeSettings) { - throw new UnsupportedOperationException("todo(lwander)"); + throw new UnsupportedOperationException( + "Please file an issue: https://github.com/spinnaker/spinnaker/issues/new"); } @Override diff --git a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/services/v1/GenerateService.java b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/services/v1/GenerateService.java index 23a02f8975..3e3e642cd3 100644 --- a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/services/v1/GenerateService.java +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/services/v1/GenerateService.java @@ -57,9 +57,7 @@ public class GenerateService { @Autowired protected ServiceProviderFactory serviceProviderFactory; - @Autowired protected HalconfigDirectoryStructure halconfigDirectoryStructure; - - @Autowired protected List spinnakerServices = new ArrayList<>(); + @Autowired private HalconfigDirectoryStructure halconfigDirectoryStructure; @Autowired protected ConfigParser configParser; diff --git a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/EchoProfileFactory.java b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/EchoProfileFactory.java index 88a27539f2..453d9bdf9a 100644 --- a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/EchoProfileFactory.java +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/EchoProfileFactory.java @@ -16,6 +16,7 @@ package com.netflix.spinnaker.halyard.deploy.spinnaker.v1.profile; +import com.netflix.spinnaker.halyard.config.config.v1.ResourceConfig; import com.netflix.spinnaker.halyard.config.model.v1.ci.gcb.GoogleCloudBuild; import com.netflix.spinnaker.halyard.config.model.v1.node.*; import com.netflix.spinnaker.halyard.deploy.spinnaker.v1.SpinnakerArtifact; @@ -24,10 +25,16 @@ import java.util.ArrayList; import java.util.List; import lombok.Data; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class EchoProfileFactory extends SpringProfileFactory { + + @Autowired String spinconfigBucket; + + @Autowired boolean gcsEnabled; + @Override public SpinnakerArtifact getArtifact() { return SpinnakerArtifact.ECHO; @@ -82,6 +89,23 @@ protected void setProfile( } } + Telemetry telemetry = deploymentConfiguration.getTelemetry(); + if (telemetry != null) { + + // We don't want to accidentally log any PII that may be stuffed into custom BOM bucket names, + // so we should only log the version if using our public releases (as indicated by using our + // public GCS bucket). + String telemetryVersion = "custom"; + if (gcsEnabled + && spinconfigBucket.equalsIgnoreCase(ResourceConfig.DEFAULT_HALCONFIG_BUCKET)) { + telemetryVersion = deploymentConfiguration.getVersion(); + } + telemetry.setSpinnakerVersion(telemetryVersion); + profile.appendContents( + yamlToString( + deploymentConfiguration.getName(), profile, new TelemetryWrapper(telemetry))); + } + profile.appendContents(profile.getBaseContents()).setRequiredFiles(files); } @@ -111,4 +135,13 @@ private static class GCBWrapper { this.gcb = gcb; } } + + @Data + private static class TelemetryWrapper { + private Telemetry telemetry; + + TelemetryWrapper(Telemetry telemetry) { + this.telemetry = telemetry; + } + } } diff --git a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/KayentaProfileFactory.java b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/KayentaProfileFactory.java index 8444331e55..424a8cd552 100644 --- a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/KayentaProfileFactory.java +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/KayentaProfileFactory.java @@ -24,6 +24,8 @@ import com.netflix.spinnaker.halyard.config.model.v1.canary.datadog.DatadogCanaryServiceIntegration; import com.netflix.spinnaker.halyard.config.model.v1.canary.google.GoogleCanaryAccount; import com.netflix.spinnaker.halyard.config.model.v1.canary.google.GoogleCanaryServiceIntegration; +import com.netflix.spinnaker.halyard.config.model.v1.canary.newrelic.NewRelicCanaryAccount; +import com.netflix.spinnaker.halyard.config.model.v1.canary.newrelic.NewRelicCanaryServiceIntegration; import com.netflix.spinnaker.halyard.config.model.v1.canary.prometheus.PrometheusCanaryAccount; import com.netflix.spinnaker.halyard.config.model.v1.canary.prometheus.PrometheusCanaryServiceIntegration; import com.netflix.spinnaker.halyard.config.model.v1.canary.signalfx.SignalfxCanaryAccount; @@ -93,6 +95,7 @@ static class KayentaConfig { AwsConfig aws; S3Config s3; SignalFxConfig signalfx; + NewRelicConfig newrelic; KayentaConfig(Canary canary) { for (AbstractCanaryServiceIntegration svc : canary.getServiceIntegrations()) { @@ -115,6 +118,9 @@ static class KayentaConfig { } else if (svc instanceof SignalfxCanaryServiceIntegration) { SignalfxCanaryServiceIntegration signalfxSvc = (SignalfxCanaryServiceIntegration) svc; signalfx = new SignalFxConfig(signalfxSvc); + } else if (svc instanceof NewRelicCanaryServiceIntegration) { + NewRelicCanaryServiceIntegration newRelicSvc = (NewRelicCanaryServiceIntegration) svc; + newrelic = new NewRelicConfig(newRelicSvc); } } } @@ -204,6 +210,17 @@ static class SignalFxConfig { accounts = signalfxSvc.getAccounts(); } } + + @Data + static class NewRelicConfig { + private boolean enabled; + List accounts; + + NewRelicConfig(NewRelicCanaryServiceIntegration newRelicSvc) { + enabled = newRelicSvc.isEnabled(); + accounts = newRelicSvc.getAccounts(); + } + } } } } diff --git a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/OrcaProfileFactory.java b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/OrcaProfileFactory.java index 1c67cacc7e..e19d2e14a9 100644 --- a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/OrcaProfileFactory.java +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/OrcaProfileFactory.java @@ -19,15 +19,12 @@ import com.netflix.spinnaker.halyard.config.model.v1.node.DeploymentConfiguration; import com.netflix.spinnaker.halyard.config.model.v1.node.Features; import com.netflix.spinnaker.halyard.config.model.v1.node.Webhook; -import com.netflix.spinnaker.halyard.config.model.v1.plugins.Plugin; import com.netflix.spinnaker.halyard.config.model.v1.providers.aws.AwsProvider; import com.netflix.spinnaker.halyard.deploy.spinnaker.v1.SpinnakerArtifact; import com.netflix.spinnaker.halyard.deploy.spinnaker.v1.SpinnakerRuntimeSettings; import com.netflix.spinnaker.halyard.deploy.spinnaker.v1.profile.integrations.IntegrationsConfigWrapper; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; import lombok.Data; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -80,19 +77,9 @@ protected void setProfile( // For backward compatibility profile.appendContents("pipelineTemplate.enabled: " + pipelineTemplates); - final List plugins = deploymentConfiguration.getPlugins().getPlugins(); - Map fullyRenderedYaml = new LinkedHashMap<>(); - Map pluginMetadata = - plugins.stream() - .filter(p -> p.getEnabled()) - .filter(p -> !p.getManifestLocation().isEmpty()) - .map(p -> p.generateManifest()) - .collect(Collectors.toMap(m -> m.getName(), m -> m.getOptions())); - - fullyRenderedYaml.put("plugins", pluginMetadata); - - profile.appendContents( - yamlToString(deploymentConfiguration.getName(), profile, fullyRenderedYaml)); + Map pluginsYaml = + deploymentConfiguration.getPlugins().getPluginConfigurations(); + profile.appendContents(yamlToString(deploymentConfiguration.getName(), profile, pluginsYaml)); } @Data diff --git a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/PluginProfileFactory.java b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/PluginProfileFactory.java index d8d921c180..3a8b8c2f33 100644 --- a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/PluginProfileFactory.java +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/PluginProfileFactory.java @@ -60,7 +60,6 @@ private Map composeMetadata(Plugin plugin, Manifest manifest) { metadata.put("enabled", plugin.getEnabled()); metadata.put("name", manifest.getName()); metadata.put("jars", manifest.getJars()); - metadata.put("manifestVersion", manifest.getManifestVersion()); return metadata; } diff --git a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/SamlConfig.java b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/SamlConfig.java index 3a400fd082..a2e4dbbcbc 100644 --- a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/SamlConfig.java +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/SamlConfig.java @@ -33,6 +33,8 @@ public class SamlConfig { boolean enabled; String issuerId; + + @SecretFile(prefix = "file:") String metadataUrl; @LocalFile @@ -57,7 +59,7 @@ public SamlConfig(Security security) { this.enabled = saml.isEnabled(); this.issuerId = saml.getIssuerId(); - this.metadataUrl = "file:" + saml.getMetadataLocal(); + this.metadataUrl = saml.getMetadataLocal(); if (StringUtils.isNotEmpty(saml.getMetadataRemote())) { this.metadataUrl = saml.getMetadataRemote(); } diff --git a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/SpinnakerMonitoringDaemonProfileFactory.java b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/SpinnakerMonitoringDaemonProfileFactory.java index 24e122511f..d329de8d2d 100644 --- a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/SpinnakerMonitoringDaemonProfileFactory.java +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/SpinnakerMonitoringDaemonProfileFactory.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.netflix.spinnaker.halyard.config.model.v1.metricStores.datadog.DatadogStore; +import com.netflix.spinnaker.halyard.config.model.v1.metricStores.newrelic.NewrelicStore; import com.netflix.spinnaker.halyard.config.model.v1.metricStores.prometheus.PrometheusStore; import com.netflix.spinnaker.halyard.config.model.v1.metricStores.stackdriver.StackdriverStore; import com.netflix.spinnaker.halyard.config.model.v1.node.DeploymentConfiguration; @@ -73,6 +74,11 @@ protected void setProfile( files.addAll(backupRequiredFiles(stackdriverStore, deploymentConfiguration.getName())); } + NewrelicStore newrelicStore = metricStores.getNewrelic(); + if (newrelicStore.isEnabled()) { + enabledMetricStores.add("newrelic"); + } + profile.appendContents(yamlToString(deploymentConfiguration.getName(), profile, metricStores)); Server server = diff --git a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/deck/DeckProfileFactory.java b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/deck/DeckProfileFactory.java index c2804c860c..e056841976 100644 --- a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/deck/DeckProfileFactory.java +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/deck/DeckProfileFactory.java @@ -106,7 +106,6 @@ protected void setProfile( // Configure feature-flags bindings.put("features.auth", Boolean.toString(features.isAuth(deploymentConfiguration))); bindings.put("features.chaos", Boolean.toString(features.isChaos())); - bindings.put("features.jobs", Boolean.toString(features.isJobs())); bindings.put( "features.fiat", Boolean.toString(deploymentConfiguration.getSecurity().getAuthz().isEnabled())); diff --git a/halyard-deploy/src/main/resources/kubernetes/manifests/clouddriver.yml b/halyard-deploy/src/main/resources/kubernetes/manifests/clouddriver.yml index fe73551e4c..f9fec56dec 100644 --- a/halyard-deploy/src/main/resources/kubernetes/manifests/clouddriver.yml +++ b/halyard-deploy/src/main/resources/kubernetes/manifests/clouddriver.yml @@ -1,4 +1,4 @@ -apiVersion: apps/v1beta2 # for versions before 1.9.0 use apps/v1beta2 +apiVersion: apps/v1 # As of Kubernetes 1.16, apps/v1beta2 has been deprecated kind: Deployment metadata: name: spin-clouddriver diff --git a/halyard-deploy/src/main/resources/kubernetes/manifests/deployment.yml b/halyard-deploy/src/main/resources/kubernetes/manifests/deployment.yml index 5c09691791..a1f9868590 100644 --- a/halyard-deploy/src/main/resources/kubernetes/manifests/deployment.yml +++ b/halyard-deploy/src/main/resources/kubernetes/manifests/deployment.yml @@ -1,4 +1,4 @@ -apiVersion: apps/v1beta2 # for versions before 1.9.0 use apps/v1beta2 +apiVersion: apps/v1 # As of Kubernetes 1.16, apps/v1beta2 has been deprecated kind: Deployment metadata: name: spin-{{ name }} diff --git a/halyard-deploy/src/test/groovy/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v2/KubernetesV2ServiceTest.groovy b/halyard-deploy/src/test/groovy/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v2/KubernetesV2ServiceTest.groovy index 4b1ee52e46..46a59e93d2 100644 --- a/halyard-deploy/src/test/groovy/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v2/KubernetesV2ServiceTest.groovy +++ b/halyard-deploy/src/test/groovy/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v2/KubernetesV2ServiceTest.groovy @@ -394,4 +394,16 @@ class KubernetesV2ServiceTest extends Specification { then: yaml.contains('"tolerations": [{"key":"test","operator":"Equal","value":"a","effect":"NoSchedule"}]') } + + def "Can we set ServiceAccountNames"() { + setup: + def executor = Mock(KubernetesV2Executor) + serviceSettings.getKubernetes().serviceAccountName = "customServiceAccount" + + when: + String podSpecYaml = testService.getPodSpecYaml(executor, details, config) + + then: + podSpecYaml.contains('"serviceAccountName": customServiceAccount') + } } diff --git a/halyard-web/src/main/java/com/netflix/spinnaker/halyard/controllers/v1/TelemetryController.java b/halyard-web/src/main/java/com/netflix/spinnaker/halyard/controllers/v1/TelemetryController.java new file mode 100644 index 0000000000..e9f2791248 --- /dev/null +++ b/halyard-web/src/main/java/com/netflix/spinnaker/halyard/controllers/v1/TelemetryController.java @@ -0,0 +1,77 @@ +/* + * Copyright 2019 Armory, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License") + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.halyard.controllers.v1; + +import com.netflix.spinnaker.halyard.config.config.v1.HalconfigDirectoryStructure; +import com.netflix.spinnaker.halyard.config.config.v1.HalconfigParser; +import com.netflix.spinnaker.halyard.config.model.v1.node.Halconfig; +import com.netflix.spinnaker.halyard.config.model.v1.node.Telemetry; +import com.netflix.spinnaker.halyard.config.services.v1.TelemetryService; +import com.netflix.spinnaker.halyard.core.tasks.v1.DaemonTask; +import com.netflix.spinnaker.halyard.models.v1.ValidationSettings; +import com.netflix.spinnaker.halyard.util.v1.GenericEnableDisableRequest; +import com.netflix.spinnaker.halyard.util.v1.GenericGetRequest; +import com.netflix.spinnaker.halyard.util.v1.GenericUpdateRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/v1/config/deployments/{deploymentName:.+}/telemetry") +@RequiredArgsConstructor +public class TelemetryController { + private final TelemetryService telemetryService; + private final HalconfigDirectoryStructure halconfigDirectoryStructure; + private final HalconfigParser halconfigParser; + + @RequestMapping(value = "/", method = RequestMethod.GET) + DaemonTask getTelemetry( + @PathVariable String deploymentName, @ModelAttribute ValidationSettings validationSettings) { + return GenericGetRequest.builder() + .getter(() -> telemetryService.getTelemetry(deploymentName)) + .validator(() -> telemetryService.validateTelemetry(deploymentName)) + .description("Get telemetry") + .build() + .execute(validationSettings); + } + + @RequestMapping(value = "/enabled", method = RequestMethod.PUT) + DaemonTask setTelemetryEnabled( + @PathVariable String deploymentName, + @ModelAttribute ValidationSettings validationSettings, + @RequestBody Boolean enabled) { + return GenericEnableDisableRequest.builder(halconfigParser) + .updater(t -> telemetryService.setTelemetryEnabled(deploymentName, false, enabled)) + .validator(() -> telemetryService.validateTelemetry(deploymentName)) + .description("Enable or disable telemetry") + .build() + .execute(validationSettings, enabled); + } + + @RequestMapping(value = "/", method = RequestMethod.PUT) + DaemonTask setTelemetry( + @PathVariable String deploymentName, + @ModelAttribute ValidationSettings validationSettings, + @RequestBody Telemetry telemetry) { + return GenericUpdateRequest.builder(halconfigParser) + .stagePath(halconfigDirectoryStructure.getStagingPath(deploymentName)) + .updater(t -> telemetryService.setTelemetry(deploymentName, t)) + .validator(() -> telemetryService.validateTelemetry(deploymentName)) + .description("Edit telemetry settings") + .build() + .execute(validationSettings, telemetry); + } +} diff --git a/install/macos/InstallHalyard.sh b/install/macos/InstallHalyard.sh index 11ec648fa5..b0b5c6e44f 100755 --- a/install/macos/InstallHalyard.sh +++ b/install/macos/InstallHalyard.sh @@ -154,6 +154,7 @@ function install_java() { [[ "$java_version" == *"10.0"* ]] || \ [[ "$java_version" == *"11"* ]] || \ [[ "$java_version" == *"12"* ]] || \ + [[ "$java_version" == *"13"* ]] || \ [[ "$java_version" == "java version \"10\""* ]]; then echo "Java is already installed & at the right version" return 0; diff --git a/release/docker.sh b/release/docker.sh index da52b392c6..1d398cf16b 100755 --- a/release/docker.sh +++ b/release/docker.sh @@ -2,4 +2,7 @@ set -e -docker build . -t halyard -f Dockerfile.local +docker build . -t halyard.compile -f Dockerfile.compile +docker build . -t halyard.local -f Dockerfile.local & +docker build . -t halyard.ubuntu -f Dockerfile.ubuntu & +wait diff --git a/release/promote.sh b/release/promote.sh index 27f070db39..79795ed539 100755 --- a/release/promote.sh +++ b/release/promote.sh @@ -14,15 +14,24 @@ if [ -z "$SOURCE_VERSION" ] || [ -z "$TARGET_VERSION" ] || [ -z "$PLATFORM" ]; t exit 1 fi -if [ "$PLATFORM" = "docker" ]; then - PUBLISH_HALYARD_DOCKER_IMAGE_BASE=${PUBLISH_HALYARD_DOCKER_IMAGE_BASE:-gcr.io/spinnaker-marketplace/halyard} - SOURCE_IMAGE=$PUBLISH_HALYARD_DOCKER_IMAGE_BASE:$SOURCE_VERSION - TARGET_IMAGE=$PUBLISH_HALYARD_DOCKER_IMAGE_BASE:$TARGET_VERSION +function pull_tag_push() { + local SOURCE_IMAGE=$1 + local TARGET_IMAGE=$2 docker pull $SOURCE_IMAGE docker tag $SOURCE_IMAGE $TARGET_IMAGE echo "Pushing docker image $TARGET_NAME" gcloud docker -- push $TARGET_IMAGE +} + +if [ "$PLATFORM" = "docker" ]; then + PUBLISH_HALYARD_DOCKER_IMAGE_BASE=${PUBLISH_HALYARD_DOCKER_IMAGE_BASE:-gcr.io/spinnaker-marketplace/halyard} + SOURCE_IMAGE=$PUBLISH_HALYARD_DOCKER_IMAGE_BASE:$SOURCE_VERSION + TARGET_IMAGE=$PUBLISH_HALYARD_DOCKER_IMAGE_BASE:$TARGET_VERSION + + pull_tag_push $SOURCE_IMAGE $TARGET_IMAGE + pull_tag_push ${SOURCE_IMAGE}-ubuntu ${TARGET_IMAGE}-ubuntu + else PUBLISH_HALYARD_BUCKET_BASE_URL=${PUBLISH_HALYARD_BUCKET_BASE_URL:-gs://spinnaker-artifacts/halyard} SOURCE_URL=$PUBLISH_HALYARD_BUCKET_BASE_URL/$SOURCE_VERSION/$PLATFORM/halyard.tar.gz diff --git a/release/publish.sh b/release/publish.sh index 5db93887e6..2e4b81111e 100755 --- a/release/publish.sh +++ b/release/publish.sh @@ -19,14 +19,21 @@ fi ./release/$PLATFORM.sh +function tag_and_push() { + local SOURCE_IMAGE=$1 + local TARGET_IMAGE=$2 + echo "Pushing docker image $TARGET_IMAGE" + docker tag $SOURCE_IMAGE $TARGET_IMAGE + gcloud docker -- push $TARGET_IMAGE +} + if [ "$PLATFORM" = "docker" ]; then PUBLISH_HALYARD_DOCKER_IMAGE_BASE=${PUBLISH_HALYARD_DOCKER_IMAGE_BASE:-gcr.io/spinnaker-marketplace/halyard} IMAGE=$PUBLISH_HALYARD_DOCKER_IMAGE_BASE:$VERSION - echo "Pushing docker image $IMAGE" - docker tag halyard $IMAGE - gcloud docker -- push $IMAGE -else + tag_and_push halyard.local $IMAGE + tag_and_push halyard.ubuntu ${IMAGE}-ubuntu +else PUBLISH_HALYARD_BUCKET_BASE_URL=${PUBLISH_HALYARD_BUCKET_BASE_URL:-gs://spinnaker-artifacts/halyard} BUCKET_URL=$PUBLISH_HALYARD_BUCKET_BASE_URL/$VERSION/$PLATFORM/halyard.tar.gz