diff --git a/build.gradle b/build.gradle index fbf0672e8c..25aa561178 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,6 @@ buildscript { ext { - korkVersion = "5.3.7" clouddriverVersion = "5.8.0" front50Version = "2.1.0" fiatVersion = "1.0.5" diff --git a/cloudbuild.yaml b/cloudbuild.yaml index 3b8e4b06d4..eada2f94ee 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -4,8 +4,8 @@ steps: entrypoint: "bash" args: [ "-c", "./gradlew halyard-web:installDist -x test"] - name: 'gcr.io/cloud-builders/docker' - args: ["build", "-t", "gcr.io/$PROJECT_ID/$REPO_NAME:$COMMIT_SHA", "-t", "gcr.io/$PROJECT_ID/$REPO_NAME:latest", "-f", "Dockerfile.slim", "."] + 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/$REPO_NAME:$COMMIT_SHA' -- 'gcr.io/$PROJECT_ID/$REPO_NAME:latest' +- '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 6687fa1911..96710f6a7b 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -308,6 +308,10 @@ * [**hal config metric-stores stackdriver edit**](#hal-config-metric-stores-stackdriver-edit) * [**hal config metric-stores stackdriver enable**](#hal-config-metric-stores-stackdriver-enable) * [**hal config notification**](#hal-config-notification) + * [**hal config notification github-status**](#hal-config-notification-github-status) + * [**hal config notification github-status disable**](#hal-config-notification-github-status-disable) + * [**hal config notification github-status edit**](#hal-config-notification-github-status-edit) + * [**hal config notification github-status enable**](#hal-config-notification-github-status-enable) * [**hal config notification pubsub**](#hal-config-notification-pubsub) * [**hal config notification pubsub google**](#hal-config-notification-pubsub-google) * [**hal config notification pubsub google add**](#hal-config-notification-pubsub-google-add) @@ -543,6 +547,15 @@ * [**hal deploy details**](#hal-deploy-details) * [**hal deploy diff**](#hal-deploy-diff) * [**hal deploy rollback**](#hal-deploy-rollback) + * [**hal plugins**](#hal-plugins) + * [**hal plugins add**](#hal-plugins-add) + * [**hal plugins delete**](#hal-plugins-delete) + * [**hal plugins disable**](#hal-plugins-disable) + * [**hal plugins disable-downloading**](#hal-plugins-disable-downloading) + * [**hal plugins edit**](#hal-plugins-edit) + * [**hal plugins enable**](#hal-plugins-enable) + * [**hal plugins enable-downloading**](#hal-plugins-enable-downloading) + * [**hal plugins list**](#hal-plugins-list) * [**hal shutdown**](#hal-shutdown) * [**hal spin**](#hal-spin) * [**hal spin install**](#hal-spin-install) @@ -586,6 +599,7 @@ hal [parameters] [subcommands] * `backup`: Backup and restore (remote or local) copies of your halconfig and all required files. * `config`: Configure, validate, and view your halconfig. * `deploy`: Manage the deployment of Spinnaker. This includes where it's deployed, what the infrastructure footprint looks like, what the currently running deployment looks like, etc... + * `plugins`: Show Spinnaker's configured plugins. * `shutdown`: Shutdown the halyard daemon. * `spin`: Manage the lifecycle of spin CLI. * `task`: This set of commands exposes utilities of dealing with Halyard's task engine. @@ -4160,6 +4174,10 @@ hal config deploy component-sizing clouddriver edit [parameters] ``` #### Parameters + * `--container-limits-cpu`: Sets the cpu limit for the container running the spinnaker service. Example: 1. + * `--container-limits-memory`: Sets the memory limit for the container running the spinnaker service. Example: 1Gi. + * `--container-requests-cpu`: Sets the cpu request for the container running the spinnaker service. Example: 250m. + * `--container-requests-memory`: Sets the memory request for the container running the spinnaker service. Example: 512Mi. * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. * `--no-validate`: (*Default*: `false`) Skip validation. * `--pod-limits-cpu`: Sets the cpu limit for the container running the spinnaker service, as well as any sidecar containers (e.g. the monitoring daemon). Example: 1. @@ -4213,6 +4231,10 @@ hal config deploy component-sizing clouddriver-bootstrap edit [parameters] ``` #### Parameters + * `--container-limits-cpu`: Sets the cpu limit for the container running the spinnaker service. Example: 1. + * `--container-limits-memory`: Sets the memory limit for the container running the spinnaker service. Example: 1Gi. + * `--container-requests-cpu`: Sets the cpu request for the container running the spinnaker service. Example: 250m. + * `--container-requests-memory`: Sets the memory request for the container running the spinnaker service. Example: 512Mi. * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. * `--no-validate`: (*Default*: `false`) Skip validation. * `--pod-limits-cpu`: Sets the cpu limit for the container running the spinnaker service, as well as any sidecar containers (e.g. the monitoring daemon). Example: 1. @@ -4266,6 +4288,10 @@ hal config deploy component-sizing clouddriver-caching edit [parameters] ``` #### Parameters + * `--container-limits-cpu`: Sets the cpu limit for the container running the spinnaker service. Example: 1. + * `--container-limits-memory`: Sets the memory limit for the container running the spinnaker service. Example: 1Gi. + * `--container-requests-cpu`: Sets the cpu request for the container running the spinnaker service. Example: 250m. + * `--container-requests-memory`: Sets the memory request for the container running the spinnaker service. Example: 512Mi. * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. * `--no-validate`: (*Default*: `false`) Skip validation. * `--pod-limits-cpu`: Sets the cpu limit for the container running the spinnaker service, as well as any sidecar containers (e.g. the monitoring daemon). Example: 1. @@ -4319,6 +4345,10 @@ hal config deploy component-sizing clouddriver-ro edit [parameters] ``` #### Parameters + * `--container-limits-cpu`: Sets the cpu limit for the container running the spinnaker service. Example: 1. + * `--container-limits-memory`: Sets the memory limit for the container running the spinnaker service. Example: 1Gi. + * `--container-requests-cpu`: Sets the cpu request for the container running the spinnaker service. Example: 250m. + * `--container-requests-memory`: Sets the memory request for the container running the spinnaker service. Example: 512Mi. * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. * `--no-validate`: (*Default*: `false`) Skip validation. * `--pod-limits-cpu`: Sets the cpu limit for the container running the spinnaker service, as well as any sidecar containers (e.g. the monitoring daemon). Example: 1. @@ -4372,6 +4402,10 @@ hal config deploy component-sizing clouddriver-ro-deck edit [parameters] ``` #### Parameters + * `--container-limits-cpu`: Sets the cpu limit for the container running the spinnaker service. Example: 1. + * `--container-limits-memory`: Sets the memory limit for the container running the spinnaker service. Example: 1Gi. + * `--container-requests-cpu`: Sets the cpu request for the container running the spinnaker service. Example: 250m. + * `--container-requests-memory`: Sets the memory request for the container running the spinnaker service. Example: 512Mi. * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. * `--no-validate`: (*Default*: `false`) Skip validation. * `--pod-limits-cpu`: Sets the cpu limit for the container running the spinnaker service, as well as any sidecar containers (e.g. the monitoring daemon). Example: 1. @@ -4425,6 +4459,10 @@ hal config deploy component-sizing clouddriver-rw edit [parameters] ``` #### Parameters + * `--container-limits-cpu`: Sets the cpu limit for the container running the spinnaker service. Example: 1. + * `--container-limits-memory`: Sets the memory limit for the container running the spinnaker service. Example: 1Gi. + * `--container-requests-cpu`: Sets the cpu request for the container running the spinnaker service. Example: 250m. + * `--container-requests-memory`: Sets the memory request for the container running the spinnaker service. Example: 512Mi. * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. * `--no-validate`: (*Default*: `false`) Skip validation. * `--pod-limits-cpu`: Sets the cpu limit for the container running the spinnaker service, as well as any sidecar containers (e.g. the monitoring daemon). Example: 1. @@ -4478,6 +4516,10 @@ hal config deploy component-sizing consul-client edit [parameters] ``` #### Parameters + * `--container-limits-cpu`: Sets the cpu limit for the container running the spinnaker service. Example: 1. + * `--container-limits-memory`: Sets the memory limit for the container running the spinnaker service. Example: 1Gi. + * `--container-requests-cpu`: Sets the cpu request for the container running the spinnaker service. Example: 250m. + * `--container-requests-memory`: Sets the memory request for the container running the spinnaker service. Example: 512Mi. * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. * `--no-validate`: (*Default*: `false`) Skip validation. * `--pod-limits-cpu`: Sets the cpu limit for the container running the spinnaker service, as well as any sidecar containers (e.g. the monitoring daemon). Example: 1. @@ -4531,6 +4573,10 @@ hal config deploy component-sizing consul-server edit [parameters] ``` #### Parameters + * `--container-limits-cpu`: Sets the cpu limit for the container running the spinnaker service. Example: 1. + * `--container-limits-memory`: Sets the memory limit for the container running the spinnaker service. Example: 1Gi. + * `--container-requests-cpu`: Sets the cpu request for the container running the spinnaker service. Example: 250m. + * `--container-requests-memory`: Sets the memory request for the container running the spinnaker service. Example: 512Mi. * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. * `--no-validate`: (*Default*: `false`) Skip validation. * `--pod-limits-cpu`: Sets the cpu limit for the container running the spinnaker service, as well as any sidecar containers (e.g. the monitoring daemon). Example: 1. @@ -4584,6 +4630,10 @@ hal config deploy component-sizing deck edit [parameters] ``` #### Parameters + * `--container-limits-cpu`: Sets the cpu limit for the container running the spinnaker service. Example: 1. + * `--container-limits-memory`: Sets the memory limit for the container running the spinnaker service. Example: 1Gi. + * `--container-requests-cpu`: Sets the cpu request for the container running the spinnaker service. Example: 250m. + * `--container-requests-memory`: Sets the memory request for the container running the spinnaker service. Example: 512Mi. * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. * `--no-validate`: (*Default*: `false`) Skip validation. * `--pod-limits-cpu`: Sets the cpu limit for the container running the spinnaker service, as well as any sidecar containers (e.g. the monitoring daemon). Example: 1. @@ -4637,6 +4687,10 @@ hal config deploy component-sizing echo edit [parameters] ``` #### Parameters + * `--container-limits-cpu`: Sets the cpu limit for the container running the spinnaker service. Example: 1. + * `--container-limits-memory`: Sets the memory limit for the container running the spinnaker service. Example: 1Gi. + * `--container-requests-cpu`: Sets the cpu request for the container running the spinnaker service. Example: 250m. + * `--container-requests-memory`: Sets the memory request for the container running the spinnaker service. Example: 512Mi. * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. * `--no-validate`: (*Default*: `false`) Skip validation. * `--pod-limits-cpu`: Sets the cpu limit for the container running the spinnaker service, as well as any sidecar containers (e.g. the monitoring daemon). Example: 1. @@ -4690,6 +4744,10 @@ hal config deploy component-sizing echo-scheduler edit [parameters] ``` #### Parameters + * `--container-limits-cpu`: Sets the cpu limit for the container running the spinnaker service. Example: 1. + * `--container-limits-memory`: Sets the memory limit for the container running the spinnaker service. Example: 1Gi. + * `--container-requests-cpu`: Sets the cpu request for the container running the spinnaker service. Example: 250m. + * `--container-requests-memory`: Sets the memory request for the container running the spinnaker service. Example: 512Mi. * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. * `--no-validate`: (*Default*: `false`) Skip validation. * `--pod-limits-cpu`: Sets the cpu limit for the container running the spinnaker service, as well as any sidecar containers (e.g. the monitoring daemon). Example: 1. @@ -4743,6 +4801,10 @@ hal config deploy component-sizing echo-worker edit [parameters] ``` #### Parameters + * `--container-limits-cpu`: Sets the cpu limit for the container running the spinnaker service. Example: 1. + * `--container-limits-memory`: Sets the memory limit for the container running the spinnaker service. Example: 1Gi. + * `--container-requests-cpu`: Sets the cpu request for the container running the spinnaker service. Example: 250m. + * `--container-requests-memory`: Sets the memory request for the container running the spinnaker service. Example: 512Mi. * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. * `--no-validate`: (*Default*: `false`) Skip validation. * `--pod-limits-cpu`: Sets the cpu limit for the container running the spinnaker service, as well as any sidecar containers (e.g. the monitoring daemon). Example: 1. @@ -4796,6 +4858,10 @@ hal config deploy component-sizing fiat edit [parameters] ``` #### Parameters + * `--container-limits-cpu`: Sets the cpu limit for the container running the spinnaker service. Example: 1. + * `--container-limits-memory`: Sets the memory limit for the container running the spinnaker service. Example: 1Gi. + * `--container-requests-cpu`: Sets the cpu request for the container running the spinnaker service. Example: 250m. + * `--container-requests-memory`: Sets the memory request for the container running the spinnaker service. Example: 512Mi. * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. * `--no-validate`: (*Default*: `false`) Skip validation. * `--pod-limits-cpu`: Sets the cpu limit for the container running the spinnaker service, as well as any sidecar containers (e.g. the monitoring daemon). Example: 1. @@ -4849,6 +4915,10 @@ hal config deploy component-sizing front50 edit [parameters] ``` #### Parameters + * `--container-limits-cpu`: Sets the cpu limit for the container running the spinnaker service. Example: 1. + * `--container-limits-memory`: Sets the memory limit for the container running the spinnaker service. Example: 1Gi. + * `--container-requests-cpu`: Sets the cpu request for the container running the spinnaker service. Example: 250m. + * `--container-requests-memory`: Sets the memory request for the container running the spinnaker service. Example: 512Mi. * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. * `--no-validate`: (*Default*: `false`) Skip validation. * `--pod-limits-cpu`: Sets the cpu limit for the container running the spinnaker service, as well as any sidecar containers (e.g. the monitoring daemon). Example: 1. @@ -4902,6 +4972,10 @@ hal config deploy component-sizing gate edit [parameters] ``` #### Parameters + * `--container-limits-cpu`: Sets the cpu limit for the container running the spinnaker service. Example: 1. + * `--container-limits-memory`: Sets the memory limit for the container running the spinnaker service. Example: 1Gi. + * `--container-requests-cpu`: Sets the cpu request for the container running the spinnaker service. Example: 250m. + * `--container-requests-memory`: Sets the memory request for the container running the spinnaker service. Example: 512Mi. * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. * `--no-validate`: (*Default*: `false`) Skip validation. * `--pod-limits-cpu`: Sets the cpu limit for the container running the spinnaker service, as well as any sidecar containers (e.g. the monitoring daemon). Example: 1. @@ -4955,6 +5029,10 @@ hal config deploy component-sizing igor edit [parameters] ``` #### Parameters + * `--container-limits-cpu`: Sets the cpu limit for the container running the spinnaker service. Example: 1. + * `--container-limits-memory`: Sets the memory limit for the container running the spinnaker service. Example: 1Gi. + * `--container-requests-cpu`: Sets the cpu request for the container running the spinnaker service. Example: 250m. + * `--container-requests-memory`: Sets the memory request for the container running the spinnaker service. Example: 512Mi. * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. * `--no-validate`: (*Default*: `false`) Skip validation. * `--pod-limits-cpu`: Sets the cpu limit for the container running the spinnaker service, as well as any sidecar containers (e.g. the monitoring daemon). Example: 1. @@ -5008,6 +5086,10 @@ hal config deploy component-sizing kayenta edit [parameters] ``` #### Parameters + * `--container-limits-cpu`: Sets the cpu limit for the container running the spinnaker service. Example: 1. + * `--container-limits-memory`: Sets the memory limit for the container running the spinnaker service. Example: 1Gi. + * `--container-requests-cpu`: Sets the cpu request for the container running the spinnaker service. Example: 250m. + * `--container-requests-memory`: Sets the memory request for the container running the spinnaker service. Example: 512Mi. * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. * `--no-validate`: (*Default*: `false`) Skip validation. * `--pod-limits-cpu`: Sets the cpu limit for the container running the spinnaker service, as well as any sidecar containers (e.g. the monitoring daemon). Example: 1. @@ -5061,6 +5143,10 @@ hal config deploy component-sizing monitoring-daemon edit [parameters] ``` #### Parameters + * `--container-limits-cpu`: Sets the cpu limit for the container running the spinnaker service. Example: 1. + * `--container-limits-memory`: Sets the memory limit for the container running the spinnaker service. Example: 1Gi. + * `--container-requests-cpu`: Sets the cpu request for the container running the spinnaker service. Example: 250m. + * `--container-requests-memory`: Sets the memory request for the container running the spinnaker service. Example: 512Mi. * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. * `--no-validate`: (*Default*: `false`) Skip validation. * `--pod-limits-cpu`: Sets the cpu limit for the container running the spinnaker service, as well as any sidecar containers (e.g. the monitoring daemon). Example: 1. @@ -5114,6 +5200,10 @@ hal config deploy component-sizing orca edit [parameters] ``` #### Parameters + * `--container-limits-cpu`: Sets the cpu limit for the container running the spinnaker service. Example: 1. + * `--container-limits-memory`: Sets the memory limit for the container running the spinnaker service. Example: 1Gi. + * `--container-requests-cpu`: Sets the cpu request for the container running the spinnaker service. Example: 250m. + * `--container-requests-memory`: Sets the memory request for the container running the spinnaker service. Example: 512Mi. * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. * `--no-validate`: (*Default*: `false`) Skip validation. * `--pod-limits-cpu`: Sets the cpu limit for the container running the spinnaker service, as well as any sidecar containers (e.g. the monitoring daemon). Example: 1. @@ -5167,6 +5257,10 @@ hal config deploy component-sizing orca-bootstrap edit [parameters] ``` #### Parameters + * `--container-limits-cpu`: Sets the cpu limit for the container running the spinnaker service. Example: 1. + * `--container-limits-memory`: Sets the memory limit for the container running the spinnaker service. Example: 1Gi. + * `--container-requests-cpu`: Sets the cpu request for the container running the spinnaker service. Example: 250m. + * `--container-requests-memory`: Sets the memory request for the container running the spinnaker service. Example: 512Mi. * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. * `--no-validate`: (*Default*: `false`) Skip validation. * `--pod-limits-cpu`: Sets the cpu limit for the container running the spinnaker service, as well as any sidecar containers (e.g. the monitoring daemon). Example: 1. @@ -5220,6 +5314,10 @@ hal config deploy component-sizing redis edit [parameters] ``` #### Parameters + * `--container-limits-cpu`: Sets the cpu limit for the container running the spinnaker service. Example: 1. + * `--container-limits-memory`: Sets the memory limit for the container running the spinnaker service. Example: 1Gi. + * `--container-requests-cpu`: Sets the cpu request for the container running the spinnaker service. Example: 250m. + * `--container-requests-memory`: Sets the memory request for the container running the spinnaker service. Example: 512Mi. * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. * `--no-validate`: (*Default*: `false`) Skip validation. * `--pod-limits-cpu`: Sets the cpu limit for the container running the spinnaker service, as well as any sidecar containers (e.g. the monitoring daemon). Example: 1. @@ -5273,6 +5371,10 @@ hal config deploy component-sizing redis-bootstrap edit [parameters] ``` #### Parameters + * `--container-limits-cpu`: Sets the cpu limit for the container running the spinnaker service. Example: 1. + * `--container-limits-memory`: Sets the memory limit for the container running the spinnaker service. Example: 1Gi. + * `--container-requests-cpu`: Sets the cpu request for the container running the spinnaker service. Example: 250m. + * `--container-requests-memory`: Sets the memory request for the container running the spinnaker service. Example: 512Mi. * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. * `--no-validate`: (*Default*: `false`) Skip validation. * `--pod-limits-cpu`: Sets the cpu limit for the container running the spinnaker service, as well as any sidecar containers (e.g. the monitoring daemon). Example: 1. @@ -5326,6 +5428,10 @@ hal config deploy component-sizing rosco edit [parameters] ``` #### Parameters + * `--container-limits-cpu`: Sets the cpu limit for the container running the spinnaker service. Example: 1. + * `--container-limits-memory`: Sets the memory limit for the container running the spinnaker service. Example: 1Gi. + * `--container-requests-cpu`: Sets the cpu request for the container running the spinnaker service. Example: 250m. + * `--container-requests-memory`: Sets the memory request for the container running the spinnaker service. Example: 512Mi. * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. * `--no-validate`: (*Default*: `false`) Skip validation. * `--pod-limits-cpu`: Sets the cpu limit for the container running the spinnaker service, as well as any sidecar containers (e.g. the monitoring daemon). Example: 1. @@ -5379,6 +5485,10 @@ hal config deploy component-sizing vault-client edit [parameters] ``` #### Parameters + * `--container-limits-cpu`: Sets the cpu limit for the container running the spinnaker service. Example: 1. + * `--container-limits-memory`: Sets the memory limit for the container running the spinnaker service. Example: 1Gi. + * `--container-requests-cpu`: Sets the cpu request for the container running the spinnaker service. Example: 250m. + * `--container-requests-memory`: Sets the memory request for the container running the spinnaker service. Example: 512Mi. * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. * `--no-validate`: (*Default*: `false`) Skip validation. * `--pod-limits-cpu`: Sets the cpu limit for the container running the spinnaker service, as well as any sidecar containers (e.g. the monitoring daemon). Example: 1. @@ -5432,6 +5542,10 @@ hal config deploy component-sizing vault-server edit [parameters] ``` #### Parameters + * `--container-limits-cpu`: Sets the cpu limit for the container running the spinnaker service. Example: 1. + * `--container-limits-memory`: Sets the memory limit for the container running the spinnaker service. Example: 1Gi. + * `--container-requests-cpu`: Sets the cpu request for the container running the spinnaker service. Example: 250m. + * `--container-requests-memory`: Sets the memory request for the container running the spinnaker service. Example: 512Mi. * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. * `--no-validate`: (*Default*: `false`) Skip validation. * `--pod-limits-cpu`: Sets the cpu limit for the container running the spinnaker service, as well as any sidecar containers (e.g. the monitoring daemon). Example: 1. @@ -5460,6 +5574,11 @@ This is only required when Spinnaker is being deployed in non-Kubernetes cluster * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. * `--git-origin-user`: This is the git user your github fork exists under. * `--git-upstream-user`: This is the upstream git user you are configuring to pull changes from & push PRs to. + * `--image-variant`: The container image variant type to use when deploying a distributed installation of Spinnaker. +Warning: variants other than the 'slim' one are only available with Spinnaker v1.16+ + slim: Based on an Alpine image + ubuntu: Based on Canonical's ubuntu:bionic image. +Default value: slim * `--liveness-probe-enabled`: When true, enable Kubernetes liveness probes on Spinnaker services deployed in a Distributed installation. See docs for more information: [https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/) * `--liveness-probe-initial-delay-seconds`: The number of seconds to wait before performing the first liveness probe. Should be set to the longest service startup time. See docs for more information: [https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/) * `--location`: This is the location spinnaker will be deployed to. When deploying to Kubernetes, use this flag to specify the namespace to deploy to (defaults to 'spinnaker') @@ -5649,6 +5768,7 @@ hal config features edit [parameters] #### Parameters * `--appengine-container-image-url-deployments`: Enable appengine deployments using a container image URL from gcr.io. * `--artifacts`: Enable artifact support. Read more at [https://spinnaker.io/reference/artifacts/](https://spinnaker.io/reference/artifacts/) + * `--artifacts-rewrite`: Enable new artifact support. Read more at [https://www.spinnaker.io/reference/artifacts-with-artifactsrewrite/](https://www.spinnaker.io/reference/artifacts-with-artifactsrewrite/) * `--chaos`: Enable Chaos Monkey support. For this to work, you'll need a running Chaos Monkey deployment. Currently, Halyard doesn't configure Chaos Monkey for you; read more instructions here [https://github.com/Netflix/chaosmonkey/wiki](https://github.com/Netflix/chaosmonkey/wiki). * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. * `--gremlin`: Enable Gremlin fault-injection support. @@ -5944,10 +6064,76 @@ hal config notification [parameters] [subcommands] * `--no-validate`: (*Default*: `false`) Skip validation. #### Subcommands + * `github-status`: Manage and view Spinnaker configuration for the github-status notification * `pubsub`: Configure, validate, and view the specified pubsub. * `slack`: Manage and view Spinnaker configuration for the slack notification * `twilio`: Manage and view Spinnaker configuration for the twilio notification +--- +## hal config notification github-status + +Manage and view Spinnaker configuration for the github-status notification + +#### Usage +``` +hal config notification github-status [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 github-status notification as disabled + * `edit`: Edit the github-status notification type + * `enable`: Set the github-status notification as enabled + +--- +## hal config notification github-status disable + +Set the github-status notification as disabled + +#### Usage +``` +hal config notification github-status 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 notification github-status edit + +Edit the github-status notification type + +#### Usage +``` +hal config notification github-status edit [parameters] +``` + +#### Parameters + * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. + * `--no-validate`: (*Default*: `false`) Skip validation. + * `--token`: (*Sensitive data* - user will be prompted on standard input) Your github account token. + + +--- +## hal config notification github-status enable + +Set the github-status notification as enabled + +#### Usage +``` +hal config notification github-status 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 notification pubsub @@ -8473,7 +8659,7 @@ hal config provider kubernetes disable [parameters] --- ## hal config provider kubernetes edit -Due to how the Kubenretes provider shards its cache resources, there is opportunity to tune how its caching should be handled. This command exists to allow you tune this caching behavior. +Due to how the Kubernetes provider shards its cache resources, there is opportunity to tune how its caching should be handled. This command exists to allow you tune this caching behavior. #### Usage ``` @@ -10202,6 +10388,7 @@ Example: "user/spinnaker" or "role/spinnakerManaged" * `--region`: This is only required if the bucket you specify doesn't exist yet. In that case, the bucket will be created in that region. See [http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region](http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region). * `--root-folder`: The root folder in the chosen bucket to place all of Spinnaker's persistent data in. * `--secret-access-key`: (*Sensitive data* - user will be prompted on standard input) Your AWS Secret Key. + * `--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'. --- @@ -10463,6 +10650,157 @@ hal deploy rollback [parameters] * `--service-names`: (*Default*: `[]`) When supplied, only install or update the specified Spinnaker services. +--- +## hal plugins + +Show Spinnaker's configured plugins. + +#### Usage +``` +hal plugins [parameters] [subcommands] +``` + +#### Parameters + * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. + * `--no-validate`: (*Default*: `false`) Skip validation. + +#### Subcommands + * `add`: Add a plugin + * `delete`: Delete a plugin + * `disable`: Enable or disable all plugins + * `disable-downloading`: Enable or disable the ability for Spinnaker services to download jars for plugins + * `edit`: Edit a plugin + * `enable`: Enable or disable all plugins + * `enable-downloading`: Enable or disable the ability for Spinnaker services to download jars for plugins + * `list`: List all plugins + +--- +## hal plugins add + +Add a plugin + +#### Usage +``` +hal plugins add PLUGIN [parameters] +``` + +#### Parameters +`PLUGIN`: The name of the plugin to operate on. + * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. + * `--enabled`: To enable or disable the plugin. + * `--manifest-location`: (*Required*) The location of the plugin's manifest file. + * `--no-validate`: (*Default*: `false`) Skip validation. + + +--- +## hal plugins delete + +Delete a plugin + +#### Usage +``` +hal plugins delete PLUGIN [parameters] +``` + +#### Parameters +`PLUGIN`: The name of the plugin to operate on. + * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. + * `--no-validate`: (*Default*: `false`) Skip validation. + + +--- +## hal plugins disable + +Enable or disable all plugins + +#### Usage +``` +hal plugins disable [parameters] +``` + +#### Parameters + * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. + * `--no-validate`: (*Default*: `false`) Skip validation. + + +--- +## hal plugins disable-downloading + +Enable or disable the ability for Spinnaker services to download jars for plugins + +#### Usage +``` +hal plugins disable-downloading [parameters] +``` + +#### Parameters + * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. + * `--no-validate`: (*Default*: `false`) Skip validation. + + +--- +## hal plugins edit + +Edit a plugin + +#### Usage +``` +hal plugins edit PLUGIN [parameters] +``` + +#### Parameters +`PLUGIN`: The name of the plugin to operate on. + * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. + * `--enabled`: To enable or disable the plugin. + * `--manifest-location`: The location of the plugin's manifest file. + * `--no-validate`: (*Default*: `false`) Skip validation. + + +--- +## hal plugins enable + +Enable or disable all plugins + +#### Usage +``` +hal plugins enable [parameters] +``` + +#### Parameters + * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. + * `--no-validate`: (*Default*: `false`) Skip validation. + + +--- +## hal plugins enable-downloading + +Enable or disable the ability for Spinnaker services to download jars for plugins + +#### Usage +``` +hal plugins enable-downloading [parameters] +``` + +#### Parameters + * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. + * `--no-validate`: (*Default*: `false`) Skip validation. + + +--- +## hal plugins list + +List all plugins + +#### Usage +``` +hal plugins list [parameters] +``` + +#### Parameters + * `--deployment`: If supplied, use this Halyard deployment. This will _not_ create a new deployment. + * `--no-validate`: (*Default*: `false`) Skip validation. + + --- ## hal shutdown diff --git a/gradle.properties b/gradle.properties index 7a48f4fe73..18d245439c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -#Tue Jul 16 18:16:36 UTC 2019 +#Thu Sep 05 14:11:55 UTC 2019 enablePublishing=false -korkVersion=5.8.7 +korkVersion=6.6.0 org.gradle.parallel=true diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/HalCommand.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/HalCommand.java index fdd0c0841e..2078fb6ec2 100644 --- a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/HalCommand.java +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/HalCommand.java @@ -58,6 +58,7 @@ public HalCommand() { registerSubcommand(new TaskCommand()); registerSubcommand(new VersionCommand()); registerSubcommand(new SpinCommand()); + registerSubcommand(new PluginCommand()); } static String getVersion() { diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/PluginCommand.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/PluginCommand.java new file mode 100644 index 0000000000..a74e9af262 --- /dev/null +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/PluginCommand.java @@ -0,0 +1,48 @@ +/* + * 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; + +import com.beust.jcommander.Parameters; +import com.netflix.spinnaker.halyard.cli.command.v1.config.AbstractConfigCommand; +import com.netflix.spinnaker.halyard.cli.command.v1.plugins.*; +import lombok.AccessLevel; +import lombok.Getter; + +@Parameters(separators = "=") +public class PluginCommand extends AbstractConfigCommand { + @Getter(AccessLevel.PUBLIC) + private String commandName = "plugins"; + + @Getter(AccessLevel.PUBLIC) + private String shortDescription = "Show Spinnaker's configured plugins."; + + public PluginCommand() { + registerSubcommand(new AddPluginCommand()); + registerSubcommand(new EditPluginCommand()); + registerSubcommand(new DeletePluginCommand()); + registerSubcommand(new ListPluginsCommand()); + registerSubcommand(new PluginEnableDisableCommandBuilder().setEnable(true).build()); + registerSubcommand(new PluginEnableDisableCommandBuilder().setEnable(false).build()); + registerSubcommand(new PluginDownloadingEnableDisableCommandBuilder().setEnable(true).build()); + registerSubcommand(new PluginDownloadingEnableDisableCommandBuilder().setEnable(false).build()); + } + + @Override + protected void executeThis() { + showHelp(); + } +} diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/EditDeploymentEnvironmentCommand.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/EditDeploymentEnvironmentCommand.java index ba2377451d..f87f88c7ff 100644 --- a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/EditDeploymentEnvironmentCommand.java +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/EditDeploymentEnvironmentCommand.java @@ -20,11 +20,13 @@ import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameters; import com.netflix.spinnaker.halyard.cli.command.v1.converter.DeploymentTypeConverter; +import com.netflix.spinnaker.halyard.cli.command.v1.converter.ImageVariantConverter; 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.AnsiUi; import com.netflix.spinnaker.halyard.config.model.v1.node.DeploymentEnvironment; import com.netflix.spinnaker.halyard.config.model.v1.node.DeploymentEnvironment.DeploymentType; +import com.netflix.spinnaker.halyard.config.model.v1.node.DeploymentEnvironment.ImageVariant; import lombok.AccessLevel; import lombok.Getter; @@ -104,6 +106,17 @@ public class EditDeploymentEnvironmentCommand extends AbstractConfigCommand { + "Kubernetes, use this flag to specify the namespace to deploy to (defaults to 'spinnaker')") private String location; + @Parameter( + names = "--image-variant", + description = + "The container image variant type to use when deploying a distributed installation of Spinnaker.\n" + + "Warning: variants other than the 'slim' one are only available with Spinnaker v1.16+\n" + + "\tslim: Based on an Alpine image\n" + + "\tubuntu: Based on Canonical's ubuntu:bionic image.\n" + + "Default value: slim", + converter = ImageVariantConverter.class) + private ImageVariant imageVariant; + @Parameter( names = "--git-upstream-user", description = @@ -194,6 +207,9 @@ protected void executeThis() { deploymentEnvironment.setLocation( isSet(location) ? location : deploymentEnvironment.getLocation()); + deploymentEnvironment.setImageVariant( + isSet(imageVariant) ? imageVariant : deploymentEnvironment.getImageVariant()); + if (originalHash == deploymentEnvironment.hashCode()) { AnsiUi.failure("No changes supplied."); return; 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 11e05d2b5d..33337d7287 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 @@ -63,6 +63,13 @@ public class EditFeaturesCommand extends AbstractConfigCommand { arity = 1) private Boolean artifacts = null; + @Parameter( + names = "--artifacts-rewrite", + description = + "Enable new artifact support. Read more at https://www.spinnaker.io/reference/artifacts-with-artifactsrewrite/", + arity = 1) + private Boolean artifactsRewrite = null; + @Parameter( names = "--mine-canary", description = @@ -119,6 +126,8 @@ protected void executeThis() { features.setPipelineTemplates( pipelineTemplates != null ? pipelineTemplates : features.getPipelineTemplates()); features.setArtifacts(artifacts != null ? artifacts : features.getArtifacts()); + features.setArtifactsRewrite( + artifactsRewrite != null ? artifactsRewrite : features.getArtifactsRewrite()); features.setMineCanary(mineCanary != null ? mineCanary : features.getMineCanary()); features.setInfrastructureStages( infrastructureStages != null ? infrastructureStages : features.getInfrastructureStages()); diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/NotificationCommand.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/NotificationCommand.java index 2437cd60aa..87e1c3067e 100644 --- a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/NotificationCommand.java +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/NotificationCommand.java @@ -19,6 +19,7 @@ package com.netflix.spinnaker.halyard.cli.command.v1.config; import com.beust.jcommander.Parameters; +import com.netflix.spinnaker.halyard.cli.command.v1.config.notifications.github.GithubStatusCommand; import com.netflix.spinnaker.halyard.cli.command.v1.config.notifications.pubsub.PubsubCommand; import com.netflix.spinnaker.halyard.cli.command.v1.config.notifications.slack.SlackCommand; import com.netflix.spinnaker.halyard.cli.command.v1.config.notifications.twilio.TwilioCommand; @@ -38,6 +39,7 @@ public class NotificationCommand extends AbstractConfigCommand { private String shortDescription = "Display the state of Spinnaker's notification settings."; public NotificationCommand() { + registerSubcommand(new GithubStatusCommand()); registerSubcommand(new PubsubCommand()); registerSubcommand(new SlackCommand()); registerSubcommand(new TwilioCommand()); diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/deploy/sizing/ComponentSizingDeleteCommand.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/deploy/sizing/ComponentSizingDeleteCommand.java index 033a782f09..11bb2e3e10 100644 --- a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/deploy/sizing/ComponentSizingDeleteCommand.java +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/deploy/sizing/ComponentSizingDeleteCommand.java @@ -40,6 +40,7 @@ protected CustomSizing update(CustomSizing customSizing) { } private CustomSizing delete(CustomSizing customSizing) { + customSizing.put(spinnakerService.getCanonicalName(), null); customSizing.put(spinnakerService.getServiceName(), null); return customSizing; } diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/deploy/sizing/ComponentSizingEditCommand.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/deploy/sizing/ComponentSizingEditCommand.java index 90787a43bf..1c36fa57ee 100644 --- a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/deploy/sizing/ComponentSizingEditCommand.java +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/deploy/sizing/ComponentSizingEditCommand.java @@ -61,6 +61,30 @@ public class ComponentSizingEditCommand extends AbstractComponentSizingUpdateCom + "sidecar containers (e.g. the monitoring daemon). Example: 1Gi.") private String limitsMemory; + @Parameter( + names = "--container-requests-cpu", + description = + "Sets the cpu request for the container running the spinnaker service. Example: 250m.") + private String containerRequestsCpu; + + @Parameter( + names = "--container-requests-memory", + description = + "Sets the memory request for the container running the spinnaker service. Example: 512Mi.") + private String containerRequestsMemory; + + @Parameter( + names = "--container-limits-cpu", + description = + "Sets the cpu limit for the container running the spinnaker service. Example: 1.") + private String containerLimitsCpu; + + @Parameter( + names = "--container-limits-memory", + description = + "Sets the memory limit for the container running the spinnaker service. Example: 1Gi.") + private String containerLimitsMemory; + public ComponentSizingEditCommand(SpinnakerService.Type spinnakerService) { super(spinnakerService, "edit"); } @@ -80,11 +104,13 @@ protected CustomSizing update(CustomSizing customSizing) { private CustomSizing edit(CustomSizing customSizing) { Map serviceSizing = customSizing.computeIfAbsent(spinnakerService.getServiceName(), (k) -> new HashMap<>()); - edit(serviceSizing); + Map containerSizing = + customSizing.computeIfAbsent(spinnakerService.getCanonicalName(), (k) -> new HashMap<>()); + edit(serviceSizing, containerSizing); return customSizing; } - private void edit(Map serviceSizing) { + private void edit(Map serviceSizing, Map containerSizing) { putIfNotNull(serviceSizing, "replicas", replicas); Map limits = (Map) serviceSizing.computeIfAbsent("limits", (k) -> new HashMap<>()); @@ -94,6 +120,15 @@ private void edit(Map serviceSizing) { Map requests = (Map) serviceSizing.computeIfAbsent("requests", (k) -> new HashMap<>()); putIfNotNull(requests, "cpu", requestsCpu); putIfNotNull(requests, "memory", requestsMemory); + + Map containerLimits = (Map) containerSizing.computeIfAbsent("limits", (k) -> new HashMap<>()); + putIfNotNull(containerLimits, "cpu", containerLimitsCpu); + putIfNotNull(containerLimits, "memory", containerLimitsMemory); + + Map containerRequests = + (Map) containerSizing.computeIfAbsent("requests", (k) -> new HashMap<>()); + putIfNotNull(containerRequests, "cpu", containerRequestsCpu); + putIfNotNull(containerRequests, "memory", containerRequestsMemory); } private void putIfNotNull(Map map, String key, Object value) { diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/deploy/sizing/NamedComponentSizingCommand.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/deploy/sizing/NamedComponentSizingCommand.java index 115c3c41ce..1a94e04105 100644 --- a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/deploy/sizing/NamedComponentSizingCommand.java +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/deploy/sizing/NamedComponentSizingCommand.java @@ -25,6 +25,7 @@ import com.netflix.spinnaker.halyard.cli.services.v1.Daemon; import com.netflix.spinnaker.halyard.cli.services.v1.OperationHandler; import com.netflix.spinnaker.halyard.deploy.spinnaker.v1.service.SpinnakerService; +import java.util.HashMap; import java.util.Map; import lombok.AccessLevel; import lombok.Getter; @@ -62,11 +63,34 @@ protected void executeThis() { .setFormat(STRING) .setUserFormatted(true) .setOperation( - () -> - Daemon.getDeploymentEnvironment(currentDeployment, !noValidate) - .get() - .getCustomSizing() - .get(spinnakerService.getServiceName())) + () -> { + Map serviceSizing = + Daemon.getDeploymentEnvironment(currentDeployment, !noValidate) + .get() + .getCustomSizing() + .get(spinnakerService.getServiceName()); + + Map containerSizing = + Daemon.getDeploymentEnvironment(currentDeployment, !noValidate) + .get() + .getCustomSizing() + .get(spinnakerService.getCanonicalName()); + + if (serviceSizing == null && containerSizing == null) { + return null; + } + + Map result = new HashMap(); + if (serviceSizing != null) { + result.put(spinnakerService.getServiceName(), serviceSizing); + } + + if (containerSizing != null) { + result.put(spinnakerService.getCanonicalName(), containerSizing); + } + + return result; + }) .get(); } } diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/notifications/github/EditGithubStatusCommand.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/notifications/github/EditGithubStatusCommand.java new file mode 100644 index 0000000000..84789e5691 --- /dev/null +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/notifications/github/EditGithubStatusCommand.java @@ -0,0 +1,41 @@ +/* + * Copyright 2019 Netflix, 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.notifications.github; + +import com.beust.jcommander.Parameter; +import com.beust.jcommander.Parameters; +import com.netflix.spinnaker.halyard.cli.command.v1.config.notifications.AbstractEditNotificationCommand; +import com.netflix.spinnaker.halyard.config.model.v1.node.Notification; +import com.netflix.spinnaker.halyard.config.model.v1.notifications.GithubStatusNotification; + +/** Interact with the github notification */ +@Parameters(separators = "=") +public class EditGithubStatusCommand + extends AbstractEditNotificationCommand { + protected String getNotificationName() { + return "github-status"; + } + + @Parameter(names = "--token", password = true, description = "Your github account token.") + private String token; + + @Override + protected Notification editNotification(GithubStatusNotification notification) { + notification.setToken(isSet(token) ? token : notification.getToken()); + return notification; + } +} diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/notifications/github/GithubStatusCommand.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/notifications/github/GithubStatusCommand.java new file mode 100644 index 0000000000..1d9336c8ca --- /dev/null +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/notifications/github/GithubStatusCommand.java @@ -0,0 +1,33 @@ +/* + * Copyright 2019 Netflix, 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.notifications.github; + +import com.beust.jcommander.Parameters; +import com.netflix.spinnaker.halyard.cli.command.v1.config.notifications.AbstractNamedNotificationCommand; + +/** Interact with the github notification */ +@Parameters(separators = "=") +public class GithubStatusCommand extends AbstractNamedNotificationCommand { + protected String getNotificationName() { + return "github-status"; + } + + public GithubStatusCommand() { + super(); + registerSubcommand(new EditGithubStatusCommand()); + } +} diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/persistentStorage/s3/S3EditCommand.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/persistentStorage/s3/S3EditCommand.java index 8e7fd46139..656fe00776 100644 --- a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/persistentStorage/s3/S3EditCommand.java +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/persistentStorage/s3/S3EditCommand.java @@ -16,6 +16,8 @@ package com.netflix.spinnaker.halyard.cli.command.v1.config.persistentStorage.s3; +import static com.netflix.spinnaker.halyard.config.model.v1.persistentStorage.S3PersistentStore.ServerSideEncryption; + import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameters; import com.netflix.spinnaker.halyard.cli.command.v1.config.persistentStorage.AbstractPersistentStoreEditCommand; @@ -67,6 +69,14 @@ protected String getPersistentStoreType() { + " See https://docs.aws.amazon.com/AmazonS3/latest/dev/VirtualHosting.html#VirtualHostingExamples.") private Boolean pathStyleAccess = false; + @Parameter( + names = "--server-side-encryption", + description = + "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'.") + private ServerSideEncryption serverSideEncryption; + @Parameter(names = "--assume-role", description = AwsCommandProperties.ASSUME_ROLE_DESCRIPTION) private String assumeRole; @@ -96,6 +106,10 @@ protected S3PersistentStore editPersistentStore(S3PersistentStore persistentStor isSet(accessKeyId) ? accessKeyId : persistentStore.getAccessKeyId()); persistentStore.setSecretAccessKey( isSet(secretAccessKey) ? secretAccessKey : persistentStore.getSecretAccessKey()); + persistentStore.setServerSideEncryption( + isSet(serverSideEncryption) + ? serverSideEncryption + : persistentStore.getServerSideEncryption()); if (persistentStore.getBucket() == null) { String bucketName = "spin-" + UUID.randomUUID().toString(); diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/providers/kubernetes/KubernetesEditProviderCommand.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/providers/kubernetes/KubernetesEditProviderCommand.java index a4524d46ab..1c8ea8ef51 100644 --- a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/providers/kubernetes/KubernetesEditProviderCommand.java +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/config/providers/kubernetes/KubernetesEditProviderCommand.java @@ -34,7 +34,7 @@ public class KubernetesEditProviderCommand String shortDescription = "Set provider-wide properties for the Kubernetes provider"; String longDescription = - "Due to how the Kubenretes provider shards its cache resources, there is opportunity to " + "Due to how the Kubernetes provider shards its cache resources, there is opportunity to " + "tune how its caching should be handled. This command exists to allow you tune this caching behavior."; protected String getProviderName() { diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/converter/ImageVariantConverter.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/converter/ImageVariantConverter.java new file mode 100644 index 0000000000..ea46949894 --- /dev/null +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/converter/ImageVariantConverter.java @@ -0,0 +1,33 @@ +/* + * Copyright 2019 Pivotal, 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.converter; + +import com.beust.jcommander.IStringConverter; +import com.beust.jcommander.ParameterException; +import com.netflix.spinnaker.halyard.config.model.v1.node.DeploymentEnvironment.ImageVariant; + +public class ImageVariantConverter implements IStringConverter { + @Override + public ImageVariant convert(String value) { + try { + return ImageVariant.fromString(value); + } catch (IllegalArgumentException e) { + throw new ParameterException(e.getMessage(), e); + } + } +} diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/plugins/AbstractHasPluginCommand.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/plugins/AbstractHasPluginCommand.java new file mode 100644 index 0000000000..e77ea0d5ee --- /dev/null +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/plugins/AbstractHasPluginCommand.java @@ -0,0 +1,56 @@ +/* + * 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.plugins; + +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.plugins.Plugin; +import java.util.ArrayList; +import java.util.List; + +/** An abstract definition for commands that accept plugins as a main parameter */ +@Parameters(separators = "=") +public abstract class AbstractHasPluginCommand extends AbstractConfigCommand { + @Parameter(description = "The name of the plugin to operate on.", arity = 1) + private List plugins = new ArrayList<>(); + + @Override + public String getMainParameter() { + return "plugin"; + } + + public Plugin getPlugin() { + return new OperationHandler() + .setFailureMesssage("Failed to get plugin") + .setOperation(Daemon.getPlugin(getCurrentDeployment(), plugins.get(0), false)) + .get(); + } + + public String getPluginName() { + switch (plugins.size()) { + case 0: + throw new IllegalArgumentException("No plugin supplied"); + case 1: + return plugins.get(0); + default: + throw new IllegalArgumentException("More than one plugin supplied"); + } + } +} diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/plugins/AddPluginCommand.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/plugins/AddPluginCommand.java new file mode 100644 index 0000000000..52becfad88 --- /dev/null +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/plugins/AddPluginCommand.java @@ -0,0 +1,60 @@ +/* + * 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.plugins; + +import com.beust.jcommander.Parameter; +import com.beust.jcommander.Parameters; +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.plugins.Plugin; +import lombok.AccessLevel; +import lombok.Getter; + +@Parameters(separators = "=") +public class AddPluginCommand extends AbstractHasPluginCommand { + @Getter(AccessLevel.PUBLIC) + private String commandName = "add"; + + @Getter(AccessLevel.PUBLIC) + private String shortDescription = "Add a plugin"; + + @Parameter( + names = "--manifest-location", + description = "The location of the plugin's manifest file.", + required = true) + private String manifestLocation; + + @Parameter(names = "--enabled", description = "To enable or disable the plugin.") + private String enabled; + + @Override + protected void executeThis() { + String currentDeployment = getCurrentDeployment(); + String name = getPluginName(); + Plugin plugin = + new Plugin() + .setName(name) + .setEnabled(isSet(enabled) ? Boolean.parseBoolean(enabled) : false) + .setManifestLocation(manifestLocation); + + new OperationHandler() + .setFailureMesssage("Failed to add plugin: " + name + ".") + .setSuccessMessage("Successfully added plugin" + name + ".") + .setOperation(Daemon.addPlugin(currentDeployment, !noValidate, plugin)) + .get(); + } +} diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/plugins/DeletePluginCommand.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/plugins/DeletePluginCommand.java new file mode 100644 index 0000000000..4ffa36bf72 --- /dev/null +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/plugins/DeletePluginCommand.java @@ -0,0 +1,46 @@ +/* + * 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.plugins; + +import com.beust.jcommander.Parameters; +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.plugins.Plugin; +import lombok.AccessLevel; +import lombok.Getter; + +@Parameters(separators = "=") +public class DeletePluginCommand extends AbstractHasPluginCommand { + @Getter(AccessLevel.PUBLIC) + private String commandName = "delete"; + + @Getter(AccessLevel.PUBLIC) + private String shortDescription = "Delete a plugin"; + + @Override + protected void executeThis() { + String currentDeployment = getCurrentDeployment(); + Plugin plugin = getPlugin(); + String name = plugin.getName(); + + new OperationHandler() + .setFailureMesssage("Failed to delete plugin " + name + ".") + .setSuccessMessage("Successfully deleted plugin " + name + ".") + .setOperation(Daemon.deletePlugin(currentDeployment, name, !noValidate)) + .get(); + } +} diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/plugins/EditPluginCommand.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/plugins/EditPluginCommand.java new file mode 100644 index 0000000000..adc34f64e8 --- /dev/null +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/plugins/EditPluginCommand.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.plugins; + +import com.beust.jcommander.Parameter; +import com.beust.jcommander.Parameters; +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.plugins.Plugin; +import lombok.AccessLevel; +import lombok.Getter; + +@Parameters(separators = "=") +public class EditPluginCommand extends AbstractHasPluginCommand { + @Getter(AccessLevel.PUBLIC) + private String commandName = "edit"; + + @Getter(AccessLevel.PUBLIC) + private String shortDescription = "Edit a plugin"; + + @Parameter( + names = "--manifest-location", + description = "The location of the plugin's manifest file.") + private String manifestLocation; + + @Parameter(names = "--enabled", description = "To enable or disable the plugin.") + private String enabled; + + @Override + protected void executeThis() { + String currentDeployment = getCurrentDeployment(); + Plugin plugin = getPlugin(); + + plugin.setEnabled(isSet(enabled) ? Boolean.parseBoolean(enabled) : plugin.getEnabled()); + plugin.setManifestLocation( + isSet(manifestLocation) ? manifestLocation : plugin.getManifestLocation()); + + new OperationHandler() + .setFailureMesssage("Failed to edit plugin " + plugin.getName() + ".") + .setSuccessMessage("Successfully edited plugin " + plugin.getName() + ".") + .setOperation(Daemon.setPlugin(currentDeployment, plugin.getName(), !noValidate, plugin)) + .get(); + } +} diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/plugins/ListPluginsCommand.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/plugins/ListPluginsCommand.java new file mode 100644 index 0000000000..2f555d006f --- /dev/null +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/plugins/ListPluginsCommand.java @@ -0,0 +1,55 @@ +/* + * 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.plugins; + +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.AnsiUi; +import com.netflix.spinnaker.halyard.config.model.v1.plugins.Plugin; +import java.util.List; +import lombok.AccessLevel; +import lombok.Getter; + +@Parameters(separators = "=") +public class ListPluginsCommand extends AbstractConfigCommand { + @Getter(AccessLevel.PUBLIC) + private String commandName = "list"; + + @Getter(AccessLevel.PUBLIC) + private String shortDescription = "List all plugins"; + + private List getPlugins() { + String currentDeployment = getCurrentDeployment(); + return new OperationHandler>() + .setFailureMesssage("Failed to get plugins.") + .setOperation(Daemon.getPlugins(currentDeployment, !noValidate)) + .get(); + } + + @Override + protected void executeThis() { + List plugins = getPlugins(); + if (plugins.isEmpty()) { + AnsiUi.success("No configured plugins."); + } else { + AnsiUi.success("Plugins:"); + plugins.forEach(plugin -> AnsiUi.listItem(plugin.getName())); + } + } +} diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/plugins/PluginDownloadingEnableDisableCommandBuilder.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/plugins/PluginDownloadingEnableDisableCommandBuilder.java new file mode 100644 index 0000000000..ac6c9b13fb --- /dev/null +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/plugins/PluginDownloadingEnableDisableCommandBuilder.java @@ -0,0 +1,66 @@ +/* + * 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.plugins; + +import com.netflix.spinnaker.halyard.cli.command.v1.AbstractEnableDisableCommand; +import com.netflix.spinnaker.halyard.cli.command.v1.CommandBuilder; +import com.netflix.spinnaker.halyard.cli.command.v1.NestableCommand; +import com.netflix.spinnaker.halyard.cli.services.v1.Daemon; +import java.util.function.Supplier; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; + +public class PluginDownloadingEnableDisableCommandBuilder implements CommandBuilder { + @Setter boolean enable; + + @Override + public NestableCommand build() { + return new PluginDownloadingEnableDisableCommand(enable); + } + + private static class PluginDownloadingEnableDisableCommand extends AbstractEnableDisableCommand { + @Override + public String getTargetName() { + return "Plugins"; + } + + @Override + public String getCommandName() { + return isEnable() ? "enable-downloading" : "disable-downloading"; + } + + private PluginDownloadingEnableDisableCommand(boolean enable) { + this.enable = enable; + } + + @Getter(AccessLevel.PROTECTED) + boolean enable; + + @Override + public String getShortDescription() { + return "Enable or disable the ability for Spinnaker services to download jars for plugins"; + } + + @Override + protected Supplier getOperationSupplier() { + String currentDeployment = getCurrentDeployment(); + boolean enabled = this.isEnable(); + return Daemon.setPluginDownloadingEnableDisable(currentDeployment, !noValidate, enabled); + } + } +} diff --git a/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/plugins/PluginEnableDisableCommandBuilder.java b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/plugins/PluginEnableDisableCommandBuilder.java new file mode 100644 index 0000000000..3d9c7ec8f3 --- /dev/null +++ b/halyard-cli/src/main/java/com/netflix/spinnaker/halyard/cli/command/v1/plugins/PluginEnableDisableCommandBuilder.java @@ -0,0 +1,63 @@ +/* + * 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.plugins; + +import com.beust.jcommander.Parameters; +import com.netflix.spinnaker.halyard.cli.command.v1.AbstractEnableDisableCommand; +import com.netflix.spinnaker.halyard.cli.command.v1.CommandBuilder; +import com.netflix.spinnaker.halyard.cli.command.v1.NestableCommand; +import com.netflix.spinnaker.halyard.cli.services.v1.Daemon; +import java.util.function.Supplier; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; + +public class PluginEnableDisableCommandBuilder implements CommandBuilder { + @Setter boolean enable; + + @Override + public NestableCommand build() { + return new PluginEnableDisableCommand(enable); + } + + @Parameters(separators = "=") + private static class PluginEnableDisableCommand extends AbstractEnableDisableCommand { + @Override + public String getTargetName() { + return "Plugins"; + } + + private PluginEnableDisableCommand(boolean enable) { + this.enable = enable; + } + + @Getter(AccessLevel.PROTECTED) + boolean enable; + + @Override + public String getShortDescription() { + return "Enable or disable all plugins"; + } + + @Override + protected Supplier getOperationSupplier() { + String currentDeployment = getCurrentDeployment(); + boolean enable = isEnable(); + return Daemon.setPluginEnableDisable(currentDeployment, !noValidate, 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 7bf9795f35..49717f9e1e 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 @@ -27,6 +27,7 @@ import com.netflix.spinnaker.halyard.config.model.v1.ha.HaService; import com.netflix.spinnaker.halyard.config.model.v1.ha.HaServices; import com.netflix.spinnaker.halyard.config.model.v1.node.*; +import com.netflix.spinnaker.halyard.config.model.v1.plugins.Plugin; import com.netflix.spinnaker.halyard.config.model.v1.security.*; import com.netflix.spinnaker.halyard.config.model.v1.webook.WebhookTrust; import com.netflix.spinnaker.halyard.core.DaemonOptions; @@ -1312,6 +1313,62 @@ public static Supplier deleteArtifactTemplate( }; } + public static Supplier> getPlugins(String deploymentName, boolean validate) { + return () -> { + Object rawPlugin = ResponseUnwrapper.get(getService().getPlugins(deploymentName, validate)); + return getObjectMapper().convertValue(rawPlugin, new TypeReference>() {}); + }; + } + + public static Supplier getPlugin( + String deploymentName, String pluginName, boolean validate) { + return () -> { + Object rawPlugin = + ResponseUnwrapper.get(getService().getPlugin(deploymentName, pluginName, validate)); + return getObjectMapper().convertValue(rawPlugin, Plugin.class); + }; + } + + public static Supplier addPlugin(String deploymentName, boolean validate, Plugin plugin) { + return () -> { + ResponseUnwrapper.get(getService().addPlugin(deploymentName, validate, plugin)); + return null; + }; + } + + public static Supplier setPlugin( + String deploymentName, String pluginName, boolean validate, Plugin plugin) { + return () -> { + ResponseUnwrapper.get(getService().setPlugin(deploymentName, pluginName, validate, plugin)); + return null; + }; + } + + public static Supplier deletePlugin( + String deploymentName, String pluginName, boolean validate) { + return () -> { + ResponseUnwrapper.get(getService().deletePlugin(deploymentName, pluginName, validate)); + return null; + }; + } + + public static Supplier setPluginEnableDisable( + String deploymentName, boolean validate, boolean enable) { + return () -> { + ResponseUnwrapper.get(getService().setPluginsEnabled(deploymentName, validate, enable)); + return null; + }; + } + + public static Supplier setPluginDownloadingEnableDisable( + String deploymentName, boolean validate, boolean enable) { + return () -> { + ResponseUnwrapper.get( + getService().setPluginsDownloadingEnabled(deploymentName, validate, enable)); + 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 e5c57d63f7..c5baaa064e 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 @@ -21,6 +21,7 @@ import com.netflix.spinnaker.halyard.config.model.v1.canary.Canary; import com.netflix.spinnaker.halyard.config.model.v1.ha.HaService; import com.netflix.spinnaker.halyard.config.model.v1.node.*; +import com.netflix.spinnaker.halyard.config.model.v1.plugins.Plugin; import com.netflix.spinnaker.halyard.config.model.v1.security.*; import com.netflix.spinnaker.halyard.config.model.v1.webook.WebhookTrust; import com.netflix.spinnaker.halyard.core.DaemonOptions; @@ -885,6 +886,47 @@ DaemonTask deleteArtifactTemplate( @Path("templateName") String templateName, @Query("validate") boolean validate); + @POST("/v1/config/deployments/{deploymentName}/plugins/") + DaemonTask addPlugin( + @Path("deploymentName") String deploymentName, + @Query("validate") boolean validate, + @Body Plugin plugin); + + @GET("/v1/config/deployments/{deploymentName}/plugins/") + DaemonTask getPlugins( + @Path("deploymentName") String deploymentName, @Query("validate") boolean validate); + + @GET("/v1/config/deployments/{deploymentName}/plugins/{pluginName}/") + DaemonTask getPlugin( + @Path("deploymentName") String deploymentName, + @Path("pluginName") String pluginName, + @Query("validate") boolean validate); + + @PUT("/v1/config/deployments/{deploymentName}/plugins/{pluginName}/") + DaemonTask setPlugin( + @Path("deploymentName") String deploymentName, + @Path("pluginName") String pluginName, + @Query("validate") boolean validate, + @Body Plugin plugin); + + @PUT("/v1/config/deployments/{deploymentName}/plugins/enabled/") + DaemonTask setPluginsEnabled( + @Path("deploymentName") String deploymentName, + @Query("validate") boolean validate, + @Body boolean enabled); + + @PUT("/v1/config/deployments/{deploymentName}/plugins/downloadingEnabled/") + DaemonTask setPluginsDownloadingEnabled( + @Path("deploymentName") String deploymentName, + @Query("validate") boolean validate, + @Body boolean enabled); + + @DELETE("/v1/config/deployments/{deploymentName}/plugins/{pluginName}/") + DaemonTask deletePlugin( + @Path("deploymentName") String deploymentName, + @Path("pluginName") String pluginName, + @Query("validate") boolean validate); + @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 ab191d618c..6175df9f61 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,10 @@ 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.plugins.AddPluginCommand +import com.netflix.spinnaker.halyard.cli.command.v1.plugins.DeletePluginCommand +import com.netflix.spinnaker.halyard.cli.command.v1.plugins.EditPluginCommand +import com.netflix.spinnaker.halyard.cli.command.v1.plugins.ListPluginsCommand import spock.lang.Specification import spock.lang.Unroll @@ -117,6 +121,11 @@ class CommandTreeSpec extends Specification { LdapCommand | "disable" | AuthnMethodEnableDisableCommand LdapCommand | "enable" | AuthnMethodEnableDisableCommand LdapCommand | "edit" | EditLdapCommand + + PluginCommand | "list" | ListPluginsCommand + PluginCommand | "edit" | EditPluginCommand + PluginCommand | "delete" | DeletePluginCommand + PluginCommand | "add" | AddPluginCommand } } diff --git a/halyard-config/halyard-config.gradle b/halyard-config/halyard-config.gradle index 4094ef36b9..d7602b0d52 100644 --- a/halyard-config/halyard-config.gradle +++ b/halyard-config/halyard-config.gradle @@ -32,6 +32,9 @@ dependencies { // TODO: add clouddriverDCOS once that's merged implementation project(':halyard-core') + testImplementation 'org.assertj:assertj-core' + testImplementation 'org.junit.jupiter:junit-jupiter-api' + testImplementation 'org.junit.platform:junit-platform-runner' testImplementation 'org.spockframework:spock-core:1.3-groovy-2.5' testImplementation 'org.springframework:spring-test' testImplementation 'org.codehaus.groovy:groovy' 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 b608868082..79d5b20bfa 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 @@ -79,12 +79,10 @@ public class HalconfigParser { * @see Halconfig */ Halconfig parseHalconfig(InputStream is) throws IllegalArgumentException { - try { - Object obj = yamlParser.load(is); - return objectMapper.convertValue(obj, Halconfig.class); - } catch (IllegalArgumentException e) { - throw e; - } + Object obj = yamlParser.load(is); + Halconfig halconfig = objectMapper.convertValue(obj, Halconfig.class); + halconfig.setPrefixToRelativeFiles(halconfigDirectoryStructure.getHalconfigDirectory()); + return halconfig; } /** @@ -239,6 +237,8 @@ private void saveConfigTo(Path path) { AtomicFileWriter writer = null; try { + local.removePrefixFromUnchangedRelativeFiles( + halconfigDirectoryStructure.getHalconfigDirectory()); writer = new AtomicFileWriter(path); writer.write(yamlParser.dump(objectMapper.convertValue(local, Map.class))); writer.commit(); diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/config/v1/StrictObjectMapper.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/config/v1/StrictObjectMapper.java index 2f6b65768e..8602ae99a1 100644 --- a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/config/v1/StrictObjectMapper.java +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/config/v1/StrictObjectMapper.java @@ -23,7 +23,7 @@ @Component public class StrictObjectMapper extends ObjectMapper { - StrictObjectMapper() { + public StrictObjectMapper() { super(); this.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true); this.setSerializationInclusion(JsonInclude.Include.NON_NULL); diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/canary/google/GoogleCanaryAccount.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/canary/google/GoogleCanaryAccount.java index d34ae58b6d..bf952283cd 100644 --- a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/canary/google/GoogleCanaryAccount.java +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/canary/google/GoogleCanaryAccount.java @@ -16,24 +16,16 @@ package com.netflix.spinnaker.halyard.config.model.v1.canary.google; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.netflix.spinnaker.clouddriver.google.security.GoogleNamedAccountCredentials; 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.LocalFile; import com.netflix.spinnaker.halyard.config.model.v1.node.SecretFile; -import com.netflix.spinnaker.halyard.config.model.v1.util.ValidatingFileReader; -import com.netflix.spinnaker.halyard.config.problem.v1.ConfigProblemSetBuilder; -import com.netflix.spinnaker.halyard.core.problem.v1.Problem; -import com.netflix.spinnaker.halyard.core.secrets.v1.SecretSessionManager; -import com.netflix.spinnaker.kork.secrets.EncryptedSecret; import java.util.SortedSet; import java.util.TreeSet; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.ToString; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; @Data @EqualsAndHashCode(callSuper = true) @@ -47,45 +39,4 @@ public class GoogleCanaryAccount extends AbstractCanaryAccount implements Clonea private String rootFolder = "kayenta"; private SortedSet supportedTypes = new TreeSet<>(); - - @JsonIgnore - public GoogleNamedAccountCredentials getNamedAccountCredentials( - String version, SecretSessionManager secretSessionManager, ConfigProblemSetBuilder p) { - String jsonKey = null; - if (!StringUtils.isEmpty(getJsonPath())) { - if (EncryptedSecret.isEncryptedSecret(getJsonPath())) { - jsonKey = secretSessionManager.decrypt(getJsonPath()); - } else { - jsonKey = ValidatingFileReader.contents(p, getJsonPath()); - } - - if (jsonKey == null) { - return null; - } else if (jsonKey.isEmpty()) { - p.addProblem(Problem.Severity.WARNING, "The supplied credentials file is empty."); - } - } - - if (StringUtils.isEmpty(getProject())) { - p.addProblem(Problem.Severity.ERROR, "No google project supplied."); - return null; - } - - try { - return new GoogleNamedAccountCredentials.Builder() - .name(getName()) - .jsonKey(jsonKey) - .project(getProject()) - .applicationName("halyard " + version) - .liveLookupsEnabled(false) - .build(); - } catch (Exception e) { - p.addProblem( - Problem.Severity.ERROR, - "Error instantiating Google credentials: " + e.getMessage() + ".") - .setRemediation( - "Do the provided credentials have access to project " + getProject() + "?"); - return null; - } - } } 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 612646def2..671df86399 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 @@ -86,6 +86,8 @@ public class DeploymentConfiguration extends Node { Canary canary = new Canary(); + Plugins plugins = new Plugins().setDownloadingEnabled(false); + Webhook webhook = new Webhook(); @Override diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/DeploymentEnvironment.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/DeploymentEnvironment.java index 3b32c4c7fa..93bd42ce6d 100644 --- a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/DeploymentEnvironment.java +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/DeploymentEnvironment.java @@ -92,9 +92,35 @@ public static Size fromString(String name) { } } + public enum ImageVariant { + SLIM("Based on an Alpine image"), + UBUNTU("Based on Canonical's ubuntu:bionic image"); + + @Getter final String description; + + ImageVariant(String description) { + this.description = description; + } + + public static ImageVariant fromString(String name) { + for (ImageVariant variant : values()) { + if (variant.toString().equalsIgnoreCase(name)) { + return variant; + } + } + + throw new IllegalArgumentException( + "ImageVariant \"" + + name + + "\" is not a valid choice. The options are: " + + Arrays.toString(ImageVariant.values())); + } + } + private Size size = Size.SMALL; private DeploymentType type = DeploymentType.LocalDebian; private String accountName; + private ImageVariant imageVariant = ImageVariant.SLIM; private Boolean bootstrapOnly; private Boolean updateVersions = true; private Consul consul = new Consul(); @@ -105,6 +131,7 @@ public static Size fromString(String name) { private Map> initContainers = new HashMap<>(); private Map> hostAliases = new HashMap<>(); private Map affinity = new HashMap<>(); + private Map> tolerations = new HashMap<>(); private Map nodeSelectors = new HashMap<>(); private GitConfig gitConfig = new GitConfig(); private LivenessProbeConfig livenessProbeConfig = new LivenessProbeConfig(); 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 13c2463820..9c340a6c5a 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 @@ -49,6 +49,12 @@ public NodeIterator getChildren() { "Artifacts are not configurable prior to this release. Will be stable at a later release.") private Boolean artifacts; + @ValidForSpinnakerVersion( + lowerBound = "1.15.0", + tooLowMessage = + "Artifacts rewrite is a rewrite of the artifacts ui. Artifacts are not configurable prior to this release. Will be stable at a later release.") + private Boolean artifactsRewrite; + @ValidForSpinnakerVersion( lowerBound = "1.5.0", tooLowMessage = diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/Node.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/Node.java index 702c1bd933..5c309c39c5 100644 --- a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/Node.java +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/Node.java @@ -36,6 +36,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.zip.CRC32; @@ -53,6 +54,9 @@ @Slf4j public abstract class Node implements Validatable { + // @LocalFile fields whose file path is relative + @JsonIgnore private Map relativeFileReferences = new ConcurrentHashMap<>(); + @JsonIgnore public abstract String getNodeName(); @@ -414,6 +418,82 @@ public NodeDiff diff(Node other) { return result; } + public void removePrefixFromUnchangedRelativeFiles(String prefix) { + Consumer fileFinder = + n -> + n.localFiles() + .forEach( + f -> { + try { + f.setAccessible(true); + if (!relativeFileReferences.containsKey(f)) { + return; + } + + String currentPath = (String) f.get(n); + if (StringUtils.isEmpty(currentPath)) { + return; + } + + // restore original relative path + String relativePath = relativeFileReferences.get(f); + String absolutePath = prefix + File.separator + relativePath; + if (currentPath.equals(absolutePath)) { + f.set(n, relativePath); + relativeFileReferences.remove(f); + } + } catch (IllegalAccessException e) { + throw new RuntimeException( + "Failed to get local files for node " + n.getNodeName(), e); + } finally { + f.setAccessible(false); + } + }); + + recursiveConsume(fileFinder); + } + + public void setPrefixToRelativeFiles(String prefix) { + Consumer fileFinder = + n -> + n.localFiles() + .forEach( + f -> { + try { + f.setAccessible(true); + String fPath = (String) f.get(n); + if (StringUtils.isEmpty(fPath)) { + return; + } + if (fPath.startsWith(LocalFile.RELATIVE_PATH_PLACEHOLDER) + || Paths.get(fPath).isAbsolute()) { + return; + } + + Path absolutePath = Paths.get(prefix + File.separator + fPath).normalize(); + if (!absolutePath.startsWith(prefix)) { + throw new HalException( + FATAL, + "Error resolving file path '" + + fPath + + "': Relative file paths must resolve to files inside " + + prefix); + } + + // backup relative path + relativeFileReferences.put(f, fPath); + f.set(n, absolutePath.toString()); + } catch (IllegalAccessException e) { + throw new RuntimeException( + "Failed to get local files for node " + n.getNodeName(), e); + } finally { + f.setAccessible(false); + } + }); + + recursiveConsume(fileFinder); + } + private void swapLocalFilePrefixes(String to, String from) { Consumer fileFinder = n -> 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 2baef63bcc..92c66c8609 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 @@ -20,6 +20,7 @@ import com.netflix.spinnaker.halyard.config.model.v1.canary.Canary; import com.netflix.spinnaker.halyard.config.model.v1.ha.HaService; import com.netflix.spinnaker.halyard.config.model.v1.ha.HaServices; +import com.netflix.spinnaker.halyard.config.model.v1.plugins.Plugin; import com.netflix.spinnaker.halyard.config.model.v1.security.*; import com.netflix.spinnaker.halyard.config.model.v1.webook.WebhookTrust; import java.util.ArrayList; @@ -331,6 +332,23 @@ public NodeFilter setArtifactTemplate(String name) { return this; } + public NodeFilter setPlugin() { + matchers.add(Node.thisNodeAcceptor(Plugins.class)); + return this; + } + + public NodeFilter setPlugin(String name) { + matchers.add(Node.thisNodeAcceptor(Plugins.class)); + matchers.add(Node.namedNodeAcceptor(Plugin.class, name)); + return this; + } + + public NodeFilter withAnyPlugin() { + matchers.add(Node.thisNodeAcceptor(Plugins.class)); + matchers.add(Node.thisNodeAcceptor(Plugin.class)); + return this; + } + public NodeFilter() { withAnyHalconfigFile(); } diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/Notification.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/Notification.java index 3a6c870c5d..36707e628f 100644 --- a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/Notification.java +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/Notification.java @@ -43,7 +43,8 @@ public NodeIterator getChildren() { public enum NotificationType { SLACK("slack"), - TWILIO("twilio"); + TWILIO("twilio"), + GITHUB_STATUS("github-status"); private final String name; @@ -51,6 +52,10 @@ public enum NotificationType { this.name = name; } + public String getName() { + return name; + } + @Override public String toString() { return name; diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/Notifications.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/Notifications.java index fce529a100..a91c10fcbc 100644 --- a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/Notifications.java +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/Notifications.java @@ -18,12 +18,16 @@ package com.netflix.spinnaker.halyard.config.model.v1.node; +import static com.google.common.base.Preconditions.checkArgument; + import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.netflix.spinnaker.halyard.config.model.v1.node.Notification.NotificationType; +import com.netflix.spinnaker.halyard.config.model.v1.notifications.GithubStatusNotification; import com.netflix.spinnaker.halyard.config.model.v1.notifications.SlackNotification; import com.netflix.spinnaker.halyard.config.model.v1.notifications.TwilioNotification; -import java.lang.reflect.Field; -import java.util.Arrays; import java.util.Optional; +import java.util.stream.Stream; import lombok.Data; import lombok.EqualsAndHashCode; @@ -33,6 +37,9 @@ public class Notifications extends Node implements Cloneable { SlackNotification slack = new SlackNotification(); TwilioNotification twilio = new TwilioNotification(); + @JsonProperty("github-status") + GithubStatusNotification githubStatus = new GithubStatusNotification(); + @Override public String getNodeName() { return "notification"; @@ -40,21 +47,31 @@ public String getNodeName() { @JsonIgnore public boolean isEnabled() { - return slack.isEnabled() || twilio.isEnabled(); + return slack.isEnabled() || twilio.isEnabled() || githubStatus.isEnabled(); } public static Class translateNotificationType(String notificationName) { - Optional> res = - Arrays.stream(Notifications.class.getDeclaredFields()) - .filter(f -> f.getName().equals(notificationName)) - .map(Field::getType) - .findFirst(); - - if (res.isPresent()) { - return (Class) res.get(); - } else { - throw new IllegalArgumentException( - "No notification type with name \"" + notificationName + "\" handled by halyard"); + + Optional notificationType = + Stream.of(NotificationType.values()) + .filter(type -> type.getName().equals(notificationName)) + .findAny(); + + checkArgument( + notificationType.isPresent(), + "No notification type with name %s handled by halyard", + notificationName); + + switch (notificationType.get()) { + case SLACK: + return SlackNotification.class; + case TWILIO: + return TwilioNotification.class; + case GITHUB_STATUS: + return GithubStatusNotification.class; + default: + throw new IllegalArgumentException( + "No notification type with name \"" + notificationName + "\" handled by halyard"); } } } 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 new file mode 100644 index 0000000000..81616e295f --- /dev/null +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/Plugins.java @@ -0,0 +1,44 @@ +/* + * 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 com.netflix.spinnaker.halyard.config.model.v1.plugins.Plugin; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = false) +public class Plugins extends Node { + + @Override + public String getNodeName() { + return "plugins"; + } + + @Override + public NodeIterator getChildren() { + return NodeIteratorFactory.makeListIterator( + plugins.stream().map(a -> (Node) a).collect(Collectors.toList())); + } + + private List plugins = new ArrayList<>(); + private boolean enabled; + private boolean downloadingEnabled; +} diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/Toleration.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/Toleration.java new file mode 100644 index 0000000000..12ba63158f --- /dev/null +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/Toleration.java @@ -0,0 +1,33 @@ +/* + * Copyright 2019 Rohit Verma + * + * 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 lombok.Data; + +@Data +public class Toleration { + private String key; + private Operator operator; + private String value; + private String effect; + + public enum Operator { + Exists, + Equal, + DoesNotExist + } +} diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/Validator.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/Validator.java index c60117c422..ea33040d63 100644 --- a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/Validator.java +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/node/Validator.java @@ -19,7 +19,6 @@ import com.netflix.spinnaker.halyard.config.model.v1.util.ValidatingFileReader; import com.netflix.spinnaker.halyard.config.problem.v1.ConfigProblemSetBuilder; import com.netflix.spinnaker.halyard.core.secrets.v1.SecretSessionManager; -import com.netflix.spinnaker.kork.secrets.EncryptedSecret; import org.springframework.beans.factory.annotation.Autowired; public abstract class Validator { @@ -28,10 +27,10 @@ public abstract class Validator { public abstract void validate(ConfigProblemSetBuilder p, T n); protected String validatingFileDecrypt(ConfigProblemSetBuilder p, String filePath) { - if (EncryptedSecret.isEncryptedSecret(filePath)) { - return secretSessionManager.decrypt(filePath); - } else { - return ValidatingFileReader.contents(p, filePath); - } + return ValidatingFileReader.contents(p, filePath, secretSessionManager); + } + + protected byte[] validatingFileDecryptBytes(ConfigProblemSetBuilder p, String filePath) { + return ValidatingFileReader.contentBytes(p, filePath, secretSessionManager); } } diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/notifications/GithubStatusNotification.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/notifications/GithubStatusNotification.java new file mode 100644 index 0000000000..e8e4501539 --- /dev/null +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/notifications/GithubStatusNotification.java @@ -0,0 +1,35 @@ +/* + * Copyright 2019 Netflix, 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.notifications; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.netflix.spinnaker.halyard.config.model.v1.node.Notification; +import com.netflix.spinnaker.halyard.config.model.v1.node.Secret; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class GithubStatusNotification extends Notification { + @Secret String token; + + @Override + @JsonIgnore + public NotificationType getNotificationType() { + return NotificationType.GITHUB_STATUS; + } +} diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/persistentStorage/S3PersistentStore.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/persistentStorage/S3PersistentStore.java index fa1bea3e29..fda99f73b5 100644 --- a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/persistentStorage/S3PersistentStore.java +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/persistentStorage/S3PersistentStore.java @@ -36,10 +36,16 @@ public class S3PersistentStore extends PersistentStore { private String endpoint; private String accessKeyId; + private ServerSideEncryption serverSideEncryption; @Secret private String secretAccessKey; @Override public PersistentStoreType persistentStoreType() { return PersistentStoreType.S3; } + + public enum ServerSideEncryption { + AES256, + AWSKMS + } } diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/plugins/Manifest.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/plugins/Manifest.java new file mode 100644 index 0000000000..93434e10dc --- /dev/null +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/plugins/Manifest.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.config.model.v1.plugins; + +import com.netflix.spinnaker.halyard.config.problem.v1.ConfigProblemBuilder; +import com.netflix.spinnaker.halyard.core.error.v1.HalException; +import com.netflix.spinnaker.halyard.core.problem.v1.Problem; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; +import lombok.Data; +import lombok.Getter; + +@Data +public class Manifest { + public String name; + public String manifestVersion; + public List jars; + public Map options; + + static final String regex = "^[a-zA-Z0-9]+\\/[\\w-]+$"; + static final Pattern pattern = Pattern.compile(regex); + + public void validate() throws HalException { + + if (Stream.of(name, manifestVersion, jars).anyMatch(Objects::isNull)) { + throw new HalException( + new ConfigProblemBuilder( + Problem.Severity.FATAL, "Invalid plugin manifest, contains null values") + .build()); + } + + Matcher matcher = pattern.matcher(name); + + if (!matcher.find()) { + throw new HalException( + new ConfigProblemBuilder(Problem.Severity.FATAL, "Invalid plugin name: " + name).build()); + } + + if (!manifestVersion.equals(ManifestVersion.V1.getName())) { + throw new HalException( + new ConfigProblemBuilder( + Problem.Severity.FATAL, "Invalid manifest version for plugin: " + name) + .build()); + } + } + + public enum ManifestVersion { + V1("plugins/v1"); + + @Getter String name; + + @Override + public String toString() { + return this.name; + } + + ManifestVersion(String name) { + this.name = name; + } + } +} 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 new file mode 100644 index 0000000000..978885699b --- /dev/null +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/plugins/Plugin.java @@ -0,0 +1,75 @@ +/* + * 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.Node; +import com.netflix.spinnaker.halyard.core.error.v1.HalException; +import com.netflix.spinnaker.halyard.core.problem.v1.Problem; +import com.netflix.spinnaker.halyard.core.problem.v1.ProblemBuilder; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.constructor.Constructor; +import org.yaml.snakeyaml.representer.Representer; + +@Data +@EqualsAndHashCode(callSuper = true) +public class Plugin extends Node { + public String name; + public Boolean enabled; + public String manifestLocation; + + @Override + public String getNodeName() { + return name; + } + + public Manifest generateManifest() { + Representer representer = new Representer(); + representer.getPropertyUtils().setSkipMissingProperties(true); + Yaml yaml = new Yaml(new Constructor(Manifest.class), representer); + + InputStream manifestContents = downloadManifest(); + Manifest manifest = yaml.load(manifestContents); + manifest.validate(); + return manifest; + } + + public InputStream downloadManifest() { + try { + if (manifestLocation.startsWith("http:") || manifestLocation.startsWith("https:")) { + URL url = new URL(manifestLocation); + return url.openStream(); + } else { + return new FileInputStream(manifestLocation); + } + } catch (IOException e) { + throw new HalException( + new ProblemBuilder( + Problem.Severity.FATAL, + "Cannot get plugin manifest file from: " + + manifestLocation + + ": " + + e.getMessage()) + .build()); + } + } +} diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/providers/kubernetes/KubernetesAccount.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/providers/kubernetes/KubernetesAccount.java index 67106d579f..07a54e6f1f 100644 --- a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/providers/kubernetes/KubernetesAccount.java +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/providers/kubernetes/KubernetesAccount.java @@ -140,4 +140,41 @@ public static class KubernetesCachingPolicy { String kubernetesKind; int maxEntriesPerAgent; } + + // These six methods exist for backwards compatibility. Versions of Halyard prior to 1.22 would + // write this field out twice: to 'oAuthScopes' and to 'oauthScopes'. Whichever came last in the + // file would end up taking precedence. These methods replicate that behavior during parsing, but + // will only write out 'oAuthScopes' during serialization. They can be deleted after a few months + // (at which point Lombok will generate the first four automatically). If you're reading this in + // 2020 or later, you can definitely delete these (and also: whoah, the future is probably so fun, + // how are those flying cars working out?) + @JsonProperty("oAuthScopes") + public List getOAuthScopes() { + return oAuthScopes; + } + + @JsonProperty("oAuthScopes") + public void setOAuthScopes(List oAuthScopes) { + this.oAuthScopes = oAuthScopes; + } + + @JsonProperty("oAuthServiceAccount") + public String getOAuthServiceAccount() { + return oAuthServiceAccount; + } + + @JsonProperty("oAuthServiceAccount") + public void setOAuthServiceAccount(String oAuthServiceAccount) { + this.oAuthServiceAccount = oAuthServiceAccount; + } + + @JsonProperty("oauthScopes") + public void setOauthScopes(List oAuthScopes) { + this.oAuthScopes = oAuthScopes; + } + + @JsonProperty("oauthServiceAccount") + public void setOauthServiceAccount(String oAuthServiceAccount) { + this.oAuthServiceAccount = oAuthServiceAccount; + } } diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/util/ValidatingFileReader.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/util/ValidatingFileReader.java index 7f2a1a8a90..453e736f0f 100644 --- a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/util/ValidatingFileReader.java +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/model/v1/util/ValidatingFileReader.java @@ -21,6 +21,8 @@ import com.netflix.spinnaker.halyard.config.problem.v1.ConfigProblemBuilder; import com.netflix.spinnaker.halyard.config.problem.v1.ConfigProblemSetBuilder; import com.netflix.spinnaker.halyard.core.problem.v1.Problem; +import com.netflix.spinnaker.halyard.core.secrets.v1.SecretSessionManager; +import com.netflix.spinnaker.kork.secrets.EncryptedSecret; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; @@ -32,17 +34,33 @@ public class ValidatingFileReader { + System.getProperty("user.name") + ". Make sure that user can read the requested file."; - public static String contents(ConfigProblemSetBuilder ps, String path) { + public static String contents( + ConfigProblemSetBuilder ps, String path, SecretSessionManager secretSessionManager) { + + byte[] contentBytes = contentBytes(ps, path, secretSessionManager); + if (contentBytes == null) { + return null; + } + return new String(contentBytes); + } + + public static byte[] contentBytes( + ConfigProblemSetBuilder ps, String path, SecretSessionManager secretSessionManager) { + if (PropertyUtils.isConfigServerResource(path)) { return null; - } else { - return readFromLocalFilesystem(ps, path); } + + if (EncryptedSecret.isEncryptedSecret(path)) { + return secretSessionManager.decryptAsBytes(path); + } + + return readFromLocalFilesystem(ps, path); } - private static String readFromLocalFilesystem(ConfigProblemSetBuilder ps, String path) { + private static byte[] readFromLocalFilesystem(ConfigProblemSetBuilder ps, String path) { try { - return IOUtils.toString(new FileInputStream(path)); + return IOUtils.toByteArray(new FileInputStream(path)); } catch (FileNotFoundException e) { buildProblem(ps, "Cannot find provided path: " + e.getMessage() + ".", e); } catch (IOException e) { diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/services/v1/NotificationService.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/services/v1/NotificationService.java index 8b4c1c4422..a88503fc86 100644 --- a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/services/v1/NotificationService.java +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/services/v1/NotificationService.java @@ -24,6 +24,7 @@ import com.netflix.spinnaker.halyard.config.model.v1.node.NodeFilter; import com.netflix.spinnaker.halyard.config.model.v1.node.Notification; import com.netflix.spinnaker.halyard.config.model.v1.node.Notifications; +import com.netflix.spinnaker.halyard.config.model.v1.notifications.GithubStatusNotification; import com.netflix.spinnaker.halyard.config.model.v1.notifications.SlackNotification; import com.netflix.spinnaker.halyard.config.model.v1.notifications.TwilioNotification; import com.netflix.spinnaker.halyard.config.problem.v1.ConfigProblemBuilder; @@ -92,6 +93,9 @@ public void setNotification(String deploymentName, Notification notification) { case TWILIO: notifications.setTwilio((TwilioNotification) notification); break; + case GITHUB_STATUS: + notifications.setGithubStatus((GithubStatusNotification) notification); + break; default: throw new IllegalArgumentException( "Unknown notification type " + notification.getNotificationType()); diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/services/v1/PluginService.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/services/v1/PluginService.java new file mode 100644 index 0000000000..692dc7c05f --- /dev/null +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/services/v1/PluginService.java @@ -0,0 +1,147 @@ +/* + * 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.error.v1.ConfigNotFoundException; +import com.netflix.spinnaker.halyard.config.error.v1.IllegalConfigException; +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.Plugins; +import com.netflix.spinnaker.halyard.config.model.v1.plugins.Plugin; +import com.netflix.spinnaker.halyard.config.problem.v1.ConfigProblemBuilder; +import com.netflix.spinnaker.halyard.core.error.v1.HalException; +import com.netflix.spinnaker.halyard.core.problem.v1.Problem; +import com.netflix.spinnaker.halyard.core.problem.v1.ProblemSet; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class PluginService { + private final LookupService lookupService; + private final ValidateService validateService; + private final DeploymentService deploymentService; + + private Plugins getPlugins(String deploymentName) { + NodeFilter filter = new NodeFilter().setDeployment(deploymentName).setPlugin(); + + return lookupService.getSingularNodeOrDefault( + filter, Plugins.class, Plugins::new, n -> setPlugins(deploymentName, n)); + } + + private void setPlugins(String deploymentName, Plugins newPlugins) { + DeploymentConfiguration deploymentConfiguration = + deploymentService.getDeploymentConfiguration(deploymentName); + deploymentConfiguration.setPlugins(newPlugins); + } + + public List getAllPlugins(String deploymentName) { + return getPlugins(deploymentName).getPlugins(); + } + + public Plugin getPlugin(String deploymentName, String pluginName) { + NodeFilter filter = new NodeFilter().setDeployment(deploymentName).setPlugin(pluginName); + List matchingPlugins = lookupService.getMatchingNodesOfType(filter, Plugin.class); + + switch (matchingPlugins.size()) { + case 0: + throw new ConfigNotFoundException( + new ConfigProblemBuilder( + Problem.Severity.FATAL, "No plugin with name \"" + pluginName + "\" was found") + .setRemediation("Create a new plugin with name \"" + pluginName + "\"") + .build()); + case 1: + return matchingPlugins.get(0); + default: + throw new IllegalConfigException( + new ConfigProblemBuilder( + Problem.Severity.FATAL, + "More than one plugin named \"" + pluginName + "\" was found") + .setRemediation( + "Manually delete/rename duplicate plugins with name \"" + + pluginName + + "\" in your halconfig file") + .build()); + } + } + + public void setPlugin(String deploymentName, String pluginName, Plugin newPlugin) { + List plugins = getAllPlugins(deploymentName); + for (int i = 0; i < plugins.size(); i++) { + if (plugins.get(i).getNodeName().equals(pluginName)) { + plugins.set(i, newPlugin); + return; + } + } + throw new HalException( + new ConfigProblemBuilder( + Problem.Severity.FATAL, "Plugin \"" + pluginName + "\" wasn't found") + .build()); + } + + public void deletePlugin(String deploymentName, String pluginName) { + List plugins = getAllPlugins(deploymentName); + boolean removed = plugins.removeIf(plugin -> plugin.getName().equals(pluginName)); + + if (!removed) { + throw new HalException( + new ConfigProblemBuilder( + Problem.Severity.FATAL, "Plugin \"" + pluginName + "\" wasn't found") + .build()); + } + } + + public void addPlugin(String deploymentName, Plugin newPlugin) { + String newPluginName = newPlugin.getName(); + List plugins = getAllPlugins(deploymentName); + for (Plugin plugin : plugins) { + if (plugin.getName().equals(newPluginName)) { + throw new HalException( + new ConfigProblemBuilder( + Problem.Severity.FATAL, "Plugin \"" + newPluginName + "\" already exists") + .build()); + } + } + plugins.add(newPlugin); + } + + public void setPluginsEnabled(String deploymentName, boolean validate, boolean enable) { + DeploymentConfiguration deploymentConfiguration = + deploymentService.getDeploymentConfiguration(deploymentName); + Plugins plugins = deploymentConfiguration.getPlugins(); + plugins.setEnabled(enable); + } + + public void setPluginsDownloadingEnabled( + String deploymentName, boolean validate, boolean enable) { + DeploymentConfiguration deploymentConfiguration = + deploymentService.getDeploymentConfiguration(deploymentName); + Plugins plugins = deploymentConfiguration.getPlugins(); + plugins.setDownloadingEnabled(enable); + } + + public ProblemSet validateAllPlugins(String deploymentName) { + NodeFilter filter = new NodeFilter().setDeployment(deploymentName).withAnyPlugin(); + return validateService.validateMatchingFilter(filter); + } + + public ProblemSet validatePlugin(String deploymentName, String pluginName) { + NodeFilter filter = new NodeFilter().setDeployment(deploymentName).setPlugin(pluginName); + return validateService.validateMatchingFilter(filter); + } +} diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/validate/v1/canary/CanaryAccountValidator.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/validate/v1/canary/CanaryAccountValidator.java index aa80688ff2..858abd7ec9 100644 --- a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/validate/v1/canary/CanaryAccountValidator.java +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/validate/v1/canary/CanaryAccountValidator.java @@ -24,11 +24,11 @@ import org.springframework.stereotype.Component; @Component -public class CanaryAccountValidator extends Validator { +public class CanaryAccountValidator extends Validator { private static final String namePattern = "^[a-z0-9]+([-a-z0-9_]*[a-z0-9])?$"; @Override - public void validate(ConfigProblemSetBuilder p, AbstractCanaryAccount n) { + public void validate(ConfigProblemSetBuilder p, T n) { if (n.getName() == null) { p.addProblem(Severity.FATAL, "Canary account name must be specified"); } else if (!Pattern.matches(namePattern, n.getName())) { diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/validate/v1/canary/google/GoogleCanaryAccountValidator.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/validate/v1/canary/google/GoogleCanaryAccountValidator.java index 1f5549cf46..6761e92bc2 100644 --- a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/validate/v1/canary/google/GoogleCanaryAccountValidator.java +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/validate/v1/canary/google/GoogleCanaryAccountValidator.java @@ -20,20 +20,21 @@ import com.netflix.spinnaker.clouddriver.google.security.GoogleNamedAccountCredentials; import com.netflix.spinnaker.front50.model.GcsStorageService; import com.netflix.spinnaker.front50.model.StorageService; -import com.netflix.spinnaker.halyard.config.model.v1.canary.AbstractCanaryAccount; import com.netflix.spinnaker.halyard.config.model.v1.canary.google.GoogleCanaryAccount; import com.netflix.spinnaker.halyard.config.problem.v1.ConfigProblemSetBuilder; import com.netflix.spinnaker.halyard.config.validate.v1.canary.CanaryAccountValidator; +import com.netflix.spinnaker.halyard.core.problem.v1.Problem; import com.netflix.spinnaker.halyard.core.problem.v1.Problem.Severity; import com.netflix.spinnaker.halyard.core.secrets.v1.SecretSessionManager; import com.netflix.spinnaker.halyard.core.tasks.v1.DaemonTaskHandler; import lombok.Data; import lombok.EqualsAndHashCode; +import org.apache.commons.lang3.StringUtils; import org.springframework.scheduling.TaskScheduler; @Data @EqualsAndHashCode(callSuper = false) -public class GoogleCanaryAccountValidator extends CanaryAccountValidator { +public class GoogleCanaryAccountValidator extends CanaryAccountValidator { private String halyardVersion; @@ -53,33 +54,30 @@ public class GoogleCanaryAccountValidator extends CanaryAccountValidator { } @Override - public void validate(ConfigProblemSetBuilder p, AbstractCanaryAccount n) { + public void validate(ConfigProblemSetBuilder p, GoogleCanaryAccount n) { super.validate(p, n); - GoogleCanaryAccount canaryAccount = (GoogleCanaryAccount) n; - DaemonTaskHandler.message( "Validating " + n.getNodeName() + " with " + GoogleCanaryAccountValidator.class.getSimpleName()); - GoogleNamedAccountCredentials credentials = - canaryAccount.getNamedAccountCredentials(halyardVersion, secretSessionManager, p); + GoogleNamedAccountCredentials credentials = getNamedAccountCredentials(p, n); if (credentials == null) { return; } - String jsonPath = canaryAccount.getJsonPath(); + String jsonPath = n.getJsonPath(); try { StorageService storageService = new GcsStorageService( - canaryAccount.getBucket(), - canaryAccount.getBucketLocation(), - canaryAccount.getRootFolder(), - canaryAccount.getProject(), + n.getBucket(), + n.getBucketLocation(), + n.getRootFolder(), + n.getProject(), jsonPath != null ? secretSessionManager.decryptAsFile(jsonPath) : "", "halyard", connectTimeoutSec, @@ -96,9 +94,47 @@ public void validate(ConfigProblemSetBuilder p, AbstractCanaryAccount n) { p.addProblem( Severity.ERROR, "Failed to ensure the required bucket \"" - + canaryAccount.getBucket() + + n.getBucket() + "\" exists: " + e.getMessage()); } } + + private GoogleNamedAccountCredentials getNamedAccountCredentials( + ConfigProblemSetBuilder p, GoogleCanaryAccount canaryAccount) { + String jsonKey = null; + if (!StringUtils.isEmpty(canaryAccount.getJsonPath())) { + jsonKey = validatingFileDecrypt(p, canaryAccount.getJsonPath()); + + if (jsonKey == null) { + return null; + } else if (jsonKey.isEmpty()) { + p.addProblem(Problem.Severity.WARNING, "The supplied credentials file is empty."); + } + } + + if (StringUtils.isEmpty(canaryAccount.getProject())) { + p.addProblem(Problem.Severity.ERROR, "No google project supplied."); + return null; + } + + try { + return new GoogleNamedAccountCredentials.Builder() + .name(canaryAccount.getName()) + .jsonKey(jsonKey) + .project(canaryAccount.getProject()) + .applicationName("halyard " + halyardVersion) + .liveLookupsEnabled(false) + .build(); + } catch (Exception e) { + p.addProblem( + Problem.Severity.ERROR, + "Error instantiating Google credentials: " + e.getMessage() + ".") + .setRemediation( + "Do the provided credentials have access to project " + + canaryAccount.getProject() + + "?"); + return null; + } + } } diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/validate/v1/canary/prometheus/PrometheusCanaryAccountValidator.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/validate/v1/canary/prometheus/PrometheusCanaryAccountValidator.java index 7b1dbb7835..b255855b2f 100644 --- a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/validate/v1/canary/prometheus/PrometheusCanaryAccountValidator.java +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/validate/v1/canary/prometheus/PrometheusCanaryAccountValidator.java @@ -18,7 +18,6 @@ import static com.netflix.spinnaker.halyard.core.problem.v1.Problem.Severity.ERROR; -import com.netflix.spinnaker.halyard.config.model.v1.canary.AbstractCanaryAccount; import com.netflix.spinnaker.halyard.config.model.v1.canary.prometheus.PrometheusCanaryAccount; import com.netflix.spinnaker.halyard.config.problem.v1.ConfigProblemSetBuilder; import com.netflix.spinnaker.halyard.config.validate.v1.canary.CanaryAccountValidator; @@ -33,23 +32,22 @@ @Data @EqualsAndHashCode(callSuper = false) @Component -public class PrometheusCanaryAccountValidator extends CanaryAccountValidator { +public class PrometheusCanaryAccountValidator + extends CanaryAccountValidator { @Autowired private SecretSessionManager secretSessionManager; @Override - public void validate(ConfigProblemSetBuilder p, AbstractCanaryAccount n) { + public void validate(ConfigProblemSetBuilder p, PrometheusCanaryAccount n) { super.validate(p, n); - PrometheusCanaryAccount canaryAccount = (PrometheusCanaryAccount) n; - DaemonTaskHandler.message( "Validating " + n.getNodeName() + " with " + PrometheusCanaryAccountValidator.class.getSimpleName()); - String usernamePasswordFile = canaryAccount.getUsernamePasswordFile(); + String usernamePasswordFile = n.getUsernamePasswordFile(); if (StringUtils.isNotEmpty(usernamePasswordFile)) { String usernamePassword = validatingFileDecrypt(p, usernamePasswordFile); diff --git a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/validate/v1/security/LdapValidator.java b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/validate/v1/security/LdapValidator.java index 956a382ed2..edecf9f2b4 100644 --- a/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/validate/v1/security/LdapValidator.java +++ b/halyard-config/src/main/java/com/netflix/spinnaker/halyard/config/validate/v1/security/LdapValidator.java @@ -46,28 +46,27 @@ public void validate(ConfigProblemSetBuilder p, Ldap ldap) { switch (UserSearchMethod.toUserSearchMethod(ldap)) { case DN_PATTERN: // fall through. - case SEARCH_AND_FILTER: + case SEARCH_AND_OR_BASE: break; case UNSPECIFIED_OR_INVALID: // fall through. default: p.addProblem( Problem.Severity.ERROR, "No valid user search method defined. Please " - + "specify with either --user-dn-pattern OR (--user-search-base and --user-search-filter)."); + + "specify with either --user-dn-pattern OR (--user-search-filter with an optional --user-search-base)."); } } enum UserSearchMethod { UNSPECIFIED_OR_INVALID, DN_PATTERN, - SEARCH_AND_FILTER; + SEARCH_AND_OR_BASE; static UserSearchMethod toUserSearchMethod(Ldap ldap) { if (StringUtils.isNotEmpty(ldap.getUserDnPattern())) { return DN_PATTERN; - } else if (StringUtils.isNotEmpty(ldap.getUserSearchBase()) - && StringUtils.isNotEmpty(ldap.getUserSearchFilter())) { - return SEARCH_AND_FILTER; + } else if (StringUtils.isNotEmpty(ldap.getUserSearchFilter())) { + return SEARCH_AND_OR_BASE; } return UNSPECIFIED_OR_INVALID; } 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 801c300fb5..72d5c16992 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 @@ -79,13 +79,13 @@ public void validate(ConfigProblemSetBuilder p, Saml saml) { } try { - String keyStore = validatingFileDecrypt(p, saml.getKeyStore()); + byte[] keyStore = validatingFileDecryptBytes(p, saml.getKeyStore()); if (keyStore != null) { val keystore = KeyStore.getInstance(KeyStore.getDefaultType()); // will throw an exception if `keyStorePassword` is invalid keystore.load( - new ByteArrayInputStream(keyStore.getBytes()), + new ByteArrayInputStream(keyStore), secretSessionManager.decrypt(saml.getKeyStorePassword()).toCharArray()); Collections.list(keystore.aliases()).stream() diff --git a/halyard-config/src/test/groovy/com/netflix/spinnaker/halyard/config/config/v1/HalconfigParserSpec.groovy b/halyard-config/src/test/groovy/com/netflix/spinnaker/halyard/config/config/v1/HalconfigParserSpec.groovy index 49c311dab0..251aee170e 100644 --- a/halyard-config/src/test/groovy/com/netflix/spinnaker/halyard/config/config/v1/HalconfigParserSpec.groovy +++ b/halyard-config/src/test/groovy/com/netflix/spinnaker/halyard/config/config/v1/HalconfigParserSpec.groovy @@ -17,6 +17,7 @@ package com.netflix.spinnaker.halyard.config.config.v1 import com.netflix.spinnaker.halyard.config.model.v1.node.Halconfig +import com.netflix.spinnaker.halyard.core.error.v1.HalException import org.yaml.snakeyaml.Yaml import org.yaml.snakeyaml.constructor.SafeConstructor import spock.lang.Specification @@ -28,12 +29,16 @@ class HalconfigParserSpec extends Specification { String HALYARD_VERSION = "0.1.0" String SPINNAKER_VERSION = "1.0.0" String CURRENT_DEPLOYMENT = "my-spinnaker-deployment" + String HALCONFIG_HOME = "/home/spinnaker/.hal" HalconfigParser parser void setup() { parser = new HalconfigParser() parser.yamlParser = new Yaml(new SafeConstructor()) parser.objectMapper = new StrictObjectMapper() + def hcDirStructure = new HalconfigDirectoryStructure() + hcDirStructure.halconfigDirectory = HALCONFIG_HOME + parser.halconfigDirectoryStructure = hcDirStructure } void "Accept minimal config"() { @@ -159,4 +164,84 @@ deploymentConfigurations: // Uncomment the below to implement LDAP. Then fill in the rest of the LDAP properties, one per line. // "ldap" | "enabled" | true } + + void "Adds hal config home to relative file paths"() { + setup: + String config = """ +halyardVersion: 1 +currentDeployment: $CURRENT_DEPLOYMENT +deploymentConfigurations: +- name: $CURRENT_DEPLOYMENT + version: $SPINNAKER_VERSION + providers: + kubernetes: + enabled: true + accounts: + - name: kubernetes + requiredGroupMembership: [] + providerVersion: V2 + permissions: {} + dockerRegistries: [] + configureImagePullSecrets: true + cacheThreads: 1 + namespaces: [] + omitNamespaces: [] + kinds: [] + omitKinds: [] + customResources: [] + cachingPolicies: [] + kubeconfigFile: required-files/kubecfg + oAuthScopes: [] + onlySpinnakerManaged: false + primaryAccount: kubernetes +""" + InputStream stream = new ByteArrayInputStream(config.getBytes(StandardCharsets.UTF_8)) + Halconfig out = null + + when: + out = parser.parseHalconfig(stream) + + then: + out.deploymentConfigurations[0].providers.kubernetes.accounts[0].kubeconfigFile == "$HALCONFIG_HOME/required-files/kubecfg" + } + + void "Throws error when trying to escape hal config home with relative local file paths"() { + setup: + String config = """ +halyardVersion: 1 +currentDeployment: $CURRENT_DEPLOYMENT +deploymentConfigurations: +- name: $CURRENT_DEPLOYMENT + version: $SPINNAKER_VERSION + providers: + kubernetes: + enabled: true + accounts: + - name: kubernetes + requiredGroupMembership: [] + providerVersion: V2 + permissions: {} + dockerRegistries: [] + configureImagePullSecrets: true + cacheThreads: 1 + namespaces: [] + omitNamespaces: [] + kinds: [] + omitKinds: [] + customResources: [] + cachingPolicies: [] + kubeconfigFile: poison/../../.kube/config + oAuthScopes: [] + onlySpinnakerManaged: false + primaryAccount: kubernetes +""" + InputStream stream = new ByteArrayInputStream(config.getBytes(StandardCharsets.UTF_8)) + Halconfig out = null + + when: + out = parser.parseHalconfig(stream) + + then: + thrown HalException + } } diff --git a/halyard-config/src/test/groovy/com/netflix/spinnaker/halyard/config/model/v1/NodeSpec.groovy b/halyard-config/src/test/groovy/com/netflix/spinnaker/halyard/config/model/v1/NodeSpec.groovy index e3cfd71572..f9245d7243 100644 --- a/halyard-config/src/test/groovy/com/netflix/spinnaker/halyard/config/model/v1/NodeSpec.groovy +++ b/halyard-config/src/test/groovy/com/netflix/spinnaker/halyard/config/model/v1/NodeSpec.groovy @@ -18,7 +18,6 @@ package com.netflix.spinnaker.halyard.config.model.v1 import com.netflix.spinnaker.halyard.config.model.v1.node.* import com.netflix.spinnaker.halyard.config.problem.v1.ConfigProblemSetBuilder -import junit.framework.Test import spock.lang.Specification class NodeSpec extends Specification { 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 new file mode 100644 index 0000000000..091e72b381 --- /dev/null +++ b/halyard-config/src/test/groovy/com/netflix/spinnaker/halyard/config/model/v1/plugins/ManifestSpec.groovy @@ -0,0 +1,59 @@ +/* + * Copyright 2018 Bol.com + * + * 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.core.error.v1.HalException +import spock.lang.Specification + +class ManifestSpec extends Specification { + + void "plugin manifests must have the required fields"() { + setup: + Manifest manifest = new Manifest(); + + when: + manifest.setJars(jars) + manifest.setManifestVersion(manifestVersion) + manifest.setName(name) + manifest.setOptions(options) + manifest.validate() + + then: + thrown HalException + + where: + name | manifestVersion | jars | options + "foo" | "plugins/v1" | Arrays.asList("jar") | null + "foo/bar" | null | Arrays.asList("jar") | null + "foo/bar" | "plugins/v1" | null | null + } + + void "plugin manifests pass validation"() { + setup: + Manifest manifest = new Manifest(); + + when: + manifest.setJars(Arrays.asList("one", "two")) + manifest.setManifestVersion("plugins/v1") + manifest.setName("foo/bar") + manifest.validate() + + then: + noExceptionThrown() + + } +} 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 6854526041..e4344605dc 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 @@ -16,6 +16,7 @@ package com.netflix.spinnaker.halyard.config.services.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.config.v1.StrictObjectMapper import com.netflix.spinnaker.halyard.config.model.v1.node.Halconfig @@ -30,7 +31,9 @@ class HalconfigParserMocker extends Specification { def parserStub = new HalconfigParser() parserStub.objectMapper = new StrictObjectMapper() parserStub.yamlParser = new Yaml(new SafeConstructor()) - parserStub.halconfigPath = "/some/nonsense/file" + def hcDirStructure = new HalconfigDirectoryStructure() + hcDirStructure.halconfigDirectory = "/home/spinnaker/.hal" + parserStub.halconfigDirectoryStructure = hcDirStructure 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 new file mode 100644 index 0000000000..735805ca35 --- /dev/null +++ b/halyard-config/src/test/groovy/com/netflix/spinnaker/halyard/config/services/v1/PluginServiceSpec.groovy @@ -0,0 +1,176 @@ +/* + * 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.error.v1.ConfigNotFoundException +import spock.lang.Specification + +class PluginServiceSpec extends Specification { + final String DEPLOYMENT = "default" + final HalconfigParserMocker mocker = new HalconfigParserMocker() + + LookupService getMockLookupService(String config) { + def lookupService = new LookupService() + lookupService.parser = mocker.mockHalconfigParser(config) + return lookupService + } + + PluginService makePluginService(String config) { + def lookupService = getMockLookupService(config) + def deploymentService = new DeploymentService() + deploymentService.lookupService = lookupService + new PluginService(lookupService, new ValidateService(), deploymentService) + } + + def "load an existing plugin node"() { + setup: + String config = """ +halyardVersion: 1 +currentDeployment: $DEPLOYMENT +deploymentConfigurations: +- name: $DEPLOYMENT + version: 1 + providers: null + plugins: + plugins: + - name: test-plugin + manifestLocation: /home/user/test-plugin.yaml +""" + def pluginService = makePluginService(config) + + when: + def result = pluginService.getAllPlugins(DEPLOYMENT) + + then: + result != null + result.size() == 1 + result[0].getName() == "test-plugin" + result[0].getManifestLocation() == "/home/user/test-plugin.yaml" + + when: + result = pluginService.getPlugin(DEPLOYMENT, "test-plugin") + + then: + result != null + result.getName() == "test-plugin" + result.getManifestLocation() == "/home/user/test-plugin.yaml" + + when: + pluginService.getPlugin(DEPLOYMENT, 'non-existent-plugin') + + then: + thrown(ConfigNotFoundException) + } + + def "no error if plugin is empty"() { + setup: + String config = """ +halyardVersion: 1 +currentDeployment: $DEPLOYMENT +deploymentConfigurations: +- name: $DEPLOYMENT + version: 1 + providers: null + plugins: + plugins: [] +""" + def pluginService = makePluginService(config) + + when: + def result = pluginService.getAllPlugins(DEPLOYMENT) + + then: + result != null + result.size() == 0 + + when: + pluginService.getPlugin(DEPLOYMENT, "test-plugin") + + then: + thrown(ConfigNotFoundException) + } + + def "no error if plugin is missing"() { + setup: + String config = """ +halyardVersion: 1 +currentDeployment: $DEPLOYMENT +deploymentConfigurations: +- name: $DEPLOYMENT + version: 1 + providers: null + plugins: +""" + def pluginService = makePluginService(config) + + when: + def result = pluginService.getAllPlugins(DEPLOYMENT) + + then: + result != null + result.size() == 0 + + when: + + pluginService.getPlugin(DEPLOYMENT, "test-template") + + then: + thrown(ConfigNotFoundException) + } + + def "multiple templates are correctly parsed"() { + setup: + String config = """ +halyardVersion: 1 +currentDeployment: $DEPLOYMENT +deploymentConfigurations: +- name: $DEPLOYMENT + version: 1 + providers: null + plugins: + plugins: + - name: test-plugin + manifestLocation: /home/user/test-plugin.yaml + - name: test-plugin-2 + manifestLocation: /home/user/test-plugin-2.yaml +""" + def pluginService = makePluginService(config) + + when: + def result = pluginService.getAllPlugins(DEPLOYMENT) + + then: + result != null + result.size() == 2 + + when: + result = pluginService.getPlugin(DEPLOYMENT, "test-plugin") + + then: + result != null + result.getName() == "test-plugin" + result.getManifestLocation() == "/home/user/test-plugin.yaml" + + when: + result = pluginService.getPlugin(DEPLOYMENT, "test-plugin-2") + + then: + result != null + result.getName() == "test-plugin-2" + result.getManifestLocation() == "/home/user/test-plugin-2.yaml" + } +} diff --git a/halyard-config/src/test/groovy/com/netflix/spinnaker/halyard/config/validate/v1/security/LdapValidatorSpec.groovy b/halyard-config/src/test/groovy/com/netflix/spinnaker/halyard/config/validate/v1/security/LdapValidatorSpec.groovy index 4036218682..17da913804 100644 --- a/halyard-config/src/test/groovy/com/netflix/spinnaker/halyard/config/validate/v1/security/LdapValidatorSpec.groovy +++ b/halyard-config/src/test/groovy/com/netflix/spinnaker/halyard/config/validate/v1/security/LdapValidatorSpec.groovy @@ -32,11 +32,13 @@ class LdapValidatorSpec extends Specification { problemSet.empty where: - description | enabled | ldapUrl | userDnPattern | userSearchBase | userSearchFilter | managerDn | managerPassword | groupSearchBase - "not enabled" | false | null | null | null | null | null | null | null - "user DN pattern" | true | "ldaps://ldap.some.com:123" | "some pattern" | null | null | null | null | null - "search and filter" | true | "ldap://ldap.some.com:123" | null | "sub" | "ou=foo" | null | null | null - "search and filter" | true | "ldap://ldap.some.com:123" | null | "sub" | "ou=foo" | "admin" | "secret" | "ou=company" + description | enabled | ldapUrl | userDnPattern | userSearchBase | userSearchFilter | managerDn | managerPassword | groupSearchBase + "not enabled" | false | null | null | null | null | null | null | null + "user DN pattern" | true | "ldaps://ldap.some.com:123" | "some pattern" | null | null | null | null | null + "search and filter" | true | "ldap://ldap.some.com:123" | null | "sub" | "ou=foo" | null | null | null + "search and filter" | true | "ldap://ldap.some.com:123" | null | "sub" | "ou=foo" | "admin" | "secret" | "ou=company" + "search and root in url" | true | "ldap://ldap.some.com:123/root_dn" | null | null | "ou=foo" | "admin" | "secret" | "ou=company" + "search and root no mgr" | true | "ldap://ldap.some.com:123/root_dn" | null | null | "ou=foo" | null | null | "ou=company" } @Unroll diff --git a/halyard-config/src/test/java/com/netflix/spinnaker/halyard/config/model/v1/providers/kubernetes/KubernetesAccountTest.java b/halyard-config/src/test/java/com/netflix/spinnaker/halyard/config/model/v1/providers/kubernetes/KubernetesAccountTest.java new file mode 100644 index 0000000000..924d85782b --- /dev/null +++ b/halyard-config/src/test/java/com/netflix/spinnaker/halyard/config/model/v1/providers/kubernetes/KubernetesAccountTest.java @@ -0,0 +1,113 @@ +/* + * Copyright 2019 Google, LLC + * + * 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.providers.kubernetes; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.netflix.spinnaker.halyard.config.config.v1.StrictObjectMapper; +import java.io.IOException; +import java.io.StringWriter; +import org.junit.jupiter.api.Test; +import org.junit.platform.runner.JUnitPlatform; +import org.junit.runner.RunWith; +import org.yaml.snakeyaml.Yaml; + +@RunWith(JUnitPlatform.class) +final class KubernetesAccountTest { + + @Test + void testLastOAuthScopeIsKept_capitalA() { + Yaml yamlParser = new Yaml(); + Object parsedYaml = + yamlParser.load( + Joiner.on('\n') + .join( + "oauthScopes: [\"lowercase-a\"]", // + "oAuthScopes: [\"uppercase-a\"]")); + KubernetesAccount account = + new StrictObjectMapper().convertValue(parsedYaml, KubernetesAccount.class); + + assertThat(account.getOAuthScopes()).containsExactly("uppercase-a"); + } + + @Test + void testLastOAuthScopeIsKept_lowercaseA() { + Yaml yamlParser = new Yaml(); + Object parsedYaml = + yamlParser.load( + Joiner.on('\n') + .join( + "oAuthScopes: [\"uppercase-a\"]", // + "oauthScopes: [\"lowercase-a\"]")); + KubernetesAccount account = + new StrictObjectMapper().convertValue(parsedYaml, KubernetesAccount.class); + + assertThat(account.getOAuthScopes()).containsExactly("lowercase-a"); + } + + @Test + void testLastOAuthServiceAccountIsKept_capitalA() { + Yaml yamlParser = new Yaml(); + Object parsedYaml = + yamlParser.load( + Joiner.on('\n') + .join( + "oauthServiceAccount: \"lowercase-a\"", // + "oAuthServiceAccount: \"uppercase-a\"")); + KubernetesAccount account = + new StrictObjectMapper().convertValue(parsedYaml, KubernetesAccount.class); + + assertThat(account.getOAuthServiceAccount()).isEqualTo("uppercase-a"); + } + + @Test + void testLastOAuthServiceAccountIsKept_lowercaseA() { + Yaml yamlParser = new Yaml(); + Object parsedYaml = + yamlParser.load( + Joiner.on('\n') + .join( + "oAuthServiceAccount: \"uppercase-a\"", // + "oauthServiceAccount: \"lowercase-a\"")); + KubernetesAccount account = + new StrictObjectMapper().convertValue(parsedYaml, KubernetesAccount.class); + + assertThat(account.getOAuthServiceAccount()).isEqualTo("lowercase-a"); + } + + @Test + void testOnlyCapitalAIsWritten() throws IOException { + KubernetesAccount account = new KubernetesAccount(); + account.setOAuthScopes(ImmutableList.of("my-scope")); + account.setOAuthServiceAccount("my-service-account"); + + String result = getYaml(account); + + assertThat(result).contains("oAuthScopes"); + assertThat(result).doesNotContain("oauthScopes"); + assertThat(result).contains("oAuthServiceAccount"); + assertThat(result).doesNotContain("oauthServiceAccount"); + } + + private static String getYaml(KubernetesAccount account) throws IOException { + StringWriter stringWriter = new StringWriter(); + new StrictObjectMapper().writeValue(stringWriter, account); + return stringWriter.toString(); + } +} diff --git a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/config/v1/secrets/BindingsSecretDecrypter.java b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/config/v1/secrets/BindingsSecretDecrypter.java new file mode 100644 index 0000000000..2157f150ea --- /dev/null +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/config/v1/secrets/BindingsSecretDecrypter.java @@ -0,0 +1,32 @@ +package com.netflix.spinnaker.halyard.deploy.config.v1.secrets; + +import com.netflix.spinnaker.halyard.core.secrets.v1.SecretSessionManager; +import com.netflix.spinnaker.halyard.deploy.spinnaker.v1.profile.Profile; +import com.netflix.spinnaker.kork.secrets.EncryptedSecret; +import java.nio.file.Path; +import org.apache.commons.lang3.RandomStringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class BindingsSecretDecrypter { + private SecretSessionManager secretSessionManager; + + @Autowired + BindingsSecretDecrypter(SecretSessionManager secretSessionManager) { + this.secretSessionManager = secretSessionManager; + } + + public String trackSecretFile(Profile profile, Path outputDir, String value, String fieldName) { + if (!EncryptedSecret.isEncryptedSecret(value)) { + return value; + } + String decryptedFilename = newRandomFileName(fieldName); + profile.getDecryptedFiles().put(decryptedFilename, secretSessionManager.decryptAsBytes(value)); + return outputDir.resolve(decryptedFilename).toString(); + } + + private String newRandomFileName(String fieldName) { + return fieldName + "-" + RandomStringUtils.randomAlphanumeric(5); + } +} diff --git a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/services/v1/RequestGenerateService.java b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/services/v1/RequestGenerateService.java index f1543ce7fc..764213ff71 100644 --- a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/services/v1/RequestGenerateService.java +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/services/v1/RequestGenerateService.java @@ -22,16 +22,23 @@ import com.netflix.spinnaker.halyard.config.config.v1.HalconfigDirectoryStructure; import com.netflix.spinnaker.halyard.config.model.v1.node.DeploymentConfiguration; import com.netflix.spinnaker.halyard.config.model.v1.node.Halconfig; +import com.netflix.spinnaker.halyard.core.error.v1.HalException; +import com.netflix.spinnaker.halyard.core.problem.v1.Problem; import java.io.*; -import java.util.*; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Map; import lombok.Getter; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartHttpServletRequest; import org.yaml.snakeyaml.Yaml; public class RequestGenerateService { + private static final String CONFIG_KEY = "config"; private static final String SERVICE_SETTINGS_KEY = "serviceSettings"; + private static final String LOCAL_FILE_PREFIX = "files__"; + protected @Getter File baseDirectory; private Yaml yaml = new Yaml(); private ObjectMapper objectMapper = new ObjectMapper(); @@ -54,17 +61,15 @@ public void prepare(MultipartHttpServletRequest request) throws IOException { request.getFileMap().entrySet().stream() .forEach( et -> { - if (!et.getKey().equals(CONFIG_KEY)) { - File targetFile = getTargetFile(deploymentConfiguration, et.getKey()); - if (targetFile != null) { - targetFile.getParentFile().mkdirs(); - try { - FileOutputStream outStream = new FileOutputStream(targetFile); - outStream.write(et.getValue().getBytes()); - } catch (IOException e) { - throw new RuntimeException(e); - } - } + if (et.getKey().equals(CONFIG_KEY)) { + return; + } + if (et.getKey().startsWith(LOCAL_FILE_PREFIX)) { + // write all local files in hal config root + writeFile("", et.getKey(), et.getValue()); + } else { + String filePath = et.getKey().replaceAll("__", File.separator); + writeFile(deploymentConfiguration.getName(), filePath, et.getValue()); } }); } @@ -73,15 +78,6 @@ public void cleanup() { HalconfigDirectoryStructure.setDirectoryOverride(null); } - protected File getTargetFile(DeploymentConfiguration deploymentConfiguration, String paramKey) { - String path = paramKey.replaceAll("__", File.separator); - // Don't allow parent reference - if (path.indexOf("..") > -1) { - return null; - } - return new File(baseDirectory, deploymentConfiguration.getName() + File.separator + path); - } - protected void writeHalConfig(DeploymentConfiguration deploymentConfiguration) throws IOException { Halconfig config = new Halconfig(); @@ -124,6 +120,27 @@ protected void writeServiceSettings( } } + private void writeFile(String deploymentName, String filePath, MultipartFile fileContents) { + Path target = Paths.get(baseDirectory.toString(), deploymentName, filePath).normalize(); + if (!target.startsWith(baseDirectory.toString())) { + throw new HalException( + Problem.Severity.ERROR, + "File path " + + filePath + + " must not resolve to a dir outside of " + + baseDirectory.toString()); + } + + File targetFile = target.toFile(); + targetFile.getParentFile().mkdirs(); + try { + FileOutputStream outStream = new FileOutputStream(targetFile); + outStream.write(fileContents.getBytes()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + protected DeploymentConfiguration parseDeploymentConfiguration(MultipartFile deploymentConfigFile) throws IOException { if (deploymentConfigFile == null) { diff --git a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/AwsCredentialsProfileFactoryBuilder.java b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/AwsCredentialsProfileFactoryBuilder.java index ad2638d9e6..317b330525 100644 --- a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/AwsCredentialsProfileFactoryBuilder.java +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/AwsCredentialsProfileFactoryBuilder.java @@ -83,7 +83,9 @@ protected ArtifactService getArtifactService() { @Override protected Map getBindings( - DeploymentConfiguration deploymentConfiguration, SpinnakerRuntimeSettings endpoints) { + DeploymentConfiguration deploymentConfiguration, + Profile profile, + SpinnakerRuntimeSettings endpoints) { Map result = new HashMap<>(); result.put("accessKeyId", accessKeyId); result.put( diff --git a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/KubernetesV2ClouddriverProfileFactory.java b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/KubernetesV2ClouddriverProfileFactory.java index 8eb95618c4..884b89c587 100644 --- a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/KubernetesV2ClouddriverProfileFactory.java +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/KubernetesV2ClouddriverProfileFactory.java @@ -25,6 +25,7 @@ import com.netflix.spinnaker.halyard.core.error.v1.HalException; import com.netflix.spinnaker.halyard.core.problem.v1.Problem; import com.netflix.spinnaker.halyard.core.secrets.v1.SecretSessionManager; +import com.netflix.spinnaker.kork.configserver.CloudConfigResourceService; import com.netflix.spinnaker.kork.configserver.ConfigFileService; import com.netflix.spinnaker.kork.secrets.EncryptedSecret; import java.io.FileWriter; @@ -48,16 +49,19 @@ public class KubernetesV2ClouddriverProfileFactory extends ClouddriverProfileFac private final SecretSessionManager secretSessionManager; private final ConfigFileService configFileService; + private final CloudConfigResourceService cloudConfigResourceService; public KubernetesV2ClouddriverProfileFactory( ObjectMapper objectMapper, Yaml yamlParser, SecretSessionManager secretSessionManager, - ConfigFileService configFileService) { + ConfigFileService configFileService, + CloudConfigResourceService cloudConfigResourceService) { this.objectMapper = objectMapper; this.yamlParser = yamlParser; this.secretSessionManager = secretSessionManager; this.configFileService = configFileService; + this.cloudConfigResourceService = cloudConfigResourceService; } @Override @@ -73,22 +77,8 @@ private void processKubernetesAccount(KubernetesAccount account) { } String kubeconfigFile = account.getKubeconfigFile(); - String kubeconfigContents; + String kubeconfigContents = getKubconfigFileContents(kubeconfigFile); String context = account.getContext(); - try { - if (EncryptedSecret.isEncryptedSecret(kubeconfigFile)) { - kubeconfigContents = secretSessionManager.decrypt(kubeconfigFile); - } else { - kubeconfigContents = configFileService.getContents(kubeconfigFile); - } - } catch (Exception e) { - throw new IllegalStateException( - "Failed to read kubeconfig file '" - + kubeconfigFile - + "', but validation passed: " - + e.getMessage(), - e); - } Object obj = yamlParser.load(kubeconfigContents); Map parsedKubeconfig = @@ -162,4 +152,26 @@ private void processKubernetesAccount(KubernetesAccount account) { "Unable to write the kubeconfig file to the staging area. This may be a user permissions issue."); } } + + private String getKubconfigFileContents(String kubeconfigFile) { + try { + if (EncryptedSecret.isEncryptedSecret(kubeconfigFile)) { + return secretSessionManager.decrypt(kubeconfigFile); + } + + String localPath = kubeconfigFile; + if (CloudConfigResourceService.isCloudConfigResource(kubeconfigFile)) { + localPath = cloudConfigResourceService.getLocalPath(kubeconfigFile); + } + + return configFileService.getContents(localPath); + } catch (Exception e) { + throw new IllegalStateException( + "Failed to read kubeconfig file '" + + kubeconfigFile + + "', but validation passed: " + + e.getMessage(), + e); + } + } } 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 8d564747af..1c67cacc7e 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,21 @@ 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; import org.springframework.stereotype.Component; +@Slf4j @Component public class OrcaProfileFactory extends SpringProfileFactory { @Override @@ -73,6 +79,20 @@ protected void setProfile( profile.appendContents("pipelineTemplates.enabled: " + pipelineTemplates); // 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)); } @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 new file mode 100644 index 0000000000..d8d921c180 --- /dev/null +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/PluginProfileFactory.java @@ -0,0 +1,81 @@ +/* + * 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.deploy.spinnaker.v1.profile; + +import com.netflix.spinnaker.halyard.config.model.v1.node.DeploymentConfiguration; +import com.netflix.spinnaker.halyard.config.model.v1.node.Plugins; +import com.netflix.spinnaker.halyard.config.model.v1.plugins.Manifest; +import com.netflix.spinnaker.halyard.config.model.v1.plugins.Plugin; +import com.netflix.spinnaker.halyard.deploy.spinnaker.v1.SpinnakerArtifact; +import com.netflix.spinnaker.halyard.deploy.spinnaker.v1.SpinnakerRuntimeSettings; +import java.util.*; +import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class PluginProfileFactory extends StringBackedProfileFactory { + @Override + protected void setProfile( + Profile profile, + DeploymentConfiguration deploymentConfiguration, + SpinnakerRuntimeSettings endpoints) { + final Plugins plugins = deploymentConfiguration.getPlugins(); + + Map pluginsYaml = new HashMap<>(); + Map fullyRenderedYaml = new HashMap<>(); + + List> pluginMetadata = + plugins.getPlugins().stream() + .filter(p -> p.getEnabled()) + .filter(p -> !p.getManifestLocation().isEmpty()) + .map(p -> composeMetadata(p, p.generateManifest())) + .collect(Collectors.toList()); + + pluginsYaml.put("pluginConfigurations", pluginMetadata); + pluginsYaml.put("downloadingEnabled", plugins.isDownloadingEnabled()); + fullyRenderedYaml.put("plugins", pluginsYaml); + + profile.appendContents( + yamlToString(deploymentConfiguration.getName(), profile, fullyRenderedYaml)); + } + + private Map composeMetadata(Plugin plugin, Manifest manifest) { + Map metadata = new LinkedHashMap<>(); + metadata.put("enabled", plugin.getEnabled()); + metadata.put("name", manifest.getName()); + metadata.put("jars", manifest.getJars()); + metadata.put("manifestVersion", manifest.getManifestVersion()); + return metadata; + } + + @Override + protected String getRawBaseProfile() { + return ""; + } + + @Override + public SpinnakerArtifact getArtifact() { + return SpinnakerArtifact.SPINNAKER; + } + + @Override + protected String commentPrefix() { + return "## "; + } +} diff --git a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/ProfileFactory.java b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/ProfileFactory.java index ccb04a9f72..1880bd403c 100644 --- a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/ProfileFactory.java +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/ProfileFactory.java @@ -50,6 +50,9 @@ protected String getMinimumSecretDecryptionVersion(String deploymentName) { * @return true if the target service supports decryption of secrets */ protected boolean supportsSecretDecryption(String deploymentName) { + if (getArtifact().equals(SpinnakerArtifact.DECK)) { + return false; + } String minVersion = getMinimumSecretDecryptionVersion(deploymentName); if (minVersion == null) { return false; diff --git a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/TemplateBackedProfileFactory.java b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/TemplateBackedProfileFactory.java index 4c4de2e5d4..626d4a810f 100644 --- a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/TemplateBackedProfileFactory.java +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/TemplateBackedProfileFactory.java @@ -28,7 +28,9 @@ public abstract class TemplateBackedProfileFactory extends ProfileFactory { protected abstract String getTemplate(); protected abstract Map getBindings( - DeploymentConfiguration deploymentConfiguration, SpinnakerRuntimeSettings endpoints); + DeploymentConfiguration deploymentConfiguration, + Profile profile, + SpinnakerRuntimeSettings endpoints); protected List requiredFiles(DeploymentConfiguration deploymentConfiguration) { return new ArrayList<>(); @@ -46,7 +48,7 @@ protected void setProfile( SpinnakerRuntimeSettings endpoints) { StringResource template = new StringResource(profile.getBaseContents()); profile.setRequiredFiles(requiredFiles(deploymentConfiguration)); - Map bindings = getBindings(deploymentConfiguration, endpoints); + Map bindings = getBindings(deploymentConfiguration, profile, endpoints); profile.setContents(template.setBindings(bindings).toString()); } } diff --git a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/consul/ConsulClientProfileFactory.java b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/consul/ConsulClientProfileFactory.java index 06f4a385a6..f11f8fd9f9 100644 --- a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/consul/ConsulClientProfileFactory.java +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/consul/ConsulClientProfileFactory.java @@ -20,6 +20,7 @@ import com.netflix.spinnaker.halyard.config.model.v1.node.DeploymentConfiguration; 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.Profile; import com.netflix.spinnaker.halyard.deploy.spinnaker.v1.profile.TemplateBackedProfileFactory; import com.netflix.spinnaker.halyard.deploy.spinnaker.v1.service.ServiceSettings; import com.netflix.spinnaker.halyard.deploy.spinnaker.v1.service.SpinnakerService.Type; @@ -51,7 +52,9 @@ public class ConsulClientProfileFactory extends TemplateBackedProfileFactory { @Override protected Map getBindings( - DeploymentConfiguration deploymentConfiguration, SpinnakerRuntimeSettings endpoints) { + DeploymentConfiguration deploymentConfiguration, + Profile profile, + SpinnakerRuntimeSettings endpoints) { Map bindings = new HashMap<>(); ServiceSettings consul = endpoints.getServiceSettings(Type.CONSUL_CLIENT); bindings.put("scheme", consul.getScheme()); diff --git a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/deck/ApachePassphraseProfileFactory.java b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/deck/ApachePassphraseProfileFactory.java index ba30dcd175..9e7d6cc329 100644 --- a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/deck/ApachePassphraseProfileFactory.java +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/deck/ApachePassphraseProfileFactory.java @@ -23,6 +23,7 @@ import com.netflix.spinnaker.halyard.deploy.spinnaker.v1.SpinnakerRuntimeSettings; import com.netflix.spinnaker.halyard.deploy.spinnaker.v1.profile.Profile; import com.netflix.spinnaker.halyard.deploy.spinnaker.v1.profile.TemplateBackedProfileFactory; +import com.netflix.spinnaker.kork.secrets.EncryptedSecret; import java.util.HashMap; import java.util.Map; import org.springframework.stereotype.Component; @@ -52,10 +53,17 @@ protected void setProfile( @Override protected Map getBindings( - DeploymentConfiguration deploymentConfiguration, SpinnakerRuntimeSettings endpoints) { + DeploymentConfiguration deploymentConfiguration, + Profile profile, + SpinnakerRuntimeSettings endpoints) { Map bindings = new HashMap<>(); ApacheSsl ssl = deploymentConfiguration.getSecurity().getUiSecurity().getSsl(); - bindings.put("passphrase", ssl.getSslCertificatePassphrase()); + if (EncryptedSecret.isEncryptedSecret(ssl.getSslCertificatePassphrase()) + && !supportsSecretDecryption(deploymentConfiguration.getName())) { + bindings.put("passphrase", secretSessionManager.decrypt(ssl.getSslCertificatePassphrase())); + } else { + bindings.put("passphrase", ssl.getSslCertificatePassphrase()); + } return bindings; } diff --git a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/deck/ApachePortsProfileFactory.java b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/deck/ApachePortsProfileFactory.java index b48d7880fb..25c3e47973 100644 --- a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/deck/ApachePortsProfileFactory.java +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/deck/ApachePortsProfileFactory.java @@ -54,7 +54,9 @@ protected void setProfile( @Override protected Map getBindings( - DeploymentConfiguration deploymentConfiguration, SpinnakerRuntimeSettings endpoints) { + DeploymentConfiguration deploymentConfiguration, + Profile profile, + SpinnakerRuntimeSettings endpoints) { Map bindings = new HashMap<>(); bindings.put("deck-host", endpoints.getServiceSettings(Type.DECK).getHost()); bindings.put("deck-port", endpoints.getServiceSettings(Type.DECK).getPort() + ""); diff --git a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/deck/ApacheSpinnakerProfileFactory.java b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/deck/ApacheSpinnakerProfileFactory.java index 81eba3953e..e928122f10 100644 --- a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/deck/ApacheSpinnakerProfileFactory.java +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/deck/ApacheSpinnakerProfileFactory.java @@ -22,6 +22,7 @@ import com.netflix.spinnaker.halyard.config.model.v1.security.UiSecurity; import com.netflix.spinnaker.halyard.core.resource.v1.StringResource; import com.netflix.spinnaker.halyard.core.resource.v1.TemplatedResource; +import com.netflix.spinnaker.halyard.deploy.config.v1.secrets.BindingsSecretDecrypter; 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.Profile; @@ -30,6 +31,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component @@ -55,6 +57,8 @@ public class ApacheSpinnakerProfileFactory extends TemplateBackedProfileFactory " ", ""); + @Autowired BindingsSecretDecrypter bindingsSecretDecrypter; + @Override protected String getTemplate() { return SPINNAKER_TEMPLATE; @@ -77,13 +81,35 @@ protected void setProfile( @Override protected Map getBindings( - DeploymentConfiguration deploymentConfiguration, SpinnakerRuntimeSettings endpoints) { + DeploymentConfiguration deploymentConfiguration, + Profile profile, + SpinnakerRuntimeSettings endpoints) { TemplatedResource resource = new StringResource(SSL_TEMPLATE); Map bindings = new HashMap<>(); UiSecurity uiSecurity = deploymentConfiguration.getSecurity().getUiSecurity(); ApacheSsl apacheSsl = uiSecurity.getSsl(); - bindings.put("cert-file", apacheSsl.getSslCertificateFile()); - bindings.put("key-file", apacheSsl.getSslCertificateKeyFile()); + if (supportsSecretDecryption(deploymentConfiguration.getName())) { + bindings.put("cert-file", apacheSsl.getSslCertificateFile()); + bindings.put("key-file", apacheSsl.getSslCertificateKeyFile()); + } else { + bindings.put( + "cert-file", + bindingsSecretDecrypter.trackSecretFile( + profile, + halconfigDirectoryStructure.getStagingDependenciesPath( + deploymentConfiguration.getName()), + apacheSsl.getSslCertificateFile(), + "sslCertificateFile")); + bindings.put( + "key-file", + bindingsSecretDecrypter.trackSecretFile( + profile, + halconfigDirectoryStructure.getStagingDependenciesPath( + deploymentConfiguration.getName()), + apacheSsl.getSslCertificateKeyFile(), + "sslCertificateKeyFile")); + } + String ssl = resource.setBindings(bindings).toString(); bindings.clear(); bindings.put("ssl", ssl); diff --git a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/deck/DeckDockerProfileFactory.java b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/deck/DeckDockerProfileFactory.java index ee891c688e..29a29857a6 100644 --- a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/deck/DeckDockerProfileFactory.java +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/profile/deck/DeckDockerProfileFactory.java @@ -20,6 +20,7 @@ import com.netflix.spinnaker.halyard.config.model.v1.node.DeploymentConfiguration; import com.netflix.spinnaker.halyard.config.model.v1.security.ApacheSsl; import com.netflix.spinnaker.halyard.config.services.v1.AccountService; +import com.netflix.spinnaker.halyard.deploy.config.v1.secrets.BindingsSecretDecrypter; 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.Profile; @@ -33,6 +34,8 @@ public class DeckDockerProfileFactory extends DeckProfileFactory { @Autowired AccountService accountService; + @Autowired BindingsSecretDecrypter bindingsSecretDecrypter; + @Override public String commentPrefix() { return "// "; @@ -59,9 +62,30 @@ protected void setProfile( env.put("DECK_HOST", deckSettings.getHost()); env.put("DECK_PORT", deckSettings.getPort() + ""); env.put("API_HOST", gateSettings.getBaseUrl()); - env.put("DECK_CERT", apacheSsl.getSslCertificateFile()); - env.put("DECK_KEY", apacheSsl.getSslCertificateKeyFile()); - env.put("PASSPHRASE", apacheSsl.getSslCertificatePassphrase()); + if (supportsSecretDecryption(deploymentConfiguration.getName())) { + env.put("DECK_CERT", apacheSsl.getSslCertificateFile()); + env.put("DECK_KEY", apacheSsl.getSslCertificateKeyFile()); + env.put("PASSPHRASE", apacheSsl.getSslCertificatePassphrase()); + } else { + env.put( + "DECK_CERT", + bindingsSecretDecrypter.trackSecretFile( + profile, + halconfigDirectoryStructure.getStagingDependenciesPath( + deploymentConfiguration.getName()), + apacheSsl.getSslCertificateFile(), + "sslCertificateFile")); + env.put( + "DECK_KEY", + bindingsSecretDecrypter.trackSecretFile( + profile, + halconfigDirectoryStructure.getStagingDependenciesPath( + deploymentConfiguration.getName()), + apacheSsl.getSslCertificateKeyFile(), + "sslCertificateKeyFile")); + env.put( + "PASSPHRASE", secretSessionManager.decrypt(apacheSsl.getSslCertificatePassphrase())); + } } env.put( 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 6cb8802729..c2804c860c 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 @@ -21,6 +21,7 @@ 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.Notifications; +import com.netflix.spinnaker.halyard.config.model.v1.notifications.GithubStatusNotification; import com.netflix.spinnaker.halyard.config.model.v1.notifications.SlackNotification; import com.netflix.spinnaker.halyard.config.model.v1.notifications.TwilioNotification; import com.netflix.spinnaker.halyard.config.model.v1.providers.appengine.AppengineProvider; @@ -116,6 +117,10 @@ protected void setProfile( bindings.put( "features.artifacts", Boolean.toString(features.getArtifacts() != null ? features.getArtifacts() : false)); + bindings.put( + "features.artifactsRewrite", + Boolean.toString( + features.getArtifactsRewrite() != null ? features.getArtifactsRewrite() : false)); bindings.put( "features.mineCanary", Boolean.toString(features.getMineCanary() != null ? features.getMineCanary() : false)); @@ -208,6 +213,9 @@ protected void setProfile( TwilioNotification twilioNotification = notifications.getTwilio(); bindings.put("notifications.twilio.enabled", twilioNotification.isEnabled() + ""); + GithubStatusNotification githubStatusNotification = notifications.getGithubStatus(); + bindings.put("notifications.github-status.enabled", githubStatusNotification.isEnabled() + ""); + // Configure canary Canary canary = deploymentConfiguration.getCanary(); bindings.put("canary.atlasWebComponentsUrl", canary.getAtlasWebComponentsUrl()); diff --git a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/OrcaService.java b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/OrcaService.java index ff964c23c5..aead64d01d 100644 --- a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/OrcaService.java +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/OrcaService.java @@ -21,6 +21,7 @@ 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.OrcaProfileFactory; +import com.netflix.spinnaker.halyard.deploy.spinnaker.v1.profile.PluginProfileFactory; import com.netflix.spinnaker.halyard.deploy.spinnaker.v1.profile.Profile; import java.nio.file.Paths; import java.util.HashMap; @@ -39,6 +40,8 @@ public abstract class OrcaService extends SpringService { @Autowired OrcaProfileFactory orcaProfileFactory; + @Autowired PluginProfileFactory pluginProfileFactory; + @Override public SpinnakerArtifact getArtifact() { return SpinnakerArtifact.ORCA; @@ -72,6 +75,14 @@ public List getProfiles( appendReadonlyClouddriver(profile, deploymentConfiguration, endpoints); profiles.add(profile); + + // Plugins + String pluginFilename = "plugins.yml"; + String pluginPath = Paths.get(getConfigOutputPath(), pluginFilename).toString(); + Profile pluginProfile = + pluginProfileFactory.getProfile( + pluginFilename, pluginPath, deploymentConfiguration, endpoints); + profiles.add(pluginProfile); return profiles; } diff --git a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/KubernetesService.java b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/KubernetesService.java new file mode 100644 index 0000000000..229fb6f1be --- /dev/null +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/KubernetesService.java @@ -0,0 +1,62 @@ +/* + * Copyright 2019 Pivotal, 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.deploy.spinnaker.v1.service.distributed.kubernetes; + +import static com.netflix.spinnaker.halyard.config.model.v1.node.DeploymentEnvironment.ImageVariant.SLIM; + +import com.netflix.spinnaker.clouddriver.kubernetes.v1.deploy.KubernetesUtil; +import com.netflix.spinnaker.clouddriver.kubernetes.v1.deploy.description.servergroup.KubernetesImageDescription; +import com.netflix.spinnaker.halyard.config.model.v1.node.DeploymentConfiguration; +import com.netflix.spinnaker.halyard.config.model.v1.node.DeploymentEnvironment.ImageVariant; +import com.netflix.spinnaker.halyard.core.registry.v1.Versions; +import com.netflix.spinnaker.halyard.deploy.services.v1.ArtifactService; +import com.netflix.spinnaker.halyard.deploy.spinnaker.v1.SpinnakerArtifact; + +public interface KubernetesService { + String getDockerRegistry(String deploymentName, SpinnakerArtifact artifact); + + SpinnakerArtifact getArtifact(); + + ArtifactService getArtifactService(); + + default String getArtifactId(DeploymentConfiguration deploymentConfiguration) { + String deploymentName = deploymentConfiguration.getName(); + String artifactName = getArtifact().getName(); + String version = getArtifactService().getArtifactVersion(deploymentName, getArtifact()); + version = Versions.isLocal(version) ? Versions.fromLocal(version) : version; + + ImageVariant imageVariant = + deploymentConfiguration.getDeploymentEnvironment().getImageVariant(); + + final String tag; + if (imageVariant == SLIM) { + // Keep using the variantless tag until `gs://halconfig/versions.yml` only contains + // versions >= 1.16.0 + tag = version; + } else { + tag = String.format("%s-%s", version, imageVariant.toString().toLowerCase()); + } + + KubernetesImageDescription image = + KubernetesImageDescription.builder() + .registry(getDockerRegistry(deploymentName, getArtifact())) + .repository(artifactName) + .tag(tag) + .build(); + return KubernetesUtil.getImageId(image); + } +} diff --git a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1ClouddriverBootstrapService.java b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1ClouddriverBootstrapService.java index 98ab691f7b..144bcb8776 100644 --- a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1ClouddriverBootstrapService.java +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1ClouddriverBootstrapService.java @@ -55,17 +55,13 @@ public Settings buildServiceSettings(DeploymentConfiguration deploymentConfigura String location = kubernetesSharedServiceSettings.getDeployLocation(); settings .setAddress(buildAddress(location)) - .setArtifactId(getArtifactId(deploymentConfiguration.getName())) + .setArtifactId(getArtifactId(deploymentConfiguration)) .setLocation(location) .setMonitored(false) .setEnabled(true); return settings; } - public String getArtifactId(String deploymentName) { - return KubernetesV1DistributedService.super.getArtifactId(deploymentName); - } - final DeployPriority deployPriority = new DeployPriority(10); final boolean requiredToBootstrap = true; } diff --git a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1ClouddriverService.java b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1ClouddriverService.java index 1ca4622059..8db95ae818 100644 --- a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1ClouddriverService.java +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1ClouddriverService.java @@ -63,16 +63,12 @@ public Settings buildServiceSettings(DeploymentConfiguration deploymentConfigura String location = kubernetesSharedServiceSettings.getDeployLocation(); settings .setAddress(buildAddress(location)) - .setArtifactId(getArtifactId(deploymentConfiguration.getName())) + .setArtifactId(getArtifactId(deploymentConfiguration)) .setLocation(location) .setEnabled(true); return settings; } - public String getArtifactId(String deploymentName) { - return KubernetesV1DistributedService.super.getArtifactId(deploymentName); - } - final DeployPriority deployPriority = new DeployPriority(4); final boolean requiredToBootstrap = false; } diff --git a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1DeckService.java b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1DeckService.java index cbcf6ca6a0..41a6f1d3e0 100644 --- a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1DeckService.java +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1DeckService.java @@ -58,16 +58,12 @@ public Settings buildServiceSettings(DeploymentConfiguration deploymentConfigura new KubernetesSharedServiceSettings(deploymentConfiguration); Settings settings = new Settings(deploymentConfiguration.getSecurity().getUiSecurity()); settings - .setArtifactId(getArtifactId(deploymentConfiguration.getName())) + .setArtifactId(getArtifactId(deploymentConfiguration)) .setLocation(kubernetesSharedServiceSettings.getDeployLocation()) .setEnabled(true); return settings; } - public String getArtifactId(String deploymentName) { - return KubernetesV1DistributedService.super.getArtifactId(deploymentName); - } - @Override protected Optional customProfileOutputPath(String profileName) { if (profileName.equals("settings.js") || profileName.equals("settings-local.js")) { diff --git a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1DistributedService.java b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1DistributedService.java index 6ef908a64d..e1497204e7 100644 --- a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1DistributedService.java +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1DistributedService.java @@ -48,14 +48,12 @@ import com.netflix.spinnaker.halyard.core.job.v1.JobRequest; import com.netflix.spinnaker.halyard.core.job.v1.JobStatus; import com.netflix.spinnaker.halyard.core.problem.v1.Problem; -import com.netflix.spinnaker.halyard.core.registry.v1.Versions; import com.netflix.spinnaker.halyard.core.tasks.v1.DaemonTaskHandler; import com.netflix.spinnaker.halyard.deploy.deployment.v1.AccountDeploymentDetails; import com.netflix.spinnaker.halyard.deploy.services.v1.ArtifactService; import com.netflix.spinnaker.halyard.deploy.services.v1.GenerateService; import com.netflix.spinnaker.halyard.deploy.spinnaker.v1.RunningServiceDetails; import com.netflix.spinnaker.halyard.deploy.spinnaker.v1.RunningServiceDetails.Instance; -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.Profile; import com.netflix.spinnaker.halyard.deploy.spinnaker.v1.service.ConfigSource; @@ -66,6 +64,7 @@ import com.netflix.spinnaker.halyard.deploy.spinnaker.v1.service.SpinnakerService; import com.netflix.spinnaker.halyard.deploy.spinnaker.v1.service.distributed.DistributedService; import com.netflix.spinnaker.halyard.deploy.spinnaker.v1.service.distributed.SidecarService; +import com.netflix.spinnaker.halyard.deploy.spinnaker.v1.service.distributed.kubernetes.KubernetesService; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.ContainerStatus; import io.fabric8.kubernetes.api.model.LocalObjectReference; @@ -97,9 +96,8 @@ public interface KubernetesV1DistributedService extends DistributedService, - LogCollector> { - String getDockerRegistry(String deploymentName, SpinnakerArtifact artifact); - + LogCollector>, + KubernetesService { ArtifactService getArtifactService(); ServiceInterfaceFactory getServiceInterfaceFactory(); @@ -126,20 +124,6 @@ default String buildAddress(String namespace) { return Strings.join(".", getServiceName(), namespace); } - default String getArtifactId(String deploymentName) { - String artifactName = getArtifact().getName(); - String version = getArtifactService().getArtifactVersion(deploymentName, getArtifact()); - version = Versions.isLocal(version) ? Versions.fromLocal(version) : version; - - KubernetesImageDescription image = - KubernetesImageDescription.builder() - .registry(getDockerRegistry(deploymentName, getArtifact())) - .repository(artifactName) - .tag(version) - .build(); - return KubernetesUtil.getImageId(image); - } - default List getImagePullSecrets(ServiceSettings settings) { List imagePullSecrets = new ArrayList<>(); if (settings.getKubernetes().getImagePullSecrets() != null) { diff --git a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1EchoService.java b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1EchoService.java index 67a7fc849c..b1dfcc0a48 100644 --- a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1EchoService.java +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1EchoService.java @@ -49,16 +49,12 @@ public Settings buildServiceSettings(DeploymentConfiguration deploymentConfigura String location = kubernetesSharedServiceSettings.getDeployLocation(); settings .setAddress(buildAddress(location)) - .setArtifactId(getArtifactId(deploymentConfiguration.getName())) + .setArtifactId(getArtifactId(deploymentConfiguration)) .setLocation(location) .setEnabled(true); return settings; } - public String getArtifactId(String deploymentName) { - return KubernetesV1DistributedService.super.getArtifactId(deploymentName); - } - final DeployPriority deployPriority = new DeployPriority(0); final boolean requiredToBootstrap = false; } diff --git a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1FiatService.java b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1FiatService.java index c8292c3abd..284c048746 100644 --- a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1FiatService.java +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1FiatService.java @@ -49,16 +49,12 @@ public Settings buildServiceSettings(DeploymentConfiguration deploymentConfigura String location = kubernetesSharedServiceSettings.getDeployLocation(); settings .setAddress(buildAddress(location)) - .setArtifactId(getArtifactId(deploymentConfiguration.getName())) + .setArtifactId(getArtifactId(deploymentConfiguration)) .setLocation(location) .setEnabled(deploymentConfiguration.getSecurity().getAuthz().isEnabled()); return settings; } - public String getArtifactId(String deploymentName) { - return KubernetesV1DistributedService.super.getArtifactId(deploymentName); - } - final DeployPriority deployPriority = new DeployPriority(0); final boolean requiredToBootstrap = false; } diff --git a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1Front50Service.java b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1Front50Service.java index b1e2cd4457..5d9cf284b4 100644 --- a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1Front50Service.java +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1Front50Service.java @@ -63,16 +63,12 @@ public Settings buildServiceSettings(DeploymentConfiguration deploymentConfigura String location = kubernetesSharedServiceSettings.getDeployLocation(); settings .setAddress(buildAddress(location)) - .setArtifactId(getArtifactId(deploymentConfiguration.getName())) + .setArtifactId(getArtifactId(deploymentConfiguration)) .setLocation(location) .setEnabled(true); return settings; } - public String getArtifactId(String deploymentName) { - return KubernetesV1DistributedService.super.getArtifactId(deploymentName); - } - final DeployPriority deployPriority = new DeployPriority(4); final boolean requiredToBootstrap = false; } diff --git a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1GateService.java b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1GateService.java index 2f38b7175f..8220b2a775 100644 --- a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1GateService.java +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1GateService.java @@ -47,16 +47,12 @@ public Settings buildServiceSettings(DeploymentConfiguration deploymentConfigura new KubernetesSharedServiceSettings(deploymentConfiguration); Settings settings = new Settings(deploymentConfiguration.getSecurity().getApiSecurity()); settings - .setArtifactId(getArtifactId(deploymentConfiguration.getName())) + .setArtifactId(getArtifactId(deploymentConfiguration)) .setLocation(kubernetesSharedServiceSettings.getDeployLocation()) .setEnabled(true); return settings; } - public String getArtifactId(String deploymentName) { - return KubernetesV1DistributedService.super.getArtifactId(deploymentName); - } - final DeployPriority deployPriority = new DeployPriority(0); final boolean requiredToBootstrap = false; } diff --git a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1IgorService.java b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1IgorService.java index 43a96f9a0f..207796fbe9 100644 --- a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1IgorService.java +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1IgorService.java @@ -49,16 +49,12 @@ public Settings buildServiceSettings(DeploymentConfiguration deploymentConfigura String location = kubernetesSharedServiceSettings.getDeployLocation(); settings .setAddress(buildAddress(location)) - .setArtifactId(getArtifactId(deploymentConfiguration.getName())) + .setArtifactId(getArtifactId(deploymentConfiguration)) .setLocation(location) .setEnabled(true); return settings; } - public String getArtifactId(String deploymentName) { - return KubernetesV1DistributedService.super.getArtifactId(deploymentName); - } - final DeployPriority deployPriority = new DeployPriority(0); final boolean requiredToBootstrap = false; } diff --git a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1KayentaService.java b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1KayentaService.java index f1e71c9507..0eb92ccf5a 100644 --- a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1KayentaService.java +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1KayentaService.java @@ -61,16 +61,12 @@ public Settings buildServiceSettings(DeploymentConfiguration deploymentConfigura String location = kubernetesSharedServiceSettings.getDeployLocation(); settings .setAddress(buildAddress(location)) - .setArtifactId(getArtifactId(deploymentConfiguration.getName())) + .setArtifactId(getArtifactId(deploymentConfiguration)) .setLocation(location) .setEnabled(deploymentConfiguration.getCanary().isEnabled()); return settings; } - public String getArtifactId(String deploymentName) { - return KubernetesV1DistributedService.super.getArtifactId(deploymentName); - } - final DeployPriority deployPriority = new DeployPriority(0); final boolean requiredToBootstrap = false; } diff --git a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1MonitoringDaemonService.java b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1MonitoringDaemonService.java index d0c1755c13..e3b7b33856 100644 --- a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1MonitoringDaemonService.java +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1MonitoringDaemonService.java @@ -18,10 +18,9 @@ package com.netflix.spinnaker.halyard.deploy.spinnaker.v1.service.distributed.kubernetes.v1; -import com.netflix.spinnaker.clouddriver.kubernetes.v1.deploy.KubernetesUtil; -import com.netflix.spinnaker.clouddriver.kubernetes.v1.deploy.description.servergroup.KubernetesImageDescription; import com.netflix.spinnaker.halyard.config.model.v1.node.DeploymentConfiguration; import com.netflix.spinnaker.halyard.deploy.spinnaker.v1.service.SpinnakerMonitoringDaemonService; +import com.netflix.spinnaker.halyard.deploy.spinnaker.v1.service.distributed.kubernetes.KubernetesService; import com.netflix.spinnaker.halyard.deploy.spinnaker.v1.service.distributed.kubernetes.KubernetesSharedServiceSettings; import lombok.Data; import lombok.EqualsAndHashCode; @@ -32,7 +31,8 @@ @EqualsAndHashCode(callSuper = true) @Component @Data -public class KubernetesV1MonitoringDaemonService extends SpinnakerMonitoringDaemonService { +public class KubernetesV1MonitoringDaemonService extends SpinnakerMonitoringDaemonService + implements KubernetesService { @Delegate @Autowired KubernetesV1DistributedServiceDelegate distributedServiceDelegate; @Override @@ -41,22 +41,9 @@ public Settings buildServiceSettings(DeploymentConfiguration deploymentConfigura new KubernetesSharedServiceSettings(deploymentConfiguration); Settings settings = new Settings(); settings - .setArtifactId(getArtifactId(deploymentConfiguration.getName())) + .setArtifactId(getArtifactId(deploymentConfiguration)) .setLocation(kubernetesSharedServiceSettings.getDeployLocation()) .setEnabled(deploymentConfiguration.getMetricStores().isEnabled()); return settings; } - - private String getArtifactId(String deploymentName) { - String artifactName = getArtifact().getName(); - String version = getArtifactService().getArtifactVersion(deploymentName, getArtifact()); - - KubernetesImageDescription image = - KubernetesImageDescription.builder() - .registry(getDockerRegistry(deploymentName, getArtifact())) - .repository(artifactName) - .tag(version) - .build(); - return KubernetesUtil.getImageId(image); - } } diff --git a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1OrcaBootstrapService.java b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1OrcaBootstrapService.java index f2364da56d..77011c0506 100644 --- a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1OrcaBootstrapService.java +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1OrcaBootstrapService.java @@ -55,17 +55,13 @@ public Settings buildServiceSettings(DeploymentConfiguration deploymentConfigura String location = kubernetesSharedServiceSettings.getDeployLocation(); settings .setAddress(buildAddress(location)) - .setArtifactId(getArtifactId(deploymentConfiguration.getName())) + .setArtifactId(getArtifactId(deploymentConfiguration)) .setLocation(location) .setMonitored(false) .setEnabled(true); return settings; } - public String getArtifactId(String deploymentName) { - return KubernetesV1DistributedService.super.getArtifactId(deploymentName); - } - final DeployPriority deployPriority = new DeployPriority(10); final boolean requiredToBootstrap = true; } diff --git a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1OrcaService.java b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1OrcaService.java index 0920a94139..39ea1f15cc 100644 --- a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1OrcaService.java +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1OrcaService.java @@ -49,16 +49,12 @@ public Settings buildServiceSettings(DeploymentConfiguration deploymentConfigura String location = kubernetesSharedServiceSettings.getDeployLocation(); settings .setAddress(buildAddress(location)) - .setArtifactId(getArtifactId(deploymentConfiguration.getName())) + .setArtifactId(getArtifactId(deploymentConfiguration)) .setLocation(location) .setEnabled(true); return settings; } - public String getArtifactId(String deploymentName) { - return KubernetesV1DistributedService.super.getArtifactId(deploymentName); - } - final DeployPriority deployPriority = new DeployPriority(1); final boolean requiredToBootstrap = false; final boolean stateful = true; diff --git a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1RedisBootstrapService.java b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1RedisBootstrapService.java index 51082b9244..ce0f99860f 100644 --- a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1RedisBootstrapService.java +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1RedisBootstrapService.java @@ -35,6 +35,8 @@ @Data public class KubernetesV1RedisBootstrapService extends RedisBootstrapService implements KubernetesV1DistributedService { + private static final String artifactId = "gcr.io/kubernetes-spinnaker/redis-cluster:v2"; + @Delegate @Autowired KubernetesV1DistributedServiceDelegate distributedServiceDelegate; @Delegate(excludes = HasServiceSettings.class) @@ -50,7 +52,7 @@ public Settings buildServiceSettings(DeploymentConfiguration deploymentConfigura String location = kubernetesSharedServiceSettings.getDeployLocation(); settings .setAddress(buildAddress(location)) - .setArtifactId(getArtifactId(deploymentConfiguration.getName())) + .setArtifactId(artifactId) .setLocation(location) .setSafeToUpdate( true) // It's OK to flush this redis fully since we generally redeploy bootstrap @@ -59,10 +61,6 @@ public Settings buildServiceSettings(DeploymentConfiguration deploymentConfigura return settings; } - public String getArtifactId(String deploymentName) { - return "gcr.io/kubernetes-spinnaker/redis-cluster:v2"; - } - final DeployPriority deployPriority = new DeployPriority(20); final boolean requiredToBootstrap = true; } diff --git a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1RedisService.java b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1RedisService.java index 8d6837b74d..57698342a5 100644 --- a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1RedisService.java +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1RedisService.java @@ -48,6 +48,8 @@ @Data public class KubernetesV1RedisService extends RedisService implements KubernetesV1DistributedService { + private static final String artifactId = "gcr.io/kubernetes-spinnaker/redis-cluster:v2"; + @Delegate @Autowired KubernetesV1DistributedServiceDelegate distributedServiceDelegate; @Delegate(excludes = HasServiceSettings.class) @@ -92,16 +94,12 @@ public Settings buildServiceSettings(DeploymentConfiguration deploymentConfigura String location = kubernetesSharedServiceSettings.getDeployLocation(); settings .setAddress(buildAddress(location)) - .setArtifactId(getArtifactId(deploymentConfiguration.getName())) + .setArtifactId(artifactId) .setLocation(location) .setEnabled(true); return settings; } - public String getArtifactId(String deploymentName) { - return "gcr.io/kubernetes-spinnaker/redis-cluster:v2"; - } - final DeployPriority deployPriority = new DeployPriority(5); final boolean requiredToBootstrap = false; } diff --git a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1RoscoService.java b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1RoscoService.java index e4a060f829..7db8876889 100644 --- a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1RoscoService.java +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v1/KubernetesV1RoscoService.java @@ -49,16 +49,12 @@ public Settings buildServiceSettings(DeploymentConfiguration deploymentConfigura String location = kubernetesSharedServiceSettings.getDeployLocation(); settings .setAddress(buildAddress(location)) - .setArtifactId(getArtifactId(deploymentConfiguration.getName())) + .setArtifactId(getArtifactId(deploymentConfiguration)) .setLocation(location) .setEnabled(true); return settings; } - public String getArtifactId(String deploymentName) { - return KubernetesV1DistributedService.super.getArtifactId(deploymentName); - } - final DeployPriority deployPriority = new DeployPriority(0); final boolean requiredToBootstrap = false; final boolean stateful = true; diff --git a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v2/KubernetesV2DeckService.java b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v2/KubernetesV2DeckService.java index fb01eb0b37..5d2691a5bd 100644 --- a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v2/KubernetesV2DeckService.java +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v2/KubernetesV2DeckService.java @@ -86,7 +86,7 @@ public ServiceSettings buildServiceSettings(DeploymentConfiguration deploymentCo new KubernetesSharedServiceSettings(deploymentConfiguration); ServiceSettings settings = defaultServiceSettings(deploymentConfiguration); settings - .setArtifactId(getArtifactId(deploymentConfiguration.getName())) + .setArtifactId(getArtifactId(deploymentConfiguration)) .setLocation(kubernetesSharedServiceSettings.getDeployLocation()) .setEnabled(true); return settings; diff --git a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v2/KubernetesV2GateService.java b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v2/KubernetesV2GateService.java index b7d05168bf..265dfc1d07 100644 --- a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v2/KubernetesV2GateService.java +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v2/KubernetesV2GateService.java @@ -58,7 +58,7 @@ public ServiceSettings buildServiceSettings(DeploymentConfiguration deploymentCo new KubernetesSharedServiceSettings(deploymentConfiguration); ServiceSettings settings = defaultServiceSettings(deploymentConfiguration); settings - .setArtifactId(getArtifactId(deploymentConfiguration.getName())) + .setArtifactId(getArtifactId(deploymentConfiguration)) .setLocation(kubernetesSharedServiceSettings.getDeployLocation()) .setEnabled(true); return settings; diff --git a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v2/KubernetesV2RedisService.java b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v2/KubernetesV2RedisService.java index 841e6057cc..c19c821003 100644 --- a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v2/KubernetesV2RedisService.java +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v2/KubernetesV2RedisService.java @@ -42,7 +42,7 @@ public boolean runsOnJvm() { return false; } - public String getArtifactId(String deploymentName) { + public String getArtifactId(DeploymentConfiguration deploymentConfiguration) { return "gcr.io/kubernetes-spinnaker/redis-cluster:v2"; } diff --git a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v2/KubernetesV2Service.java b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v2/KubernetesV2Service.java index 489fc19cb8..b7817fe8d3 100644 --- a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v2/KubernetesV2Service.java +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v2/KubernetesV2Service.java @@ -20,24 +20,20 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import com.netflix.spinnaker.clouddriver.kubernetes.v1.deploy.KubernetesUtil; -import com.netflix.spinnaker.clouddriver.kubernetes.v1.deploy.description.servergroup.KubernetesImageDescription; import com.netflix.spinnaker.halyard.config.model.v1.node.AffinityConfig; import com.netflix.spinnaker.halyard.config.model.v1.node.CustomSizing; import com.netflix.spinnaker.halyard.config.model.v1.node.DeploymentConfiguration; import com.netflix.spinnaker.halyard.config.model.v1.node.DeploymentEnvironment; import com.netflix.spinnaker.halyard.config.model.v1.node.SidecarConfig; +import com.netflix.spinnaker.halyard.config.model.v1.node.Toleration; import com.netflix.spinnaker.halyard.config.model.v1.providers.kubernetes.KubernetesAccount; import com.netflix.spinnaker.halyard.core.error.v1.HalException; import com.netflix.spinnaker.halyard.core.problem.v1.Problem; -import com.netflix.spinnaker.halyard.core.registry.v1.Versions; import com.netflix.spinnaker.halyard.core.resource.v1.JinjaJarResource; import com.netflix.spinnaker.halyard.core.resource.v1.TemplatedResource; import com.netflix.spinnaker.halyard.deploy.deployment.v1.AccountDeploymentDetails; import com.netflix.spinnaker.halyard.deploy.deployment.v1.KubernetesManifestExecutor; -import com.netflix.spinnaker.halyard.deploy.services.v1.ArtifactService; import com.netflix.spinnaker.halyard.deploy.services.v1.GenerateService; -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.Profile; import com.netflix.spinnaker.halyard.deploy.spinnaker.v1.service.ConfigSource; @@ -47,6 +43,7 @@ import com.netflix.spinnaker.halyard.deploy.spinnaker.v1.service.SpinnakerMonitoringDaemonService; import com.netflix.spinnaker.halyard.deploy.spinnaker.v1.service.distributed.DistributedService.DeployPriority; import com.netflix.spinnaker.halyard.deploy.spinnaker.v1.service.distributed.SidecarService; +import com.netflix.spinnaker.halyard.deploy.spinnaker.v1.service.distributed.kubernetes.KubernetesService; import com.netflix.spinnaker.halyard.deploy.spinnaker.v1.service.distributed.kubernetes.KubernetesSharedServiceSettings; import com.netflix.spinnaker.halyard.deploy.spinnaker.v1.service.distributed.kubernetes.v2.KubernetesV2Utils.SecretMountPair; import io.fabric8.utils.Strings; @@ -66,17 +63,13 @@ import java.util.stream.Stream; import org.apache.commons.lang3.StringUtils; -public interface KubernetesV2Service extends HasServiceSettings { +public interface KubernetesV2Service extends HasServiceSettings, KubernetesService { String getServiceName(); - String getDockerRegistry(String deploymentName, SpinnakerArtifact artifact); - String getSpinnakerStagingPath(String deploymentName); String getSpinnakerStagingDependenciesPath(String deploymentName); - ArtifactService getArtifactService(); - ServiceSettings defaultServiceSettings(DeploymentConfiguration deploymentConfiguration); ObjectMapper getObjectMapper(); @@ -262,6 +255,7 @@ default String getPodSpecYaml( .addBinding("terminationGracePeriodSeconds", terminationGracePeriodSeconds()) .addBinding("nodeSelector", settings.getKubernetes().getNodeSelector()) .addBinding("affinity", getAffinity(details)) + .addBinding("tolerations", getTolerations(details)) .addBinding( "volumes", combineVolumes(configSources, settings.getKubernetes(), sidecarConfigs)) .addBinding("securityContext", settings.getKubernetes().getSecurityContext()) @@ -678,6 +672,31 @@ default String getAffinity(AccountDeploymentDetails details) } } + default String getTolerations(AccountDeploymentDetails details) { + List toleration = + details + .getDeploymentConfiguration() + .getDeploymentEnvironment() + .getTolerations() + .getOrDefault(getService().getServiceName(), new ArrayList<>()); + + if (toleration.isEmpty()) { + toleration = + details + .getDeploymentConfiguration() + .getDeploymentEnvironment() + .getTolerations() + .getOrDefault(getService().getBaseCanonicalName(), new ArrayList<>()); + } + + try { + return getObjectMapper().writeValueAsString(toleration); + } catch (JsonProcessingException e) { + throw new HalException( + Problem.Severity.FATAL, "Invalid tolerations format: " + e.getMessage(), e); + } + } + default List getInitContainers(AccountDeploymentDetails details) { List initContainersConfig = details @@ -730,20 +749,6 @@ default List getSidecars(SpinnakerRuntimeSettings runtimeSetting return result; } - default String getArtifactId(String deploymentName) { - String artifactName = getArtifact().getName(); - String version = getArtifactService().getArtifactVersion(deploymentName, getArtifact()); - version = Versions.isLocal(version) ? Versions.fromLocal(version) : version; - - KubernetesImageDescription image = - KubernetesImageDescription.builder() - .registry(getDockerRegistry(deploymentName, getArtifact())) - .repository(artifactName) - .tag(version) - .build(); - return KubernetesUtil.getImageId(image); - } - default String buildAddress(String namespace) { return Strings.join(".", getServiceName(), namespace); } @@ -755,7 +760,7 @@ default ServiceSettings buildServiceSettings(DeploymentConfiguration deploymentC String location = kubernetesSharedServiceSettings.getDeployLocation(); settings .setAddress(buildAddress(location)) - .setArtifactId(getArtifactId(deploymentConfiguration.getName())) + .setArtifactId(getArtifactId(deploymentConfiguration)) .setLocation(location) .setEnabled(isEnabled(deploymentConfiguration)); if (runsOnJvm()) { diff --git a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v2/KubernetesV2Utils.java b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v2/KubernetesV2Utils.java index aa0e4d46fb..b6e0e25254 100644 --- a/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v2/KubernetesV2Utils.java +++ b/halyard-deploy/src/main/java/com/netflix/spinnaker/halyard/deploy/spinnaker/v1/service/distributed/kubernetes/v2/KubernetesV2Utils.java @@ -23,7 +23,7 @@ import com.netflix.spinnaker.halyard.config.model.v1.providers.kubernetes.KubernetesAccount; import com.netflix.spinnaker.halyard.core.resource.v1.TemplatedResource; import com.netflix.spinnaker.halyard.core.secrets.v1.SecretSessionManager; -import com.netflix.spinnaker.kork.configserver.ConfigFileService; +import com.netflix.spinnaker.kork.configserver.CloudConfigResourceService; import com.netflix.spinnaker.kork.secrets.EncryptedSecret; import java.io.File; import java.util.ArrayList; @@ -43,12 +43,13 @@ public class KubernetesV2Utils { private final SecretSessionManager secretSessionManager; - private final ConfigFileService configFileService; + private final CloudConfigResourceService cloudConfigResourceService; public KubernetesV2Utils( - SecretSessionManager secretSessionManager, ConfigFileService configFileService) { + SecretSessionManager secretSessionManager, + CloudConfigResourceService cloudConfigResourceService) { this.secretSessionManager = secretSessionManager; - this.configFileService = configFileService; + this.cloudConfigResourceService = cloudConfigResourceService; } public List kubectlPrefix(KubernetesAccount account) { @@ -65,13 +66,7 @@ public List kubectlPrefix(KubernetesAccount account) { command.add(context); } - String kubeconfig; - String kubeconfigFile = account.getKubeconfigFile(); - if (EncryptedSecret.isEncryptedSecret(kubeconfigFile)) { - kubeconfig = secretSessionManager.decryptAsFile(kubeconfigFile); - } else { - kubeconfig = configFileService.getLocalPath(kubeconfigFile); - } + String kubeconfig = getKubeconfigFile(account); if (kubeconfig != null && !kubeconfig.isEmpty()) { command.add("--kubeconfig"); command.add(kubeconfig); @@ -80,6 +75,20 @@ public List kubectlPrefix(KubernetesAccount account) { return command; } + private String getKubeconfigFile(KubernetesAccount account) { + String kubeconfigFile = account.getKubeconfigFile(); + + if (EncryptedSecret.isEncryptedSecret(kubeconfigFile)) { + return secretSessionManager.decryptAsFile(kubeconfigFile); + } + + if (CloudConfigResourceService.isCloudConfigResource(kubeconfigFile)) { + return cloudConfigResourceService.getLocalPath(kubeconfigFile); + } + + return kubeconfigFile; + } + List kubectlPodServiceCommand( KubernetesAccount account, String namespace, String service) { List command = kubectlPrefix(account); diff --git a/halyard-deploy/src/main/resources/kubernetes/manifests/podSpec.yml b/halyard-deploy/src/main/resources/kubernetes/manifests/podSpec.yml index 24d91348d0..0c6e1bd406 100644 --- a/halyard-deploy/src/main/resources/kubernetes/manifests/podSpec.yml +++ b/halyard-deploy/src/main/resources/kubernetes/manifests/podSpec.yml @@ -47,6 +47,10 @@ "affinity": {{ affinity }}, {% endif %} + {% if tolerations %} + "tolerations": {{ tolerations }}, + {% endif %} + {% if securityContext != null %} "securityContext": { {% if securityContext.runAsUser != null %} diff --git a/halyard-deploy/src/test/groovy/com/netflix/spinnaker/halyard/deploy/services/v1/RequestGenerateServiceSpec.groovy b/halyard-deploy/src/test/groovy/com/netflix/spinnaker/halyard/deploy/services/v1/RequestGenerateServiceSpec.groovy index 58f410b40d..5264ac80c6 100644 --- a/halyard-deploy/src/test/groovy/com/netflix/spinnaker/halyard/deploy/services/v1/RequestGenerateServiceSpec.groovy +++ b/halyard-deploy/src/test/groovy/com/netflix/spinnaker/halyard/deploy/services/v1/RequestGenerateServiceSpec.groovy @@ -8,7 +8,7 @@ import java.nio.charset.Charset class RequestGenerateServiceSpec extends Specification { def "Parse valid deployment configuration"() { given: - def generate = new RequestGenerateService(null, null, null, null) + def generate = new RequestGenerateService() def content = """ name: default version: 2.5.2 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 26ab680697..4b1ee52e46 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 @@ -21,6 +21,7 @@ import com.netflix.spinnaker.halyard.config.config.v1.StrictObjectMapper import com.netflix.spinnaker.halyard.config.model.v1.node.AffinityConfig import com.netflix.spinnaker.halyard.config.model.v1.node.DeploymentConfiguration import com.netflix.spinnaker.halyard.config.model.v1.node.SidecarConfig +import com.netflix.spinnaker.halyard.config.model.v1.node.Toleration import com.netflix.spinnaker.halyard.config.model.v1.providers.kubernetes.KubernetesAccount import com.netflix.spinnaker.halyard.deploy.deployment.v1.AccountDeploymentDetails import com.netflix.spinnaker.halyard.deploy.services.v1.GenerateService @@ -373,4 +374,24 @@ class KubernetesV2ServiceTest extends Specification { then: yaml.contains('"podAntiAffinity":{"preferredDuringSchedulingIgnoredDuringExecution":[{"weight":100,"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"test_key","operator":"In","values":["test_value"]}]},"namespaces":["test_namespace"],"topologyKey":"failure-domain.beta.kubernetes.io/zone"}}]}}') } + + def "Can we set PodTolerations"() { + setup: + def executor = Mock(KubernetesV2Executor) + def toleration = new Toleration( + key: "test", + value: "a", + effect: "NoSchedule", + operator: Toleration.Operator.Equal + ) + + details.deploymentConfiguration = new DeploymentConfiguration() + details.deploymentConfiguration.deploymentEnvironment.tolerations.put("spin-orca", Collections.singletonList(toleration)) + + when: + String yaml = testService.getPodSpecYaml(executor, details, config) + + then: + yaml.contains('"tolerations": [{"key":"test","operator":"Equal","value":"a","effect":"NoSchedule"}]') + } } diff --git a/halyard-web/src/main/java/com/netflix/spinnaker/halyard/controllers/v1/PluginsController.java b/halyard-web/src/main/java/com/netflix/spinnaker/halyard/controllers/v1/PluginsController.java new file mode 100644 index 0000000000..d5504edadb --- /dev/null +++ b/halyard-web/src/main/java/com/netflix/spinnaker/halyard/controllers/v1/PluginsController.java @@ -0,0 +1,134 @@ +/* + * 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.plugins.Plugin; +import com.netflix.spinnaker.halyard.config.services.v1.PluginService; +import com.netflix.spinnaker.halyard.core.tasks.v1.DaemonTask; +import com.netflix.spinnaker.halyard.models.v1.ValidationSettings; +import com.netflix.spinnaker.halyard.util.v1.GenericDeleteRequest; +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 java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/v1/config/deployments/{deploymentName:.+}/plugins") +@RequiredArgsConstructor +public class PluginsController { + private final PluginService pluginService; + private final HalconfigDirectoryStructure halconfigDirectoryStructure; + private final HalconfigParser halconfigParser; + + @RequestMapping(value = "/", method = RequestMethod.GET) + DaemonTask> getPlugins( + @PathVariable String deploymentName, @ModelAttribute ValidationSettings validationSettings) { + return GenericGetRequest.>builder() + .getter(() -> pluginService.getAllPlugins(deploymentName)) + .validator(() -> pluginService.validateAllPlugins(deploymentName)) + .description("Get plugins") + .build() + .execute(validationSettings); + } + + @RequestMapping(value = "/{pluginName:.+}", method = RequestMethod.GET) + DaemonTask getPlugin( + @PathVariable String deploymentName, + @PathVariable String pluginName, + @ModelAttribute ValidationSettings validationSettings) { + return GenericGetRequest.builder() + .getter(() -> pluginService.getPlugin(deploymentName, pluginName)) + .validator(() -> pluginService.validatePlugin(deploymentName, pluginName)) + .description("Get the " + pluginName + " plugin") + .build() + .execute(validationSettings); + } + + @RequestMapping(value = "/{pluginName:.+}", method = RequestMethod.PUT) + DaemonTask setPlugin( + @PathVariable String deploymentName, + @PathVariable String pluginName, + @ModelAttribute ValidationSettings validationSettings, + @RequestBody Plugin plugin) { + return GenericUpdateRequest.builder(halconfigParser) + .stagePath(halconfigDirectoryStructure.getStagingPath(deploymentName)) + .updater(t -> pluginService.setPlugin(deploymentName, pluginName, t)) + .validator(() -> pluginService.validatePlugin(deploymentName, pluginName)) + .description("Edit the " + pluginName + " plugin") + .build() + .execute(validationSettings, plugin); + } + + @RequestMapping(value = "/", method = RequestMethod.POST) + DaemonTask addPlugin( + @PathVariable String deploymentName, + @ModelAttribute ValidationSettings validationSettings, + @RequestBody Plugin plugin) { + return GenericUpdateRequest.builder(halconfigParser) + .stagePath(halconfigDirectoryStructure.getStagingPath(deploymentName)) + .updater(t -> pluginService.addPlugin(deploymentName, t)) + .validator(() -> pluginService.validatePlugin(deploymentName, plugin.getName())) + .description("Add the " + plugin.getName() + " plugin") + .build() + .execute(validationSettings, plugin); + } + + @RequestMapping(value = "/{pluginName:.+}", method = RequestMethod.DELETE) + DaemonTask deletePlugin( + @PathVariable String deploymentName, + @PathVariable String pluginName, + @ModelAttribute ValidationSettings validationSettings) { + return GenericDeleteRequest.builder(halconfigParser) + .stagePath(halconfigDirectoryStructure.getStagingPath(deploymentName)) + .deleter(() -> pluginService.deletePlugin(deploymentName, pluginName)) + .validator(() -> pluginService.validateAllPlugins(deploymentName)) + .description("Delete the " + pluginName + " plugin") + .build() + .execute(validationSettings); + } + + @RequestMapping(value = "/enabled", method = RequestMethod.PUT) + DaemonTask setPluginsEnabled( + @PathVariable String deploymentName, + @ModelAttribute ValidationSettings validationSettings, + @RequestBody Boolean enabled) { + return GenericEnableDisableRequest.builder(halconfigParser) + .updater(t -> pluginService.setPluginsEnabled(deploymentName, false, enabled)) + .validator(() -> pluginService.validateAllPlugins(deploymentName)) + .description("Enable or disable plugins") + .build() + .execute(validationSettings, enabled); + } + + @RequestMapping(value = "/downloadingEnabled", method = RequestMethod.PUT) + DaemonTask setPluginsDownloadingEnabled( + @PathVariable String deploymentName, + @ModelAttribute ValidationSettings validationSettings, + @RequestBody Boolean enabled) { + return GenericEnableDisableRequest.builder(halconfigParser) + .updater(t -> pluginService.setPluginsDownloadingEnabled(deploymentName, false, enabled)) + .validator(() -> pluginService.validateAllPlugins(deploymentName)) + .description("Enable or disable downloading plugins") + .build() + .execute(validationSettings, enabled); + } +}