Skip to content

Commit

Permalink
feat(cf): add SpEL support for cf manifests (#3299)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jammy Louie committed Nov 20, 2019
1 parent 8c311b8 commit c36b504
Show file tree
Hide file tree
Showing 10 changed files with 342 additions and 181 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
import com.netflix.spinnaker.orca.clouddriver.pipeline.servergroup.support.DetermineTargetServerGroupStage;
import com.netflix.spinnaker.orca.clouddriver.pipeline.servergroup.support.TargetServerGroup;
import com.netflix.spinnaker.orca.clouddriver.pipeline.servergroup.support.TargetServerGroupResolver;
import com.netflix.spinnaker.orca.clouddriver.tasks.providers.cf.Manifest;
import com.netflix.spinnaker.orca.clouddriver.tasks.providers.cf.DeploymentManifest;
import com.netflix.spinnaker.orca.front50.pipeline.PipelineStage;
import com.netflix.spinnaker.orca.kato.pipeline.support.ResizeStrategy;
import com.netflix.spinnaker.orca.kato.pipeline.support.ResizeStrategySupport;
Expand Down Expand Up @@ -146,8 +146,8 @@ List<Stage> composeFlow(Stage stage) {
throw new IllegalStateException(e);
}
}
Manifest.Direct directManifestAttributes =
objectMapper.convertValue(manifest.get("direct"), Manifest.Direct.class);
DeploymentManifest.Direct directManifestAttributes =
objectMapper.convertValue(manifest.get("direct"), DeploymentManifest.Direct.class);

if (!stage.getContext().containsKey("savedCapacity")) {
int instances = directManifestAttributes.getInstances();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,19 @@

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

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableMap;
import com.netflix.spinnaker.kork.artifacts.model.Artifact;
import com.netflix.spinnaker.orca.ExecutionStatus;
import com.netflix.spinnaker.orca.TaskResult;
import com.netflix.spinnaker.orca.clouddriver.KatoService;
import com.netflix.spinnaker.orca.clouddriver.model.TaskId;
import com.netflix.spinnaker.orca.clouddriver.tasks.AbstractCloudProviderAwareTask;
import com.netflix.spinnaker.orca.clouddriver.tasks.manifest.ManifestContext;
import com.netflix.spinnaker.orca.clouddriver.tasks.manifest.ManifestEvaluator;
import com.netflix.spinnaker.orca.pipeline.model.Stage;
import com.netflix.spinnaker.orca.pipeline.util.ArtifactResolver;
import java.util.*;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import javax.annotation.Nonnull;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
Expand All @@ -36,10 +37,9 @@
@RequiredArgsConstructor
@Component
public class CloudFoundryDeployServiceTask extends AbstractCloudProviderAwareTask {
private final ObjectMapper mapper;
private final KatoService kato;
private final ArtifactResolver artifactResolver;
private final ObjectMapper artifactMapper =
new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
private final ManifestEvaluator manifestEvaluator;

@Nonnull
@Override
Expand All @@ -66,22 +66,19 @@ public TaskResult execute(@Nonnull Stage stage) {
@NotNull
private Map<String, Object> bindArtifactIfNecessary(@Nonnull Stage stage) {
Map<String, Object> context = stage.getContext();
Map manifest = (Map) context.get("manifest");
if (manifest.get("artifactId") != null || manifest.get("artifact") != null) {
Artifact artifact =
manifest.get("artifact") != null
? artifactMapper.convertValue(manifest.get("artifact"), Artifact.class)
: null;
Artifact boundArtifact =
artifactResolver.getBoundArtifactForStage(
stage, (String) manifest.get("artifactId"), artifact);
if (boundArtifact == null) {
throw new IllegalArgumentException("Unable to bind the service manifest artifact");
}
manifest.remove("artifactId"); // replacing with the bound artifact now
//noinspection unchecked
manifest.put("artifact", artifactMapper.convertValue(boundArtifact, Map.class));
}
ServiceManifest manifest = mapper.convertValue(context.get("manifest"), ServiceManifest.class);
CloudFoundryManifestContext manifestContext =
CloudFoundryManifestContext.builder()
.source(ManifestContext.Source.Artifact)
.manifestArtifactId(manifest.getArtifactId())
.manifestArtifact(manifest.getArtifact())
.manifestArtifactAccount(manifest.getArtifact().getArtifactAccount())
.skipExpressionEvaluation(
(Boolean)
Optional.ofNullable(context.get("skipExpressionEvaluation")).orElse(false))
.build();
ManifestEvaluator.Result manifestResult = manifestEvaluator.evaluate(stage, manifestContext);
context.put("manifest", manifestResult.getManifests());
return context;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright 2019 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

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

import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import com.netflix.spinnaker.kork.artifacts.model.Artifact;
import com.netflix.spinnaker.orca.clouddriver.tasks.manifest.ManifestContext;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import lombok.Builder;
import lombok.Value;

@Builder(builderClassName = "CloudFoundryManifestContextBuilder", toBuilder = true)
@Value
public class CloudFoundryManifestContext implements ManifestContext {
@Nullable private List<Map<Object, Object>> manifests;

private Source source;

private String manifestArtifactId;
private Artifact manifestArtifact;
private String manifestArtifactAccount;

@Builder.Default private List<String> requiredArtifactIds = Collections.emptyList();
@Builder.Default private List<BindArtifact> requiredArtifacts = Collections.emptyList();

@Builder.Default private boolean skipExpressionEvaluation = false;

@Override
public List<Map<Object, Object>> getManifests() {
return manifests;
}

@JsonPOJOBuilder(withPrefix = "")
public static class CloudFoundryManifestContextBuilder {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,30 +19,47 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableMap;
import com.netflix.spinnaker.kork.artifacts.model.Artifact;
import com.netflix.spinnaker.orca.clouddriver.tasks.manifest.ManifestContext;
import com.netflix.spinnaker.orca.clouddriver.tasks.manifest.ManifestEvaluator;
import com.netflix.spinnaker.orca.clouddriver.tasks.servergroup.ServerGroupCreator;
import com.netflix.spinnaker.orca.pipeline.model.Execution;
import com.netflix.spinnaker.orca.pipeline.model.Stage;
import com.netflix.spinnaker.orca.pipeline.util.ArtifactResolver;
import java.util.*;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.annotation.Nullable;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@RequiredArgsConstructor
class CloudFoundryServerGroupCreator implements ServerGroupCreator {
private final ObjectMapper mapper;
private final ArtifactResolver artifactResolver;

CloudFoundryServerGroupCreator(ObjectMapper mapper, ArtifactResolver artifactResolver) {
this.mapper = mapper;
this.artifactResolver = artifactResolver;
}
private final ManifestEvaluator manifestEvaluator;

@Override
public List<Map> getOperations(Stage stage) {
Map<String, Object> context = stage.getContext();

DeploymentManifest manifest =
mapper.convertValue(context.get("manifest"), DeploymentManifest.class);
CloudFoundryManifestContext manifestContext =
CloudFoundryManifestContext.builder()
.source(ManifestContext.Source.Artifact)
.manifestArtifactId(manifest.getArtifactId())
.manifestArtifact(manifest.getArtifact())
.manifestArtifactAccount(manifest.getArtifact().getArtifactAccount())
.skipExpressionEvaluation(
(Boolean)
Optional.ofNullable(context.get("skipExpressionEvaluation")).orElse(false))
.build();
ManifestEvaluator.Result manifestResult = manifestEvaluator.evaluate(stage, manifestContext);
final Execution execution = stage.getExecution();
ImmutableMap.Builder<String, Object> operation =
ImmutableMap.<String, Object>builder()
Expand All @@ -54,8 +71,8 @@ public List<Map> getOperations(Stage stage) {
.put("trigger", execution.getTrigger().getOther())
.put(
"applicationArtifact",
applicationArtifact(stage, context.get("applicationArtifact")))
.put("manifest", manifestArtifact(stage, context.get("manifest")));
getCloudFoundryDeployArtifact(stage, context.get("applicationArtifact")))
.put("manifest", manifestResult.getManifests());

if (context.get("stack") != null) {
operation.put("stack", context.get("stack"));
Expand All @@ -69,9 +86,9 @@ public List<Map> getOperations(Stage stage) {
ImmutableMap.<String, Object>builder().put(OPERATION, operation.build()).build());
}

private Artifact applicationArtifact(Stage stage, Object input) {
ApplicationArtifact applicationArtifactInput =
mapper.convertValue(input, ApplicationArtifact.class);
private Artifact getCloudFoundryDeployArtifact(Stage stage, Object input) {
CloudFoundryDeployArtifact applicationArtifactInput =
mapper.convertValue(input, CloudFoundryDeployArtifact.class);
Artifact artifact =
artifactResolver.getBoundArtifactForStage(
stage,
Expand All @@ -84,10 +101,6 @@ private Artifact applicationArtifact(Stage stage, Object input) {
return artifact;
}

private Artifact manifestArtifact(Stage stage, Object input) {
return mapper.convertValue(input, Manifest.class).toArtifact(artifactResolver, stage);
}

@Override
public boolean isKatoResultExpected() {
return false;
Expand All @@ -104,7 +117,7 @@ public Optional<String> getHealthProviderName() {
}

@Data
private static class ApplicationArtifact {
private static class CloudFoundryDeployArtifact {
@Nullable private String artifactId;

@Nullable private Artifact artifact;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,8 @@
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 java.util.Base64;
import java.util.Collections;
import java.util.List;
Expand All @@ -36,16 +30,19 @@
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Setter
public class Manifest {
@Getter
public class DeploymentManifest {
@Nullable private Direct direct;

@Nullable private String artifactId;

@Nullable private Artifact artifact;

public Artifact toArtifact(ArtifactResolver artifactResolver, Stage stage) {
public Artifact getArtifact() {
if (direct != null) {
return Artifact.builder()
.name("manifest")
Expand All @@ -54,38 +51,24 @@ public Artifact toArtifact(ArtifactResolver artifactResolver, Stage stage) {
.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;
return this.artifact;
}

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.
@Data
public static class Direct extends DirectManifest {
private final String name = "app"; // doesn't matter, has no effect on the CF app name.

@Getter @Setter private List<String> buildpacks;
private List<String> buildpacks;

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

@Getter @Setter private String healthCheckType;
private String healthCheckType;

@Getter @Setter private String healthCheckHttpEndpoint;
private String healthCheckHttpEndpoint;

@Getter private Map<String, String> env;
private Map<String, String> env;

public void setEnvironment(List<EnvironmentVariable> environment) {
this.env =
Expand All @@ -94,20 +77,21 @@ public void setEnvironment(List<EnvironmentVariable> environment) {
Collectors.toMap(EnvironmentVariable::getKey, EnvironmentVariable::getValue));
}

@Setter private List<String> routes;
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;
private List<String> services;

@Getter @Setter private Integer instances;
private Integer instances;

@Getter @Setter private String memory;
private String memory;

@Override
String toManifestYml() {
try {
Map<String, List<Direct>> apps =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright 2019 Pivotal, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License")
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

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

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;

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

abstract String toManifestYml();
}
Loading

0 comments on commit c36b504

Please sign in to comment.