Skip to content

Commit

Permalink
fix(provider/cf): Bind clone manifest artifacts (#2815)
Browse files Browse the repository at this point in the history
  • Loading branch information
jkschneider committed Apr 5, 2019
1 parent 75f8b6a commit 6cc4aa7
Show file tree
Hide file tree
Showing 9 changed files with 352 additions and 157 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import com.netflix.spinnaker.orca.clouddriver.FeaturesService
import com.netflix.spinnaker.orca.clouddriver.pipeline.servergroup.strategies.AbstractDeployStrategyStage
import com.netflix.spinnaker.orca.clouddriver.tasks.MonitorKatoTask
import com.netflix.spinnaker.orca.clouddriver.tasks.instance.WaitForUpInstancesTask
import com.netflix.spinnaker.orca.clouddriver.tasks.servergroup.CloneServerGroupTask
import com.netflix.spinnaker.orca.clouddriver.tasks.servergroup.clone.CloneServerGroupTask
import com.netflix.spinnaker.orca.clouddriver.tasks.servergroup.ServerGroupCacheForceRefreshTask
import com.netflix.spinnaker.orca.clouddriver.tasks.servergroup.AddServerGroupEntityTagsTask
import com.netflix.spinnaker.orca.pipeline.TaskNode
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,18 @@

package com.netflix.spinnaker.orca.clouddriver.tasks.providers.cf;

import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
import com.google.common.collect.ImmutableMap;
import com.netflix.spinnaker.kork.artifacts.model.Artifact;
import com.netflix.spinnaker.orca.clouddriver.tasks.servergroup.ServerGroupCreator;
import com.netflix.spinnaker.orca.pipeline.model.Stage;
import com.netflix.spinnaker.orca.pipeline.util.ArtifactResolver;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.Nullable;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Component
Expand Down Expand Up @@ -85,22 +76,7 @@ private Artifact applicationArtifact(Stage stage, Object input) {
}

private Artifact manifestArtifact(Stage stage, Object input) {
Manifest manifest = mapper.convertValue(input, Manifest.class);
if (manifest.getDirect() != null) {
return Artifact.builder()
.name("manifest")
.type("embedded/base64")
.artifactAccount("embedded-artifact")
.reference(Base64.getEncoder().encodeToString(manifest.getDirect().toManifestYml().getBytes()))
.build();
}

Artifact artifact = artifactResolver.getBoundArtifactForStage(stage, manifest.getArtifactId(), manifest.getArtifact());
if(artifact == null) {
throw new IllegalArgumentException("Unable to bind the manifest artifact");
}

return artifact;
return mapper.convertValue(input, Manifest.class).toArtifact(artifactResolver, stage);
}

@Override
Expand All @@ -126,89 +102,4 @@ private static class ApplicationArtifact {
@Nullable
private Artifact artifact;
}

@Data
static class Manifest {
@Nullable
private DirectManifest direct;

@Nullable
private String artifactId;

@Nullable
private Artifact artifact;
}

static class DirectManifest {
private static ObjectMapper manifestMapper = new ObjectMapper(new YAMLFactory()
.enable(YAMLGenerator.Feature.MINIMIZE_QUOTES)
.enable(YAMLGenerator.Feature.INDENT_ARRAYS))
.setPropertyNamingStrategy(PropertyNamingStrategy.KEBAB_CASE);

@Getter
private final String name = "app"; // doesn't matter, has no effect on the CF app name.

@Getter
@Setter
private List<String> buildpacks;

@Getter
@Setter
@JsonProperty("disk_quota")
@JsonAlias("diskQuota")
private String diskQuota;

@Getter
@Setter
private String healthCheckType;

@Getter
@Setter
private String healthCheckHttpEndpoint;

@Getter
private Map<String, String> env;

public void setEnvironment(List<EnvironmentVariable> environment) {
this.env = environment.stream().collect(Collectors.toMap(EnvironmentVariable::getKey, EnvironmentVariable::getValue));
}

@Setter
private List<String> routes;

public List<Map<String, String>> getRoutes() {
return routes.stream()
.map(r -> ImmutableMap.<String, String>builder().put("route", r).build())
.collect(Collectors.toList());
}

@Getter
@Setter
private List<String> services;

@Getter
@Setter
private Integer instances;

@Getter
@Setter
private String memory;

String toManifestYml() {
try {
Map<String, List<DirectManifest>> apps = ImmutableMap.<String, List<DirectManifest>>builder()
.put("applications", Collections.singletonList(this))
.build();
return manifestMapper.writeValueAsString(apps);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("Unable to generate Cloud Foundry Manifest", e);
}
}
}

@Data
static class EnvironmentVariable {
String key;
String value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*
* 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.orca.clouddriver.tasks.providers.cf;

import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
import com.google.common.collect.ImmutableMap;
import com.netflix.spinnaker.kork.artifacts.model.Artifact;
import com.netflix.spinnaker.orca.pipeline.model.Stage;
import com.netflix.spinnaker.orca.pipeline.util.ArtifactResolver;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;

import javax.annotation.Nullable;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Setter
public class Manifest {
@Nullable
private Direct direct;

@Nullable
private String artifactId;

@Nullable
private Artifact artifact;

public Artifact toArtifact(ArtifactResolver artifactResolver, Stage stage) {
if (direct != null) {
return Artifact.builder()
.name("manifest")
.type("embedded/base64")
.artifactAccount("embedded-artifact")
.reference(Base64.getEncoder().encodeToString(direct.toManifestYml().getBytes()))
.build();
}

Artifact boundArtifact = artifactResolver.getBoundArtifactForStage(stage, artifactId, this.artifact);
if(boundArtifact == null) {
throw new IllegalArgumentException("Unable to bind the manifest artifact");
}
return boundArtifact;
}

public static class Direct {
private static ObjectMapper manifestMapper = new ObjectMapper(new YAMLFactory()
.enable(YAMLGenerator.Feature.MINIMIZE_QUOTES)
.enable(YAMLGenerator.Feature.INDENT_ARRAYS))
.setPropertyNamingStrategy(PropertyNamingStrategy.KEBAB_CASE);

@Getter
private final String name = "app"; // doesn't matter, has no effect on the CF app name.

@Getter
@Setter
private List<String> buildpacks;

@Getter
@Setter
@JsonProperty("disk_quota")
@JsonAlias("diskQuota")
private String diskQuota;

@Getter
@Setter
private String healthCheckType;

@Getter
@Setter
private String healthCheckHttpEndpoint;

@Getter
private Map<String, String> env;

public void setEnvironment(List<EnvironmentVariable> environment) {
this.env = environment.stream().collect(Collectors.toMap(EnvironmentVariable::getKey, EnvironmentVariable::getValue));
}

@Setter
private List<String> routes;

public List<Map<String, String>> getRoutes() {
return routes.stream()
.map(r -> ImmutableMap.<String, String>builder().put("route", r).build())
.collect(Collectors.toList());
}

@Getter
@Setter
private List<String> services;

@Getter
@Setter
private Integer instances;

@Getter
@Setter
private String memory;

String toManifestYml() {
try {
Map<String, List<Direct>> apps = ImmutableMap.<String, List<Direct>>builder()
.put("applications", Collections.singletonList(this))
.build();
return manifestMapper.writeValueAsString(apps);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("Unable to generate Cloud Foundry Manifest", e);
}
}
}

@Data
public static class EnvironmentVariable {
private String key;
private String value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* 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.orca.clouddriver.tasks.servergroup.clone

import com.netflix.spinnaker.orca.pipeline.model.Stage
import groovy.util.logging.Slf4j
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component

/**
* Netflix bakes images in their test account. This rigmarole is to allow the
* prod account access to that image.
*/
@Slf4j
@Component
class BakeryImageAccessDescriptionDecorator implements CloneDescriptionDecorator {
@Value('${default.bake.account:default}')
String defaultBakeAccount

@Override
boolean shouldDecorate(Map operation) {
Collection<String> targetRegions = targetRegions(operation)

return getCloudProvider(operation) == "aws" && // the operation is a clone of stage.context.
operation.credentials != defaultBakeAccount &&
targetRegions &&
operation.amiName
}

@Override
void decorate(Map<String, Object> operation, List<Map<String, Object>> descriptions, Stage stage) {
def allowLaunchDescriptions = targetRegions(operation).collect { String region ->
[
allowLaunchDescription: [
account : operation.credentials,
credentials: defaultBakeAccount,
region : region,
amiName : operation.amiName
]
]
}
descriptions.addAll(allowLaunchDescriptions)

log.info("Generated `allowLaunchDescriptions` (allowLaunchDescriptions: ${allowLaunchDescriptions})")
}

private static Collection<String> targetRegions(Map operation) {
return operation.region ? [operation.region] :
operation.availabilityZones ? operation.availabilityZones.keySet() : []
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* 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.orca.clouddriver.tasks.servergroup.clone;

import com.netflix.spinnaker.orca.clouddriver.utils.CloudProviderAware;
import com.netflix.spinnaker.orca.pipeline.model.Stage;

import java.util.List;
import java.util.Map;

public interface CloneDescriptionDecorator extends CloudProviderAware {
boolean shouldDecorate(Map<String, Object> operation);

void decorate(Map<String, Object> operation, List<Map<String, Object>> descriptions, Stage stage);
}
Loading

0 comments on commit 6cc4aa7

Please sign in to comment.