diff --git a/clouddriver-artifacts/clouddriver-artifacts.gradle b/clouddriver-artifacts/clouddriver-artifacts.gradle index b98059708d8..89d6bb56c78 100644 --- a/clouddriver-artifacts/clouddriver-artifacts.gradle +++ b/clouddriver-artifacts/clouddriver-artifacts.gradle @@ -2,6 +2,63 @@ test { useJUnitPlatform() } +class DownloadTask extends DefaultTask { + @Input + String sourceUrl + + @OutputFile + File target + + @TaskAction + void download() { + ant.get(src: sourceUrl, dest: target) + } +} + +final File sdkDownloadLocation = project.file('build/sdkdownload') +final File sdkLocation = project.file('build/oci-java-sdk') + +// Oracle BMCS SDK isn't published to any maven repo (yet!), so we manually download, unpack and add to compile/runtime deps +// https://github.com/oracle/oci-java-sdk/issues/25 +task fetchSdk(type: DownloadTask) { + sourceUrl = 'https://github.com/oracle/oci-java-sdk/releases/download/v1.3.2/oci-java-sdk.zip' + target = sdkDownloadLocation +} + +task unpackSdk(type: Sync) { + dependsOn('fetchSdk') + from zipTree(tasks.fetchSdk.target) + into sdkLocation + include "**/*.jar" + exclude "**/*-sources.jar" + exclude "**/*-javadoc.jar" + exclude "apidocs/**" + exclude "examples/**" + + // Scary but works. I think clouddriver deps in general need cleaning at some point + // Even without the oracle bmc sdk 3rd party deps there's still multiple javax.inject and commons-X JARs + exclude "**/*jackson*.jar" + exclude "**/*jersey*.jar" + exclude "**/hk2*.jar" + exclude "**/*guava*.jar" + exclude "**/commons*.jar" + exclude "**/aopalliance*.jar" + exclude "**/javassist*.jar" + exclude "**/slf*.jar" + exclude "**/osgi*.jar" + exclude "**/validation*.jar" + exclude "**/jsr305*.jar" + exclude "**/json-smart*.jar" + exclude "**/oci-java-sdk-full-shaded-*.jar" +} + +task cleanSdk(type: Delete) { + delete sdkLocation, sdkDownloadLocation +} + +tasks.clean.dependsOn('cleanSdk') +tasks.compileJava.dependsOn('unpackSdk') + dependencies { implementation project(":clouddriver-core") @@ -16,7 +73,6 @@ dependencies { implementation "com.netflix.frigga:frigga" implementation "com.netflix.spinnaker.kork:kork-artifacts" implementation "com.netflix.spinnaker.kork:kork-exceptions" - implementation "com.oracle.oci.sdk:oci-java-sdk-core:1.5.2" implementation "com.squareup.okhttp:okhttp" implementation "com.sun.jersey:jersey-client:1.9.1" implementation "org.apache.commons:commons-compress:1.14" @@ -28,6 +84,8 @@ dependencies { implementation "org.springframework.boot:spring-boot-actuator" implementation "org.springframework.boot:spring-boot-starter-web" + implementation fileTree(sdkLocation) + testImplementation "com.github.tomakehurst:wiremock:latest.release" testImplementation "org.assertj:assertj-core" testImplementation "org.junit-pioneer:junit-pioneer:0.3.0" diff --git a/clouddriver-cloudfoundry/src/main/java/com/netflix/spinnaker/clouddriver/cloudfoundry/security/CloudFoundryCredentials.java b/clouddriver-cloudfoundry/src/main/java/com/netflix/spinnaker/clouddriver/cloudfoundry/security/CloudFoundryCredentials.java index aaeecda1e2d..668df6a39eb 100644 --- a/clouddriver-cloudfoundry/src/main/java/com/netflix/spinnaker/clouddriver/cloudfoundry/security/CloudFoundryCredentials.java +++ b/clouddriver-cloudfoundry/src/main/java/com/netflix/spinnaker/clouddriver/cloudfoundry/security/CloudFoundryCredentials.java @@ -16,7 +16,12 @@ package com.netflix.spinnaker.clouddriver.cloudfoundry.security; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonMap; +import static java.util.stream.Collectors.toList; + import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.netflix.spinnaker.clouddriver.cloudfoundry.client.CloudFoundryApiException; import com.netflix.spinnaker.clouddriver.cloudfoundry.client.CloudFoundryClient; import com.netflix.spinnaker.clouddriver.cloudfoundry.client.HttpCloudFoundryClient; import com.netflix.spinnaker.clouddriver.security.AccountCredentials; @@ -27,7 +32,7 @@ @Slf4j @Getter -@JsonIgnoreProperties({"credentials", "client"}) +@JsonIgnoreProperties({"credentials", "client", "password"}) public class CloudFoundryCredentials implements AccountCredentials { private final String name; @@ -80,6 +85,17 @@ public CloudFoundryClient getClient() { return getCredentials(); } + public Collection> getRegions() { + try { + return getCredentials().getSpaces().all().stream() + .map(space -> singletonMap("name", space.getRegion())) + .collect(toList()); + } catch (CloudFoundryApiException e) { + log.warn("Unable to determine regions for Cloud Foundry account " + name, e); + return emptyList(); + } + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/clouddriver-cloudfoundry/src/main/java/com/netflix/spinnaker/clouddriver/cloudfoundry/security/CloudFoundryCredentialsSynchronizer.java b/clouddriver-cloudfoundry/src/main/java/com/netflix/spinnaker/clouddriver/cloudfoundry/security/CloudFoundryCredentialsSynchronizer.java index 40f2f4abdb7..20213a865f9 100644 --- a/clouddriver-cloudfoundry/src/main/java/com/netflix/spinnaker/clouddriver/cloudfoundry/security/CloudFoundryCredentialsSynchronizer.java +++ b/clouddriver-cloudfoundry/src/main/java/com/netflix/spinnaker/clouddriver/cloudfoundry/security/CloudFoundryCredentialsSynchronizer.java @@ -119,6 +119,7 @@ private List getDeletedAccountNames( CloudFoundryConfigurationProperties cloudFoundryConfigurationProperties) { List existingNames = accountCredentialsRepository.getAll().stream() + .filter(c -> CloudFoundryProvider.PROVIDER_ID.equals(c.getCloudProvider())) .map(AccountCredentials::getName) .collect(Collectors.toList()); diff --git a/clouddriver-cloudfoundry/src/test/java/com/netflix/spinnaker/clouddriver/cloudfoundry/security/CloudFoundryCredentialsSynchronizerTest.java b/clouddriver-cloudfoundry/src/test/java/com/netflix/spinnaker/clouddriver/cloudfoundry/security/CloudFoundryCredentialsSynchronizerTest.java index 728d8672e99..c613c43d3fa 100644 --- a/clouddriver-cloudfoundry/src/test/java/com/netflix/spinnaker/clouddriver/cloudfoundry/security/CloudFoundryCredentialsSynchronizerTest.java +++ b/clouddriver-cloudfoundry/src/test/java/com/netflix/spinnaker/clouddriver/cloudfoundry/security/CloudFoundryCredentialsSynchronizerTest.java @@ -67,12 +67,45 @@ void setUp() { provider, configurationProperties, repository, catsModule, registry); } + private class StaticOtherProviderCredentials implements AccountCredentials { + @Override + public String getName() { + return "unchanged-other-provider"; + } + + @Override + public String getEnvironment() { + return null; + } + + @Override + public String getAccountType() { + return null; + } + + @Override + public Void getCredentials() { + return null; + } + + @Override + public String getCloudProvider() { + return "other"; + } + + @Override + public List getRequiredGroupMembership() { + return null; + } + } + @Test void synchronize() { repository.save("to-be-changed", createCredentials("to-be-changed")); repository.save("unchanged2", createCredentials("unchanged2")); repository.save("unchanged3", createCredentials("unchanged3")); repository.save("to-be-deleted", createCredentials("to-be-deleted")); + repository.save("unchanged-other-provider", new StaticOtherProviderCredentials()); loadProviderFromRepository(); @@ -91,7 +124,8 @@ void synchronize() { assertThat(repository.getAll()) .extracting(AccountCredentials::getName) - .containsExactlyInAnyOrder("unchanged2", "unchanged3", "added", "to-be-changed"); + .containsExactlyInAnyOrder( + "unchanged2", "unchanged3", "added", "to-be-changed", "unchanged-other-provider"); assertThat(ProviderUtils.getScheduledAccounts(provider)) .containsExactlyInAnyOrder("unchanged2", "unchanged3", "added", "to-be-changed"); diff --git a/clouddriver-core/src/main/groovy/com/netflix/spinnaker/clouddriver/jobs/local/JobExecutorLocal.java b/clouddriver-core/src/main/groovy/com/netflix/spinnaker/clouddriver/jobs/local/JobExecutorLocal.java index c117db58748..bac1970dfdf 100644 --- a/clouddriver-core/src/main/groovy/com/netflix/spinnaker/clouddriver/jobs/local/JobExecutorLocal.java +++ b/clouddriver-core/src/main/groovy/com/netflix/spinnaker/clouddriver/jobs/local/JobExecutorLocal.java @@ -87,8 +87,13 @@ private JobResult executeStreaming(JobRequest jobRequest, ReaderConsumer< DefaultExecuteResultHandler resultHandler = new DefaultExecuteResultHandler(); executor.execute(jobRequest.getCommandLine(), jobRequest.getEnvironment(), resultHandler); - T result = - consumer.consume(new BufferedReader(new InputStreamReader(new PipedInputStream(stdOut)))); + T result; + try { + result = + consumer.consume(new BufferedReader(new InputStreamReader(new PipedInputStream(stdOut)))); + } catch (IOException e) { + return JobResult.builder().result(JobResult.Result.FAILURE).error(e.toString()).build(); + } try { resultHandler.waitFor(); diff --git a/clouddriver-ecs/src/main/java/com/netflix/spinnaker/clouddriver/ecs/deploy/ops/CreateServerGroupAtomicOperation.java b/clouddriver-ecs/src/main/java/com/netflix/spinnaker/clouddriver/ecs/deploy/ops/CreateServerGroupAtomicOperation.java index 3cd006c9e07..cf14c212b00 100644 --- a/clouddriver-ecs/src/main/java/com/netflix/spinnaker/clouddriver/ecs/deploy/ops/CreateServerGroupAtomicOperation.java +++ b/clouddriver-ecs/src/main/java/com/netflix/spinnaker/clouddriver/ecs/deploy/ops/CreateServerGroupAtomicOperation.java @@ -267,8 +267,8 @@ private Service createService( String ecsServiceRole, String newServerGroupName, Service sourceService) { - Collection loadBalancers = - retrieveLoadBalancers(EcsServerGroupNameResolver.getEcsContainerName(newServerGroupName)); + + String taskDefinitionArn = taskDefinition.getTaskDefinitionArn(); Integer desiredCount = description.getCapacity().getDesired(); if (sourceService != null @@ -278,6 +278,38 @@ private Service createService( desiredCount = sourceService.getDesiredCount(); } + CreateServiceRequest request = + makeServiceRequest(taskDefinitionArn, ecsServiceRole, newServerGroupName, desiredCount); + + updateTaskStatus( + String.format( + "Creating %s of %s with %s for %s.", + desiredCount, + newServerGroupName, + taskDefinitionArn, + description.getCredentialAccount())); + + Service service = ecs.createService(request).getService(); + + updateTaskStatus( + String.format( + "Done creating %s of %s with %s for %s.", + desiredCount, + newServerGroupName, + taskDefinitionArn, + description.getCredentialAccount())); + + return service; + } + + protected CreateServiceRequest makeServiceRequest( + String taskDefinitionArn, + String ecsServiceRole, + String newServerGroupName, + Integer desiredCount) { + Collection loadBalancers = + retrieveLoadBalancers(EcsServerGroupNameResolver.getEcsContainerName(newServerGroupName)); + Collection serviceRegistries = new LinkedList<>(); if (description.getServiceDiscoveryAssociations() != null) { for (CreateServerGroupDescription.ServiceDiscoveryAssociation config : @@ -300,8 +332,6 @@ private Service createService( } } - String taskDefinitionArn = taskDefinition.getTaskDefinitionArn(); - DeploymentConfiguration deploymentConfiguration = new DeploymentConfiguration().withMinimumHealthyPercent(100).withMaximumPercent(200); @@ -325,7 +355,11 @@ private Service createService( request.withTags(taskDefTags).withEnableECSManagedTags(true).withPropagateTags("SERVICE"); } - if (!AWSVPC_NETWORK_MODE.equals(description.getNetworkMode())) { + // Load balancer management role: + // N/A for non-load-balanced services + // Services using awsvpc mode must not specify a role in order to use the + // ECS service-linked role + if (!AWSVPC_NETWORK_MODE.equals(description.getNetworkMode()) && !loadBalancers.isEmpty()) { request.withRole(ecsServiceRole); } @@ -367,25 +401,7 @@ private Service createService( request.withHealthCheckGracePeriodSeconds(description.getHealthCheckGracePeriodSeconds()); } - updateTaskStatus( - String.format( - "Creating %s of %s with %s for %s.", - desiredCount, - newServerGroupName, - taskDefinitionArn, - description.getCredentialAccount())); - - Service service = ecs.createService(request).getService(); - - updateTaskStatus( - String.format( - "Done creating %s of %s with %s for %s.", - desiredCount, - newServerGroupName, - taskDefinitionArn, - description.getCredentialAccount())); - - return service; + return request; } private String registerAutoScalingGroup( diff --git a/clouddriver-ecs/src/test/groovy/com/netflix/spinnaker/clouddriver/ecs/deploy/ops/CreateServerGroupAtomicOperationSpec.groovy b/clouddriver-ecs/src/test/groovy/com/netflix/spinnaker/clouddriver/ecs/deploy/ops/CreateServerGroupAtomicOperationSpec.groovy index 805b6dc849e..9f6bf345eca 100644 --- a/clouddriver-ecs/src/test/groovy/com/netflix/spinnaker/clouddriver/ecs/deploy/ops/CreateServerGroupAtomicOperationSpec.groovy +++ b/clouddriver-ecs/src/test/groovy/com/netflix/spinnaker/clouddriver/ecs/deploy/ops/CreateServerGroupAtomicOperationSpec.groovy @@ -129,7 +129,35 @@ class CreateServerGroupAtomicOperationSpec extends CommonAtomicOperation { iamClient.getRole(_) >> new GetRoleResult().withRole(role) iamPolicyReader.getTrustedEntities(_) >> trustRelationships loadBalancingV2.describeTargetGroups(_) >> new DescribeTargetGroupsResult().withTargetGroups(targetGroup) - ecs.createService(_) >> new CreateServiceResult().withService(service) + ecs.createService({ CreateServiceRequest request -> + request.cluster == 'test-cluster' + request.serviceName == 'myapp-kcats-liated-v008' + request.taskDefinition == 'task-def-arn' + request.loadBalancers.size() == 1 + request.loadBalancers.get(0).targetGroupArn == 'target-group-arn' + request.loadBalancers.get(0).containerName == 'v008' + request.loadBalancers.get(0).containerPort == 1337 + request.serviceRegistries == [] + request.desiredCount == 3 + request.role == 'arn:aws:iam::test:test-role' + request.placementConstraints.size() == 1 + request.placementConstraints.get(0).type == 'memberOf' + request.placementConstraints.get(0).expression == 'attribute:ecs.instance-type =~ t2.*' + request.placementStrategy.size() == 1 + request.placementStrategy.get(0).type == 'spread' + request.placementStrategy.get(0).field == 'attribute:ecs.availability-zone' + request.networkConfiguration == null + request.healthCheckGracePeriodSeconds == null + request.enableECSManagedTags == true + request.propagateTags == 'SERVICE' + request.tags.size() == 2 + request.tags.get(0).key == 'label1' + request.tags.get(0).value == 'value1' + request.tags.get(1).key == 'fruit' + request.tags.get(1).value == 'tomato' + request.launchType == null + request.platformVersion == null + }) >> new CreateServiceResult().withService(service) result.getServerGroupNames().size() == 1 result.getServerGroupNameByRegion().size() == 1 @@ -233,21 +261,32 @@ class CreateServerGroupAtomicOperationSpec extends CommonAtomicOperation { loadBalancingV2.describeTargetGroups(_) >> new DescribeTargetGroupsResult().withTargetGroups(targetGroup) ecs.createService({ CreateServiceRequest request -> - request.networkConfiguration.awsvpcConfiguration.subnets == ['subnet-12345'] - request.networkConfiguration.awsvpcConfiguration.securityGroups == ['sg-12345'] - request.networkConfiguration.awsvpcConfiguration.assignPublicIp == 'ENABLED' - request.role == null - request.launchType == 'FARGATE' - request.platformVersion == '1.0.0' - request.placementStrategy == [] - request.placementConstraints == [] - request.desiredCount == 1 + request.cluster == 'test-cluster' + request.serviceName == 'myapp-kcats-liated-v008' + request.taskDefinition == 'task-def-arn' + request.loadBalancers.size() == 1 + request.loadBalancers.get(0).targetGroupArn == 'target-group-arn' + request.loadBalancers.get(0).containerName == 'v008' + request.loadBalancers.get(0).containerPort == 1337 request.serviceRegistries.size() == 1 request.serviceRegistries.get(0) == new ServiceRegistry( registryArn: 'srv-registry-arn', containerPort: 9090, containerName: 'v008' ) + request.desiredCount == 1 + request.role == null + request.placementStrategy == [] + request.placementConstraints == [] + request.networkConfiguration.awsvpcConfiguration.subnets == ['subnet-12345'] + request.networkConfiguration.awsvpcConfiguration.securityGroups == ['sg-12345'] + request.networkConfiguration.awsvpcConfiguration.assignPublicIp == 'ENABLED' + request.healthCheckGracePeriodSeconds == null + request.enableECSManagedTags == null + request.propagateTags == null + request.tags == [] + request.launchType == 'FARGATE' + request.platformVersion == '1.0.0' } as CreateServiceRequest) >> new CreateServiceResult().withService(service) result.getServerGroupNames().size() == 1 @@ -257,6 +296,28 @@ class CreateServerGroupAtomicOperationSpec extends CommonAtomicOperation { result.getServerGroupNameByRegion().get('us-west-1').contains(serviceName + "-v008") } + def 'should create services without load balancers'() { + given: + def description = Mock(CreateServerGroupDescription) + + description.getApplication() >> 'mygreatapp' + description.getStack() >> 'stack1' + description.getFreeFormDetails() >> 'details2' + description.getTargetGroup() >> null + + def operation = new CreateServerGroupAtomicOperation(description) + + when: + def request = operation.makeServiceRequest('task-def-arn', + 'arn:aws:iam::test:test-role', + 'mygreatapp-stack1-details2-v0011', + 1) + + then: + request.getLoadBalancers() == [] + request.getRole() == null + } + def 'should create default Docker labels'() { given: def description = Mock(CreateServerGroupDescription) diff --git a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/description/manifest/KubernetesKind.java b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/description/manifest/KubernetesKind.java index f94f04f541d..ab2f343c2ae 100644 --- a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/description/manifest/KubernetesKind.java +++ b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/description/manifest/KubernetesKind.java @@ -82,7 +82,7 @@ public final class KubernetesKind { public static KubernetesKind PERSISTENT_VOLUME_CLAIM = new KubernetesKind("persistentVolumeClaim", KubernetesApiGroup.CORE, "pvc", true, false); public static KubernetesKind POD = - new KubernetesKind("pod", KubernetesApiGroup.CORE, "po", true, true); + new KubernetesKind("pod", KubernetesApiGroup.CORE, "po", true, false); public static KubernetesKind POD_PRESET = new KubernetesKind("podPreset", KubernetesApiGroup.SETTINGS_K8S_IO, null, true, false); public static KubernetesKind POD_SECURITY_POLICY = diff --git a/clouddriver-oracle/clouddriver-oracle.gradle b/clouddriver-oracle/clouddriver-oracle.gradle index a9459c07090..b6cf83d02b3 100644 --- a/clouddriver-oracle/clouddriver-oracle.gradle +++ b/clouddriver-oracle/clouddriver-oracle.gradle @@ -1,8 +1,68 @@ + +class DownloadTask extends DefaultTask { + @Input + String sourceUrl + + @OutputFile + File target + + @TaskAction + void download() { + ant.get(src: sourceUrl, dest: target) + } +} + +final File sdkDownloadLocation = project.file('build/sdkdownload') +final File sdkLocation = project.file('build/oci-java-sdk') + +// Oracle BMCS SDK isn't published to any maven repo (yet!), so we manually download, unpack and add to compile/runtime deps +// https://github.com/oracle/oci-java-sdk/issues/25 +task fetchSdk(type: DownloadTask) { + sourceUrl = 'https://github.com/oracle/oci-java-sdk/releases/download/v1.3.2/oci-java-sdk.zip' + target = sdkDownloadLocation +} + +task unpackSdk(type: Sync) { + dependsOn('fetchSdk') + from zipTree(tasks.fetchSdk.target) + into sdkLocation + include "**/*.jar" + exclude "**/*-sources.jar" + exclude "**/*-javadoc.jar" + exclude "apidocs/**" + exclude "examples/**" + + // Scary but works. I think clouddriver deps in general need cleaning at some point + // Even without the oracle bmc sdk 3rd party deps there's still multiple javax.inject and commons-X JARs + exclude "**/*jackson*.jar" + exclude "**/*jersey*.jar" + exclude "**/hk2*.jar" + exclude "**/*guava*.jar" + exclude "**/commons*.jar" + exclude "**/aopalliance*.jar" + exclude "**/javassist*.jar" + exclude "**/slf*.jar" + exclude "**/osgi*.jar" + exclude "**/validation*.jar" + exclude "**/jsr305*.jar" + exclude "**/json-smart*.jar" + exclude "**/oci-java-sdk-full-shaded-*.jar" +} + +task cleanSdk(type: Delete) { + delete sdkLocation, sdkDownloadLocation +} + +tasks.clean.dependsOn('cleanSdk') +tasks.compileJava.dependsOn('unpackSdk') + dependencies { implementation project(":clouddriver-core") implementation project(":cats:cats-core") implementation project(":clouddriver-security") + implementation fileTree(sdkLocation) + compileOnly "org.projectlombok:lombok" annotationProcessor "org.projectlombok:lombok" testAnnotationProcessor "org.projectlombok:lombok" @@ -13,10 +73,6 @@ dependencies { implementation "com.netflix.spinnaker.fiat:fiat-api:$fiatVersion" implementation "com.netflix.spinnaker.fiat:fiat-core:$fiatVersion" implementation "com.netflix.spinnaker.moniker:moniker" - implementation "com.oracle.oci.sdk:oci-java-sdk-core:1.5.2" - implementation "com.oracle.oci.sdk:oci-java-sdk-identity:1.5.2" - implementation "com.oracle.oci.sdk:oci-java-sdk-loadbalancer:1.5.2" - implementation "com.oracle.oci.sdk:oci-java-sdk-objectstorage:1.5.2" implementation "org.codehaus.groovy:groovy-all" implementation "org.springframework.boot:spring-boot-starter-actuator" implementation "org.springframework.boot:spring-boot-starter-web" diff --git a/clouddriver-web/clouddriver-web.gradle b/clouddriver-web/clouddriver-web.gradle index 7ad599df0ae..e56a2b8c327 100644 --- a/clouddriver-web/clouddriver-web.gradle +++ b/clouddriver-web/clouddriver-web.gradle @@ -1,5 +1,10 @@ apply plugin: 'spinnaker.application' +if (gradle.includedCloudProviderProjects.contains(':clouddriver-oracle')) { + tasks.startScripts.dependsOn(':clouddriver-oracle:unpackSdk') + tasks.startScripts.mustRunAfter(':clouddriver-oracle:unpackSdk') +} + ext { springConfigLocation = System.getProperty('spring.config.additional-location', "${System.getProperty('user.home')}/.spinnaker/") } diff --git a/gradle.properties b/gradle.properties index dff27cffc7c..89ade02c760 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ -#Mon Jun 03 21:49:34 UTC 2019 -includeCloudProviders=all +#Wed Jun 05 20:00:19 UTC 2019 fiatVersion=1.0.4 +includeCloudProviders=all enablePublishing=false -korkVersion=5.4.4 spinnakerGradleVersion=6.5.0 +korkVersion=5.4.6 org.gradle.parallel=true