diff:
@@ -83,10 +83,8 @@ class GCEUtil {
}
}
- static Image queryImage(String projectName,
- String imageName,
+ static Image queryImage(String imageName,
GoogleNamedAccountCredentials credentials,
- Compute compute,
Task task,
String phase,
String clouddriverUserAgentApplicationName,
@@ -95,10 +93,10 @@ class GCEUtil {
task.updateStatus phase, "Looking up image $imageName..."
def filter = "name eq $imageName"
- def imageProjects = [projectName] + credentials?.imageProjects + baseImageProjects - null
+ def imageProjects = [credentials.project] + credentials?.imageProjects + baseImageProjects - null
def sourceImage = null
- def imageListBatch = new GoogleBatchRequest(compute, clouddriverUserAgentApplicationName)
+ def imageListBatch = new GoogleBatchRequest(credentials.compute, clouddriverUserAgentApplicationName)
def imageListCallback = new JsonBatchCallback<ImageList>() {
@Override
void onFailure(GoogleJsonError e, HttpHeaders responseHeaders) throws IOException {
@@ -114,7 +112,7 @@ class GCEUtil {
}
imageProjects.each { imageProject ->
- def imagesList = compute.images().list(imageProject)
+ def imagesList = credentials.compute.images().list(imageProject)
imagesList.setFilter(filter)
imageListBatch.queue(imagesList, imageListCallback)
}
@@ -176,10 +174,8 @@ class GCEUtil {
executor
)
} else {
- return queryImage(description.credentials.project,
- description.image,
+ return queryImage(description.image,
description.credentials,
- description.credentials.compute,
task,
phase,
clouddriverUserAgentApplicationName,
@@ -799,10 +795,8 @@ class GCEUtil {
sourceImage =
disk.is(firstPersistentDisk)
? bootImage
- : queryImage(credentials.project,
- disk.sourceImage,
+ : queryImage(disk.sourceImage,
credentials,
- credentials.compute,
task,
phase,
clouddriverUserAgentApplicationName,

diff:
@@ -22,7 +22,6 @@ import com.netflix.spinnaker.clouddriver.data.task.TaskRepository
import com.netflix.spinnaker.clouddriver.google.config.GoogleConfigurationProperties
import com.netflix.spinnaker.clouddriver.google.deploy.GCEUtil
import com.netflix.spinnaker.clouddriver.google.deploy.description.UpsertGoogleImageTagsDescription
-import com.netflix.spinnaker.clouddriver.orchestration.AtomicOperation
import org.springframework.beans.factory.annotation.Autowired
/**
@@ -62,10 +61,8 @@ class UpsertGoogleImageTagsAtomicOperation extends GoogleAtomicOperation<Void> {
def project = credentials.project
def imageName = description.imageName
def tags = description.tags
- def image = GCEUtil.queryImage(project,
- imageName,
+ def image = GCEUtil.queryImage(imageName,
credentials,
- compute,
task,
BASE_PHASE,
clouddriverUserAgentApplicationName,

diff:
@@ -39,6 +39,7 @@ package com.netflix.spinnaker.clouddriver.google.deploy
import com.netflix.spinnaker.clouddriver.google.model.loadbalancing.GoogleHttpLoadBalancer
import com.netflix.spinnaker.clouddriver.google.model.loadbalancing.GoogleNetworkLoadBalancer
import com.netflix.spinnaker.clouddriver.google.provider.view.GoogleLoadBalancerProvider
+ import com.netflix.spinnaker.clouddriver.google.security.FakeGoogleCredentials
import com.netflix.spinnaker.clouddriver.google.security.GoogleNamedAccountCredentials
import com.netflix.spinnaker.kork.artifacts.model.Artifact
import spock.lang.Shared
@@ -92,6 +93,10 @@ package com.netflix.spinnaker.clouddriver.google.deploy
new GoogleCredential.Builder().setTransport(httpTransport).setJsonFactory(jsonFactory).build()
def compute = new Compute.Builder(
httpTransport, jsonFactory, httpRequestInitializer).setApplicationName(GOOGLE_APPLICATION_NAME).build()
+ GoogleNamedAccountCredentials credentials = new GoogleNamedAccountCredentials.Builder().project(PROJECT_NAME)
+ .compute(compute)
+ .credentials(new FakeGoogleCredentials(PROJECT_NAME))
+ .build();
def soughtImage = new Image(name: IMAGE_NAME)
def imageList = new ImageList(
selfLink: "https://compute.googleapis.com/compute/alpha/projects/$PROJECT_NAME/global/images",
@@ -99,7 +104,7 @@ package com.netflix.spinnaker.clouddriver.google.deploy
)
when:
- def sourceImage = GCEUtil.queryImage(PROJECT_NAME, IMAGE_NAME, null, compute, taskMock, PHASE, GOOGLE_APPLICATION_NAME, BASE_IMAGE_PROJECTS, executorMock)
+ def sourceImage = GCEUtil.queryImage(IMAGE_NAME, credentials, taskMock, PHASE, GOOGLE_APPLICATION_NAME, BASE_IMAGE_PROJECTS, executorMock)
then:
1 * executorMock.timeExecuteBatch(_, "findImage", _) >> {
@@ -124,7 +129,7 @@ package com.netflix.spinnaker.clouddriver.google.deploy
new GoogleCredential.Builder().setTransport(httpTransport).setJsonFactory(jsonFactory).build()
def compute = new Compute.Builder(
httpTransport, jsonFactory, httpRequestInitializer).setApplicationName(GOOGLE_APPLICATION_NAME).build()
- def credentials = new GoogleNamedAccountCredentials.Builder().compute(compute).imageProjects([IMAGE_PROJECT_NAME]).build()
+ def credentials = new GoogleNamedAccountCredentials.Builder().project(PROJECT_NAME).compute(compute).imageProjects([IMAGE_PROJECT_NAME]).build()
def soughtImage = new Image(name: IMAGE_NAME)
def imageList = new ImageList(
selfLink: "https://compute.googleapis.com/compute/alpha/projects/$PROJECT_NAME/global/images",
@@ -132,7 +137,7 @@ package com.netflix.spinnaker.clouddriver.google.deploy
)
when:
- def sourceImage = GCEUtil.queryImage(PROJECT_NAME, IMAGE_NAME, credentials, compute, taskMock, PHASE, GOOGLE_APPLICATION_NAME, BASE_IMAGE_PROJECTS, executorMock)
+ def sourceImage = GCEUtil.queryImage(IMAGE_NAME, credentials, taskMock, PHASE, GOOGLE_APPLICATION_NAME, BASE_IMAGE_PROJECTS, executorMock)
then:
1 * executorMock.timeExecuteBatch(_, "findImage", _) >> {
@@ -157,10 +162,14 @@ package com.netflix.spinnaker.clouddriver.google.deploy
new GoogleCredential.Builder().setTransport(httpTransport).setJsonFactory(jsonFactory).build()
def compute = new Compute.Builder(
httpTransport, jsonFactory, httpRequestInitializer).setApplicationName(GOOGLE_APPLICATION_NAME).build()
+ GoogleNamedAccountCredentials credentials = new GoogleNamedAccountCredentials.Builder().project(PROJECT_NAME)
+ .compute(compute)
+ .credentials(new FakeGoogleCredentials(PROJECT_NAME))
+ .build();
def emptyImageList = new ImageList()
when:
- GCEUtil.queryImage(PROJECT_NAME, IMAGE_NAME, null, compute, taskMock, PHASE, GOOGLE_APPLICATION_NAME, BASE_IMAGE_PROJECTS, executorMock)
+ GCEUtil.queryImage(IMAGE_NAME, credentials, taskMock, PHASE, GOOGLE_APPLICATION_NAME, BASE_IMAGE_PROJECTS, executorMock)
then:
1 * executorMock.timeExecuteBatch(_, "findImage", _) >> {
@@ -247,10 +256,8 @@ package com.netflix.spinnaker.clouddriver.google.deploy
executor)
then:
- 1 * GCEUtil.queryImage(PROJECT_NAME,
- IMAGE_NAME,
+ 1 * GCEUtil.queryImage(IMAGE_NAME,
credentials,
- compute,
taskMock,
PHASE,
GOOGLE_APPLICATION_NAME,

original_message: refactor(gce): Remove some redundant arguments from GCEUtil.queryImage (#3624)


change_type: MODIFY

diff:
@@ -2,63 +2,6 @@ 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")
@@ -73,6 +16,7 @@ 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"
@@ -84,8 +28,6 @@ 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:
-
-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"
@@ -73,6 +13,10 @@ 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:
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/")
}

license: Apache License 2.0

repo: spinnaker/clouddriver

original_message: chore(clouddriver/oracle): Get the Oracle API jar from Maven. (#3695)


diff:
@@ -523,7 +523,8 @@ public class Applications {
.map(Build::getState)
.orElse(Build.State.FAILED)) {
case FAILED:
- throw new CloudFoundryApiException("Failed to build droplet");
+ throw new CloudFoundryApiException(
+ "Failed to build droplet or there are not enough resources available");
case STAGED:
return true;
default:

original_message: chore(cf): additional info when droplet fails (#3700)


message:
fix(gce): fix waiting on regional operations from the new MIG compute wrapper
I tested this locally with a ZonalOperation and fixed it there. I'm not
sure why it didn't occur to me to also make the same fix in
RegionalOperation.

diff:
@@ -26,7 +26,7 @@ class RegionalOperation implements WaitableComputeOperation {
return poller.waitForRegionalOperation(
credentials.getCompute(),
credentials.getProject(),
- operation.getRegion(),
+ GCEUtil.getLocalName(operation.getRegion()),
operation.getName(),
/* timeoutSeconds= */ null,
task,

original_message:
fix(gce): fix waiting on regional operations from the new MIG compute wrapper (#3718)

I tested this locally with a ZonalOperation and fixed it there. I'm not
sure why it didn't occur to me to also make the same fix in
RegionalOperation.


diff:
@@ -26,6 +26,7 @@ import com.netflix.spinnaker.clouddriver.kubernetes.v2.description.manifest.Kube
import com.netflix.spinnaker.clouddriver.kubernetes.v2.description.manifest.KubernetesManifest;
import com.netflix.spinnaker.clouddriver.kubernetes.v2.description.manifest.KubernetesManifestAnnotater;
import com.netflix.spinnaker.clouddriver.kubernetes.v2.description.manifest.KubernetesManifestStrategy;
+import com.netflix.spinnaker.clouddriver.kubernetes.v2.op.OperationResult;
import com.netflix.spinnaker.clouddriver.kubernetes.v2.op.handler.KubernetesHandler;
import com.netflix.spinnaker.clouddriver.kubernetes.v2.security.KubernetesV2Credentials;
import com.netflix.spinnaker.clouddriver.model.ArtifactProvider;
@@ -41,7 +42,7 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
@Slf4j
-public class KubernetesCleanupArtifactsOperation implements AtomicOperation<Void> {
+public class KubernetesCleanupArtifactsOperation implements AtomicOperation<OperationResult> {
private final KubernetesCleanupArtifactsDescription description;
private final KubernetesV2Credentials credentials;
private final String accountName;
@@ -65,7 +66,9 @@ public class KubernetesCleanupArtifactsOperation implements AtomicOperation<Void
}
@Override
- public Void operate(List priorOutputs) {
+ public OperationResult operate(List priorOutputs) {
+ OperationResult result = new OperationResult();
+
List<Artifact> artifacts =
description.getManifests().stream()
.map(this::artifactsToDelete)
@@ -93,11 +96,12 @@ public class KubernetesCleanupArtifactsOperation implements AtomicOperation<Void
if (StringUtils.isNotEmpty(a.getVersion())) {
name = String.join("-", name, a.getVersion());
}
- // todo add to outputs
- handler.delete(credentials, a.getLocation(), name, null, new V1DeleteOptions());
+ result.merge(
+ handler.delete(credentials, a.getLocation(), name, null, new V1DeleteOptions()));
});
- return null;
+ result.setManifests(null);
+ return result;
}
private List<Artifact> artifactsToDelete(KubernetesManifest manifest) {

original_message: fix(kubernetes): add cleaned up artifact manifests to output (#3711)


diff:
@@ -27,7 +27,7 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j
@Getter
-@JsonIgnoreProperties({"credentials", "client"})
+@JsonIgnoreProperties({"credentials", "client", "password"})
public class CloudFoundryCredentials implements AccountCredentials<CloudFoundryClient> {
private final String name;

original_message: fix(cf): remove sensitive information from credentials payload (#3752)


diff:
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;
@@ -80,6 +85,17 @@ public class CloudFoundryCredentials implements AccountCredentials<CloudFoundryC
return getCredentials();
}
+ public Collection<Map<String, String>> 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;

original_message: fix(cf): regions must be associated with accounts (#3760)


diff:
@@ -53,9 +53,11 @@ public class DestroyCloudFoundryServiceAtomicOperation
+ description.getSpace().getName());
LastOperation.State state = response.getState();
if (state == NOT_FOUND) {
- task.updateStatus(
- PHASE,
- "Finished removing service instance '" + description.getServiceInstanceName() + "'");
+ throw new RuntimeException(
+ "Service instance "
+ + description.getServiceInstanceName()
+ + " not found, in "
+ + description.getSpace().getRegion());
}
return response;
}

diff:
@@ -19,6 +19,7 @@ package com.netflix.spinnaker.clouddriver.cloudfoundry.deploy.ops;
import static com.netflix.spinnaker.clouddriver.cloudfoundry.client.model.v2.LastOperation.State.IN_PROGRESS;
import static com.netflix.spinnaker.clouddriver.cloudfoundry.client.model.v2.LastOperation.State.NOT_FOUND;
import static com.netflix.spinnaker.clouddriver.cloudfoundry.client.model.v2.LastOperation.Type.DELETE;
+import static com.netflix.spinnaker.clouddriver.cloudfoundry.utils.TestUtils.assertThrows;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.atIndex;
import static org.mockito.Matchers.any;
@@ -26,6 +27,7 @@ import static org.mockito.Mockito.when;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.model.ServiceInstanceResponse;
import com.netflix.spinnaker.clouddriver.cloudfoundry.deploy.description.DestroyCloudFoundryServiceDescription;
+import com.netflix.spinnaker.clouddriver.cloudfoundry.model.CloudFoundryOrganization;
import com.netflix.spinnaker.clouddriver.cloudfoundry.model.CloudFoundrySpace;
import com.netflix.spinnaker.clouddriver.data.task.Task;
import java.util.List;
@@ -35,12 +37,18 @@ class DestroyCloudFoundryServiceAtomicOperationTest
extends AbstractCloudFoundryAtomicOperationTest {
private DestroyCloudFoundryServiceDescription desc = new DestroyCloudFoundryServiceDescription();
- @Test
- void destroyCloudFoundryService() {
+ {
desc.setServiceInstanceName("service-instance-name");
- desc.setSpace(CloudFoundrySpace.builder().name("space-name").build());
+ desc.setSpace(
+ CloudFoundrySpace.builder()
+ .name("space-name")
+ .organization(CloudFoundryOrganization.builder().name("org-name").build())
+ .build());
desc.setClient(client);
+ }
+ @Test
+ void destroyCloudFoundryService() {
ServiceInstanceResponse serviceInstanceResponse =
new ServiceInstanceResponse()
.setServiceInstanceName("service-instance-name")
@@ -67,17 +75,37 @@ class DestroyCloudFoundryServiceAtomicOperationTest
atIndex(1));
}
+ @Test
+ void destroyCloudFoundryServiceFailsWhenInstanceNotFound() {
+ ServiceInstanceResponse serviceInstanceResponse =
+ new ServiceInstanceResponse()
+ .setServiceInstanceName("service-instance-name")
+ .setType(DELETE)
+ .setState(NOT_FOUND);
+
+ when(client.getServiceInstances().destroyServiceInstance(any(), any()))
+ .thenReturn(serviceInstanceResponse);
+
+ DestroyCloudFoundryServiceAtomicOperation op =
+ new DestroyCloudFoundryServiceAtomicOperation(desc);
+
+ assertThrows(
+ () -> runOperation(op),
+ RuntimeException.class,
+ "Service instance "
+ + desc.getServiceInstanceName()
+ + " not found, in "
+ + desc.getSpace().getRegion());
+ }
+
@Test
void destroyUserProvidedService() {
desc.setServiceInstanceName("up-service-instance-name");
- desc.setSpace(CloudFoundrySpace.builder().name("space-name").build());
- desc.setClient(client);
-
ServiceInstanceResponse serviceInstanceResponse =
new ServiceInstanceResponse()
.setServiceInstanceName("up-service-instance-name")
.setType(DELETE)
- .setState(NOT_FOUND);
+ .setState(IN_PROGRESS);
when(client.getServiceInstances().destroyServiceInstance(any(), any()))
.thenReturn(serviceInstanceResponse);
@@ -96,7 +124,6 @@ class DestroyCloudFoundryServiceAtomicOperationTest
.has(
status(
"Started removing service instance 'up-service-instance-name' from space space-name"),
- atIndex(1))
- .has(status("Finished removing service instance 'up-service-instance-name'"), atIndex(2));
+ atIndex(1));
}
}

original_message: fix(cf): fail destoryService stage if service not found (#3779)


diff:
@@ -31,22 +31,18 @@ import com.netflix.spinnaker.clouddriver.google.GoogleCloudProvider;
import com.netflix.spinnaker.clouddriver.google.cache.Keys;
import groovy.util.logging.Slf4j;
import java.io.IOException;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
-import java.util.Set;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
-import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@@ -73,31 +69,41 @@ public class GoogleNamedImageLookupController {
@RequestParam(required = false) String q,
@RequestParam(required = false) String account,
HttpServletRequest request) {
- Map<String, List<Image>> imageMap = listImagesByAccount();
-
- // Set of accounts for which we should return images: either the supplied account, or default
- // to all accounts
- Set<String> accounts;
- if (StringUtils.isNotEmpty(account)) {
- accounts = new HashSet<>();
- if (imageMap.containsKey(account)) {
- accounts.add(account);
+ Collection<CacheData> imageCacheData = getImageCacheData(account);
+ Predicate<NamedImage> queryFilter = getQueryFilter(q);
+ Predicate<NamedImage> tagFilter = getTagFilter(request);
+ return imageCacheData.stream()
+ .map(this::createNamedImageFromCacheData)
+ .filter(queryFilter)
+ .filter(tagFilter)
+ .sorted(Comparator.comparing(image -> image.imageName))
+ .collect(Collectors.toList());
}
- } else {
- accounts = imageMap.keySet();
+
+ private Collection<CacheData> getImageCacheData(String account) {
+ // If no account supplied, return images from all accounts
+ String pattern =
+ (account == null || account.isEmpty())
+ ? String.format("%s:images:*", GoogleCloudProvider.getID())
+ : String.format("%s:images:%s:*", GoogleCloudProvider.getID(), account);
+ Collection<String> identifiers = cacheView.filterIdentifiers(IMAGES.getNs(), pattern);
+ return cacheView.getAll(IMAGES.getNs(), identifiers, RelationshipCacheFilter.none());
}
- List<NamedImage> results = new ArrayList<>();
- for (String imageAccount : accounts) {
- for (Image i : imageMap.get(imageAccount)) {
+ private NamedImage createNamedImageFromCacheData(CacheData cacheDatum) throws RuntimeException {
+ try {
+ Object hashImage = cacheDatum.getAttributes().get("image");
+ Image image = jsonMapper.fromString(objectMapper.writeValueAsString(hashImage), Image.class);
+ String imageAccount = Keys.parse(cacheDatum.getId()).get("account");
Map<String, Object> attributes = new HashMap<>();
- attributes.put("creationDate", i.get("creationTimestamp"));
- NamedImage newImage =
- new NamedImage(imageAccount, i.getName(), attributes, buildTagsMap(i));
- results.add(newImage);
+ attributes.put("creationDate", image.get("creationTimestamp"));
+ return new NamedImage(imageAccount, image.getName(), attributes, buildTagsMap(image));
+ } catch (IOException e) {
+ throw new RuntimeException("Image deserialization failed");
}
}
+ private Predicate<NamedImage> getQueryFilter(String q) {
Predicate<NamedImage> queryFilter = i -> true;
if (q != null && q.trim().length() > 0) {
String glob = q.trim();
@@ -111,38 +117,16 @@ public class GoogleNamedImageLookupController {
Pattern pattern = new InMemoryCache.Glob(glob).toPattern();
queryFilter = i -> pattern.matcher(i.imageName).matches();
}
-
- return results.stream()
- .filter(queryFilter)
- .filter(namedImage -> matchesTagFilters(namedImage, extractTagFilters(request)))
- .sorted(Comparator.comparing(image -> image.imageName))
- .collect(Collectors.toList());
+ return queryFilter;
}
- private Map<String, List<Image>> listImagesByAccount() {
- Collection<String> identifiers =
- cacheView.filterIdentifiers(IMAGES.getNs(), GoogleCloudProvider.getID() + ":*");
- Map<String, List<Image>> result = new HashMap<>();
-
- Collection<CacheData> allCacheData =
- cacheView.getAll(IMAGES.getNs(), identifiers, RelationshipCacheFilter.none());
- allCacheData.forEach(
- cacheData -> {
- String account = Keys.parse(cacheData.getId()).get("account");
- if (!result.containsKey(account)) {
- result.put(account, new ArrayList<>());
+ private Predicate<NamedImage> getTagFilter(HttpServletRequest request) {
+ Predicate<NamedImage> tagFilter = i -> true;
+ Map<String, String> tagFilters = extractTagFilters(request);
+ if (!tagFilters.isEmpty()) {
+ tagFilter = i -> matchesTagFilters(i, tagFilters);
}
- Object hashImage = cacheData.getAttributes().get("image");
- try {
- Image myImage =
- jsonMapper.fromString(objectMapper.writeValueAsString(hashImage), Image.class);
- result.get(account).add(myImage);
- } catch (IOException e) {
- throw new RuntimeException("Image deserialization failed");
- }
- });
-
- return result;
+ return tagFilter;
}
@VisibleForTesting

original_message: perf(google): improve performance of gce image fetching (#3785)


message: fix(deps): bump fabric8 to 4.1.1

diff:
@@ -17,7 +17,7 @@ dependencies {
implementation "com.netflix.spinnaker.fiat:fiat-core:$fiatVersion"
implementation "com.netflix.spinnaker.kork:kork-artifacts"
implementation "com.netflix.spinnaker.moniker:moniker"
- implementation "io.fabric8:kubernetes-client:4.0.0"
+ implementation "io.fabric8:kubernetes-client:4.1.1"
implementation "io.kubernetes:client-java:1.0.0-beta1"
implementation "org.springframework.boot:spring-boot-actuator"
implementation "org.springframework.boot:spring-boot-starter-web"

original_message: fix(deps): bump fabric8 to 4.1.1 (#3800)


diff:
@@ -92,6 +92,8 @@ import io.fabric8.kubernetes.api.model.HorizontalPodAutoscalerFluentImpl
import io.fabric8.kubernetes.api.model.HostPathVolumeSourceBuilder
import io.fabric8.kubernetes.api.model.IntOrString
import io.fabric8.kubernetes.api.model.KeyToPath
+import io.fabric8.kubernetes.api.model.MetricSpec
+import io.fabric8.kubernetes.api.model.MetricSpecBuilder
import io.fabric8.kubernetes.api.model.NFSVolumeSourceBuilder
import io.fabric8.kubernetes.api.model.PersistentVolumeClaimVolumeSourceBuilder
import io.fabric8.kubernetes.api.model.PodTemplateSpec
@@ -818,7 +820,7 @@ class KubernetesApiConverter {
description.capacity = new Capacity(min: autoscaler.spec.minReplicas,
max: autoscaler.spec.maxReplicas,
desired: description.targetSize)
- def cpuUtilization = new KubernetesCpuUtilization(target: autoscaler.spec.targetCPUUtilizationPercentage)
+ def cpuUtilization = new KubernetesCpuUtilization(target: autoscaler.spec.metrics?.find { metric -> metric.resource.name == "cpu" }?.resource?.targetAverageUtilization)
description.scalingPolicy = new KubernetesScalingPolicy(cpuUtilization: cpuUtilization)
}
@@ -834,7 +836,13 @@ class KubernetesApiConverter {
.withNewSpec()
.withMinReplicas(description.capacity.min)
.withMaxReplicas(description.capacity.max)
- .withTargetCPUUtilizationPercentage(description.scalingPolicy.cpuUtilization.target)
+ .addToMetrics(new MetricSpecBuilder()
+ .withType("Resource")
+ .withNewResource()
+ .withName("cpu")
+ .withTargetAverageUtilization(description.scalingPolicy.cpuUtilization.target)
+ .endResource()
+ .build())
.withNewScaleTargetRef()
.withKind(resourceKind)
.withName(resourceName)

diff:
@@ -29,6 +29,7 @@ import com.netflix.spinnaker.clouddriver.kubernetes.v1.deploy.exception.Kubernet
import com.netflix.spinnaker.clouddriver.orchestration.AtomicOperation
import io.fabric8.kubernetes.api.model.DoneableHorizontalPodAutoscaler
import io.fabric8.kubernetes.api.model.HorizontalPodAutoscalerBuilder
+import io.fabric8.kubernetes.api.model.MetricSpecBuilder
class UpsertKubernetesAutoscalerAtomicOperation implements AtomicOperation<Void> {
KubernetesAutoscalerDescription description
@@ -81,7 +82,7 @@ class UpsertKubernetesAutoscalerAtomicOperation implements AtomicOperation<Void>
description.scalingPolicy.cpuUtilization = description.scalingPolicy.cpuUtilization ?: new KubernetesCpuUtilization()
description.scalingPolicy.cpuUtilization.target = description.scalingPolicy.cpuUtilization.target != null ?
description.scalingPolicy.cpuUtilization.target :
- autoscaler.spec.targetCPUUtilizationPercentage
+ autoscaler.spec.metrics?.find { metric -> metric.resource.name == "cpu" }?.resource?.targetAverageUtilization
((DoneableHorizontalPodAutoscaler) KubernetesApiConverter.toAutoscaler(
credentials.apiAdaptor.editAutoscaler(namespace, name), description, name, kind, version

original_message: fix(kubernetes): update fabric HPA model in v1 provider (#3801)


diff:
@@ -55,9 +55,6 @@ class KubernetesClientApiAdapter {
static final long RETRY_INITIAL_WAIT_MILLIS = 100
static final int API_CALL_TIMEOUT_SECONDS = 60
static final int TERMINATION_GRACE_PERIOD_SECONDS = 30
- static final String API_CALL_RESULT_FORMAT = ""
- static final int SHUTDOWN_ALL_PODS = 0
- static final String DEPLOYMENT_ANNOTATION = "deployment.kubernetes.io"
private final ObjectMapper mapper = new ObjectMapper();
final Registry spectatorRegistry
final Clock spectatorClock
@@ -156,7 +153,7 @@ class KubernetesClientApiAdapter {
List<V1beta1StatefulSet> getStatefulSets(String namespace) {
exceptionWrapper("statefulSets.list", "Get Stateful Sets", namespace) {
try {
- V1beta1StatefulSetList list = apiInstance.listNamespacedStatefulSet(namespace, null, null, null, true, null, null, null, API_CALL_TIMEOUT_SECONDS, false);
+ V1beta1StatefulSetList list = apiInstance.listNamespacedStatefulSet(namespace, true, null, null, null, null, null, null, API_CALL_TIMEOUT_SECONDS, false);
String apiVersion = list.getApiVersion();
for (V1beta1StatefulSet item : list.getItems()) {
item.setApiVersion(apiVersion);
@@ -187,9 +184,9 @@ class KubernetesClientApiAdapter {
}
}
- V1beta1StatefulSet createStatfulSet(String namespace, V1beta1StatefulSet statefulSet) {
+ V1beta1StatefulSet createStatefulSet(String namespace, V1beta1StatefulSet statefulSet) {
exceptionWrapper("statefulSets.create", "Create Stateful Set ${statefulSet?.metadata?.name}", namespace) {
- return apiInstance.createNamespacedStatefulSet(namespace, statefulSet, API_CALL_RESULT_FORMAT)
+ return apiInstance.createNamespacedStatefulSet(namespace, statefulSet, false, null, null)
}
}
@@ -200,7 +197,7 @@ class KubernetesClientApiAdapter {
deployedControllerSet.spec.template = statefulSet.spec.template
deployedControllerSet.spec.updateStrategy = statefulSet.spec.updateStrategy
- return apiInstance.replaceNamespacedStatefulSet(name, namespace, deployedControllerSet, API_CALL_RESULT_FORMAT)
+ return apiInstance.replaceNamespacedStatefulSet(name, namespace, deployedControllerSet, null, null)
}
}
@@ -218,7 +215,7 @@ class KubernetesClientApiAdapter {
desired.spec.replicas = targetSize
final Map[] jsonPatch = determineJsonPatch(current, desired);
- V1beta1StatefulSet statefulSet = apiInstance.patchNamespacedStatefulSet(name, namespace, jsonPatch, null)
+ V1beta1StatefulSet statefulSet = apiInstance.patchNamespacedStatefulSet(name, namespace, jsonPatch, null, null)
return statefulSet
}
@@ -234,7 +231,7 @@ class KubernetesClientApiAdapter {
})
try {
- apiInstance.deleteNamespacedStatefulSet(name, namespace, deleteOptions ?: new V1DeleteOptions(), API_CALL_RESULT_FORMAT, TERMINATION_GRACE_PERIOD_SECONDS, orphanDependents, propagationPolicy)
+ apiInstance.deleteNamespacedStatefulSet(name, namespace, null, deleteOptions ?: new V1DeleteOptions(), null, TERMINATION_GRACE_PERIOD_SECONDS, orphanDependents, propagationPolicy)
} catch (Exception e) {
log.debug(e.message)
}
@@ -246,7 +243,7 @@ class KubernetesClientApiAdapter {
V1HorizontalPodAutoscaler result = null
try {
- result = scalerApi.readNamespacedHorizontalPodAutoscalerStatus(name, namespace, API_CALL_RESULT_FORMAT)
+ result = scalerApi.readNamespacedHorizontalPodAutoscalerStatus(name, namespace, null)
} catch (Exception ex) {
log.info "Unable to find autoscaler {$name in $namespace}: $ex."
}
@@ -257,14 +254,14 @@ class KubernetesClientApiAdapter {
V1HorizontalPodAutoscaler createAutoscaler(String namespace, V1HorizontalPodAutoscaler autoscaler) {
exceptionWrapper("horizontalPodAutoscalers.create", "Create Autoscaler ${autoscaler?.metadata?.name}", namespace) {
- return scalerApi.createNamespacedHorizontalPodAutoscaler(namespace, autoscaler, API_CALL_RESULT_FORMAT)
+ return scalerApi.createNamespacedHorizontalPodAutoscaler(namespace, autoscaler, false, null, null)
}
}
V1beta1StatefulSet getStatefulSet(String statefulSetName, String namespace) {
exceptionWrapper("statefulSets.create", "Get Stateful Set ${statefulSetName}", namespace) {
try {
- return apiInstance.readNamespacedStatefulSet(statefulSetName, namespace, API_CALL_RESULT_FORMAT, null, null)
+ return apiInstance.readNamespacedStatefulSet(statefulSetName, namespace, null, false, false)
} catch (Exception e) {
log.debug(e.message)
return null
@@ -281,19 +278,19 @@ class KubernetesClientApiAdapter {
String value = entry.getValue()
label = key + "=" + value
}
- coreApi.listNamespacedPod(namespace, null, null, null, false, label, null, null, API_CALL_TIMEOUT_SECONDS,false)
+ coreApi.listNamespacedPod(namespace, false, null, null, null, label, null, null, API_CALL_TIMEOUT_SECONDS,false)
}
}
boolean deleteAutoscaler(String namespace, String name, V1DeleteOptions deleteOptions, Boolean orphanDependents, String propagationPolicy) {
exceptionWrapper("horizontalPodAutoscalers.delete", "Destroy Autoscaler $name", namespace) {
- return scalerApi.deleteNamespacedHorizontalPodAutoscaler(name, namespace, deleteOptions, API_CALL_RESULT_FORMAT, TERMINATION_GRACE_PERIOD_SECONDS, orphanDependents, propagationPolicy);
+ return scalerApi.deleteNamespacedHorizontalPodAutoscaler(name, namespace, null, deleteOptions, null, TERMINATION_GRACE_PERIOD_SECONDS, orphanDependents, propagationPolicy);
}
}
V1beta1DaemonSet createDaemonSet(String namespace, V1beta1DaemonSet daemonSet) {
exceptionWrapper("DaemonSet.create", "Create Daemon Set ${daemonSet?.metadata?.name}", namespace) {
- return extApi.createNamespacedDaemonSet(namespace, daemonSet, API_CALL_RESULT_FORMAT)
+ return extApi.createNamespacedDaemonSet(namespace, daemonSet, false, null, null)
}
}
@@ -303,14 +300,14 @@ class KubernetesClientApiAdapter {
deployedControllerSet.spec.template = daemonSet.spec.template
deployedControllerSet.spec.updateStrategy = daemonSet.spec.updateStrategy
- return extApi.replaceNamespacedDaemonSet(name, namespace, deployedControllerSet, API_CALL_RESULT_FORMAT)
+ return extApi.replaceNamespacedDaemonSet(name, namespace, deployedControllerSet, null, null)
}
}
V1beta1DaemonSet getDaemonSet(String name, String namespace) {
exceptionWrapper("DaemonSet.get", "Get Daemon Set ${name}", namespace) {
try {
- return extApi.readNamespacedDaemonSet(name, namespace, API_CALL_RESULT_FORMAT, true, false)
+ return extApi.readNamespacedDaemonSet(name, namespace, null, true, false)
} catch (Exception e) {
log.debug(e.message)
return null
@@ -324,7 +321,7 @@ class KubernetesClientApiAdapter {
V1Status status
try {
- status = extApi.deleteNamespacedDaemonSet(name, namespace, deleteoptions ?: new V1DeleteOptions(), API_CALL_RESULT_FORMAT, TERMINATION_GRACE_PERIOD_SECONDS, orphanDependents, propagationPolicy);
+ status = extApi.deleteNamespacedDaemonSet(name, namespace, null, deleteoptions ?: new V1DeleteOptions(), null, TERMINATION_GRACE_PERIOD_SECONDS, orphanDependents, propagationPolicy);
} catch (Exception e) {
log.debug(e.message)
}
@@ -337,7 +334,7 @@ class KubernetesClientApiAdapter {
List<String> getNamespacesByName() {
exceptionWrapper("namespaces.list", "Get Namespaces", null) {
- V1NamespaceList result = coreApi.listNamespace(API_CALL_RESULT_FORMAT, null, null, null, null, null, null, 30, null)
+ V1NamespaceList result = coreApi.listNamespace(false, null, null, null, null, null, null, 30, false)
return result.items.collect { n -> n.getMetadata().getName() }
}
}
@@ -353,7 +350,7 @@ class KubernetesClientApiAdapter {
deleteOptions.gracePeriodSeconds = 0
}
- status = coreApi.deleteNamespacedPod(name, namespace, deleteOptions ?: new V1DeleteOptions(), API_CALL_RESULT_FORMAT, TERMINATION_GRACE_PERIOD_SECONDS, null, null)
+ status = coreApi.deleteNamespacedPod(name, namespace, null, deleteOptions ?: new V1DeleteOptions(), null, TERMINATION_GRACE_PERIOD_SECONDS, null, null)
} catch (Exception e) {
log.debug(e.message)
}
@@ -364,7 +361,7 @@ class KubernetesClientApiAdapter {
exceptionWrapper("pods.status", "Get pod status ${name}", namespace) {
V1Pod pod
try {
- pod = coreApi.readNamespacedPodStatus(name, namespace, API_CALL_RESULT_FORMAT)
+ pod = coreApi.readNamespacedPodStatus(name, namespace, null)
} catch (Exception e) {
log.debug(e.message)
}

diff:
@@ -211,11 +211,11 @@ class DeployKubernetesAtomicOperation implements AtomicOperation<DeploymentResul
}
} else {
task.updateStatus BASE_PHASE, "Deployed stateful set ${controllerName}"
- controllerSet = credentials.clientApiAdaptor.createStatfulSet(namespace, controllerSet)
+ controllerSet = credentials.clientApiAdaptor.createStatefulSet(namespace, controllerSet)
}
} else {
task.updateStatus BASE_PHASE, "Deployed stateful set ${controllerName}"
- controllerSet = credentials.clientApiAdaptor.createStatfulSet(namespace, controllerSet)
+ controllerSet = credentials.clientApiAdaptor.createStatefulSet(namespace, controllerSet)
}
if (description.scalingPolicy) {

original_message: fix(kubernetes): fix breaking api changes in kubernetes java client library (#3805)


diff:
@@ -313,14 +313,17 @@ public class Applications {
.build()))
.collect(toList());
- Map<String, String> environmentVars =
+ Map<String, Object> environmentVars =
applicationEnv == null || applicationEnv.getEnvironmentJson() == null
? emptyMap()
: applicationEnv.getEnvironmentJson();
final CloudFoundryBuildInfo buildInfo = getBuildInfoFromEnvVars(environmentVars);
final ArtifactInfo artifactInfo = getArtifactInfoFromEnvVars(environmentVars);
- final String pipelineId = environmentVars.get(ServerGroupMetaDataEnvVar.PipelineId.envVarName);
+ final String pipelineId =
+ Optional.ofNullable(environmentVars.get(ServerGroupMetaDataEnvVar.PipelineId.envVarName))
+ .map(Object::toString)
+ .orElse(null);
Arrays.asList(ServerGroupMetaDataEnvVar.values())
.forEach(envVar -> environmentVars.remove(envVar.envVarName));
@@ -375,19 +378,40 @@ public class Applications {
.build();
}
- private CloudFoundryBuildInfo getBuildInfoFromEnvVars(Map<String, String> environmentVars) {
+ private CloudFoundryBuildInfo getBuildInfoFromEnvVars(Map<String, Object> environmentVars) {
return CloudFoundryBuildInfo.builder()
- .jobName(environmentVars.get(ServerGroupMetaDataEnvVar.JobName.envVarName))
- .jobNumber(environmentVars.get(ServerGroupMetaDataEnvVar.JobNumber.envVarName))
- .jobUrl(environmentVars.get(ServerGroupMetaDataEnvVar.JobUrl.envVarName))
+ .jobName(
+ Optional.ofNullable(environmentVars.get(ServerGroupMetaDataEnvVar.JobName.envVarName))
+ .map(Object::toString)
+ .orElse(null))
+ .jobNumber(
+ Optional.ofNullable(environmentVars.get(ServerGroupMetaDataEnvVar.JobNumber.envVarName))
+ .map(Object::toString)
+ .orElse(null))
+ .jobUrl(
+ Optional.ofNullable(environmentVars.get(ServerGroupMetaDataEnvVar.JobUrl.envVarName))
+ .map(Object::toString)
+ .orElse(null))
.build();
}
- private ArtifactInfo getArtifactInfoFromEnvVars(Map<String, String> environmentVars) {
+ private ArtifactInfo getArtifactInfoFromEnvVars(Map<String, Object> environmentVars) {
return ArtifactInfo.builder()
- .name(environmentVars.get(ServerGroupMetaDataEnvVar.ArtifactName.envVarName))
- .version(environmentVars.get(ServerGroupMetaDataEnvVar.ArtifactVersion.envVarName))
- .url(environmentVars.get(ServerGroupMetaDataEnvVar.ArtifactUrl.envVarName))
+ .name(
+ Optional.ofNullable(
+ environmentVars.get(ServerGroupMetaDataEnvVar.ArtifactName.envVarName))
+ .map(Object::toString)
+ .orElse(null))
+ .version(
+ Optional.ofNullable(
+ environmentVars.get(ServerGroupMetaDataEnvVar.ArtifactVersion.envVarName))
+ .map(Object::toString)
+ .orElse(null))
+ .url(
+ Optional.ofNullable(
+ environmentVars.get(ServerGroupMetaDataEnvVar.ArtifactUrl.envVarName))
+ .map(Object::toString)
+ .orElse(null))
.build();
}

diff:
@@ -24,7 +24,7 @@ import lombok.Data;
@Data
public class ApplicationEnv {
private SystemEnv systemEnvJson;
- private Map<String, String> environmentJson;
+ private Map<String, Object> environmentJson;
@Data
public static class SystemEnv {

diff:
@@ -93,7 +93,7 @@ public class CloudFoundryServerGroup extends CloudFoundryModel implements Server
Long createdTime;
@JsonView(Views.Cache.class)
- Map<String, String> env;
+ Map<String, Object> env;
@Wither
@JsonView(Views.Cache.class)

original_message: fix(cf): allow environmentVars values to be deserializable to objects (#3804)


change_type: ADD

diff:
+/*
+ * 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.clouddriver.artifacts.custom;
+
+import com.netflix.spinnaker.clouddriver.artifacts.config.ArtifactAccount;
+import lombok.Data;
+
+@Data
+public class CustomArtifactAccount implements ArtifactAccount {
+ private String name = "custom-artifact";
+}

diff:
+/*
+ * 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.clouddriver.artifacts.custom;
+
+import java.util.Collections;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@RequiredArgsConstructor
+@Slf4j
+public class CustomArtifactConfiguration {
+ @Bean
+ List<? extends CustomArtifactCredentials> customArtifactCredentials() {
+ CustomArtifactAccount account = new CustomArtifactAccount();
+ CustomArtifactCredentials credentials = new CustomArtifactCredentials(account);
+ return Collections.singletonList(credentials);
+ }
+}

diff:
+/*
+ * 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.clouddriver.artifacts.custom;
+
+import com.netflix.spinnaker.clouddriver.artifacts.config.ArtifactCredentials;
+import com.netflix.spinnaker.kork.artifacts.model.Artifact;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.List;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class CustomArtifactCredentials implements ArtifactCredentials {
+ @Getter private final String name;
+ @Getter private final List<String> types = Collections.singletonList("custom/object");
+
+ CustomArtifactCredentials(CustomArtifactAccount account) {
+ name = account.getName();
+ }
+
+ public InputStream download(Artifact artifact) {
+ throw new UnsupportedOperationException(
+ "Custom references are passed on to cloud platforms to handle or process");
+ }
+}

original_message: feat(core): add custom artifact type for new artifacts refactor (#3835)


diff:
@@ -22,6 +22,7 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo
import com.google.api.services.compute.model.AutoscalingPolicy
import com.google.api.services.compute.model.InstanceGroupManagerActionsSummary
import com.google.api.services.compute.model.InstanceGroupManagerAutoHealingPolicy
+import com.google.api.services.compute.model.StatefulPolicy
import com.google.api.services.iam.v1.model.ServiceAccount
import com.netflix.spinnaker.clouddriver.google.GoogleCloudProvider
import com.netflix.spinnaker.clouddriver.google.model.loadbalancing.GoogleHttpLoadBalancingPolicy
@@ -74,6 +75,9 @@ class GoogleServerGroup implements GoogleLabeledResource {
@JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="class")
AutoscalingPolicy autoscalingPolicy
+ @JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="class")
+ StatefulPolicy statefulPolicy
+
List<String> autoscalingMessages
@JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="class")
@@ -124,6 +128,7 @@ class GoogleServerGroup implements GoogleLabeledResource {
Boolean discovery = GoogleServerGroup.this.discovery
InstanceGroupManagerActionsSummary currentActions = GoogleServerGroup.this.currentActions
AutoscalingPolicy autoscalingPolicy = GoogleServerGroup.this.autoscalingPolicy
+ StatefulPolicy statefulPolicy = GoogleServerGroup.this.statefulPolicy
List<String> autoscalingMessages = GoogleServerGroup.this.autoscalingMessages
InstanceGroupManagerAutoHealingPolicy autoHealingPolicy = GoogleServerGroup.this.autoHealingPolicy
GoogleDistributionPolicy distributionPolicy = GoogleServerGroup.this.distributionPolicy

diff:
@@ -480,6 +480,7 @@ class GoogleZonalServerGroupCachingAgent extends AbstractGoogleCachingAgent impl
asg: [minSize : instanceGroupManager.targetSize,
maxSize : instanceGroupManager.targetSize,
desiredCapacity: instanceGroupManager.targetSize],
+ statefulPolicy: instanceGroupManager.statefulPolicy,
autoHealingPolicy: instanceGroupManager.autoHealingPolicies?.getAt(0)
)
}

original_message: feat(google): propagate the GCE statefulPolicy through to deck (#3855)


diff:
@@ -262,11 +262,11 @@ public class KubernetesDeployManifestOperation implements AtomicOperation<Operat
}
private void validateManifestsForRolloutStrategies(List<KubernetesManifest> manifests) {
- if (description.getStrategy() != null
- && (manifests.size() != 1
- || !manifests.get(0).getKind().equals(KubernetesKind.REPLICA_SET))) {
+ long numReplicaSets =
+ manifests.stream().filter(m -> m.getKind().equals(KubernetesKind.REPLICA_SET)).count();
+ if (description.getStrategy() != null && numReplicaSets != 1) {
throw new RuntimeException(
- "Spinnaker can manage traffic for ReplicaSets only. Please deploy exactly one ReplicaSet manifest or disable rollout strategies.");
+ "Spinnaker can manage traffic for one ReplicaSet only. Please deploy one ReplicaSet manifest or disable rollout strategies.");
}
}

original_message: feat(kubernetes): allow multi-manifest with rollout strategies (#3874)


diff:
+# Spinnaker IntelliJ IDEA files
+
+IntelliJ IDEA will modify some of these files from their checked-in versions when the project is
+opened. To work around this, the Spinnaker Gradle plugin will mark these files in Git as "assume
+unchanged", telling Git to ignore any local changes. If you want to commit changes to these files,
+you will need to undo that.
+
+```bash
+$ git update-index --no-assume-unchanged $FILENAME
+```

diff:
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="CompilerConfiguration">
+ <annotationProcessing>
+ <profile default="true" name="Default" enabled="true" />
+ </annotationProcessing>
+ </component>
+</project>
\ No newline at end of file

diff:
+<component name="CopyrightManager">
+ <copyright>
+ <option name="notice" value="Copyright &#36;today.year 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.
" />
+ <option name="myName" value="ALS2" />
+ </copyright>
+</component>
\ No newline at end of file

diff:
+<component name="CopyrightManager">
+ <settings default="ALS2" />
+</component>
\ No newline at end of file

diff:
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="GoogleJavaFormatSettings">
+ <option name="state" value="ENABLED" />
+ </component>
+</project>
\ No newline at end of file

diff:
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="GradleSettings">
+ <option name="linkedExternalProjectsSettings">
+ <GradleProjectSettings>
+ <option name="distributionType" value="DEFAULT_WRAPPED" />
+ <option name="externalProjectPath" value="$PROJECT_DIR$" />
+ <option name="useAutoImport" value="true" />
+ </GradleProjectSettings>
+ </option>
+ </component>
+</project>
\ No newline at end of file

diff:
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="VcsDirectoryMappings">
+ <mapping directory="" vcs="Git" />
+ </component>
+</project>
\ No newline at end of file

diff:
@@ -3,5 +3,5 @@ includeCloudProviders=all
fiatVersion=1.4.0
enablePublishing=false
korkVersion=5.8.4
-spinnakerGradleVersion=6.5.0
+spinnakerGradleVersion=7.0.1
org.gradle.parallel=true

original_message: chore(intellij): Add IDEA project files and upgrade the Spinnaker Gradle project (#3873)


message:
refactor(google): convert GoogleApplicationProvider to Java
* refactor(google): convert GoogleApplicationProvider to Java
I did this while debugging something. It serves no particular purpose
aside from the helping along the slow gradual death of Groovy.
* style(google): fix copyright header

change_type: DELETE

diff:
-/*
- * Copyright 2016 Google, 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.clouddriver.google.provider.view
-
-import com.fasterxml.jackson.databind.ObjectMapper
-import com.netflix.spectator.api.Registry
-import com.netflix.spinnaker.cats.cache.Cache
-import com.netflix.spinnaker.cats.cache.CacheData
-import com.netflix.spinnaker.cats.cache.RelationshipCacheFilter
-import com.netflix.spinnaker.clouddriver.google.GoogleCloudProvider
-import com.netflix.spinnaker.clouddriver.google.cache.Keys
-import com.netflix.spinnaker.clouddriver.google.model.GoogleApplication
-import com.netflix.spinnaker.clouddriver.model.ApplicationProvider
-import com.netflix.spinnaker.clouddriver.security.AccountCredentialsProvider
-import org.springframework.beans.factory.annotation.Autowired
-import org.springframework.stereotype.Component
-
-import static com.netflix.spinnaker.clouddriver.google.cache.Keys.Namespace.APPLICATIONS
-import static com.netflix.spinnaker.clouddriver.google.cache.Keys.Namespace.CLUSTERS
-import static com.netflix.spinnaker.clouddriver.google.cache.Keys.Namespace.INSTANCES
-
-@Component
-class GoogleApplicationProvider implements ApplicationProvider {
- @Autowired
- Registry registry
-
- @Autowired
- AccountCredentialsProvider accountCredentialsProvider
-
- @Autowired
- Cache cacheView
-
- @Autowired
- ObjectMapper objectMapper
-
- @Override
- Set<GoogleApplication.View> getApplications(boolean expand) {
- def filter = expand ? RelationshipCacheFilter.include(CLUSTERS.ns) : RelationshipCacheFilter.none()
- cacheView.getAll(APPLICATIONS.ns,
- cacheView.filterIdentifiers(APPLICATIONS.ns, "$GoogleCloudProvider.ID:*"),
- filter).collect { applicationFromCacheData(it) } as Set
- }
-
- @Override
- GoogleApplication.View getApplication(String name) {
- CacheData cacheData = cacheView.get(APPLICATIONS.ns,
- Keys.getApplicationKey(name),
- RelationshipCacheFilter.include(CLUSTERS.ns, INSTANCES.ns))
- if (cacheData) {
- return applicationFromCacheData(cacheData)
- }
- }
-
- GoogleApplication.View applicationFromCacheData(CacheData cacheData) {
- GoogleApplication.View applicationView = objectMapper.convertValue(cacheData.attributes, GoogleApplication)?.view
-
- cacheData.relationships[CLUSTERS.ns].each { String clusterKey ->
- def clusterKeyParsed = Keys.parse(clusterKey)
- applicationView.clusterNames[clusterKeyParsed.account] << clusterKeyParsed.name
- }
-
- applicationView.instances = cacheData?.relationships?.get(INSTANCES.ns).collect { Keys.parse(it) } ?: []
-
- applicationView
- }
-}

diff:
+/*
+ * 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.clouddriver.google.provider.view;
+
+import static com.netflix.spinnaker.clouddriver.google.cache.Keys.Namespace.APPLICATIONS;
+import static com.netflix.spinnaker.clouddriver.google.cache.Keys.Namespace.CLUSTERS;
+import static com.netflix.spinnaker.clouddriver.google.cache.Keys.Namespace.INSTANCES;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toSet;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.ImmutableSet;
+import com.netflix.spinnaker.cats.cache.Cache;
+import com.netflix.spinnaker.cats.cache.CacheData;
+import com.netflix.spinnaker.cats.cache.RelationshipCacheFilter;
+import com.netflix.spinnaker.clouddriver.google.GoogleCloudProvider;
+import com.netflix.spinnaker.clouddriver.google.cache.Keys;
+import com.netflix.spinnaker.clouddriver.google.cache.Keys.Namespace;
+import com.netflix.spinnaker.clouddriver.google.model.GoogleApplication;
+import com.netflix.spinnaker.clouddriver.model.Application;
+import com.netflix.spinnaker.clouddriver.model.ApplicationProvider;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+final class GoogleApplicationProvider implements ApplicationProvider {
+
+ private final Cache cacheView;
+ private final ObjectMapper objectMapper;
+
+ @Autowired
+ GoogleApplicationProvider(Cache cacheView, ObjectMapper objectMapper) {
+ this.cacheView = cacheView;
+ this.objectMapper = objectMapper;
+ }
+
+ @Override
+ public Set<? extends Application> getApplications(boolean expand) {
+
+ RelationshipCacheFilter filter =
+ expand ? RelationshipCacheFilter.include(CLUSTERS.getNs()) : RelationshipCacheFilter.none();
+ Collection<CacheData> data =
+ cacheView.getAll(
+ APPLICATIONS.getNs(),
+ cacheView.filterIdentifiers(APPLICATIONS.getNs(), GoogleCloudProvider.getID() + ":*"),
+ filter);
+ return data.stream().map(this::applicationFromCacheData).collect(toSet());
+ }
+
+ @Override
+ public Application getApplication(String name) {
+
+ CacheData cacheData =
+ cacheView.get(
+ APPLICATIONS.getNs(),
+ Keys.getApplicationKey(name),
+ RelationshipCacheFilter.include(CLUSTERS.getNs(), INSTANCES.getNs()));
+ if (cacheData == null) {
+ return null;
+ }
+
+ return applicationFromCacheData(cacheData);
+ }
+
+ private GoogleApplication.View applicationFromCacheData(CacheData cacheData) {
+
+ GoogleApplication application =
+ objectMapper.convertValue(cacheData.getAttributes(), GoogleApplication.class);
+ if (application == null) {
+ return null;
+ }
+
+ GoogleApplication.View applicationView = application.getView();
+
+ Collection<String> clusters = getRelationships(cacheData, CLUSTERS);
+ clusters.forEach(
+ key -> {
+ Map<String, String> parsedKey = Keys.parse(key);
+ applicationView
+ .getClusterNames()
+ .get(parsedKey.get("account"))
+ .add(parsedKey.get("name"));
+ });
+
+ List<Map<String, String>> instances =
+ getRelationships(cacheData, INSTANCES).stream().map(Keys::parse).collect(toList());
+ applicationView.setInstances(instances);
+
+ return applicationView;
+ }
+
+ private Collection<String> getRelationships(CacheData cacheData, Namespace namespace) {
+ Collection<String> result = cacheData.getRelationships().get(namespace.getNs());
+ return result != null ? result : ImmutableSet.of();
+ }
+}

original_message:
refactor(google): convert GoogleApplicationProvider to Java (#3880)

* refactor(google): convert GoogleApplicationProvider to Java

I did this while debugging something. It serves no particular purpose
aside from the helping along the slow gradual death of Groovy.

* style(google): fix copyright header


diff:
@@ -121,6 +121,13 @@ public class KubernetesV2InstanceProvider
List<ContainerLog> result = new ArrayList();
+ // Short-circuit if pod cannot be found
+ if (pod == null) {
+ result.add(
+ new ContainerLog("Error", "Failed to retrieve pod data; pod may have been deleted."));
+ return result;
+ }
+
// Make live calls rather than abuse the cache for storing all logs
for (V1Container container : pod.getSpec().getContainers()) {
ContainerLog log = new ContainerLog();

original_message: fix(kubernetes): improve error messaging when fetching logs for deleted pods (#3890)


diff:
@@ -122,6 +122,18 @@ public class EcsCreateServerGroupDescriptionValidator extends CommonValidator {
} else {
rejectValue(errors, "reservedMemory", "not.nullable");
}
+ } else {
+ // Verify load balanced services w/ an artifact specify which container to load balance on
+ boolean hasTargetGroup =
+ createServerGroupDescription.getTargetGroup() != null
+ && !createServerGroupDescription.getTargetGroup().isEmpty();
+ boolean hasLoadBalancedContainer =
+ createServerGroupDescription.getLoadBalancedContainer() != null
+ && !createServerGroupDescription.getLoadBalancedContainer().isEmpty();
+
+ if (hasTargetGroup && !hasLoadBalancedContainer) {
+ rejectValue(errors, "loadBalancedContainer", "not.nullable");
+ }
}
if (createServerGroupDescription.getContainerPort() != null) {

diff:
@@ -119,6 +119,19 @@ class EcsCreateServergroupDescriptionValidatorSpec extends AbstractValidatorSpec
0 * errors.rejectValue(_, _)
}
+ void '(with artifact) should fail when load balancer specified but loadBalanced container missing'() {
+ given:
+ def description = getDescription()
+ description.useTaskDefinitionArtifact = true
+ def errors = Mock(Errors)
+
+ when:
+ validator.validate([], description, errors)
+
+ then:
+ 1 * errors.rejectValue('loadBalancedContainer', "${getDescriptionName()}.loadBalancedContainer.not.nullable")
+ }
+
@Override
AbstractECSDescription getNulledDescription() {
def description = (CreateServerGroupDescription) getDescription()

original_message: fix(ecs): Add validation for loadBalancedContainer when using artifact (#3888)


message:
refactor(cats): Make the AgentDataType constructor private
just a tiny semi-pointless cleanup

diff:
@@ -25,7 +25,7 @@ package com.netflix.spinnaker.cats.agent;
* complete set of data, so will not result in deletions when elements are no longer present.
*/
public class AgentDataType {
- public static enum Authority {
+ public enum Authority {
AUTHORITATIVE,
INFORMATIVE;
@@ -37,7 +37,7 @@ public class AgentDataType {
private final String typeName;
private final Authority authority;
- public AgentDataType(String typeName, Authority authority) {
+ private AgentDataType(String typeName, Authority authority) {
this.typeName = typeName;
this.authority = authority;
}

diff:
package com.netflix.spinnaker.clouddriver.aws.provider.agent;
+import static com.netflix.spinnaker.cats.agent.AgentDataType.Authority.AUTHORITATIVE;
+
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -35,9 +37,21 @@ import com.netflix.spinnaker.clouddriver.security.AccountCredentialsRepository;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
import java.util.stream.Collectors;
-import org.apache.http.*;
+import org.apache.http.Header;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
@@ -79,9 +93,8 @@ public class AmazonInstanceTypeCachingAgent implements CachingAgent {
public Collection<AgentDataType> getProvidedDataTypes() {
return Collections.unmodifiableList(
Arrays.asList(
- new AgentDataType(
- Keys.Namespace.INSTANCE_TYPES.getNs(), AgentDataType.Authority.AUTHORITATIVE),
- new AgentDataType(getAgentType(), AgentDataType.Authority.AUTHORITATIVE)));
+ AUTHORITATIVE.forType(Keys.Namespace.INSTANCE_TYPES.getNs()),
+ AUTHORITATIVE.forType(getAgentType())));
}
@Override

original_message:
refactor(cats): Make the AgentDataType constructor private (#3904)

just a tiny semi-pointless cleanup


message: fix(gce): add a TODO about removing the ONLY_DOWN autoscaler policy

diff:
@@ -61,6 +61,9 @@ class GoogleAutoscalingPolicy {
ON,
OFF,
ONLY_UP,
+ // ONLY_DOWN is being removed as an option from GCE. We can remove this option once we have
+ // confirmation that all the autoscaler policies have been migrated away from it (by the GCE
+ // team).
ONLY_DOWN
}
}


message:
chore(dependencies): manually bump korkVersion
Upgrading Guava in Kork also upgraded errorprone which caused a build
error, so this forces error-prone back to a version that works with our
build.

diff:
@@ -63,6 +63,18 @@ allprojects {
dependencies {
implementation platform("com.netflix.spinnaker.kork:kork-bom:$korkVersion")
+ // This can be removed once there is a version of error-prone > 2.3.2. In 2.3.0, the
+ // @DoNotMock annotation was accidentally removed. It was put back, but hasn't made it into a
+ // release yet.
+ //
+ // In the meantime, kork depends on Guava 28.0, which depends on error-prone. There's no
+ // version listed, so Gradle resolves it to the newest version (2.3.2) which breaks the build
+ // because of that missing annotation. This block forces the build back to 2.2.0, the latest
+ // version that contains @DoNotMock.
+ implementation('com.google.errorprone:error_prone_annotations:2.2.0') {
+ force = true
+ }
+
annotationProcessor platform("com.netflix.spinnaker.kork:kork-bom:$korkVersion")
annotationProcessor "org.projectlombok:lombok"
testAnnotationProcessor platform("com.netflix.spinnaker.kork:kork-bom:$korkVersion")

diff:
includeCloudProviders=all
fiatVersion=1.4.0
enablePublishing=false
-korkVersion=5.10.3
+korkVersion=5.11.0
spinnakerGradleVersion=7.0.2
org.gradle.parallel=true

original_message:
chore(dependencies): manually bump korkVersion (#3935)

Upgrading Guava in Kork also upgraded errorprone which caused a build
error, so this forces error-prone back to a version that works with our
build.


message: fix(kubernetes): improve error messaging around invalid service names

diff:
@@ -78,7 +78,9 @@ public abstract class AbstractKubernetesEnableDisableManifestOperation
name = KubernetesManifest.fromFullResourceName(loadBalancerName);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(
- "Load balancers must be specified in the form '<kind> <name>', e.g. 'service my-service'",
+ String.format(
+ "Failed to perform operation with load balancer '%s'. Load balancers must be specified in the form '{kind} {name}', e.g. 'service my-service'",
+ loadBalancerName),
e);
}

diff:
@@ -269,7 +269,9 @@ public class KubernetesDeployManifestOperation implements AtomicOperation<Operat
name = KubernetesManifest.fromFullResourceName(loadBalancerName);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(
- "Load balancers must be specified in the form '<kind> <name>', e.g. 'service my-service'",
+ String.format(
+ "Failed to attach load balancer '%s'. Load balancers must be specified in the form '{kind} {name}', e.g. 'service my-service'.",
+ loadBalancerName),
e);
}


message:
refactor(cats-core): Remove Cache.StoreType
We don't need to leak this information to clients. They don't care what
the storage type is; they care whether or not getAllByApplication works,
so just let them ask that.

diff:
@@ -23,11 +23,6 @@ import java.util.Map;
/** Cache provides view access to data keyed by type and identifier. */
public interface Cache {
- enum StoreType {
- REDIS,
- IN_MEMORY,
- SQL
- }
/**
* Gets a single item from the cache by type and id
@@ -122,10 +117,18 @@ public interface Cache {
*/
Collection<CacheData> getAll(String type, String... identifiers);
+ /** Returns whether or not the three {@code getAllByApplication} methods are supported */
+ default boolean supportsGetAllByApplication() {
+ return false;
+ }
+
/**
* Retrieves all items for the specified type associated with the provided application. Requires a
* storeType with secondary indexes and support in the type's caching agent.
*
+ *
Clients should check {@link #supportsGetAllByApplication()} to check if this method is
+ * supported before calling it.
+ *
* @param type the type for which to retrieve items
* @param application the application name
* @return the matching items, keyed by type
@@ -138,6 +141,9 @@ public interface Cache {
* Retrieves all items for the specified type associated with the provided application. Requires a
* storeType with secondary indexes and support in the type's caching agent.
*
+ *
Clients should check {@link #supportsGetAllByApplication()} to check if this method is
+ * supported before calling it.
+ *
* @param type the type for which to retrieve items
* @param application the application name
* @param cacheFilter the cacheFilter to govern which relationships to fetch
@@ -152,6 +158,9 @@ public interface Cache {
* Retrieves all items for the specified type associated with the provided application. Requires a
* storeType with secondary indexes and support in the type's caching agent.
*
+ *
Clients should check {@link #supportsGetAllByApplication()} to check if this method is
+ * supported before calling it.
+ *
* @param types collection of types for which to retrieve items
* @param application the application name
* @param cacheFilters cacheFilters to govern which relationships to fetch, as type to filter
@@ -161,13 +170,4 @@ public interface Cache {
Collection<String> types, String application, Map<String, CacheFilter> cacheFilters) {
throw new UnsupportedCacheMethodException("Method only implemented for StoreType.SQL");
}
-
- /**
- * Get backing store type for Cache implementation
- *
- * @return the backing StoreType
- */
- default StoreType storeType() {
- return StoreType.REDIS;
- }
}

diff:
package com.netflix.spinnaker.cats.cache;
import java.util.*;
-import java.util.stream.Collectors;
/** A cache that provides a unified view of multiples, merging items from each cache together. */
public class CompositeCache implements Cache {
@@ -28,8 +27,9 @@ public class CompositeCache implements Cache {
this.caches = caches;
}
- public Set<StoreType> getStoreTypes() {
- return caches.stream().map(c -> ((Cache) c).storeType()).collect(Collectors.toSet());
+ @Override
+ public boolean supportsGetAllByApplication() {
+ return caches.stream().allMatch(Cache::supportsGetAllByApplication);
}
@Override

diff:
@@ -39,11 +39,6 @@ public class InMemoryCache implements WriteableCache {
private ConcurrentMap<String, ConcurrentMap<String, CacheData>> typeMap =
new ConcurrentHashMap<>();
- @Override
- public StoreType storeType() {
- return StoreType.IN_MEMORY;
- }
-
@Override
public void merge(String type, CacheData cacheData) {
merge(getOrCreate(type, cacheData.getId()), cacheData);

diff:
package com.netflix.spinnaker.cats.sql
import com.netflix.spinnaker.cats.agent.CacheResult
-import com.netflix.spinnaker.cats.cache.Cache.StoreType
-import com.netflix.spinnaker.cats.cache.Cache.StoreType.SQL
import com.netflix.spinnaker.cats.cache.CacheData
import com.netflix.spinnaker.cats.cache.CacheFilter
import com.netflix.spinnaker.cats.cache.DefaultCacheData
@@ -30,8 +28,6 @@ class SqlProviderCache(private val backingStore: WriteableCache) : ProviderCache
}
}
- override fun storeType(): StoreType = SQL
-
/**
* Filters the supplied list of identifiers to only those that exist in the cache.
*
@@ -82,6 +78,10 @@ class SqlProviderCache(private val backingStore: WriteableCache) : ProviderCache
return backingStore.getAll(type, identifiers, cacheFilter)
}
+ override fun supportsGetAllByApplication(): Boolean {
+ return true
+ }
+
override fun getAllByApplication(type: String, application: String): Map<String, MutableCollection<CacheData>> {
return getAllByApplication(type, application, null)
}

diff:
package com.netflix.spinnaker.cats.sql.cache
import com.fasterxml.jackson.databind.ObjectMapper
-import com.netflix.spinnaker.cats.cache.Cache.StoreType
-import com.netflix.spinnaker.cats.cache.Cache.StoreType.SQL
import com.netflix.spinnaker.cats.cache.CacheData
import com.netflix.spinnaker.cats.cache.CacheFilter
import com.netflix.spinnaker.cats.cache.DefaultCacheData
@@ -76,8 +74,6 @@ class SqlCache(
log.info("Configured for $name")
}
- override fun storeType(): StoreType = SQL
-
/**
* Only evicts cache records but not relationship rows
*/
@@ -267,6 +263,10 @@ class SqlCache(
return getAll(type, ids)
}
+ override fun supportsGetAllByApplication(): Boolean {
+ return true
+ }
+
override fun getAllByApplication(
type: String,
application: String,

diff:
@@ -21,7 +21,6 @@ import com.netflix.frigga.ami.AppVersion
import com.netflix.spinnaker.cats.cache.Cache
import com.netflix.spinnaker.cats.cache.CacheData
import com.netflix.spinnaker.cats.cache.CacheFilter
-import com.netflix.spinnaker.cats.cache.CompositeCache
import com.netflix.spinnaker.cats.cache.RelationshipCacheFilter
import com.netflix.spinnaker.clouddriver.aws.AmazonCloudProvider
import com.netflix.spinnaker.clouddriver.aws.data.Keys
@@ -34,7 +33,6 @@ import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component
-import static com.netflix.spinnaker.cats.cache.Cache.StoreType.SQL
import static com.netflix.spinnaker.clouddriver.core.provider.agent.Namespace.*
@Component
@@ -259,12 +257,7 @@ class AmazonClusterProvider implements ClusterProvider<AmazonCluster>, ServerGro
Collection<AmazonCluster> clusters
- // TODO: remove special casing for sql vs. redis; possibly via dropping redis support in the future
- if ((includeDetails && sqlEnabled) &&
- ((cacheView instanceof CompositeCache &&
- (cacheView as CompositeCache).getStoreTypes().every { (it == SQL) }) ||
- (cacheView.storeType() == SQL))
- ) {
+ if (includeDetails && cacheView.supportsGetAllByApplication()) {
clusters = allClustersByApplication(applicationName)
} else {
clusters = translateClusters(resolveRelationshipData(application, CLUSTERS.ns), includeDetails)

original_message:
refactor(cats-core): Remove Cache.StoreType (#4050)

We don't need to leak this information to clients. They don't care what
the storage type is; they care whether or not getAllByApplication works,
so just let them ask that.


diff:
@@ -28,6 +28,7 @@ import com.netflix.spinnaker.clouddriver.helpers.OperationPoller;
import com.netflix.spinnaker.clouddriver.model.ServerGroup;
import com.netflix.spinnaker.clouddriver.orchestration.AtomicOperation;
import java.util.List;
+import java.util.Optional;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@@ -48,11 +49,18 @@ public class ScaleCloudFoundryServerGroupAtomicOperation implements AtomicOperat
final CloudFoundryClient client = description.getClient();
ServerGroup.Capacity capacity = description.getCapacity();
+ boolean scaleStoppedInstance =
+ Optional.ofNullable(description.getScaleStoppedServerGroup()).orElse(false);
+ Integer numInstances =
+ Optional.ofNullable(capacity)
+ .map(c -> scaleStoppedInstance ? capacity.getMax() : capacity.getDesired())
+ .orElse(null);
+
client
.getApplications()
.scaleApplication(
description.getServerGroupId(),
- capacity == null ? null : capacity.getDesired(),
+ numInstances,
description.getMemory(),
description.getDiskQuota());

diff:
@@ -19,7 +19,9 @@ package com.netflix.spinnaker.clouddriver.cloudfoundry.deploy.ops;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.atIndex;
import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.CloudFoundryApiException;
@@ -60,6 +62,25 @@ class ScaleCloudFoundryServerGroupAtomicOperationTest
.has(status("Resized 'myapp'"), atIndex(2));
}
+ @Test
+ void scaleForStoppedServerGroup() {
+ desc.setScaleStoppedServerGroup(true);
+ desc.setCapacity(ServerGroup.Capacity.builder().desired(2).min(2).max(3).build());
+ OperationPoller poller = mock(OperationPoller.class);
+
+ //noinspection unchecked
+ when(poller.waitForOperation(any(Supplier.class), any(), any(), any(), any(), any()))
+ .thenReturn(ProcessStats.State.RUNNING);
+
+ ScaleCloudFoundryServerGroupAtomicOperation op =
+ new ScaleCloudFoundryServerGroupAtomicOperation(poller, desc);
+
+ assertThat(runOperation(op).getHistory())
+ .has(status("Resizing 'myapp'"), atIndex(1))
+ .has(status("Resized 'myapp'"), atIndex(2));
+ verify(client.getApplications()).scaleApplication(any(), eq(3), any(), any());
+ }
+
@Test
void failedToScale() {
OperationPoller poller = mock(OperationPoller.class);

original_message: fix(cf): when scaling cf disabled SG we should use max capacity (#4067)


message: feat(google): permit guest accelerator config for regional server groups with zones explicitly selected

diff:
@@ -64,6 +64,7 @@ import groovy.util.logging.Slf4j
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
+import static com.google.common.base.Preconditions.checkArgument
import static com.netflix.spinnaker.clouddriver.google.deploy.GCEUtil.BACKEND_SERVICE_NAMES
import static com.netflix.spinnaker.clouddriver.google.deploy.GCEUtil.GLOBAL_LOAD_BALANCER_NAMES
import static com.netflix.spinnaker.clouddriver.google.deploy.GCEUtil.LOAD_BALANCING_POLICY
@@ -395,11 +396,14 @@ class BasicGoogleDeployHandler implements DeployHandler<BasicGoogleDeployDescrip
namer.applyMoniker(new GoogleInstanceTemplate(labels: labels), moniker)
// Accelerators are supported for zonal server groups only.
- List<AcceleratorConfig> acceleratorConfigs = description.regional ? [] : description.acceleratorConfigs
+ if (description.acceleratorConfigs) {
+ checkArgument(!description.regional || description.selectZones,
+ "Accelerators are only supported with regional server groups if the zones are specified by the user.");
+ }
def instanceProperties = new InstanceProperties(machineType: machineTypeName,
disks: attachedDisks,
- guestAccelerators: acceleratorConfigs ?: [],
+ guestAccelerators: description.acceleratorConfigs ?: [],
networkInterfaces: [networkInterface],
canIpForward: canIpForward,
metadata: metadata,


message: feat(kubernetes): support rolling restart operation for deployments

diff:
@@ -87,6 +87,7 @@ public final class AtomicOperations {
public static final String PAUSE_ROLLOUT_MANIFEST = "pauseRolloutManifest";
public static final String RESUME_ROLLOUT_MANIFEST = "resumeRolloutManifest";
public static final String UNDO_ROLLOUT_MANIFEST = "undoRolloutManifest";
+ public static final String ROLLING_RESTART_MANIFEST = "rollingRestartManifest";
public static final String DISABLE_MANIFEST = "disableManifest";
public static final String ENABLE_MANIFEST = "enableManifest";

diff:
+/*
+ * Copyright 2019 Google, 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.clouddriver.kubernetes.v2.converter.manifest;
+
+import static com.netflix.spinnaker.clouddriver.orchestration.AtomicOperations.ROLLING_RESTART_MANIFEST;
+
+import com.netflix.spinnaker.clouddriver.kubernetes.KubernetesOperation;
+import com.netflix.spinnaker.clouddriver.kubernetes.deploy.converters.KubernetesAtomicOperationConverterHelper;
+import com.netflix.spinnaker.clouddriver.kubernetes.v2.description.manifest.KubernetesRollingRestartManifestDescription;
+import com.netflix.spinnaker.clouddriver.kubernetes.v2.op.manifest.KubernetesRollingRestartManifestOperation;
+import com.netflix.spinnaker.clouddriver.orchestration.AtomicOperation;
+import com.netflix.spinnaker.clouddriver.security.AbstractAtomicOperationsCredentialsSupport;
+import com.netflix.spinnaker.clouddriver.security.ProviderVersion;
+import java.util.Map;
+import org.springframework.stereotype.Component;
+
+@KubernetesOperation(ROLLING_RESTART_MANIFEST)
+@Component
+public class KubernetesRollingRestartManifestConverter
+ extends AbstractAtomicOperationsCredentialsSupport {
+ @Override
+ public AtomicOperation convertOperation(Map input) {
+ return new KubernetesRollingRestartManifestOperation(convertDescription(input));
+ }
+
+ @Override
+ public KubernetesRollingRestartManifestDescription convertDescription(Map input) {
+ return KubernetesAtomicOperationConverterHelper.convertDescription(
+ input, this, KubernetesRollingRestartManifestDescription.class);
+ }
+
+ @Override
+ public boolean acceptsVersion(ProviderVersion version) {
+ return version == ProviderVersion.v2;
+ }
+}

diff:
+/*
+ * Copyright 2019 Google, 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.clouddriver.kubernetes.v2.description.manifest;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class KubernetesRollingRestartManifestDescription
+ extends KubernetesManifestOperationDescription {}

diff:
+/*
+ * Copyright 2019 Google, 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.clouddriver.kubernetes.v2.op.handler;
+
+import com.netflix.spinnaker.clouddriver.kubernetes.v2.description.manifest.KubernetesKind;
+import com.netflix.spinnaker.clouddriver.kubernetes.v2.security.KubernetesV2Credentials;
+
+public interface CanRollingRestart {
+ KubernetesKind kind();
+
+ default void rollingRestart(KubernetesV2Credentials credentials, String namespace, String name) {
+ credentials.rollingRestart(kind(), namespace, name);
+ }
+}

diff:
@@ -38,7 +38,12 @@ import org.springframework.stereotype.Component;
@Component
public class KubernetesDaemonSetHandler extends KubernetesHandler
- implements CanResize, CanPauseRollout, CanResumeRollout, CanUndoRollout, ServerGroupHandler {
+ implements CanResize,
+ CanPauseRollout,
+ CanResumeRollout,
+ CanUndoRollout,
+ CanRollingRestart,
+ ServerGroupHandler {
@Nonnull
@Override

diff:
@@ -47,6 +47,7 @@ public class KubernetesDeploymentHandler extends KubernetesHandler
CanPauseRollout,
CanResumeRollout,
CanUndoRollout,
+ CanRollingRestart,
ServerGroupManagerHandler {
private static final ImmutableSet<KubernetesApiVersion> SUPPORTED_API_VERSIONS =

diff:
@@ -52,6 +52,7 @@ public class KubernetesStatefulSetHandler extends KubernetesHandler
CanPauseRollout,
CanResumeRollout,
CanUndoRollout,
+ CanRollingRestart,
ServerGroupHandler {
@Nonnull
@Override

diff:
@@ -346,6 +346,31 @@ public class KubectlJobExecutor {
return null;
}
+ public Void rollingRestart(
+ KubernetesV2Credentials credentials, KubernetesKind kind, String namespace, String name) {
+ List<String> command = kubectlNamespacedAuthPrefix(credentials, namespace);
+
+ command.add("rollout");
+ command.add("restart");
+ command.add(kind.toString() + "/" + name);
+
+ JobResult<String> status = jobExecutor.runJob(new JobRequest(command));
+
+ if (status.getResult() != JobResult.Result.SUCCESS) {
+ throw new KubectlException(
+ "Failed to complete rolling restart of "
+ + kind
+ + "/"
+ + name
+ + " from "
+ + namespace
+ + ": "
+ + status.getError());
+ }
+
+ return null;
+ }
+
public KubernetesManifest get(
KubernetesV2Credentials credentials, KubernetesKind kind, String namespace, String name) {
List<String> command =

diff:
+/*
+ * Copyright 2019 Google, 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.clouddriver.kubernetes.v2.op.manifest;
+
+import com.netflix.spinnaker.clouddriver.data.task.Task;
+import com.netflix.spinnaker.clouddriver.data.task.TaskRepository;
+import com.netflix.spinnaker.clouddriver.kubernetes.v2.description.KubernetesCoordinates;
+import com.netflix.spinnaker.clouddriver.kubernetes.v2.description.KubernetesResourceProperties;
+import com.netflix.spinnaker.clouddriver.kubernetes.v2.description.manifest.KubernetesRollingRestartManifestDescription;
+import com.netflix.spinnaker.clouddriver.kubernetes.v2.op.handler.CanRollingRestart;
+import com.netflix.spinnaker.clouddriver.kubernetes.v2.op.handler.KubernetesHandler;
+import com.netflix.spinnaker.clouddriver.kubernetes.v2.security.KubernetesV2Credentials;
+import com.netflix.spinnaker.clouddriver.orchestration.AtomicOperation;
+import java.util.List;
+
+public class KubernetesRollingRestartManifestOperation implements AtomicOperation<Void> {
+ private final KubernetesRollingRestartManifestDescription description;
+ private final KubernetesV2Credentials credentials;
+ private static final String OP_NAME = "ROLLING_RESTART_KUBERNETES_MANIFEST";
+
+ public KubernetesRollingRestartManifestOperation(
+ KubernetesRollingRestartManifestDescription description) {
+ this.description = description;
+ this.credentials = (KubernetesV2Credentials) description.getCredentials().getCredentials();
+ }
+
+ private static Task getTask() {
+ return TaskRepository.threadLocalTask.get();
+ }
+
+ @Override
+ public Void operate(List priorOutputs) {
+ getTask().updateStatus(OP_NAME, "Starting rolling restart operation...");
+ KubernetesCoordinates coordinates = description.getPointCoordinates();
+
+ getTask().updateStatus(OP_NAME, "Looking up resource properties...");
+ KubernetesResourceProperties properties =
+ credentials.getResourcePropertyRegistry().get(coordinates.getKind());
+ KubernetesHandler deployer = properties.getHandler();
+
+ if (!(deployer instanceof CanRollingRestart)) {
+ throw new IllegalArgumentException(
+ "Resource with " + coordinates + " does not support rolling restart");
+ }
+
+ CanRollingRestart canRollingRestart = (CanRollingRestart) deployer;
+
+ getTask().updateStatus(OP_NAME, "Calling rolling restart operation...");
+ canRollingRestart.rollingRestart(
+ credentials, coordinates.getNamespace(), coordinates.getName());
+
+ return null;
+ }
+}

diff:
@@ -531,6 +531,14 @@ public class KubernetesV2Credentials implements KubernetesCredentials {
() -> jobExecutor.resumeRollout(this, kind, namespace, name));
}
+ public void rollingRestart(KubernetesKind kind, String namespace, String name) {
+ runAndRecordMetrics(
+ "rollingRestart",
+ kind,
+ namespace,
+ () -> jobExecutor.rollingRestart(this, kind, namespace, name));
+ }
+
public void patch(
KubernetesKind kind,
String namespace,

diff:
+/*
+ * Copyright 2019 Google, 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.clouddriver.kubernetes.v2.validator.manifest;
+
+import static com.netflix.spinnaker.clouddriver.orchestration.AtomicOperations.ROLLING_RESTART_MANIFEST;
+
+import com.netflix.spinnaker.clouddriver.deploy.DescriptionValidator;
+import com.netflix.spinnaker.clouddriver.kubernetes.KubernetesOperation;
+import com.netflix.spinnaker.clouddriver.kubernetes.v2.description.manifest.KubernetesRollingRestartManifestDescription;
+import com.netflix.spinnaker.clouddriver.kubernetes.v2.validator.KubernetesValidationUtil;
+import com.netflix.spinnaker.clouddriver.security.AccountCredentialsProvider;
+import com.netflix.spinnaker.clouddriver.security.ProviderVersion;
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.validation.Errors;
+
+@KubernetesOperation(ROLLING_RESTART_MANIFEST)
+@Component
+public class KubernetesRollingRestartManifestValidator
+ extends DescriptionValidator<KubernetesRollingRestartManifestDescription> {
+ private final AccountCredentialsProvider provider;
+
+ @Autowired
+ public KubernetesRollingRestartManifestValidator(AccountCredentialsProvider provider) {
+ this.provider = provider;
+ }
+
+ @Override
+ public void validate(
+ List priorDescriptions,
+ KubernetesRollingRestartManifestDescription description,
+ Errors errors) {
+ KubernetesValidationUtil util =
+ new KubernetesValidationUtil("rollingRestartKubernetesManifest", errors);
+ if (!util.validateV2Credentials(
+ provider,
+ description.getAccount(),
+ description.getPointCoordinates().getKind(),
+ description.getPointCoordinates().getNamespace())) {
+ return;
+ }
+ }
+
+ @Override
+ public boolean acceptsVersion(ProviderVersion version) {
+ return version == ProviderVersion.v2;
+ }
+}


message: fix(kubernetes): add param to KubernetesAtomicOperationDescription to prevent having to cast credentials

diff:
@@ -20,13 +20,15 @@ package com.netflix.spinnaker.clouddriver.kubernetes.v2.description.manifest;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.netflix.spinnaker.clouddriver.kubernetes.description.KubernetesAtomicOperationDescription;
import com.netflix.spinnaker.clouddriver.kubernetes.v2.description.KubernetesCoordinates;
+import com.netflix.spinnaker.clouddriver.kubernetes.v2.security.KubernetesV2Credentials;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.lang3.tuple.Pair;
@EqualsAndHashCode(callSuper = true)
@Data
-public class KubernetesManifestOperationDescription extends KubernetesAtomicOperationDescription {
+public class KubernetesManifestOperationDescription
+ extends KubernetesAtomicOperationDescription<KubernetesV2Credentials> {
private String manifestName;
private String location;

diff:
@@ -35,7 +35,7 @@ public class KubernetesRollingRestartManifestOperation implements AtomicOperatio
public KubernetesRollingRestartManifestOperation(
KubernetesRollingRestartManifestDescription description) {
this.description = description;
- this.credentials = (KubernetesV2Credentials) description.getCredentials().getCredentials();
+ this.credentials = description.getCredentials().getCredentials();
}
private static Task getTask() {

diff:
@@ -19,6 +19,7 @@ package com.netflix.spinnaker.clouddriver.kubernetes.description;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.netflix.spinnaker.clouddriver.deploy.DeployDescription;
+import com.netflix.spinnaker.clouddriver.kubernetes.security.KubernetesCredentials;
import com.netflix.spinnaker.clouddriver.kubernetes.security.KubernetesNamedAccountCredentials;
import com.netflix.spinnaker.clouddriver.security.resources.CredentialsNameable;
import lombok.AllArgsConstructor;
@@ -28,10 +29,10 @@ import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
-public class KubernetesAtomicOperationDescription
+public class KubernetesAtomicOperationDescription<C extends KubernetesCredentials>
implements DeployDescription, CredentialsNameable {
@JsonProperty("account")
String account;
- KubernetesNamedAccountCredentials credentials;
+ KubernetesNamedAccountCredentials<C> credentials;
}


diff:
@@ -132,12 +132,7 @@ class UpsertGoogleLoadBalancerAtomicOperation extends GoogleAtomicOperation<Map>
project, region, GCEUtil.getLocalName(existingForwardingRule.target), compute, task, BASE_PHASE, this)
if (existingTargetPool) {
- GoogleSessionAffinity newSessionAffinity
- if (description.sessionAffinity == null) {
- newSessionAffinity = GoogleSessionAffinity.NONE
- } else {
- newSessionAffinity = description.sessionAffinity
- }
+ GoogleSessionAffinity newSessionAffinity = Optional.ofNullable(description.sessionAffinity).orElse(GoogleSessionAffinity.NONE)
boolean sessionAffinityChanged = newSessionAffinity != GoogleSessionAffinity.valueOf(existingTargetPool.getSessionAffinity())
if (sessionAffinityChanged && existingTargetPool.instances.any()) {
task.updateStatus BASE_PHASE, "Impossible to change Session Affinity for target pool with existing instances."

original_message: refactor(google): use Optional.ofNullable() instead of if/else (#4093)


message:
chore(dependencies): upgrade kork
I don't know why we I was using these deprecated (and now deleted)
classes in the tests; we don't use them in the actual stateful disk
code. (The test is just making sure we copy statefulPolicy over, so
doesn't really care what is in it.)

diff:
@@ -13,8 +13,7 @@ dependencies {
implementation "org.codehaus.groovy:groovy-all"
implementation "org.apache.commons:commons-lang3"
implementation "com.google.apis:google-api-services-compute"
- // Move this to spinnaker-dependencies when we can confirm we'll use this feature.
- implementation "com.google.apis:google-api-services-iam:v1-rev267-1.25.0"
+ implementation "com.google.apis:google-api-services-iam"
implementation 'com.google.auth:google-auth-library-oauth2-http'
implementation "com.netflix.frigga:frigga"
implementation "com.netflix.spectator:spectator-api"

diff:
@@ -44,8 +44,8 @@ import com.google.api.services.compute.model.NamedPort;
import com.google.api.services.compute.model.NetworkInterface;
import com.google.api.services.compute.model.ServiceAccount;
import com.google.api.services.compute.model.StatefulPolicy;
-import com.google.api.services.compute.model.StatefulPolicyPreservedDisk;
-import com.google.api.services.compute.model.StatefulPolicyPreservedResources;
+import com.google.api.services.compute.model.StatefulPolicyPreservedState;
+import com.google.api.services.compute.model.StatefulPolicyPreservedStateDiskDevice;
import com.google.api.services.compute.model.Tags;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -111,11 +111,11 @@ class AbstractGoogleServerGroupCachingAgentTest {
.setTargetSize(3)
.setStatefulPolicy(
new StatefulPolicy()
- .setPreservedResources(
- new StatefulPolicyPreservedResources()
+ .setPreservedState(
+ new StatefulPolicyPreservedState()
.setDisks(
- ImmutableList.of(
- new StatefulPolicyPreservedDisk().setDeviceName("myDisk")))))
+ ImmutableMap.of(
+ "myDisk", new StatefulPolicyPreservedStateDiskDevice()))))
.setAutoHealingPolicies(
ImmutableList.of(
new InstanceGroupManagerAutoHealingPolicy().setInitialDelaySec(92)));

diff:
includeCloudProviders=all
fiatVersion=1.7.2
enablePublishing=false
-korkVersion=6.14.3
+korkVersion=6.15.0
spinnakerGradleVersion=7.0.2
org.gradle.parallel=true

original_message:
chore(dependencies): upgrade kork (#4124)

I don't know why we I was using these deprecated (and now deleted)
classes in the tests; we don't use them in the actual stateful disk
code. (The test is just making sure we copy statefulPolicy over, so
doesn't really care what is in it.)


diff:
@@ -98,8 +98,6 @@ public class KubernetesDeployManifestOperation implements AtomicOperation<Operat
Set<Artifact> boundArtifacts = new HashSet<>();
- validateManifestsForRolloutStrategies(inputManifests);
-
for (KubernetesManifest manifest : inputManifests) {
if (credentials.getKindRegistry().getKindProperties(manifest.getKind()).isNamespaced()) {
if (!StringUtils.isEmpty(description.getNamespaceOverride())) {
@@ -251,15 +249,6 @@ public class KubernetesDeployManifestOperation implements AtomicOperation<Operat
traffic.getLoadBalancers().forEach(l -> attachLoadBalancer(l, target));
}
- private void validateManifestsForRolloutStrategies(List<KubernetesManifest> manifests) {
- long numReplicaSets =
- manifests.stream().filter(m -> m.getKind().equals(KubernetesKind.REPLICA_SET)).count();
- if (description.getStrategy() != null && numReplicaSets != 1) {
- throw new RuntimeException(
- "Spinnaker can manage traffic for one ReplicaSet only. Please deploy one ReplicaSet manifest or disable rollout strategies.");
- }
- }
-
private void attachLoadBalancer(String loadBalancerName, KubernetesManifest target) {
Pair<KubernetesKind, String> name;
try {

original_message: feat(kubernetes): permit multiple ReplicaSets to be deployed with a single rollout strategy config (#4126)