message: feat(cf): add clone SG pipeline stage

diff:
@@ -294,7 +294,7 @@ public class Applications {
public void updateProcess(String guid, @Nullable String command, @Nullable String healthCheckType, @Nullable String healthCheckEndpoint) throws CloudFoundryApiException {
final Process.HealthCheck healthCheck = healthCheckType != null ?
new Process.HealthCheck().setType(healthCheckType) : null;
- if (healthCheckEndpoint != null) {
+ if (healthCheckEndpoint != null && !healthCheckEndpoint.isEmpty()) {
healthCheck.setData(new Process.HealthCheckData().setEndpoint(healthCheckEndpoint));
}
safelyCall(() -> api.updateProcess(guid, new UpdateProcess(command, healthCheck)));

diff:
@@ -42,6 +42,7 @@ import javax.annotation.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import static io.vavr.API.*;
import static java.util.stream.Collectors.toList;
@@ -69,16 +70,31 @@ public class DeployCloudFoundryServerGroupAtomicOperationConverter extends Abstr
@Override
public DeployCloudFoundryServerGroupDescription convertDescription(Map input) {
DeployCloudFoundryServerGroupDescription converted = getObjectMapper().convertValue(input, DeployCloudFoundryServerGroupDescription.class);
- CloudFoundryCredentials credentials = getCredentialsObject(input.get("credentials").toString());
+ String deployCredentials = Optional.ofNullable(converted.getDestination())
+ .map(DeployCloudFoundryServerGroupDescription.Destination::getAccount)
+ .orElse(input.get("credentials").toString());
+ CloudFoundryCredentials credentials = getCredentialsObject(deployCredentials);
converted.setClient(credentials.getClient());
converted.setAccountName(credentials.getName());
- converted.setSpace(findSpace(converted.getRegion(), converted.getClient())
- .orElseThrow(() -> new IllegalArgumentException("Unable to find space '" + converted.getRegion() + "'.")));
+ String region = Optional.ofNullable(converted.getDestination())
+ .map(DeployCloudFoundryServerGroupDescription.Destination::getRegion)
+ .orElse(converted.getRegion());
+ converted.setSpace(findSpace(region, converted.getClient())
+ .orElseThrow(() -> new IllegalArgumentException("Unable to find space '" + region + "'.")));
Map artifactSource = (Map) input.get("artifact");
- if ("artifact".equals(artifactSource.get("type"))) {
+ if (Optional.ofNullable(converted.getSource()).isPresent()) {
+ CloudFoundryCredentials artifactCredentials = getCredentialsObject(converted.getSource().getAccount());
+ converted.setArtifactCredentials(new PackageArtifactCredentials(artifactCredentials.getClient()));
+
+ Artifact artifact = new Artifact();
+ artifact.setType("package");
+ artifact.setReference(getServerGroupId(converted.getSource().getAsgName(),
+ converted.getSource().getRegion(), artifactCredentials.getClient()));
+ converted.setArtifact(artifact);
+ } else {
ArtifactCredentials artifactCredentials = credentialsRepository.getAllCredentials().stream()
.filter(creds -> creds.getName().equals(artifactSource.get("account")))
.findAny()
@@ -86,15 +102,6 @@ public class DeployCloudFoundryServerGroupAtomicOperationConverter extends Abstr
converted.setArtifact(convertToArtifact(artifactCredentials, artifactSource.get("reference").toString()));
converted.setArtifactCredentials(artifactCredentials);
- } else if ("package".equals(artifactSource.get("type"))) {
- CloudFoundryCredentials artifactCredentials = getCredentialsObject(artifactSource.get("account").toString());
- converted.setArtifactCredentials(new PackageArtifactCredentials(artifactCredentials.getClient()));
-
- Artifact artifact = new Artifact();
- artifact.setType("package");
- artifact.setReference(getServerGroupId(artifactSource.get("serverGroupName").toString(),
- artifactSource.get("region").toString(), artifactCredentials.getClient()));
- converted.setArtifact(artifact);
}
Map manifest = (Map) input.get("manifest");

diff:
@@ -35,9 +35,15 @@ public class DeployCloudFoundryServerGroupDescription extends AbstractCloudFound
private String accountName;
private String application;
private String stack;
- private String detail;
+ private String freeFormDetails;
private CloudFoundrySpace space;
+ @Nullable
+ private Source source;
+
+ @Nullable
+ private Destination destination;
+
@JsonIgnore
private Artifact artifact;
@@ -71,4 +77,17 @@ public class DeployCloudFoundryServerGroupDescription extends AbstractCloudFound
@Nullable
private List<String> services;
}
+
+ @Data
+ public static class Source {
+ String account;
+ String region;
+ String asgName;
+ }
+
+ @Data
+ public static class Destination {
+ String account;
+ String region;
+ }
}

diff:
@@ -33,6 +33,7 @@ import javax.annotation.Nullable;
import java.io.*;
import java.util.Collections;
import java.util.List;
+import java.util.Optional;
import java.util.function.Function;
import static com.netflix.spinnaker.clouddriver.cloudfoundry.deploy.ops.CloudFoundryOperationUtils.describeProcessState;
@@ -63,7 +64,7 @@ public class DeployCloudFoundryServerGroupAtomicOperation
clusterProvider, description.getSpace());
description.setServerGroupName(serverGroupNameResolver.resolveNextServerGroupName(description.getApplication(),
- description.getStack(), description.getDetail(), false));
+ description.getStack(), description.getFreeFormDetails(), false));
final CloudFoundryServerGroup serverGroup = createApplication(description);
String packageId = buildPackage(serverGroup.getId(), description);
@@ -99,8 +100,11 @@ public class DeployCloudFoundryServerGroupAtomicOperation
private DeploymentResult deploymentResult() {
DeploymentResult deploymentResult = new DeploymentResult();
- deploymentResult.setServerGroupNames(Collections.singletonList(description.getRegion() + ":" + description.getServerGroupName()));
- deploymentResult.getServerGroupNameByRegion().put(description.getRegion(), description.getServerGroupName());
+ String destinationRegion = Optional.ofNullable(description.getDestination())
+ .map(DeployCloudFoundryServerGroupDescription.Destination::getRegion)
+ .orElse(description.getRegion());
+ deploymentResult.setServerGroupNames(Collections.singletonList(destinationRegion + ":" + description.getServerGroupName()));
+ deploymentResult.getServerGroupNameByRegion().put(destinationRegion, description.getServerGroupName());
deploymentResult.setMessages(getTask().getHistory().stream()
.map(hist -> hist.getPhase() + ":" + hist.getStatus())
.collect(toList()));

diff:
@@ -62,16 +62,16 @@ class DeployCloudFoundryServerGroupAtomicOperationConverterTest {
});
}
- return new CloudFoundryCredentials(name, "", "", "", "", "", "")
- {
+ return new CloudFoundryCredentials(name, "", "", "", "", "", "") {
public CloudFoundryClient getClient() {
return cloudFoundryClient;
}
};
}
- private List<String> accounts = List.of("test", "sourceAccount", "destinationAccount");
+ private List<String> accounts = List.of("test", "sourceAccount", "sourceAccount1", "sourceAccount2", "destinationAccount");
private final ArtifactCredentialsRepository artifactCredentialsRepository = new ArtifactCredentialsRepository();
+
{
accounts.toStream().forEach(account -> artifactCredentialsRepository.save(new ArtifactCredentialsFromString(
account,
@@ -81,9 +81,11 @@ class DeployCloudFoundryServerGroupAtomicOperationConverterTest {
}
private final AccountCredentialsRepository accountCredentialsRepository = new MapBackedAccountCredentialsRepository();
+
{
accounts.toStream().forEach(account -> accountCredentialsRepository.update(account, createCredentials(account)));
}
+
private final AccountCredentialsProvider accountCredentialsProvider =
new DefaultAccountCredentialsProvider(accountCredentialsRepository);
@@ -97,14 +99,13 @@ class DeployCloudFoundryServerGroupAtomicOperationConverterTest {
}
@Test
- void convertDescriptionWithPackageArtifactSourceAndDownloadedManifest() {
+ void convertDescriptionWitSourceSet() {
final Map input = HashMap.of(
"credentials", "destinationAccount",
"region", "org > space",
- "artifact", HashMap.of(
- "type", "package",
+ "source", HashMap.of(
"account", "sourceAccount",
- "serverGroupName", "serverGroupName1",
+ "asgName", "serverGroupName1",
"region", "org > space"
).toJavaMap(),
"manifest", HashMap.of(
@@ -132,6 +133,45 @@ class DeployCloudFoundryServerGroupAtomicOperationConverterTest {
);
}
+ @Test
+ void convertDescriptionWithDestinationSet() {
+ final Map input = HashMap.of(
+ "credentials", "destinationAccount",
+ "region", "org > space",
+ "destination", HashMap.of(
+ "account", "sourceAccount1",
+ "region", "org1 > space1"
+ ).toJavaMap(),
+ "source", HashMap.of(
+ "account", "sourceAccount2",
+ "asgName", "serverGroupName1",
+ "region", "org > space"
+ ).toJavaMap(),
+ "manifest", HashMap.of(
+ "type", "artifact",
+ "account", "test",
+ "reference", "ref1"
+ ).toJavaMap()
+ ).toJavaMap();
+
+ final DeployCloudFoundryServerGroupDescription result = converter.convertDescription(input);
+
+ CloudFoundryCredentials sourceCredentials = converter.getCredentialsObject("sourceAccount2");
+ assertThat(result.getAccountName()).isEqualTo("sourceAccount1");
+ assertThat(result.getArtifactCredentials())
+ .isEqualToComparingFieldByFieldRecursively(new PackageArtifactCredentials(sourceCredentials.getClient()));
+ assertThat(result.getSpace()).isEqualToComparingFieldByFieldRecursively(
+ CloudFoundrySpace.builder().id("space1ID").name("space1").organization(
+ CloudFoundryOrganization.builder().id("org1ID").name("org1").build()).build());
+ assertThat(result.getApplicationAttributes()).isEqualToComparingFieldByFieldRecursively(
+ new DeployCloudFoundryServerGroupDescription.ApplicationAttributes()
+ .setInstances(42)
+ .setMemory("1024")
+ .setDiskQuota("1024")
+ .setBuildpacks(Collections.emptyList())
+ );
+ }
+
@Test
void convertManifestMapToApplicationAttributes() {
final Map input = HashMap.of(

diff:
@@ -72,7 +72,7 @@ class DeployCloudFoundryServerGroupAtomicOperationTest extends AbstractCloudFoun
.setAccountName("account1")
.setApplication("app1")
.setStack("stack1")
- .setDetail("detail1")
+ .setFreeFormDetails("detail1")
.setArtifactCredentials(new ArtifactCredentialsFromString(
"test",
io.vavr.collection.List.of("a").asJava(),

original_message: feat(cf): add clone SG pipeline stage (#3384)


change_type: MODIFY

diff:
@@ -63,6 +63,8 @@ public class KubernetesUnregisteredCustomResourceCachingAgent extends Kubernetes
@Override
protected List<KubernetesKind> primaryKinds() {
- return credentials.getCrds();
+ return credentials.getCrds().stream()
+ .filter(credentials::isValidKind)
+ .collect(Collectors.toList());
}
}

diff:
@@ -385,10 +385,6 @@ public class KubernetesV2Credentials implements KubernetesCredentials {
.map(KubernetesManifest::getName)
.collect(Collectors.toList()), namespaceExpirySeconds, TimeUnit.SECONDS);
- if (checkPermissionsOnStartup) {
- determineOmitKinds();
- }
-
this.liveCrdSupplier = Suppliers.memoizeWithExpiration(() -> {
try {
return this.list(KubernetesKind.CUSTOM_RESOURCE_DEFINITION, "")
@@ -410,6 +406,10 @@ public class KubernetesV2Credentials implements KubernetesCredentials {
// ensure this is called at least once before the credentials object is created to ensure all crds are registered
this.liveCrdSupplier.get();
+
+ if (checkPermissionsOnStartup) {
+ determineOmitKinds();
+ }
}
public List<KubernetesKind> getCrds() {

license: Apache License 2.0

repo: spinnaker/clouddriver

original_message: fix(kubernetes): return only valid kinds from UnregisteredCrdCachingAgent.primaryKinds (#3394)


diff:
@@ -63,7 +63,7 @@ public class JobExecutorLocal implements JobExecutor {
success = true;
}
} catch (IOException e) {
- throw new RuntimeException("Failed to execute job");
+ throw new RuntimeException("Failed to execute job", e);
}
return JobStatus.builder()

original_message: chore(core): Throw with underlying exception when LocalJobExecutor fails (#3423)


diff:
@@ -24,10 +24,12 @@ import com.netflix.spinnaker.clouddriver.security.AccountCredentials;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
+import javax.annotation.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonMap;
@@ -37,9 +39,14 @@ import static java.util.stream.Collectors.toList;
@Getter
@JsonIgnoreProperties({"credentials", "client"})
public class CloudFoundryCredentials implements AccountCredentials<CloudFoundryClient> {
+
private final String name;
+
+ @Nullable
private final String environment;
+
private final String accountType = "cloudfoundry";
+
private final String cloudProvider = "cloudfoundry";
@Deprecated
@@ -49,7 +56,7 @@ public class CloudFoundryCredentials implements AccountCredentials<CloudFoundryC
public CloudFoundryCredentials(String name, String appsManagerUri, String metricsUri, String apiHost, String userName, String password, String environment) {
this.name = name;
- this.environment = environment;
+ this.environment = Optional.ofNullable(environment).orElse("dev");
this.credentials = new HttpCloudFoundryClient(name, appsManagerUri, metricsUri, apiHost, userName, password);
}

original_message:
feat(cf): default env if not set for account (#3425)

Co-Authored-By: Ria Stein <eleftheria.kousathana@gmail.com>


diff:
package com.netflix.spinnaker.clouddriver.cloudfoundry.deploy.ops;
+import com.netflix.spinnaker.clouddriver.cloudfoundry.client.CloudFoundryApiException;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.CloudFoundryClient;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.model.RouteId;
import com.netflix.spinnaker.clouddriver.cloudfoundry.deploy.description.UpsertCloudFoundryLoadBalancerDescription;
@@ -47,8 +48,7 @@ public class UpsertCloudFoundryLoadBalancerAtomicOperation implements AtomicOper
if (loadBalancer != null) {
getTask().updateStatus(PHASE, "Done creating load balancer");
} else {
- getTask().updateStatus(PHASE, "Load balancer already exists in another organization and space");
- getTask().fail();
+ throw new CloudFoundryApiException("Load balancer already exists in another organization and space");
}
return loadBalancer;

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.cloudfoundry.deploy.ops;
+
+import com.netflix.spinnaker.clouddriver.cloudfoundry.client.CloudFoundryApiException;
+import com.netflix.spinnaker.clouddriver.cloudfoundry.client.Routes;
+import com.netflix.spinnaker.clouddriver.cloudfoundry.deploy.description.UpsertCloudFoundryLoadBalancerDescription;
+import com.netflix.spinnaker.clouddriver.cloudfoundry.model.CloudFoundryDomain;
+import com.netflix.spinnaker.clouddriver.cloudfoundry.model.CloudFoundryLoadBalancer;
+import com.netflix.spinnaker.clouddriver.cloudfoundry.model.CloudFoundrySpace;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.when;
+
+class UpsertCloudFoundryLoadBalancerAtomicOperationTest extends AbstractCloudFoundryAtomicOperationTest {
+ private final UpsertCloudFoundryLoadBalancerDescription desc;
+ private final Routes routes;
+
+ {
+ desc = new UpsertCloudFoundryLoadBalancerDescription();
+ desc.setClient(client);
+ desc.setRegion("org>space");
+ desc.setSpace(CloudFoundrySpace.fromRegion("org>space"));
+ desc.setHost("some-host");
+ desc.setPath("some-path");
+ desc.setPort(8080);
+ desc.setDomain(CloudFoundryDomain.builder().build());
+ routes = client.getRoutes();
+ }
+
+ @Test
+ void operateSuccessfullyCreatedLoadBalancer() {
+ when(routes.createRoute(any(), any())).thenReturn(CloudFoundryLoadBalancer.builder().build());
+
+ UpsertCloudFoundryLoadBalancerAtomicOperation op = new UpsertCloudFoundryLoadBalancerAtomicOperation(desc);
+
+ assertThat(runOperation(op).getHistory())
+ .has(status("Creating load balancer in 'org>space'"), atIndex(1))
+ .has(status("Done creating load balancer"), atIndex(2));
+ }
+
+ @Test
+ void operateThrowCloudFoundryApiExceptionWhenRouteExists() {
+ when(routes.createRoute(any(), any())).thenReturn(null);
+
+ UpsertCloudFoundryLoadBalancerAtomicOperation op = new UpsertCloudFoundryLoadBalancerAtomicOperation(desc);
+
+ try {
+ runOperation(op);
+ failBecauseExceptionWasNotThrown(CloudFoundryApiException.class);
+ } catch (CloudFoundryApiException e) {
+ assertThat(e.getMessage()).contains("Load balancer already exists in another organization and space");
+ } catch (Throwable t) {
+ fail("Expected CloudFoundryApiException; got " + t);
+ }
+ }
+}

original_message:
feat(cf): standardize exceptions when creating load balancer (#3426)

Co-Authored-By: Ria Stein <eleftheria.kousathana@gmail.com>


diff:
package com.netflix.spinnaker.clouddriver.cloudfoundry.deploy.ops;
+import com.netflix.spinnaker.clouddriver.cloudfoundry.client.CloudFoundryApiException;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.CloudFoundryClient;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.model.RouteId;
import com.netflix.spinnaker.clouddriver.cloudfoundry.deploy.description.AbstractCloudFoundryServerGroupDescription;
@@ -47,35 +48,22 @@ public abstract class AbstractCloudFoundryLoadBalancerMappingOperation {
getTask().updateStatus(getPhase(), "Creating or updating load balancers");
- List<String> invalidRoutes = new ArrayList<>();
-
CloudFoundryClient client = description.getClient();
List<RouteId> routeIds = routes.stream()
.map(routePath -> {
RouteId routeId = client.getRoutes().toRouteId(routePath);
if (routeId == null) {
- invalidRoutes.add(routePath);
+ throw new IllegalArgumentException(routePath + "is an invalid route");
}
return routeId;
})
.filter(Objects::nonNull)
.collect(toList());
- for (String routePath : invalidRoutes) {
- getTask().updateStatus(getPhase(), "Invalid format or domain for load balancer '" + routePath + "'");
- }
-
- if (!invalidRoutes.isEmpty()) {
- getTask().fail();
- return false;
- }
-
for (RouteId routeId : routeIds) {
CloudFoundryLoadBalancer loadBalancer = client.getRoutes().createRoute(routeId, space.getId());
if (loadBalancer == null) {
- getTask().updateStatus(getPhase(), "Load balancer already exists in another organization and space");
- getTask().fail();
- return false;
+ throw new CloudFoundryApiException("Load balancer already exists in another organization and space");
}
getTask().updateStatus(getPhase(), "Mapping load balancer '" + loadBalancer.getName() + "' to " + description.getServerGroupName());
client.getApplications().mapRoute(serverGroupId, loadBalancer.getId());

diff:
package com.netflix.spinnaker.clouddriver.cloudfoundry.deploy.ops;
+import com.netflix.spinnaker.clouddriver.cloudfoundry.client.CloudFoundryApiException;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.CloudFoundryClient;
import com.netflix.spinnaker.clouddriver.cloudfoundry.deploy.description.DeleteCloudFoundryLoadBalancerDescription;
import com.netflix.spinnaker.clouddriver.data.task.Task;
@@ -39,8 +40,7 @@ public class DeleteCloudFoundryLoadBalancerAtomicOperation implements AtomicOper
CloudFoundryClient client = description.getClient();
if (description.getLoadBalancer() == null) {
- getTask().updateStatus(PHASE, "Load balancer does not exist");
- getTask().fail();
+ throw new CloudFoundryApiException("Load balancer does not exist");
} else {
getTask().updateStatus(PHASE, "Deleting load balancer " + description.getLoadBalancer().getName());
client.getRoutes().deleteRoute(description.getLoadBalancer().getId());

diff:
package com.netflix.spinnaker.clouddriver.cloudfoundry.deploy.ops;
+import com.netflix.spinnaker.clouddriver.cloudfoundry.client.CloudFoundryApiException;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.CloudFoundryClient;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.model.v3.ProcessStats;
import com.netflix.spinnaker.clouddriver.cloudfoundry.deploy.CloudFoundryServerGroupNameResolver;
@@ -98,9 +99,7 @@ public class DeployCloudFoundryServerGroupAtomicOperation
null, getTask(), description.getServerGroupName(), PHASE);
if (state != ProcessStats.State.RUNNING) {
- getTask().updateStatus(PHASE, "Failed to start '" + description.getServerGroupName() + "' which instead " + describeProcessState(state));
- getTask().fail();
- return null;
+ throw new CloudFoundryApiException("Failed to start '" + description.getServerGroupName() + "' which instead " + describeProcessState(state));
}
} else {
getTask().updateStatus(PHASE, "Stop state requested for '" + description.getServerGroupName());

diff:
package com.netflix.spinnaker.clouddriver.cloudfoundry.deploy.ops;
+import com.netflix.spinnaker.clouddriver.cloudfoundry.client.CloudFoundryApiException;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.CloudFoundryClient;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.model.v3.ProcessStats;
import com.netflix.spinnaker.clouddriver.cloudfoundry.deploy.description.ScaleCloudFoundryServerGroupDescription;
@@ -67,8 +68,7 @@ public class ScaleCloudFoundryServerGroupAtomicOperation implements AtomicOperat
(state == ProcessStats.State.DOWN && description.getCapacity().getDesired() == 0)) {
getTask().updateStatus(PHASE, "Resized '" + description.getServerGroupName() + "'");
} else {
- getTask().updateStatus(PHASE, "Failed to start '" + description.getServerGroupName() + "' which instead " + describeProcessState(state));
- getTask().fail();
+ throw new CloudFoundryApiException("Failed to start '" + description.getServerGroupName() + "' which instead " + describeProcessState(state));
}
return null;

diff:
package com.netflix.spinnaker.clouddriver.cloudfoundry.deploy.ops;
+import com.netflix.spinnaker.clouddriver.cloudfoundry.client.CloudFoundryApiException;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.CloudFoundryClient;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.model.v3.ProcessStats;
import com.netflix.spinnaker.clouddriver.cloudfoundry.deploy.description.StartCloudFoundryServerGroupDescription;
@@ -54,9 +55,7 @@ public class StartCloudFoundryServerGroupAtomicOperation implements AtomicOperat
null, getTask(), description.getServerGroupName(), PHASE);
if (state != ProcessStats.State.RUNNING) {
- getTask().updateStatus(PHASE, "Failed to start '" + description.getServerGroupName() + "' which instead " + describeProcessState(state));
- getTask().fail();
- return null;
+ throw new CloudFoundryApiException("Failed to start '" + description.getServerGroupName() + "' which instead " + describeProcessState(state));
}
getTask().updateStatus(PHASE, "Started '" + description.getServerGroupName() + "'");

diff:
package com.netflix.spinnaker.clouddriver.cloudfoundry.deploy.ops;
+import com.netflix.spinnaker.clouddriver.cloudfoundry.client.CloudFoundryApiException;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.CloudFoundryClient;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.model.v3.ProcessStats;
import com.netflix.spinnaker.clouddriver.cloudfoundry.deploy.description.StopCloudFoundryServerGroupDescription;
@@ -52,9 +53,7 @@ public class StopCloudFoundryServerGroupAtomicOperation implements AtomicOperati
null, getTask(), description.getServerGroupName(), PHASE);
if (state != ProcessStats.State.DOWN) {
- getTask().updateStatus(PHASE, "Failed to stop '" + description.getServerGroupName() + "' which instead " + describeProcessState(state));
- getTask().fail();
- return null;
+ throw new CloudFoundryApiException("Failed to stop '" + description.getServerGroupName() + "' which instead " + describeProcessState(state));
}
getTask().updateStatus(PHASE, "Stopped '" + description.getServerGroupName() + "'");

diff:
@@ -43,7 +43,6 @@ public class TerminateCloudFoundryInstancesAtomicOperation implements AtomicOper
getTask().updateStatus(PHASE, "Terminating " + instanceDescription());
final CloudFoundryClient client = description.getClient();
- boolean oneOrMoreFailed = false;
for (String instance : description.getInstanceIds()) {
try {
String serverGroupId = instance.substring(0, instance.lastIndexOf("-"));
@@ -51,15 +50,10 @@ public class TerminateCloudFoundryInstancesAtomicOperation implements AtomicOper
client.getApplications().deleteAppInstance(serverGroupId, instanceIndex);
getTask().updateStatus(PHASE, "Terminated " + instanceDescription());
} catch (CloudFoundryApiException e) {
- getTask().updateStatus(PHASE, "Failed to terminate '" + instance + "': " + e.getMessage());
- oneOrMoreFailed = true;
+ throw new CloudFoundryApiException("Failed to terminate '" + instance + "': " + e.getMessage());
}
}
- if (oneOrMoreFailed) {
- getTask().fail();
- }
-
return null;
}

diff:
package com.netflix.spinnaker.clouddriver.cloudfoundry.deploy.ops;
+import com.netflix.spinnaker.clouddriver.cloudfoundry.client.CloudFoundryApiException;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.Routes;
import com.netflix.spinnaker.clouddriver.cloudfoundry.deploy.description.LoadBalancersDescription;
import com.netflix.spinnaker.clouddriver.cloudfoundry.model.CloudFoundryLoadBalancer;
@@ -47,33 +48,26 @@ public class UnmapLoadBalancersAtomicOperation
List<String> routeList = description.getRoutes();
if (routeList == null || routeList.size() == 0) {
- getTask().updateStatus(PHASE, "No load balancer specified");
- getTask().fail();
+ throw new CloudFoundryApiException("No load balancer specified");
} else {
Routes routes = description.getClient().getRoutes();
Map<String, Optional<CloudFoundryLoadBalancer>> lbMap = routeList.stream()
.collect(Collectors.toMap(uri -> uri, uri -> Optional.ofNullable(routes.find(routes.toRouteId(uri), description.getSpace().getId()))));
- AtomicReference<Boolean> failedUnmap = new AtomicReference<>(Boolean.FALSE);
routeList.forEach(uri -> {
if (!Routes.isValidRouteFormat(uri)) {
- getTask().updateStatus(PHASE, "Invalid format for load balancer '" + uri + "'");
- failedUnmap.set(Boolean.TRUE);
+ throw new CloudFoundryApiException("Invalid format for load balancer '" + uri + "'");
} else if (!lbMap.get(uri).isPresent()) {
- getTask().updateStatus(PHASE, "Load balancer '" + uri + "' does not exist");
- failedUnmap.set(Boolean.TRUE);
+ throw new CloudFoundryApiException("Load balancer '" + uri + "' does not exist");
}
});
- if (failedUnmap.get()) {
- getTask().fail();
- } else {
+
lbMap.forEach((uri, o) -> {
getTask().updateStatus(PHASE, "Unmapping load balancer '" + uri + "'");
o.ifPresent(lb -> routes.deleteRoute(lb.getId()));
getTask().updateStatus(PHASE, "Unmapped load balancer '" + uri + "'");
});
}
- }
return null;
}

diff:
package com.netflix.spinnaker.clouddriver.cloudfoundry.deploy.ops;
+import com.netflix.spinnaker.clouddriver.cloudfoundry.client.CloudFoundryApiException;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.CloudFoundryClient;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.MockCloudFoundryClient;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.model.RouteId;
@@ -91,7 +92,7 @@ class AbstractCloudFoundryLoadBalancerMappingOperationTest {
}
@Test
- void mapRoutesShouldReturnFalseWhenInvalidRoutesAreFound() {
+ void mapRoutesShouldThrowAnExceptionWhenInvalidRoutesAreFound() {
AbstractCloudFoundryServerGroupDescription description = new AbstractCloudFoundryServerGroupDescription() {};
description.setClient(client);
description.setServerGroupName("sg-name");
@@ -100,12 +101,18 @@ class AbstractCloudFoundryLoadBalancerMappingOperationTest {
List<String> routeList = Collections.singletonList("road.to.nowhere");
- assertThat(operation.mapRoutes(description, routeList, null, null)).isFalse();
- assertThat(testTask.getHistory()).has(status("Invalid format or domain for load balancer 'road.to.nowhere'"), atIndex(2));
+ Exception exception = null;
+ try {
+ operation.mapRoutes(description, routeList, null, null);
+ } catch (IllegalArgumentException illegalArgumentException) {
+ exception = illegalArgumentException;
+ }
+ assertThat(exception).isNotNull();
+ assertThat(exception.getMessage()).isEqualTo("road.to.nowhereis an invalid route");
}
@Test
- void mapRoutesShouldReturnFalseWhenRoutesExistInOtherOrgSpace() {
+ void mapRoutesShouldThrowAnExceptionWhenRoutesExistInOtherOrgSpace() {
AbstractCloudFoundryServerGroupDescription description = new AbstractCloudFoundryServerGroupDescription() {};
description.setClient(client);
description.setServerGroupName("sg-name");
@@ -119,8 +126,14 @@ class AbstractCloudFoundryLoadBalancerMappingOperationTest {
List<String> routeList = Collections.singletonList("road.to.nowhere");
- assertThat(operation.mapRoutes(description, routeList, space, null)).isFalse();
- assertThat(testTask.getHistory()).has(status("Load balancer already exists in another organization and space"), atIndex(2));
+ Exception exception = null;
+ try {
+ operation.mapRoutes(description, routeList, space, null);
+ } catch (CloudFoundryApiException cloudFoundryApiException) {
+ exception = cloudFoundryApiException;
+ }
+ assertThat(exception).isNotNull();
+ assertThat(exception.getMessage()).isEqualTo("Cloud Foundry API returned with error(s): Load balancer already exists in another organization and space");
}
private static class AbstractCloudFoundryAtomicOperationTestClass extends AbstractCloudFoundryLoadBalancerMappingOperation {

diff:
package com.netflix.spinnaker.clouddriver.cloudfoundry.deploy.ops;
+import com.netflix.spinnaker.clouddriver.cloudfoundry.client.CloudFoundryApiException;
import com.netflix.spinnaker.clouddriver.cloudfoundry.deploy.description.DeleteCloudFoundryLoadBalancerDescription;
import com.netflix.spinnaker.clouddriver.cloudfoundry.model.CloudFoundryDomain;
import com.netflix.spinnaker.clouddriver.cloudfoundry.model.CloudFoundryLoadBalancer;
@@ -59,7 +60,13 @@ class DeleteCloudFoundryLoadBalancerAtomicOperationTest extends AbstractCloudFou
DeleteCloudFoundryLoadBalancerAtomicOperation op = new DeleteCloudFoundryLoadBalancerAtomicOperation(desc);
- assertThat(runOperation(op).getHistory())
- .has(status("Load balancer does not exist"), atIndex(1));
+ Exception exception = null;
+ try {
+ runOperation(op);
+ } catch (CloudFoundryApiException cloudFoundryApiException) {
+ exception = cloudFoundryApiException;
+ }
+ assertThat(exception).isNotNull();
+ assertThat(exception.getMessage()).isEqualTo("Cloud Foundry API returned with error(s): Load balancer does not exist");
}
}

diff:
@@ -18,6 +18,7 @@ package com.netflix.spinnaker.clouddriver.cloudfoundry.deploy.ops;
import com.netflix.spinnaker.clouddriver.cloudfoundry.artifacts.ArtifactCredentialsFromString;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.Applications;
+import com.netflix.spinnaker.clouddriver.cloudfoundry.client.CloudFoundryApiException;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.CloudFoundryClient;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.MockCloudFoundryClient;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.model.v3.ProcessStats;
@@ -95,13 +96,17 @@ class DeployCloudFoundryServerGroupAtomicOperationTest extends AbstractCloudFoun
new DeployCloudFoundryServerGroupAtomicOperation(new PassThroughOperationPoller(), description, clusterProvider);
final Applications apps = getApplications(clusterProvider, ProcessStats.State.CRASHED);
+ Exception exception = null;
// When
- final DeploymentResult result = operation.operate(Lists.emptyList());
+ try {
+ operation.operate(Lists.emptyList());
+ } catch (CloudFoundryApiException cloudFoundryApiException) {
+ exception = cloudFoundryApiException;
+ }
//Then
- verifyInOrder(apps, () -> atLeastOnce());
-
- assertThat(testTask.getStatus().isFailed()).isTrue();
+ assertThat(exception).isNotNull();
+ assertThat(exception.getMessage()).isEqualTo("Cloud Foundry API returned with error(s): Failed to start 'app1-stack1-detail1-v000' which instead crashed");
}
@Test

diff:
package com.netflix.spinnaker.clouddriver.cloudfoundry.deploy.ops;
+import com.netflix.spinnaker.clouddriver.cloudfoundry.client.CloudFoundryApiException;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.model.v3.ProcessStats;
import com.netflix.spinnaker.clouddriver.cloudfoundry.deploy.description.ScaleCloudFoundryServerGroupDescription;
import com.netflix.spinnaker.clouddriver.helpers.OperationPoller;
import com.netflix.spinnaker.clouddriver.model.ServerGroup;
+import org.assertj.core.util.Lists;
import org.junit.jupiter.api.Test;
import java.util.function.Supplier;
@@ -64,9 +66,14 @@ class ScaleCloudFoundryServerGroupAtomicOperationTest extends AbstractCloudFound
ScaleCloudFoundryServerGroupAtomicOperation op = new ScaleCloudFoundryServerGroupAtomicOperation(poller, desc);
- assertThat(runOperation(op).getHistory())
- .has(status("Resizing 'myapp'"), atIndex(1))
- .has(status("Failed to start 'myapp' which instead crashed"), atIndex(2));
+ Exception exception = null;
+ try {
+ runOperation(op);
+ } catch (CloudFoundryApiException cloudFoundryApiException) {
+ exception = cloudFoundryApiException;
+ }
+ assertThat(exception).isNotNull();
+ assertThat(exception.getMessage()).isEqualTo("Cloud Foundry API returned with error(s): Failed to start 'myapp' which instead crashed");
}
@Test

diff:
package com.netflix.spinnaker.clouddriver.cloudfoundry.deploy.ops;
+import com.netflix.spinnaker.clouddriver.cloudfoundry.client.CloudFoundryApiException;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.model.v3.ProcessStats;
import com.netflix.spinnaker.clouddriver.cloudfoundry.deploy.description.StartCloudFoundryServerGroupDescription;
import com.netflix.spinnaker.clouddriver.helpers.OperationPoller;
@@ -67,8 +68,13 @@ class StartCloudFoundryServerGroupAtomicOperationTest extends AbstractCloudFound
StartCloudFoundryServerGroupAtomicOperation op = new StartCloudFoundryServerGroupAtomicOperation(poller, desc);
- assertThat(runOperation(op).getHistory())
- .has(status("Starting 'myapp'"), atIndex(1))
- .has(status("Failed to start 'myapp' which instead crashed"), atIndex(2));
+ Exception exception = null;
+ try {
+ runOperation(op);
+ } catch (CloudFoundryApiException cloudFoundryApiException) {
+ exception = cloudFoundryApiException;
+ }
+ assertThat(exception).isNotNull();
+ assertThat(exception.getMessage()).isEqualTo("Cloud Foundry API returned with error(s): Failed to start 'myapp' which instead crashed");
}
}

diff:
package com.netflix.spinnaker.clouddriver.cloudfoundry.deploy.ops;
+import com.netflix.spinnaker.clouddriver.cloudfoundry.client.CloudFoundryApiException;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.model.v3.ProcessStats;
import com.netflix.spinnaker.clouddriver.cloudfoundry.deploy.description.StopCloudFoundryServerGroupDescription;
import com.netflix.spinnaker.clouddriver.helpers.OperationPoller;
@@ -67,8 +68,13 @@ class StopCloudFoundryServerGroupAtomicOperationTest extends AbstractCloudFoundr
StopCloudFoundryServerGroupAtomicOperation op = new StopCloudFoundryServerGroupAtomicOperation(poller, desc);
- assertThat(runOperation(op).getHistory())
- .has(status("Stopping 'myapp'"), atIndex(1))
- .has(status("Failed to stop 'myapp' which instead is running"), atIndex(2));
+ Exception exception = null;
+ try {
+ runOperation(op);
+ } catch (CloudFoundryApiException cloudFoundryApiException) {
+ exception = cloudFoundryApiException;
+ }
+ assertThat(exception).isNotNull();
+ assertThat(exception.getMessage()).isEqualTo("Cloud Foundry API returned with error(s): Failed to stop 'myapp' which instead is running");
}
}

diff:
package com.netflix.spinnaker.clouddriver.cloudfoundry.deploy.ops;
+import com.netflix.spinnaker.clouddriver.cloudfoundry.client.CloudFoundryApiException;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.Routes;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.model.RouteId;
import com.netflix.spinnaker.clouddriver.cloudfoundry.deploy.description.LoadBalancersDescription;
@@ -51,16 +52,30 @@ class UnmapLoadBalancersAtomicOperationTest extends AbstractCloudFoundryAtomicOp
@Test
void operateWithNullRoutes() {
UnmapLoadBalancersAtomicOperation op = new UnmapLoadBalancersAtomicOperation(desc);
- assertThat(runOperation(op).getHistory())
- .has(status("No load balancer specified"), atIndex(2));
+
+ Exception exception = null;
+ try {
+ runOperation(op);
+ } catch (CloudFoundryApiException cloudFoundryApiException) {
+ exception = cloudFoundryApiException;
+ }
+ assertThat(exception).isNotNull();
+ assertThat(exception.getMessage()).isEqualTo("Cloud Foundry API returned with error(s): No load balancer specified");
}
@Test
void operateWithEmptyRoutes() {
desc.setRoutes(Collections.emptyList());
UnmapLoadBalancersAtomicOperation op = new UnmapLoadBalancersAtomicOperation(desc);
- assertThat(runOperation(op).getHistory())
- .has(status("No load balancer specified"), atIndex(2));
+
+ Exception exception = null;
+ try {
+ runOperation(op);
+ } catch (CloudFoundryApiException cloudFoundryApiException) {
+ exception = cloudFoundryApiException;
+ }
+ assertThat(exception).isNotNull();
+ assertThat(exception.getMessage()).isEqualTo("Cloud Foundry API returned with error(s): No load balancer specified");
}
@Test
@@ -70,9 +85,14 @@ class UnmapLoadBalancersAtomicOperationTest extends AbstractCloudFoundryAtomicOp
"bad.route 2.example.com"
).asJava());
UnmapLoadBalancersAtomicOperation op = new UnmapLoadBalancersAtomicOperation(desc);
- assertThat(runOperation(op).getHistory())
- .has(status("Load balancer 'bad.route-1.example.com' does not exist"), atIndex(2))
- .has(status("Invalid format for load balancer 'bad.route 2.example.com'"), atIndex(3));
+ Exception exception = null;
+ try {
+ runOperation(op);
+ } catch (CloudFoundryApiException cloudFoundryApiException) {
+ exception = cloudFoundryApiException;
+ }
+ assertThat(exception).isNotNull();
+ assertThat(exception.getMessage()).isEqualTo("Cloud Foundry API returned with error(s): Load balancer 'bad.route-1.example.com' does not exist");
}
@Test

original_message: chore(cf): remove usage of task fail and throw an exception (#3462)


message: feat(core): add jenkins artifact type

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.jenkins;
+
+import com.netflix.spinnaker.clouddriver.artifacts.config.ArtifactAccount;
+import com.netflix.spinnaker.clouddriver.artifacts.config.BasicAuth;
+import lombok.Data;
+import lombok.RequiredArgsConstructor;
+
+@Data
+@RequiredArgsConstructor
+public class JenkinsArtifactAccount implements ArtifactAccount, BasicAuth {
+ private final String name;
+ private final String username;
+ private final String password;
+ private final String address;
+
+ public String getUsernamePasswordFile() {
+ return null;
+ }
+}

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.jenkins;
+
+import com.squareup.okhttp.OkHttpClient;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+@Configuration
+@ConditionalOnProperty("jenkins.enabled")
+@EnableConfigurationProperties(JenkinsProperties.class)
+@RequiredArgsConstructor
+@Slf4j
+public class JenkinsArtifactConfiguration {
+ private final JenkinsProperties jenkinsProperties;
+
+ @Bean
+ List<? extends JenkinsArtifactCredentials> jenkinsArtifactCredentials(OkHttpClient okHttpClient) {
+ return jenkinsProperties.getMasters()
+ .stream()
+ .map(m -> {
+ try {
+ return new JenkinsArtifactCredentials(new JenkinsArtifactAccount(m.getName(), m.getUsername(), m.getPassword(), m.getAddress()),
+ okHttpClient);
+ } catch (Exception e) {
+ log.warn("Failure instantiating jenkins artifact account {}: ", m, e);
+ return null;
+ }
+ })
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList());
+ }
+}

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.jenkins;
+
+
+import com.netflix.spinnaker.clouddriver.artifacts.config.ArtifactCredentials;
+import com.netflix.spinnaker.clouddriver.artifacts.config.SimpleHttpArtifactCredentials;
+import com.netflix.spinnaker.kork.artifacts.model.Artifact;
+import com.squareup.okhttp.HttpUrl;
+import com.squareup.okhttp.OkHttpClient;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Collections;
+import java.util.List;
+
+@Slf4j
+public class JenkinsArtifactCredentials extends SimpleHttpArtifactCredentials<JenkinsArtifactAccount> implements ArtifactCredentials {
+ public static final String TYPE = "jenkins/file";
+
+ @Getter
+ private final String name;
+
+ @Getter
+ private final List<String> types = Collections.singletonList(TYPE);
+
+ private final JenkinsArtifactAccount jenkinsArtifactAccount;
+
+ JenkinsArtifactCredentials(JenkinsArtifactAccount account, OkHttpClient okHttpClient) {
+ super(okHttpClient, account);
+ this.jenkinsArtifactAccount = account;
+ this.name = account.getName();
+ }
+
+ @Override
+ protected HttpUrl getDownloadUrl(Artifact artifact) {
+ String formattedJenkinsAddress = jenkinsArtifactAccount.getAddress().endsWith("/") ? jenkinsArtifactAccount.getAddress() : jenkinsArtifactAccount.getAddress() + "/";
+ String formattedReference = artifact.getReference().startsWith("/") ? artifact.getReference() : "/" + artifact.getReference();
+ String buildUrl = formattedJenkinsAddress + "job/" + artifact.getName() + "/" + artifact.getVersion() + "/artifact" + formattedReference;
+ HttpUrl url = HttpUrl.parse(buildUrl);
+ if (url == null) {
+ throw new IllegalArgumentException("Malformed content URL in reference: " + buildUrl + ". Read more here https://www.spinnaker.io/reference/artifacts/types/");
+ }
+ return url;
+ }
+
+}

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.jenkins;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+@ConfigurationProperties("jenkins")
+public class JenkinsProperties {
+ private boolean enabled;
+ private List<Master> masters = new ArrayList<>();
+
+ @Data
+ public static class Master {
+ private String name;
+ private String address;
+ private String username;
+ private String password;
+ }
+}

original_message:
feat(core): add jenkins artifact type (#3469)

Co-Authored-By: Ria Stein <eleftheria.kousathana@gmail.com>


diff:
@@ -75,7 +75,8 @@ class AppengineLoadBalancer implements LoadBalancer, Serializable {
isDisabled: serverGroup.isDisabled(),
allowsGradualTrafficMigration: serverGroup.allowsGradualTrafficMigration,
instances: instances as Set,
- detachedInstances: detachedInstances as Set
+ detachedInstances: detachedInstances as Set,
+ cloudProvider: AppengineCloudProvider.ID
)
} as Set<LoadBalancerServerGroup>
null

diff:
@@ -198,7 +198,8 @@ class AmazonLoadBalancerProvider implements LoadBalancerProvider<AmazonLoadBalan
]
)
} : [],
- detachedInstances: serverGroup.any().detachedInstances
+ detachedInstances: serverGroup.any().detachedInstances,
+ cloudProvider: AmazonCloudProvider.ID
)
}

diff:
@@ -95,7 +95,8 @@ class AzureAppGatewayProvider implements LoadBalancerProvider<AzureLoadBalancer>
name: serverGroup,
isDisabled: false,
detachedInstances: [],
- instances: []
+ instances: [],
+ cloudProvider: AzureCloudProvider.ID
))
}

diff:
@@ -84,7 +84,7 @@ class AzureLoadBalancerProvider /*implements LoadBalancerProvider<AzureLoadBalan
region: loadBalancerDescription.region,
vnet: loadBalancerDescription.vnet ?: "vnet-unassigned",
subnet: loadBalancerDescription.subnet ?: "subnet-unassigned",
- serverGroups: [new LoadBalancerServerGroup(name: loadBalancerDescription.serverGroup, isDisabled: false, detachedInstances: [], instances: [])]
+ serverGroups: [new LoadBalancerServerGroup(name: loadBalancerDescription.serverGroup, isDisabled: false, detachedInstances: [], instances: [], cloudProvider: AzureCloudProvider.ID)]
)
}

diff:
@@ -94,7 +94,8 @@ public class CloudFoundryLoadBalancer extends CloudFoundryModel implements LoadB
app.getInstances()
.stream()
.map(it -> new LoadBalancerInstance(it.getId(), it.getName(), null, it.getHealth().get(0)))
- .collect(toSet())
+ .collect(toSet()),
+ CloudFoundryCloudProvider.ID
)
).collect(toSet());
}

diff:
@@ -34,4 +34,5 @@ public class LoadBalancerServerGroup {
Boolean isDisabled;
Set<String> detachedInstances;
Set<LoadBalancerInstance> instances;
+ String cloudProvider;
}

diff:
@@ -90,7 +90,9 @@ class DcosLoadBalancer implements LoadBalancer, Serializable, LoadBalancerProvid
//}
} as Set,
// TODO once we can do this
- detachedInstances: [])
+ detachedInstances: [],
+ cloudProvider: DcosCloudProvider.ID
+ )
} as Set
}

diff:
@@ -146,6 +146,7 @@ class GoogleLoadBalancerProvider implements LoadBalancerProvider<GoogleLoadBalan
isDisabled: isDisabled,
detachedInstances: [],
instances: [],
+ cloudProvider: GoogleCloudProvider.ID,
)
// TODO(duftler): De-frigga this.

diff:
@@ -85,7 +85,9 @@ class KubernetesV1LoadBalancer implements LoadBalancer, Serializable, LoadBalanc
} else {
return (String) null
}
- } as Set)
+ } as Set,
+ cloudProvider: KubernetesCloudProvider.ID
+ )
} as Set
}

diff:
@@ -21,6 +21,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import com.google.common.collect.ImmutableMap;
import com.google.common.primitives.Ints;
import com.netflix.spinnaker.cats.cache.CacheData;
+import com.netflix.spinnaker.clouddriver.kubernetes.KubernetesCloudProvider;
import com.netflix.spinnaker.clouddriver.kubernetes.v2.artifact.ArtifactReplacer;
import com.netflix.spinnaker.clouddriver.kubernetes.v2.artifact.ArtifactReplacerFactory;
import com.netflix.spinnaker.clouddriver.kubernetes.v2.caching.Keys;
@@ -205,6 +206,7 @@ public class KubernetesV2ServerGroup extends ManifestBasedModel implements Serve
.name(getName())
.region(getRegion())
.isDisabled(isDisabled())
+ .cloudProvider(KubernetesCloudProvider.getID())
.build();
}

diff:
@@ -115,7 +115,7 @@ class OpenstackLoadBalancerProvider implements LoadBalancerProvider<OpenstackLoa
LoadBalancerServerGroup loadBalancerServerGroup = null
ServerGroup serverGroup = clusterProvider.getServerGroup(loadBalancer.account, loadBalancer.region, Keys.parse(key)['serverGroup'])
if (serverGroup) {
- loadBalancerServerGroup = new LoadBalancerServerGroup(name: serverGroup.name, isDisabled: serverGroup.isDisabled())
+ loadBalancerServerGroup = new LoadBalancerServerGroup(name: serverGroup.name, isDisabled: serverGroup.isDisabled(), cloudProvider: OpenstackCloudProvider.ID)
loadBalancerServerGroup.instances = serverGroup.instances?.collect { instance ->
new LoadBalancerInstance(id: instance.name, health: [state: instance.healthState?.toString()])
}?.toSet()

diff:
@@ -23,6 +23,7 @@ import com.netflix.spinnaker.cats.cache.RelationshipCacheFilter
import com.netflix.spinnaker.clouddriver.model.LoadBalancerInstance
import com.netflix.spinnaker.clouddriver.model.LoadBalancerServerGroup
import com.netflix.spinnaker.clouddriver.model.ServerGroup
+import com.netflix.spinnaker.clouddriver.openstack.OpenstackCloudProvider
import com.netflix.spinnaker.clouddriver.openstack.cache.Keys
import com.netflix.spinnaker.clouddriver.openstack.model.OpenstackFloatingIP
import com.netflix.spinnaker.clouddriver.openstack.model.OpenstackLoadBalancer
@@ -329,7 +330,7 @@ class OpenstackLoadBalancerProviderSpec extends Specification {
def healthMonitor = new OpenstackLoadBalancer.OpenstackHealthMonitor(id: "health$i", httpMethod: 'GET',
maxRetries: 5, adminStateUp: 'UP', delay: 5, expectedCodes: [200])
def serverGroups = [new LoadBalancerServerGroup(name: 'sg1', isDisabled: false,
- instances: [new LoadBalancerInstance(id: 'id', zone: "zone$i", health: [state:'up', zone: "zone$i"])])]
+ instances: [new LoadBalancerInstance(id: 'id', zone: "zone$i", health: [state:'up', zone: "zone$i"])], cloudProvider: OpenstackCloudProvider.ID)]
new OpenstackLoadBalancer.View(account: account, region: region, id: id, name: name, description: description,
status: status, algorithm: algorithm, ip: ip, subnetId: subnet, subnetName: subnet, networkId: network, networkName: network,
healthMonitor: healthMonitor, serverGroups: serverGroups)

diff:
@@ -24,6 +24,7 @@ import com.netflix.spinnaker.clouddriver.aws.model.AmazonTargetGroup;
import com.netflix.spinnaker.clouddriver.aws.model.TargetGroupServerGroupProvider;
import com.netflix.spinnaker.clouddriver.model.LoadBalancerInstance;
import com.netflix.spinnaker.clouddriver.model.LoadBalancerServerGroup;
+import com.netflix.spinnaker.clouddriver.titus.TitusCloudProvider;
import com.netflix.spinnaker.clouddriver.titus.caching.Keys;
import com.netflix.spinnaker.clouddriver.titus.caching.utils.AwsLookupUtil;
import lombok.extern.slf4j.Slf4j;
@@ -118,7 +119,8 @@ public class TitusTargetGroupServerGroupProvider implements TargetGroupServerGro
attributes.get("region").toString(),
!(Boolean) job.get("inService"),
Collections.emptySet(),
- targetGroupInstances
+ targetGroupInstances,
+ TitusCloudProvider.ID
);
if (allTargetGroups.containsKey(targetGroup)) {

original_message: fix(core): add `cloudProvider` to `LoadBalancerServerGroup` as expected by deck (#3517)


diff:
@@ -181,7 +181,7 @@ class GoogleClusterProvider implements ClusterProvider<GoogleCluster.View> {
def securityGroups = securityGroupProvider.getAllByAccount(false, clusterView.accountName)
def instanceCacheData = instanceProvider.getInstanceCacheData(instanceKeySuperSet as List).findAll { instance ->
- instance.relationships.get(CLUSTERS.ns)?.collect { Keys.parse(it).cluster }?.contains(clusterView.name)
+ instance.relationships.get(CLUSTERS.ns)?.collect { Keys.parse(it).cluster }?.any { it.contains(clusterView.name) }
}
def instances = instanceProvider.getInstancesFromCacheData(clusterView.accountName, instanceCacheData, securityGroups)

original_message: fix(google): correctly filter instanceCacheData in GoogleClusterProvider.clusterFromCacheData (#3562)


diff:
@@ -33,7 +33,7 @@ class LoggingInstrumentation implements ExecutionInstrumentation {
@Override
void executionCompleted(Agent agent, long durationMs) {
- logger.info("${agent.providerName}:${agent.agentType} completed in ${durationMs / 1000}s")
+ logger.debug("${agent.providerName}:${agent.agentType} completed in ${durationMs / 1000}s")
}
@Override

diff:
@@ -141,7 +141,7 @@ abstract class AbstractGoogleLoadBalancerCachingAgent extends AbstractGoogleCach
evictions[LOAD_BALANCERS.ns].addAll(identifiers)
}
- log.info("On demand cache refresh succeeded. Data: ${data}. Added ${loadBalancer ? 1 : 0} items to the cache.")
+ log.debug("On demand cache refresh succeeded. Data: ${data}. Added ${loadBalancer ? 1 : 0} items to the cache.")
return new OnDemandAgent.OnDemandResult(
sourceAgentType: getOnDemandAgentType(),
@@ -214,7 +214,7 @@ abstract class AbstractGoogleLoadBalancerCachingAgent extends AbstractGoogleCach
abstract List<GoogleLoadBalancer> constructLoadBalancers(String onDemandLoadBalancerName = null)
CacheResult buildCacheResult(CacheResultBuilder cacheResultBuilder, List<GoogleLoadBalancer> googleLoadBalancers) {
- log.info "Describing items in ${agentType}"
+ log.debug "Describing items in ${agentType}"
googleLoadBalancers.each { GoogleLoadBalancer loadBalancer ->
// TODO(duftler): Pull out getLoadBalancerKey() like getServerGroupKey()?
@@ -239,10 +239,10 @@ abstract class AbstractGoogleLoadBalancerCachingAgent extends AbstractGoogleCach
}
}
- log.info "Caching ${cacheResultBuilder.namespace(LOAD_BALANCERS.ns).keepSize()} load balancers in ${agentType}"
- log.info "Caching ${cacheResultBuilder.namespace(INSTANCES.ns).keepSize()} instance relationships in ${agentType}"
- log.info "Caching ${cacheResultBuilder.onDemand.toKeep.size()} onDemand entries in ${agentType}"
- log.info "Evicting ${cacheResultBuilder.onDemand.toEvict.size()} onDemand entries in ${agentType}"
+ log.debug "Caching ${cacheResultBuilder.namespace(LOAD_BALANCERS.ns).keepSize()} load balancers in ${agentType}"
+ log.debug "Caching ${cacheResultBuilder.namespace(INSTANCES.ns).keepSize()} instance relationships in ${agentType}"
+ log.debug "Caching ${cacheResultBuilder.onDemand.toKeep.size()} onDemand entries in ${agentType}"
+ log.debug "Evicting ${cacheResultBuilder.onDemand.toEvict.size()} onDemand entries in ${agentType}"
return cacheResultBuilder.build()
}

diff:
@@ -109,7 +109,7 @@ class GoogleBackendServiceCachingAgent extends AbstractGoogleCachingAgent {
}
private CacheResult buildCacheResult(ProviderCache _, List<GoogleBackendService> backendServiceList) {
- log.info("Describing items in ${agentType}")
+ log.debug("Describing items in ${agentType}")
def cacheResultBuilder = new CacheResultBuilder()
@@ -129,7 +129,7 @@ class GoogleBackendServiceCachingAgent extends AbstractGoogleCachingAgent {
}
}
- log.info("Caching ${cacheResultBuilder.namespace(BACKEND_SERVICES.ns).keepSize()} items in ${agentType}")
+ log.debug("Caching ${cacheResultBuilder.namespace(BACKEND_SERVICES.ns).keepSize()} items in ${agentType}")
cacheResultBuilder.build()
}

diff:
@@ -78,7 +78,7 @@ class GoogleGlobalAddressCachingAgent extends AbstractGoogleCachingAgent {
}
private CacheResult buildCacheResult(ProviderCache _, List<Address> addressList) {
- log.info("Describing items in ${agentType}")
+ log.debug("Describing items in ${agentType}")
def cacheResultBuilder = new CacheResultBuilder()
@@ -90,7 +90,7 @@ class GoogleGlobalAddressCachingAgent extends AbstractGoogleCachingAgent {
}
}
- log.info("Caching ${cacheResultBuilder.namespace(ADDRESSES.ns).keepSize()} items in ${agentType}")
+ log.debug("Caching ${cacheResultBuilder.namespace(ADDRESSES.ns).keepSize()} items in ${agentType}")
cacheResultBuilder.build()
}

diff:
@@ -194,7 +194,7 @@ class GoogleHealthCheckCachingAgent extends AbstractGoogleCachingAgent {
}
private CacheResult buildCacheResult(ProviderCache _, List<GoogleHealthCheck> healthCheckList) {
- log.info("Describing items in ${agentType}")
+ log.debug("Describing items in ${agentType}")
def cacheResultBuilder = new CacheResultBuilder()
@@ -206,7 +206,7 @@ class GoogleHealthCheckCachingAgent extends AbstractGoogleCachingAgent {
}
}
- log.info("Caching ${cacheResultBuilder.namespace(HEALTH_CHECKS.ns).keepSize()} items in ${agentType}")
+ log.debug("Caching ${cacheResultBuilder.namespace(HEALTH_CHECKS.ns).keepSize()} items in ${agentType}")
cacheResultBuilder.build()
}

diff:
@@ -62,7 +62,7 @@ class GoogleHttpHealthCheckCachingAgent extends AbstractGoogleCachingAgent {
}
private CacheResult buildCacheResult(ProviderCache _, List<HttpHealthCheck> httpHealthCheckList) {
- log.info("Describing items in ${agentType}")
+ log.debug("Describing items in ${agentType}")
def cacheResultBuilder = new CacheResultBuilder()
@@ -74,7 +74,7 @@ class GoogleHttpHealthCheckCachingAgent extends AbstractGoogleCachingAgent {
}
}
- log.info("Caching ${cacheResultBuilder.namespace(HTTP_HEALTH_CHECKS.ns).keepSize()} items in ${agentType}")
+ log.debug("Caching ${cacheResultBuilder.namespace(HTTP_HEALTH_CHECKS.ns).keepSize()} items in ${agentType}")
cacheResultBuilder.build()
}

diff:
@@ -239,7 +239,7 @@ class GoogleHttpLoadBalancerCachingAgent extends AbstractGoogleLoadBalancerCachi
targetProxyRequest.queue(compute.targetHttpsProxies().get(project, targetProxyName), targetHttpsProxyCallback)
break
default:
- log.info("Non-Http target type found for global forwarding rule ${forwardingRule.name}")
+ log.debug("Non-Http target type found for global forwarding rule ${forwardingRule.name}")
break
}
}

diff:
@@ -104,7 +104,7 @@ class GoogleImageCachingAgent extends AbstractGoogleCachingAgent {
}
private CacheResult buildCacheResult(ProviderCache _, List<Image> imageList) {
- log.info("Describing items in ${agentType}")
+ log.debug("Describing items in ${agentType}")
def cacheResultBuilder = new CacheResultBuilder()
@@ -116,7 +116,7 @@ class GoogleImageCachingAgent extends AbstractGoogleCachingAgent {
}
}
- log.info("Caching ${cacheResultBuilder.namespace(IMAGES.ns).keepSize()} items in ${agentType}")
+ log.debug("Caching ${cacheResultBuilder.namespace(IMAGES.ns).keepSize()} items in ${agentType}")
cacheResultBuilder.build()
}

diff:
@@ -62,7 +62,7 @@ class GoogleInstanceCachingAgent extends AbstractGoogleCachingAgent {
}
}
- log.info("Caching ${cacheResultBuilder.namespace(INSTANCES.ns).keepSize()} instances in ${agentType}")
+ log.debug("Caching ${cacheResultBuilder.namespace(INSTANCES.ns).keepSize()} instances in ${agentType}")
cacheResultBuilder.build()
}

diff:
@@ -62,7 +62,7 @@ class GoogleNetworkCachingAgent extends AbstractGoogleCachingAgent {
}
private CacheResult buildCacheResult(ProviderCache _, List<Network> networkList) {
- log.info("Describing items in ${agentType}")
+ log.debug("Describing items in ${agentType}")
def cacheResultBuilder = new CacheResultBuilder()
@@ -74,7 +74,7 @@ class GoogleNetworkCachingAgent extends AbstractGoogleCachingAgent {
}
}
- log.info("Caching ${cacheResultBuilder.namespace(NETWORKS.ns).keepSize()} items in ${agentType}")
+ log.debug("Caching ${cacheResultBuilder.namespace(NETWORKS.ns).keepSize()} items in ${agentType}")
cacheResultBuilder.build()
}

diff:
@@ -67,7 +67,7 @@ class GoogleRegionalAddressCachingAgent extends AbstractGoogleCachingAgent {
}
private CacheResult buildCacheResult(ProviderCache _, List<Address> addressList) {
- log.info("Describing items in ${agentType}")
+ log.debug("Describing items in ${agentType}")
def cacheResultBuilder = new CacheResultBuilder()
@@ -79,7 +79,7 @@ class GoogleRegionalAddressCachingAgent extends AbstractGoogleCachingAgent {
}
}
- log.info("Caching ${cacheResultBuilder.namespace(ADDRESSES.ns).keepSize()} items in ${agentType}")
+ log.debug("Caching ${cacheResultBuilder.namespace(ADDRESSES.ns).keepSize()} items in ${agentType}")
cacheResultBuilder.build()
}

diff:
@@ -222,7 +222,7 @@ class GoogleRegionalServerGroupCachingAgent extends AbstractGoogleCachingAgent i
evictions[SERVER_GROUPS.ns].add(serverGroupKey)
}
- log.info("On demand cache refresh succeeded. Data: ${data}. Added ${serverGroup ? 1 : 0} items to the cache. Evicted ${evictions[SERVER_GROUPS.ns]}.")
+ log.debug("On demand cache refresh succeeded. Data: ${data}. Added ${serverGroup ? 1 : 0} items to the cache. Evicted ${evictions[SERVER_GROUPS.ns]}.")
return new OnDemandAgent.OnDemandResult(
sourceAgentType: getOnDemandAgentType(),
@@ -257,7 +257,7 @@ class GoogleRegionalServerGroupCachingAgent extends AbstractGoogleCachingAgent i
}
private CacheResult buildCacheResult(CacheResultBuilder cacheResultBuilder, List<GoogleServerGroup> serverGroups) {
- log.info "Describing items in $agentType"
+ log.debug "Describing items in $agentType"
serverGroups.each { GoogleServerGroup serverGroup ->
Moniker moniker = naming.deriveMoniker(serverGroup)
@@ -307,12 +307,12 @@ class GoogleRegionalServerGroupCachingAgent extends AbstractGoogleCachingAgent i
}
}
- log.info("Caching ${cacheResultBuilder.namespace(APPLICATIONS.ns).keepSize()} applications in ${agentType}")
- log.info("Caching ${cacheResultBuilder.namespace(CLUSTERS.ns).keepSize()} clusters in ${agentType}")
- log.info("Caching ${cacheResultBuilder.namespace(SERVER_GROUPS.ns).keepSize()} server groups in ${agentType}")
- log.info("Caching ${cacheResultBuilder.namespace(LOAD_BALANCERS.ns).keepSize()} load balancer relationships in ${agentType}")
- log.info("Caching ${cacheResultBuilder.onDemand.toKeep.size()} onDemand entries in ${agentType}")
- log.info("Evicting ${cacheResultBuilder.onDemand.toEvict.size()} onDemand entries in ${agentType}")
+ log.debug("Caching ${cacheResultBuilder.namespace(APPLICATIONS.ns).keepSize()} applications in ${agentType}")
+ log.debug("Caching ${cacheResultBuilder.namespace(CLUSTERS.ns).keepSize()} clusters in ${agentType}")
+ log.debug("Caching ${cacheResultBuilder.namespace(SERVER_GROUPS.ns).keepSize()} server groups in ${agentType}")
+ log.debug("Caching ${cacheResultBuilder.namespace(LOAD_BALANCERS.ns).keepSize()} load balancer relationships in ${agentType}")
+ log.debug("Caching ${cacheResultBuilder.onDemand.toKeep.size()} onDemand entries in ${agentType}")
+ log.debug("Evicting ${cacheResultBuilder.onDemand.toEvict.size()} onDemand entries in ${agentType}")
cacheResultBuilder.build()
}

diff:
@@ -135,7 +135,7 @@ class GoogleSecurityGroupCachingAgent extends AbstractGoogleCachingAgent impleme
evictions[SECURITY_GROUPS.ns].addAll(identifiers)
}
- log.info("On demand cache refresh succeeded. Data: ${data}. Added ${firewall ? 1 : 0} items to the cache.")
+ log.debug("On demand cache refresh succeeded. Data: ${data}. Added ${firewall ? 1 : 0} items to the cache.")
return new OnDemandAgent.OnDemandResult(
sourceAgentType: getOnDemandAgentType(),
@@ -223,7 +223,7 @@ class GoogleSecurityGroupCachingAgent extends AbstractGoogleCachingAgent impleme
}
private CacheResult buildCacheResult(CacheResultBuilder cacheResultBuilder, List<Firewall> firewalls) {
- log.info("Describing items in ${agentType}")
+ log.debug("Describing items in ${agentType}")
firewalls.each { Firewall firewall ->
def securityGroupKey = Keys.getSecurityGroupKey(firewall.getName(),
@@ -240,9 +240,9 @@ class GoogleSecurityGroupCachingAgent extends AbstractGoogleCachingAgent impleme
}
}
- log.info("Caching ${cacheResultBuilder.namespace(SECURITY_GROUPS.ns).keepSize()} security groups in ${agentType}")
- log.info "Caching ${cacheResultBuilder.onDemand.toKeep.size()} onDemand entries in ${agentType}"
- log.info "Evicting ${cacheResultBuilder.onDemand.toEvict.size()} onDemand entries in ${agentType}"
+ log.debug("Caching ${cacheResultBuilder.namespace(SECURITY_GROUPS.ns).keepSize()} security groups in ${agentType}")
+ log.debug "Caching ${cacheResultBuilder.onDemand.toKeep.size()} onDemand entries in ${agentType}"
+ log.debug "Evicting ${cacheResultBuilder.onDemand.toEvict.size()} onDemand entries in ${agentType}"
return cacheResultBuilder.build()
}

diff:
@@ -62,7 +62,7 @@ class GoogleSslCertificateCachingAgent extends AbstractGoogleCachingAgent {
}
private CacheResult buildCacheResult(ProviderCache _, List<SslCertificate> sslCertificateList) {
- log.info("Describing items in ${agentType}")
+ log.debug("Describing items in ${agentType}")
def cacheResultBuilder = new CacheResultBuilder()
@@ -74,7 +74,7 @@ class GoogleSslCertificateCachingAgent extends AbstractGoogleCachingAgent {
}
}
- log.info("Caching ${cacheResultBuilder.namespace(SSL_CERTIFICATES.ns).keepSize()} items in ${agentType}")
+ log.debug("Caching ${cacheResultBuilder.namespace(SSL_CERTIFICATES.ns).keepSize()} items in ${agentType}")
cacheResultBuilder.build()
}

diff:
@@ -78,7 +78,7 @@ class GoogleSubnetCachingAgent extends AbstractGoogleCachingAgent {
}
private CacheResult buildCacheResult(ProviderCache _, List<Subnetwork> subnetList) {
- log.info("Describing items in ${agentType}")
+ log.debug("Describing items in ${agentType}")
def cacheResultBuilder = new CacheResultBuilder()
@@ -90,7 +90,7 @@ class GoogleSubnetCachingAgent extends AbstractGoogleCachingAgent {
}
}
- log.info("Caching ${cacheResultBuilder.namespace(SUBNETS.ns).keepSize()} items in ${agentType}")
+ log.debug("Caching ${cacheResultBuilder.namespace(SUBNETS.ns).keepSize()} items in ${agentType}")
cacheResultBuilder.build()
}

diff:
@@ -260,7 +260,7 @@ class GoogleZonalServerGroupCachingAgent extends AbstractGoogleCachingAgent impl
evictions[SERVER_GROUPS.ns].addAll(identifiers)
}
- log.info("On demand cache refresh succeeded. Data: ${data}. Added ${serverGroup ? 1 : 0} items to the cache. Evicted ${evictions[SERVER_GROUPS.ns]}.")
+ log.debug("On demand cache refresh succeeded. Data: ${data}. Added ${serverGroup ? 1 : 0} items to the cache. Evicted ${evictions[SERVER_GROUPS.ns]}.")
return new OnDemandResult(
sourceAgentType: getOnDemandAgentType(),
@@ -295,7 +295,7 @@ class GoogleZonalServerGroupCachingAgent extends AbstractGoogleCachingAgent impl
}
private CacheResult buildCacheResult(CacheResultBuilder cacheResultBuilder, List<GoogleServerGroup> serverGroups) {
- log.info "Describing items in $agentType"
+ log.debug "Describing items in $agentType"
serverGroups.each { GoogleServerGroup serverGroup ->
Moniker moniker = naming.deriveMoniker(serverGroup)
@@ -345,12 +345,12 @@ class GoogleZonalServerGroupCachingAgent extends AbstractGoogleCachingAgent impl
}
}
- log.info("Caching ${cacheResultBuilder.namespace(APPLICATIONS.ns).keepSize()} applications in ${agentType}")
- log.info("Caching ${cacheResultBuilder.namespace(CLUSTERS.ns).keepSize()} clusters in ${agentType}")
- log.info("Caching ${cacheResultBuilder.namespace(SERVER_GROUPS.ns).keepSize()} server groups in ${agentType}")
- log.info("Caching ${cacheResultBuilder.namespace(LOAD_BALANCERS.ns).keepSize()} load balancer relationships in ${agentType}")
- log.info("Caching ${cacheResultBuilder.onDemand.toKeep.size()} onDemand entries in ${agentType}")
- log.info("Evicting ${cacheResultBuilder.onDemand.toEvict.size()} onDemand entries in ${agentType}")
+ log.debug("Caching ${cacheResultBuilder.namespace(APPLICATIONS.ns).keepSize()} applications in ${agentType}")
+ log.debug("Caching ${cacheResultBuilder.namespace(CLUSTERS.ns).keepSize()} clusters in ${agentType}")
+ log.debug("Caching ${cacheResultBuilder.namespace(SERVER_GROUPS.ns).keepSize()} server groups in ${agentType}")
+ log.debug("Caching ${cacheResultBuilder.namespace(LOAD_BALANCERS.ns).keepSize()} load balancer relationships in ${agentType}")
+ log.debug("Caching ${cacheResultBuilder.onDemand.toKeep.size()} onDemand entries in ${agentType}")
+ log.debug("Evicting ${cacheResultBuilder.onDemand.toEvict.size()} onDemand entries in ${agentType}")
cacheResultBuilder.build()
}

original_message: refactor(google): switch the caching log messages from INFO to DEBUG. (#3579)


diff:
@@ -87,7 +87,7 @@ public class KubernetesSpinnakerKindMap {
public Map<String, String> kubernetesToSpinnakerKindStringMap() {
return kubernetesToSpinnaker.entrySet().stream().filter(
- x -> x.getValue() != SpinnakerKind.UNCLASSIFIED && x.getKey() != KubernetesKind.NONE
+ x -> x.getKey() != KubernetesKind.NONE
).collect(
Collectors.toMap(x -> x.getKey().toString(), x -> x.getValue().toString()));
}

original_message: fix(kubernetes): include unclassified kinds in spinnakerKindMap so they can be surfaced for deletion (#3580)


diff:
@@ -40,9 +40,16 @@ public class KubernetesDeployManifestDescription extends KubernetesAtomicOperati
private boolean enableTraffic = true;
private List<String> services;
+ private Strategy strategy;
public enum Source {
artifact,
text
}
+
+ public enum Strategy {
+ RED_BLACK,
+ HIGHLANDER,
+ NONE
+ }
}

diff:
@@ -229,6 +229,17 @@ public class KubernetesManifestAnnotater {
storeAnnotation(annotations, LOAD_BALANCERS, loadBalancers);
}
+ public static void validateAnnotationsForRolloutStrategies(KubernetesManifest manifest, KubernetesDeployManifestDescription.Strategy strategy) {
+ Map<String, String> annotations = manifest.getAnnotations();
+ Integer maxVersionHistory = getAnnotation(annotations, MAX_VERSION_HISTORY, new TypeReference<Integer>() {});
+ if (strategy == KubernetesDeployManifestDescription.Strategy.RED_BLACK && maxVersionHistory != null && maxVersionHistory < 2) {
+ throw new RuntimeException(String.format(
+ "The max version history specified in your manifest conflicts with the behavior of the Red/Black rollout strategy. Please update your %s annotation to a value greater than or equal to 2.",
+ MAX_VERSION_HISTORY
+ ));
+ }
+ }
+
public static KubernetesCachingProperties getCachingProperties(KubernetesManifest manifest) {
Map<String, String> annotations = manifest.getAnnotations();

diff:
@@ -98,6 +98,8 @@ public class KubernetesDeployManifestOperation implements AtomicOperation<Operat
Set<Artifact> boundArtifacts = new HashSet<>();
+ validateManifestsForRolloutStrategies(inputManifests);
+
for (KubernetesManifest manifest : inputManifests) {
if (StringUtils.isEmpty(manifest.getNamespace()) && manifest.getKind().isNamespaced()) {
manifest.setNamespace(credentials.getDefaultNamespace());
@@ -112,6 +114,8 @@ public class KubernetesDeployManifestOperation implements AtomicOperation<Operat
throw new IllegalArgumentException("No deployer available for Kubernetes object kind '" + manifest.getKind().toString() + "', unable to continue.");
}
+ KubernetesManifestAnnotater.validateAnnotationsForRolloutStrategies(manifest, description.getStrategy());
+
getTask().updateStatus(OP_NAME, "Swapping out artifacts in " + manifest.getFullResourceName() + " from context...");
ReplaceResult replaceResult = deployer.replaceArtifacts(manifest, artifacts, description.getAccount());
deployManifests.add(replaceResult.getManifest());
@@ -207,6 +211,14 @@ public class KubernetesDeployManifestOperation implements AtomicOperation<Operat
traffic.getLoadBalancers().forEach(l -> attachLoadBalancer(l, target));
}
+ private void validateManifestsForRolloutStrategies(List<KubernetesManifest> manifests) {
+ if (description.getStrategy() != null && (manifests.size() != 1 || manifests.get(0).getKind() != KubernetesKind.REPLICA_SET)) {
+ throw new RuntimeException(
+ "Spinnaker can manage traffic for ReplicaSets only. Please deploy exactly one ReplicaSet manifest or disable rollout strategies."
+ );
+ }
+ }
+
private void attachLoadBalancer(String loadBalancerName, KubernetesManifest target) {
Pair<KubernetesKind, String> name;
try {

original_message: feat(kubernetes): validate manifests for spinnaker-managed rollout strategies (#3589)


diff:
@@ -37,6 +37,7 @@ public final class AtomicOperations {
public static final String MIGRATE_CLUSTER_CONFIGURATIONS = "migrateClusterConfigurations";
public static final String START_SERVER_GROUP = "startServerGroup";
public static final String STOP_SERVER_GROUP = "stopServerGroup";
+ public static final String SET_STATEFUL_DISK = "setStatefulDisk";
// Instance operations
public static final String REBOOT_INSTANCES = "rebootInstances";

diff:
@@ -4,6 +4,7 @@ dependencies {
compile project(":clouddriver-consul")
compile project(":clouddriver-google-common")
+ compile spinnaker.dependency('assertj')
compile spinnaker.dependency('bootActuator')
compile spinnaker.dependency('bootWeb')
compile spinnaker.dependency('frigga')

diff:
package com.netflix.spinnaker.clouddriver.google.deploy.converters
import com.fasterxml.jackson.databind.DeserializationFeature
-import com.netflix.spinnaker.clouddriver.google.deploy.description.AbstractGoogleCredentialsDescription
import com.netflix.spinnaker.clouddriver.google.security.GoogleNamedAccountCredentials
import com.netflix.spinnaker.clouddriver.security.AbstractAtomicOperationsCredentialsSupport
+import com.netflix.spinnaker.clouddriver.security.resources.CredentialsNameable
class GoogleAtomicOperationConverterHelper {
- static <T extends AbstractGoogleCredentialsDescription> T convertDescription(Map input,
+ static <T extends CredentialsNameable> T convertDescription(Map input,
AbstractAtomicOperationsCredentialsSupport credentialsSupport,
Class<T> targetDescriptionType) {
if (!input.accountName) {

diff:
+package com.netflix.spinnaker.clouddriver.google.deploy.converters;
+
+import com.netflix.spinnaker.clouddriver.google.GoogleOperation;
+import com.netflix.spinnaker.clouddriver.google.deploy.description.SetStatefulDiskDescription;
+import com.netflix.spinnaker.clouddriver.google.deploy.instancegroups.GoogleServerGroupManagersFactory;
+import com.netflix.spinnaker.clouddriver.google.deploy.ops.SetStatefulDiskAtomicOperation;
+import com.netflix.spinnaker.clouddriver.google.provider.view.GoogleClusterProvider;
+import com.netflix.spinnaker.clouddriver.orchestration.AtomicOperations;
+import com.netflix.spinnaker.clouddriver.security.AbstractAtomicOperationsCredentialsSupport;
+import java.util.Map;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@GoogleOperation(AtomicOperations.SET_STATEFUL_DISK)
+@Component
+public class SetStatefulDiskAtomicOperationConverter
+ extends AbstractAtomicOperationsCredentialsSupport {
+
+ private final GoogleClusterProvider clusterProvider;
+ private final GoogleServerGroupManagersFactory serverGroupManagersFactory;
+
+ @Autowired
+ public SetStatefulDiskAtomicOperationConverter(
+ GoogleClusterProvider clusterProvider,
+ GoogleServerGroupManagersFactory serverGroupManagersFactory) {
+ this.clusterProvider = clusterProvider;
+ this.serverGroupManagersFactory = serverGroupManagersFactory;
+ }
+
+ @Override
+ public SetStatefulDiskAtomicOperation convertOperation(Map input) {
+ return new SetStatefulDiskAtomicOperation(
+ clusterProvider, serverGroupManagersFactory, convertDescription(input));
+ }
+
+ @Override
+ public SetStatefulDiskDescription convertDescription(Map input) {
+ return GoogleAtomicOperationConverterHelper.convertDescription(
+ input, this, SetStatefulDiskDescription.class);
+ }
+}

diff:
+package com.netflix.spinnaker.clouddriver.google.deploy.description;
+
+import com.netflix.spinnaker.clouddriver.google.security.GoogleNamedAccountCredentials;
+import com.netflix.spinnaker.clouddriver.security.resources.CredentialsNameable;
+import com.netflix.spinnaker.clouddriver.security.resources.ServerGroupNameable;
+import lombok.Data;
+
+@Data
+public class SetStatefulDiskDescription implements CredentialsNameable, ServerGroupNameable {
+
+ private GoogleNamedAccountCredentials credentials;
+ private String serverGroupName;
+ private String region;
+ private String deviceName;
+}

diff:
@@ -2,6 +2,7 @@ package com.netflix.spinnaker.clouddriver.google.deploy.instancegroups;
import com.google.api.client.googleapis.services.AbstractGoogleClientRequest;
import com.google.api.services.compute.ComputeRequest;
+import com.google.api.services.compute.model.InstanceGroupManager;
import com.google.api.services.compute.model.Operation;
import com.google.common.collect.ImmutableList;
import com.netflix.spectator.api.Registry;
@@ -39,6 +40,20 @@ public abstract class AbstractGoogleServerGroupManagers implements GoogleServerG
abstract ComputeRequest<Operation> performDelete() throws IOException;
+ @Override
+ public InstanceGroupManager get() throws IOException {
+ return timeExecute(performGet(), "get");
+ }
+
+ abstract ComputeRequest<InstanceGroupManager> performGet() throws IOException;
+
+ @Override
+ public Operation update(InstanceGroupManager content) throws IOException {
+ return timeExecute(performUpdate(content), "update");
+ }
+
+ abstract ComputeRequest<Operation> performUpdate(InstanceGroupManager content) throws IOException;
+
private <T> T timeExecute(AbstractGoogleClientRequest<T> request, String api) throws IOException {
return GoogleExecutor.timeExecute(
registry,

diff:
@@ -2,6 +2,7 @@ package com.netflix.spinnaker.clouddriver.google.deploy.instancegroups;
import com.google.api.services.compute.Compute.InstanceGroupManagers;
import com.google.api.services.compute.Compute.RegionInstanceGroupManagers;
+import com.google.api.services.compute.model.InstanceGroupManager;
import com.google.api.services.compute.model.Operation;
import com.netflix.spinnaker.clouddriver.google.model.GoogleServerGroup;
import java.io.IOException;
@@ -17,5 +18,9 @@ public interface GoogleServerGroupManagers {
Operation delete() throws IOException;
+ InstanceGroupManager get() throws IOException;
+
+ Operation update(InstanceGroupManager content) throws IOException;
+
GoogleServerGroupOperationPoller getOperationPoller();
}

diff:
@@ -2,6 +2,7 @@ package com.netflix.spinnaker.clouddriver.google.deploy.instancegroups;
import com.google.api.services.compute.Compute;
import com.google.api.services.compute.ComputeRequest;
+import com.google.api.services.compute.model.InstanceGroupManager;
import com.google.api.services.compute.model.Operation;
import com.google.api.services.compute.model.RegionInstanceGroupManagersAbandonInstancesRequest;
import com.google.common.collect.ImmutableList;
@@ -45,6 +46,16 @@ class RegionGoogleServerGroupManagers extends AbstractGoogleServerGroupManagers
return managers.delete(getProject(), region, getInstanceGroupName());
}
+ @Override
+ ComputeRequest<InstanceGroupManager> performGet() throws IOException {
+ return managers.get(getProject(), region, getInstanceGroupName());
+ }
+
+ @Override
+ ComputeRequest<Operation> performUpdate(InstanceGroupManager content) throws IOException {
+ return managers.update(getProject(), region, getInstanceGroupName(), content);
+ }
+
@Override
public GoogleServerGroupOperationPoller getOperationPoller() {
return operationPoller;

diff:
@@ -2,6 +2,7 @@ package com.netflix.spinnaker.clouddriver.google.deploy.instancegroups;
import com.google.api.services.compute.Compute;
import com.google.api.services.compute.ComputeRequest;
+import com.google.api.services.compute.model.InstanceGroupManager;
import com.google.api.services.compute.model.InstanceGroupManagersAbandonInstancesRequest;
import com.google.api.services.compute.model.Operation;
import com.google.common.collect.ImmutableList;
@@ -45,6 +46,16 @@ class ZoneGoogleServerGroupManagers extends AbstractGoogleServerGroupManagers {
return managers.delete(getProject(), zone, getInstanceGroupName());
}
+ @Override
+ ComputeRequest<InstanceGroupManager> performGet() throws IOException {
+ return managers.get(getProject(), zone, getInstanceGroupName());
+ }
+
+ @Override
+ ComputeRequest<Operation> performUpdate(InstanceGroupManager content) throws IOException {
+ return managers.update(getProject(), zone, getInstanceGroupName(), content);
+ }
+
@Override
public GoogleServerGroupOperationPoller getOperationPoller() {
return operationPoller;

diff:
+package com.netflix.spinnaker.clouddriver.google.deploy.ops;
+
+import com.google.api.services.compute.model.InstanceGroupManager;
+import com.google.api.services.compute.model.Operation;
+import com.google.api.services.compute.model.StatefulPolicy;
+import com.google.api.services.compute.model.StatefulPolicyPreservedState;
+import com.google.api.services.compute.model.StatefulPolicyPreservedStateDiskDevice;
+import com.google.common.annotations.VisibleForTesting;
+import com.netflix.spinnaker.clouddriver.data.task.Task;
+import com.netflix.spinnaker.clouddriver.data.task.TaskRepository;
+import com.netflix.spinnaker.clouddriver.google.deploy.GCEUtil;
+import com.netflix.spinnaker.clouddriver.google.deploy.description.SetStatefulDiskDescription;
+import com.netflix.spinnaker.clouddriver.google.deploy.instancegroups.GoogleServerGroupManagers;
+import com.netflix.spinnaker.clouddriver.google.deploy.instancegroups.GoogleServerGroupManagersFactory;
+import com.netflix.spinnaker.clouddriver.google.model.GoogleServerGroup;
+import com.netflix.spinnaker.clouddriver.google.provider.view.GoogleClusterProvider;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.util.HashMap;
+import java.util.List;
+
+public class SetStatefulDiskAtomicOperation extends GoogleAtomicOperation<Void> {
+
+ private static final String BASE_PHASE = "SET_STATEFUL_DISK";
+
+ private final GoogleClusterProvider clusterProvider;
+ private final GoogleServerGroupManagersFactory serverGroupManagersFactory;
+ private final SetStatefulDiskDescription description;
+
+ public SetStatefulDiskAtomicOperation(
+ GoogleClusterProvider clusterProvider,
+ GoogleServerGroupManagersFactory serverGroupManagersFactory,
+ SetStatefulDiskDescription description) {
+ this.clusterProvider = clusterProvider;
+ this.serverGroupManagersFactory = serverGroupManagersFactory;
+ this.description = description;
+ }
+
+ /*
+ curl -X POST -H "Content-Type: application/json" -d '
+ [ { "setStatefulDisk": {
+ "serverGroupName": "myapp-dev-v000",
+ "region": "us-east1",
+ "device-name": "myapp-dev-v000-1",
+ "credentials": "my-account-name"
+ } } ]' localhost:7002/gce/ops
+ */
+ @Override
+ public Void operate(List priorOutputs) {
+
+ Task task = TaskRepository.threadLocalTask.get();
+
+ task.updateStatus(
+ BASE_PHASE,
+ String.format(
+ "Initializing set stateful disk of instance group %s in region %s",
+ description.getServerGroupName(), description.getRegion()));
+
+ GoogleServerGroup.View serverGroup =
+ GCEUtil.queryServerGroup(
+ clusterProvider,
+ description.getAccount(),
+ description.getRegion(),
+ description.getServerGroupName());
+
+ try {
+ GoogleServerGroupManagers managers =
+ serverGroupManagersFactory.getManagers(description.getCredentials(), serverGroup);
+
+ task.updateStatus(BASE_PHASE, "Retrieving current instance group definition");
+
+ InstanceGroupManager instanceGroupManager = managers.get();
+
+ task.updateStatus(BASE_PHASE, "Retrieving associated instance template definition");
+
+ setStatefulPolicy(instanceGroupManager);
+
+ task.updateStatus(BASE_PHASE, "Storing updated instance group definition");
+
+ Operation operation = managers.update(instanceGroupManager);
+
+ task.updateStatus(BASE_PHASE, "Waiting for update to complete");
+
+ managers
+ .getOperationPoller()
+ .waitForOperation(
+ operation, /* timeout= */ null, TaskRepository.threadLocalTask.get(), BASE_PHASE);
+
+ return null;
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ private void setStatefulPolicy(InstanceGroupManager instanceGroupManager) {
+
+ if (instanceGroupManager.getStatefulPolicy() == null) {
+ instanceGroupManager.setStatefulPolicy(new StatefulPolicy());
+ }
+ StatefulPolicy statefulPolicy = instanceGroupManager.getStatefulPolicy();
+ if (statefulPolicy.getPreservedState() == null) {
+ statefulPolicy.setPreservedState(new StatefulPolicyPreservedState());
+ }
+ StatefulPolicyPreservedState preservedState = statefulPolicy.getPreservedState();
+ if (preservedState.getDisks() == null) {
+ preservedState.setDisks(new HashMap<>());
+ }
+ preservedState
+ .getDisks()
+ .put(description.getDeviceName(), new StatefulPolicyPreservedStateDiskDevice());
+ }
+
+ @VisibleForTesting
+ public SetStatefulDiskDescription getDescription() {
+ return description;
+ }
+}

diff:
+package com.netflix.spinnaker.clouddriver.google.deploy.validators;
+
+import com.netflix.spinnaker.clouddriver.deploy.DescriptionValidator;
+import com.netflix.spinnaker.clouddriver.google.GoogleOperation;
+import com.netflix.spinnaker.clouddriver.google.deploy.description.SetStatefulDiskDescription;
+import com.netflix.spinnaker.clouddriver.orchestration.AtomicOperations;
+import java.util.List;
+import org.springframework.stereotype.Component;
+import org.springframework.validation.Errors;
+
+@GoogleOperation(AtomicOperations.SET_STATEFUL_DISK)
+@Component
+public class SetStatefulDiskDescriptionValidator
+ extends DescriptionValidator<SetStatefulDiskDescription> {
+
+ @Override
+ public void validate(
+ List priorDescriptions, SetStatefulDiskDescription description, Errors errors) {
+ StandardGceAttributeValidator helper =
+ new StandardGceAttributeValidator("setStatefulDiskDescription", errors);
+ helper.validateRegion(description.getRegion(), description.getCredentials());
+ helper.validateServerGroupName(description.getServerGroupName());
+ helper.validateName(description.getDeviceName(), "deviceName");
+ }
+}

diff:
+package com.netflix.spinnaker.clouddriver.google.deploy.converters;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.netflix.spinnaker.clouddriver.google.deploy.description.SetStatefulDiskDescription;
+import com.netflix.spinnaker.clouddriver.google.deploy.instancegroups.GoogleServerGroupManagersFactory;
+import com.netflix.spinnaker.clouddriver.google.deploy.ops.SetStatefulDiskAtomicOperation;
+import com.netflix.spinnaker.clouddriver.google.provider.view.GoogleClusterProvider;
+import com.netflix.spinnaker.clouddriver.google.security.FakeGoogleCredentials;
+import com.netflix.spinnaker.clouddriver.google.security.GoogleNamedAccountCredentials;
+import com.netflix.spinnaker.clouddriver.security.AccountCredentialsProvider;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class SetStatefulDiskAtomicOperationConverterTest {
+
+ private static final String ACCOUNT_NAME = "spinnaker-account";
+ private static final String SERVER_GROUP_NAME = "spinnaker-test-v000";
+ private static final String REGION = "us-central1";
+ private static final String DEVICE_NAME = "spinnaker-test-v000-001";
+
+ SetStatefulDiskAtomicOperationConverter converter;
+
+ @Before
+ public void setUp() {
+ GoogleClusterProvider clusterProvider = mock(GoogleClusterProvider.class);
+ GoogleServerGroupManagersFactory serverGroupManagersFactory =
+ mock(GoogleServerGroupManagersFactory.class);
+ converter =
+ new SetStatefulDiskAtomicOperationConverter(clusterProvider, serverGroupManagersFactory);
+
+ AccountCredentialsProvider accountCredentialsProvider = mock(AccountCredentialsProvider.class);
+ GoogleNamedAccountCredentials accountCredentials =
+ new GoogleNamedAccountCredentials.Builder()
+ .name(ACCOUNT_NAME)
+ .credentials(new FakeGoogleCredentials())
+ .build();
+ when(accountCredentialsProvider.getCredentials(any())).thenReturn(accountCredentials);
+ converter.setAccountCredentialsProvider(accountCredentialsProvider);
+ converter.setObjectMapper(new ObjectMapper());
+ }
+
+ @Test
+ public void testConvertDescription() {
+ Map<String, String> input = new HashMap<>();
+ input.put("accountName", ACCOUNT_NAME);
+ input.put("serverGroupName", SERVER_GROUP_NAME);
+ input.put("region", REGION);
+ input.put("deviceName", DEVICE_NAME);
+ SetStatefulDiskDescription description = converter.convertDescription(input);
+
+ assertThat(description.getAccount()).isEqualTo(ACCOUNT_NAME);
+ assertThat(description.getServerGroupName()).isEqualTo(SERVER_GROUP_NAME);
+ assertThat(description.getRegion()).isEqualTo(REGION);
+ assertThat(description.getDeviceName()).isEqualTo(DEVICE_NAME);
+ }
+
+ @Test
+ public void testConvertOperation() {
+ Map<String, String> input = new HashMap<>();
+ input.put("accountName", ACCOUNT_NAME);
+ input.put("serverGroupName", SERVER_GROUP_NAME);
+ input.put("region", REGION);
+ input.put("deviceName", DEVICE_NAME);
+ SetStatefulDiskAtomicOperation operation = converter.convertOperation(input);
+
+ SetStatefulDiskDescription description = operation.getDescription();
+ assertThat(description.getAccount()).isEqualTo(ACCOUNT_NAME);
+ assertThat(description.getServerGroupName()).isEqualTo(SERVER_GROUP_NAME);
+ assertThat(description.getRegion()).isEqualTo(REGION);
+ assertThat(description.getDeviceName()).isEqualTo(DEVICE_NAME);
+ }
+}

diff:
+package com.netflix.spinnaker.clouddriver.google.deploy.ops
+
+import com.google.api.services.compute.model.InstanceGroupManager
+import com.google.api.services.compute.model.Operation
+import com.netflix.spinnaker.clouddriver.data.task.Task
+import com.netflix.spinnaker.clouddriver.data.task.TaskRepository
+import com.netflix.spinnaker.clouddriver.google.deploy.description.SetStatefulDiskDescription
+import com.netflix.spinnaker.clouddriver.google.deploy.instancegroups.GoogleServerGroupManagers
+import com.netflix.spinnaker.clouddriver.google.deploy.instancegroups.GoogleServerGroupManagersFactory
+import com.netflix.spinnaker.clouddriver.google.deploy.instancegroups.GoogleServerGroupOperationPoller
+import com.netflix.spinnaker.clouddriver.google.model.GoogleServerGroup
+import com.netflix.spinnaker.clouddriver.google.provider.view.GoogleClusterProvider
+import com.netflix.spinnaker.clouddriver.google.security.FakeGoogleCredentials
+import com.netflix.spinnaker.clouddriver.google.security.GoogleNamedAccountCredentials
+import spock.lang.Specification
+
+class SetStatefulDiskAtomicOperationUnitSpec extends Specification {
+
+ private static final String SERVER_GROUP = "testapp-v000"
+ private static final String REGION = "us-central1"
+ private static final String DEVICE_NAME = "testapp-v000-001"
+ private static final GoogleNamedAccountCredentials CREDENTIALS =
+ new GoogleNamedAccountCredentials.Builder()
+ .name("spinnaker-account")
+ .credentials(new FakeGoogleCredentials())
+ .build()
+
+ Task task
+ GoogleClusterProvider clusterProvider
+ GoogleServerGroupManagersFactory serverGroupManagersFactory
+ GoogleServerGroupManagers serverGroupManagers
+ GoogleNamedAccountCredentials credentials
+ GoogleServerGroupOperationPoller poller
+
+ def setup() {
+ task = Mock(Task)
+ TaskRepository.threadLocalTask.set(task)
+
+ poller = Mock(GoogleServerGroupOperationPoller)
+
+ serverGroupManagers = Mock(GoogleServerGroupManagers) {
+ _ * getOperationPoller() >> poller
+ }
+
+ serverGroupManagersFactory = Mock(GoogleServerGroupManagersFactory) {
+ _ * getManagers(*_) >> serverGroupManagers
+ }
+
+ clusterProvider = Mock(GoogleClusterProvider) {
+ _ * getServerGroup(*_) >> new GoogleServerGroup(name: SERVER_GROUP).view
+ }
+ }
+
+ void "sets stateful policy on instance group"() {
+ setup:
+ def description = new SetStatefulDiskDescription(
+ serverGroupName: SERVER_GROUP,
+ region: REGION,
+ deviceName: DEVICE_NAME,
+ credentials: CREDENTIALS)
+ def operation = new SetStatefulDiskAtomicOperation(clusterProvider, serverGroupManagersFactory, description)
+ def updateOp = new Operation(name: "xyzzy", status: "DONE")
+ _ * serverGroupManagers.get() >> new InstanceGroupManager()
+
+ when:
+ operation.operate([])
+
+ then:
+ 1 * serverGroupManagers.update({
+ it.getStatefulPolicy().getPreservedState().getDisks().containsKey(DEVICE_NAME)
+ }) >> updateOp
+ 1 * poller.waitForOperation(updateOp, /* timeout= */ null, task, /* phase= */ _)
+ }
+}

diff:
+package com.netflix.spinnaker.clouddriver.google.deploy.validators;
+
+import static org.assertj.core.api.Java6Assertions.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.netflix.spinnaker.clouddriver.google.deploy.description.SetStatefulDiskDescription;
+import com.netflix.spinnaker.clouddriver.google.security.FakeGoogleCredentials;
+import com.netflix.spinnaker.clouddriver.google.security.GoogleNamedAccountCredentials;
+import org.assertj.core.api.Condition;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.springframework.validation.BeanPropertyBindingResult;
+import org.springframework.validation.Errors;
+import org.springframework.validation.FieldError;
+import org.springframework.validation.ObjectError;
+
+@RunWith(JUnit4.class)
+public class SetStatefulDiskDescriptionValidatorTest {
+
+ private static final String ACCOUNT_NAME = "spintest";
+ private static final String REGION = "us-central1";
+ private GoogleNamedAccountCredentials CREDENTIALS =
+ new GoogleNamedAccountCredentials.Builder()
+ .name(ACCOUNT_NAME)
+ .credentials(new FakeGoogleCredentials())
+ .regionToZonesMap(ImmutableMap.of(REGION, ImmutableList.of("us-central1-b")))
+ .build();
+
+ private SetStatefulDiskDescriptionValidator validator;
+
+ @Before
+ public void setUp() {
+ validator = new SetStatefulDiskDescriptionValidator();
+ }
+
+ @Test
+ public void testNoErrors() {
+ SetStatefulDiskDescription description = new SetStatefulDiskDescription();
+ description.setCredentials(CREDENTIALS);
+ description.setRegion(REGION);
+ description.setServerGroupName("testapp-v000");
+ description.setDeviceName("testapp-v000-1");
+
+ Errors errors = new BeanPropertyBindingResult(description, "description");
+
+ validator.validate(ImmutableList.of(), description, errors);
+
+ assertThat(errors.hasErrors()).isFalse();
+ }
+
+ @Test
+ public void testNoFields() {
+ SetStatefulDiskDescription description = new SetStatefulDiskDescription();
+
+ Errors errors = new BeanPropertyBindingResult(description, "description");
+
+ validator.validate(ImmutableList.of(), description, errors);
+
+ assertThat(errors.getAllErrors()).hasSize(3);
+ assertThat(errors.getAllErrors()).haveAtLeastOne(errorOnField("region"));
+ assertThat(errors.getAllErrors()).haveAtLeastOne(errorOnField("serverGroupName"));
+ assertThat(errors.getAllErrors()).haveAtLeastOne(errorOnField("deviceName"));
+ }
+
+ @Test
+ public void testInvalidRegion() {
+ SetStatefulDiskDescription description = new SetStatefulDiskDescription();
+ description.setCredentials(CREDENTIALS);
+ description.setRegion("some-unknown-region");
+ description.setServerGroupName("testapp-v000");
+ description.setDeviceName("testapp-v000-1");
+
+ Errors errors = new BeanPropertyBindingResult(description, "description");
+
+ validator.validate(ImmutableList.of(), description, errors);
+
+ assertThat(errors.getAllErrors()).hasSize(1);
+ assertThat(errors.getAllErrors()).haveAtLeastOne(errorOnField("region"));
+ }
+
+ private Condition<ObjectError> errorOnField(String field) {
+ return new Condition<>(
+ e -> e instanceof FieldError && ((FieldError) e).getField().equals(field),
+ "has field named " + field);
+ }
+}

original_message: feature(google): Add a SetStatefulDisk operation. (#3598)


diff:
@@ -159,7 +159,6 @@ public class Applications {
instances = emptySet();
break;
case STARTED:
- default:
try {
instances = safelyCall(() -> api.instances(appId))
.orElse(emptyMap())
@@ -195,10 +194,16 @@ public class Applications {
log.debug("Unable to retrieve instances for application '" + application.getName() + "': " +
IOUtils.toString(e.getResponse().getBody().in(), Charset.defaultCharset()));
} catch (IOException e1) {
- log.debug("Unable to retrieve instances for application '" + application.getName());
+ log.debug("Unable to retrieve droplet for application '" + application.getName() + "'");
}
instances = emptySet();
+ } catch (Exception ex) {
+ log.debug("Unable to retrieve droplet for application '" + application.getName() + "'");
+ instances = emptySet();
}
+ break;
+ default:
+ instances = emptySet();
}
CloudFoundryDroplet droplet = null;
@@ -239,7 +244,7 @@ public class Applications {
.build()
)
.orElse(null);
- } catch (RetrofitError ignored) {
+ } catch (Exception ex) {
log.debug("Unable to retrieve droplet for application '" + application.getName() + "'");
}

original_message: fix(cf): previously unhandled exception from bad application state (#3631)