Skip to content

Commit

Permalink
feat(runJob): support artifacts in k8s run job (#3086)
Browse files Browse the repository at this point in the history
adds support for artifacts in the v2 kubernetes run job implementation.
follows the same patterns as deploy manifest. the KubernetesJobRunner
class is responsible for fetching artifacts and setting up the operation
description.
  • Loading branch information
ethanfrogers authored Aug 9, 2019
1 parent c67c780 commit dc9cf8e
Show file tree
Hide file tree
Showing 3 changed files with 230 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright 2019 Armory
*
* 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.manifest;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import com.netflix.spinnaker.kork.artifacts.model.Artifact;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import lombok.Builder;
import lombok.Value;

@Builder(builderClassName = "RunJobManifestContextBuilder", toBuilder = true)
@JsonDeserialize(builder = RunJobManifestContext.RunJobManifestContextBuilder.class)
@Value
public class RunJobManifestContext implements ManifestContext {
@Nullable private Map<Object, Object> manifest;
@Nullable private List<Map<Object, Object>> manifests;

@Builder.Default private Source source = Source.Text;

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

private List<String> requiredArtifactIds;
private List<BindArtifact> requiredArtifacts;

@Builder.Default private boolean skipExpressionEvaluation = false;

@Override
public List<Map<Object, Object>> getManifests() {
if (manifest != null) {
return Arrays.asList(manifest);
}
return manifests;
}

@JsonPOJOBuilder(withPrefix = "")
public static class RunJobManifestContextBuilder {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@

import com.fasterxml.jackson.databind.ObjectMapper;
import com.netflix.spinnaker.orca.clouddriver.tasks.job.JobRunner;
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.manifest.RunJobManifestContext;
import com.netflix.spinnaker.orca.pipeline.model.Stage;
import com.netflix.spinnaker.orca.pipeline.util.ArtifactResolver;
import java.util.*;
Expand All @@ -33,10 +36,15 @@ public class KubernetesJobRunner implements JobRunner {

private ArtifactResolver artifactResolver;
private ObjectMapper objectMapper;
private ManifestEvaluator manifestEvaluator;

public KubernetesJobRunner(ArtifactResolver artifactResolver, ObjectMapper objectMapper) {
public KubernetesJobRunner(
ArtifactResolver artifactResolver,
ObjectMapper objectMapper,
ManifestEvaluator manifestEvaluator) {
this.artifactResolver = artifactResolver;
this.objectMapper = objectMapper;
this.manifestEvaluator = manifestEvaluator;
}

public List<Map> getOperations(Stage stage) {
Expand All @@ -48,6 +56,21 @@ public List<Map> getOperations(Stage stage) {
operation.putAll(stage.getContext());
}

RunJobManifestContext runJobManifestContext = stage.mapTo(RunJobManifestContext.class);
if (runJobManifestContext.getSource().equals(ManifestContext.Source.Artifact)) {
ManifestEvaluator.Result result = manifestEvaluator.evaluate(stage, runJobManifestContext);

List<Map<Object, Object>> manifests = result.getManifests();
if (manifests.size() != 1) {
throw new IllegalArgumentException("Run Job only supports manifests with a single Job.");
}

operation.put("source", "text");
operation.put("manifest", manifests.get(0));
operation.put("requiredArtifacts", result.getRequiredArtifacts());
operation.put("optionalArtifacts", result.getOptionalArtifacts());
}

KubernetesContainerFinder.populateFromStage(operation, stage, artifactResolver);

Map<String, Object> task = new HashMap<>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/*
* Copyright 2019 Armory
*
* 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.kubernetes

import com.fasterxml.jackson.databind.ObjectMapper
import com.netflix.spinnaker.kork.artifacts.model.Artifact
import com.netflix.spinnaker.orca.clouddriver.KatoService
import com.netflix.spinnaker.orca.clouddriver.OortService
import com.netflix.spinnaker.orca.clouddriver.tasks.manifest.ManifestEvaluator
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 com.netflix.spinnaker.orca.pipeline.util.ContextParameterProcessor
import retrofit.client.Header
import retrofit.client.Response
import retrofit.mime.TypedString
import spock.lang.Specification

class KubernetesJobRunnerSpec extends Specification {

def "should return a run job operation if cluster set in context"() {
given:
ArtifactResolver artifactResolver = Mock(ArtifactResolver)
ObjectMapper objectMapper = new ObjectMapper()
ManifestEvaluator manifestEvaluator = Mock(ManifestEvaluator)
def stage = new Stage(Execution.newPipeline("test"), "runJob", [
credentials: "abc", cloudProvider: "kubernetes",
cluster: [
foo: "bar"
]
])
KubernetesJobRunner kubernetesJobRunner = new KubernetesJobRunner(artifactResolver, objectMapper, manifestEvaluator)

when:
def ops = kubernetesJobRunner.getOperations(stage)
def op = ops.get(0)

then:
op.containsKey("runJob") == true
op.get("runJob").containsKey("foo") == true
op.get("runJob").get("foo") == "bar"

}

def "should return a run job operation with all context"() {
given:
ArtifactResolver artifactResolver = Mock(ArtifactResolver)
ObjectMapper objectMapper = new ObjectMapper()
ManifestEvaluator manifestEvaluator = Mock(ManifestEvaluator)
def stage = new Stage(Execution.newPipeline("test"), "runJob", [
credentials: "abc", cloudProvider: "kubernetes",
foo: "bar"
])
KubernetesJobRunner kubernetesJobRunner = new KubernetesJobRunner(artifactResolver, objectMapper, manifestEvaluator)

when:
def ops = kubernetesJobRunner.getOperations(stage)
def op = ops.get(0)

then:
op.containsKey("runJob") == true
op.get("runJob").containsKey("foo") == true
op.get("runJob").get("foo") == "bar"

}

def "getAdditionalOutputs should return manifest log template if present"() {
given:
ArtifactResolver artifactResolver = Mock(ArtifactResolver)
ObjectMapper objectMapper = new ObjectMapper()
ManifestEvaluator manifestEvaluator = Mock(ManifestEvaluator)
def stage = new Stage(Execution.newPipeline("test"), "runJob", [
credentials: "abc", cloudProvider: "kubernetes",
manifest: [
metadata: [
annotations:[
"job.spinnaker.io/logs": "foo"
]
]
]
])
KubernetesJobRunner kubernetesJobRunner = new KubernetesJobRunner(artifactResolver, objectMapper, manifestEvaluator)

when:
def ops = kubernetesJobRunner.getOperations(stage)
def outputs = kubernetesJobRunner.getAdditionalOutputs(stage, ops)

then:
outputs.execution == [logs: "foo"]
}

def "populates manifest from artifact if artifact source"() {
given:
def manifest = [metadata: [name: "manifest"]]
ArtifactResolver artifactResolver = Mock(ArtifactResolver)
ObjectMapper objectMapper = new ObjectMapper()
OortService oortService = Mock(OortService)
ContextParameterProcessor contextParameterProcessor = Mock(ContextParameterProcessor)
KatoService katoService = Mock(KatoService)
ManifestEvaluator manifestEvaluator = new ManifestEvaluator(
artifactResolver, oortService, objectMapper, contextParameterProcessor, katoService
)
def stage = new Stage(Execution.newPipeline("test"), "runJob", [
credentials: "abc", cloudProvider: "kubernetes",
source: "artifact",
manifestArtifactId: "foo",
manifestArtifactAccount: "bar",
])
KubernetesJobRunner kubernetesJobRunner = new KubernetesJobRunner(artifactResolver, objectMapper, manifestEvaluator)

when:
def ops = kubernetesJobRunner.getOperations(stage)
def op = ops.get(0).get("runJob")

then:
1 * contextParameterProcessor.process(_, _, _) >> {
return [manifests: [manifest]]
}
1 * oortService.fetchArtifact(_) >> {
return new Response(
"http://yoyo",
200,
"",
new ArrayList<Header>(),
new TypedString('{"metadata": {"name": "manifest"}}')
)
}
1 * artifactResolver.getBoundArtifactForStage(_, _, _) >> {
return new Artifact()
}
op.manifest == manifest
op.requiredArtifacts == []
op.optionalArtifacts == null
}
}

0 comments on commit dc9cf8e

Please sign in to comment.