Skip to content

Commit

Permalink
feat(servergroup): allow ad-hoc entity tags on deployments (#1627)
Browse files Browse the repository at this point in the history
  • Loading branch information
anotherchrisberry authored Sep 20, 2017
1 parent 77ff216 commit be6e239
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 85 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ 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.ServerGroupCacheForceRefreshTask
import com.netflix.spinnaker.orca.clouddriver.tasks.servergroup.ServerGroupMetadataTagTask
import com.netflix.spinnaker.orca.clouddriver.tasks.servergroup.AddServerGroupEntityTagsTask
import com.netflix.spinnaker.orca.pipeline.TaskNode
import com.netflix.spinnaker.orca.pipeline.model.Stage
import groovy.util.logging.Slf4j
Expand Down Expand Up @@ -58,7 +58,7 @@ class CloneServerGroupStage extends AbstractDeployStrategyStage {

if (taggingEnabled) {
tasks += [
new TaskNode.TaskDefinition("tagServerGroup", ServerGroupMetadataTagTask)
new TaskNode.TaskDefinition("tagServerGroup", AddServerGroupEntityTagsTask)
]
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ 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.CreateServerGroupTask
import com.netflix.spinnaker.orca.clouddriver.tasks.servergroup.ServerGroupCacheForceRefreshTask
import com.netflix.spinnaker.orca.clouddriver.tasks.servergroup.ServerGroupMetadataTagTask
import com.netflix.spinnaker.orca.clouddriver.tasks.servergroup.AddServerGroupEntityTagsTask
import com.netflix.spinnaker.orca.pipeline.TaskNode
import com.netflix.spinnaker.orca.pipeline.model.Stage
import org.springframework.beans.factory.annotation.Autowired
Expand Down Expand Up @@ -50,7 +50,7 @@ class CreateServerGroupStage extends AbstractDeployStrategyStage {
]

if (taggingEnabled) {
tasks << TaskNode.task("tagServerGroup", ServerGroupMetadataTagTask)
tasks << TaskNode.task("tagServerGroup", AddServerGroupEntityTagsTask)
}

tasks << TaskNode.task("waitForUpInstances", WaitForUpInstancesTask)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright 2017 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.servergroup

import java.util.concurrent.TimeUnit
import com.fasterxml.jackson.annotation.JsonProperty
import com.netflix.spinnaker.orca.ExecutionStatus
import com.netflix.spinnaker.orca.RetryableTask
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.pipeline.model.Stage
import groovy.util.logging.Slf4j
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component

@Component
@Slf4j
class AddServerGroupEntityTagsTask extends AbstractCloudProviderAwareTask implements RetryableTask {
long backoffPeriod = TimeUnit.SECONDS.toMillis(5)
long timeout = TimeUnit.MINUTES.toMillis(5)

@Autowired
KatoService kato

@Autowired
Collection<ServerGroupEntityTagGenerator> tagGenerators

@Override
TaskResult execute(Stage stage) {
try {
List<Map> tagOperations = buildTagOperations(stage)
if (!tagOperations) {
return new TaskResult(ExecutionStatus.SKIPPED)
}
TaskId taskId = kato.requestOperations(tagOperations).toBlocking().first()
return new TaskResult(ExecutionStatus.SUCCEEDED, new HashMap<String, Object>() {
{
put("notification.type", "upsertentitytags")
put("kato.last.task.id", taskId)
}
})
} catch (Exception e) {
log.error("Failed to tag deployed server groups (stageId: ${stage.id}, executionId: ${stage.execution.id})", e)
return new TaskResult(ExecutionStatus.FAILED_CONTINUE)
}
}

private List<Map> buildTagOperations(Stage stage) {
def operations = []
((StageData) stage.mapTo(StageData)).deployServerGroups.each { String region, Set<String> serverGroups ->
serverGroups.each { String serverGroup ->
Collection<Map<String, Object>> tags = tagGenerators ? tagGenerators.findResults { it.generateTags(stage) }.flatten() : []
if (!tags) {
return []
}
operations <<
[
"upsertEntityTags": [
tags : tags,
entityRef: [
entityType : "servergroup",
entityId : serverGroup,
account : getCredentials(stage),
region : region,
cloudProvider: getCloudProvider(stage)
]
]
]
}
}

return operations
}

static class StageData {
@JsonProperty("deploy.server.groups")
Map<String, Set<String>> deployServerGroups = [:]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2017 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.servergroup;

import com.netflix.spinnaker.orca.pipeline.model.Stage;
import org.springframework.stereotype.Component;

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

@Component
public class ContextBasedServerGroupEntityTagGenerator implements ServerGroupEntityTagGenerator {

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

if (context.containsKey("entityTags")) {
return (List<Map<String, Object>>) context.getOrDefault("entityTags", null);
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@

import com.netflix.spinnaker.orca.pipeline.model.Stage;

import java.util.Collection;
import java.util.Map;

public interface ServerGroupEntityTagGenerator {

/**
* Generates an entity tag (e.g. server group provenance metadata) to be applied to a server group after deployment
* Generates a collection of entity tags (e.g. server group provenance metadata) to be applied to a server group after deployment
* @param stage the stage that performed the deployment
* @return a map representing a tag to send to Clouddriver
* @return a collection of maps representing tags to send to Clouddriver
*/
Map<String, Object> generateTag(Stage stage);
Collection<Map<String, Object>> generateTags(Stage stage);
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/*
* Copyright 2017 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* 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
* 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,
Expand All @@ -16,80 +16,13 @@

package com.netflix.spinnaker.orca.clouddriver.tasks.servergroup

import java.util.concurrent.TimeUnit
import com.fasterxml.jackson.annotation.JsonProperty
import com.netflix.spinnaker.orca.ExecutionStatus
import com.netflix.spinnaker.orca.RetryableTask
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.pipeline.model.Stage
import groovy.util.logging.Slf4j
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component

@Component
@Slf4j
class ServerGroupMetadataTagTask extends AbstractCloudProviderAwareTask implements RetryableTask {
long backoffPeriod = TimeUnit.SECONDS.toMillis(5)
long timeout = TimeUnit.MINUTES.toMillis(5)

@Autowired
KatoService kato

@Autowired
Collection<ServerGroupEntityTagGenerator> tagGenerators

@Override
TaskResult execute(Stage stage) {
try {
List<Map> tagOperations = buildTagOperations(stage)
if (!tagOperations) {
return new TaskResult(ExecutionStatus.SKIPPED)
}
TaskId taskId = kato.requestOperations(tagOperations).toBlocking().first()
return new TaskResult(ExecutionStatus.SUCCEEDED, new HashMap<String, Object>() {
{
put("notification.type", "upsertentitytags")
put("kato.last.task.id", taskId)
}
})
} catch (Exception e) {
log.error("Failed to tag deployed server groups (stageId: ${stage.id}, executionId: ${stage.execution.id})", e)
return new TaskResult(ExecutionStatus.FAILED_CONTINUE)
}
}

private List<Map> buildTagOperations(Stage stage) {
def operations = []
((StageData) stage.mapTo(StageData)).deployServerGroups.each { String region, Set<String> serverGroups ->
serverGroups.each { String serverGroup ->
Collection<Map<String, Object>> tags = tagGenerators ? tagGenerators.findResults { it.generateTag(stage) } : []
if (!tags) {
return []
}
operations <<
[
"upsertEntityTags": [
tags : tags,
entityRef: [
entityType : "servergroup",
entityId : serverGroup,
account : getCredentials(stage),
region : region,
cloudProvider: getCloudProvider(stage)
]
]
]
}
}

return operations
}

static class StageData {
@JsonProperty("deploy.server.groups")
Map<String, Set<String>> deployServerGroups = [:]
}
@Deprecated
class ServerGroupMetadataTagTask extends AddServerGroupEntityTagsTask {
// TODO: Remove after 11/11/17 - only here to smooth over initial round of deployments
// Just use AddServerGroupEntityTagsTask
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,16 @@
import com.netflix.spinnaker.orca.pipeline.model.Stage;
import org.springframework.stereotype.Component;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

@Component
public class SpinnakerMetadataServerGroupTagGenerator implements ServerGroupEntityTagGenerator {

@Override
public Map<String, Object> generateTag(Stage stage) {
public Collection<Map<String, Object>> generateTags(Stage stage) {
Execution execution = stage.getExecution();
Map context = stage.getContext();

Expand Down Expand Up @@ -61,7 +63,7 @@ public Map<String, Object> generateTag(Stage stage) {
tag.put("name", "spinnaker:metadata");
tag.put("value", value);

return tag;
return Collections.singletonList(tag);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ import spock.lang.Unroll
import static com.netflix.spinnaker.orca.test.model.ExecutionBuilder.orchestration
import static com.netflix.spinnaker.orca.test.model.ExecutionBuilder.pipeline

class ServerGroupMetadataTagTaskSpec extends Specification {
class AddServerGroupEntityTagsTaskSpec extends Specification {

KatoService katoService = Mock(KatoService)

ServerGroupEntityTagGenerator tagGenerator = new SpinnakerMetadataServerGroupTagGenerator()

@Subject
ServerGroupMetadataTagTask task = new ServerGroupMetadataTagTask(kato: katoService, tagGenerators: [tagGenerator])
AddServerGroupEntityTagsTask task = new AddServerGroupEntityTagsTask(kato: katoService, tagGenerators: [tagGenerator])

List<Map> taggingOps = null

Expand Down Expand Up @@ -146,7 +146,7 @@ class ServerGroupMetadataTagTaskSpec extends Specification {

void "skips tagging when no tag generators or generators do not produce any tags"() {
given:
ServerGroupMetadataTagTask emptyTask = new ServerGroupMetadataTagTask(kato: katoService, tagGenerators: [])
AddServerGroupEntityTagsTask emptyTask = new AddServerGroupEntityTagsTask(kato: katoService, tagGenerators: [])

when:
def stage = new Stage<>(new Pipeline("orca"), "whatever", [
Expand Down

0 comments on commit be6e239

Please sign in to comment.