Skip to content

Commit

Permalink
Label environment parsing (#17)
Browse files Browse the repository at this point in the history
* Use JSON parser for labels and environment
* throw exception if key storage path is not found
  • Loading branch information
kdebisschop authored Jan 22, 2020
1 parent 42a5659 commit 43eacc2
Show file tree
Hide file tree
Showing 9 changed files with 193 additions and 244 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ Optional inputs:

- 0.6.6 Make File Copier binary-safe.
- 0.7.0 Provide container upgrade node step, with ability to set labels and environment variables.
- 0.7.1 Provide ability to remove labels and environment variables via container upgrade.
- 0.7.x Provide ability to remove labels and environment variables via container upgrade.
- 0.9.x Provide reasonable if not complete test coverage prior to 1.x

## Compatibility
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ ext.pluginName = 'Rancher Node Plugins'
ext.pluginDescription = 'Interface with Rancher environments'

scmVersion {
ignoreUncommittedChanges = false
ignoreUncommittedChanges = true
tag {
// Ignore tags that begin with <prefix><versionSeparator>, include all tags
// if prefix is empty.
Expand Down
92 changes: 42 additions & 50 deletions src/main/java/com/bioraft/rundeck/rancher/RancherAddService.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,8 @@
package com.bioraft.rundeck.rancher;

import com.dtolabs.rundeck.core.common.Framework;
import com.dtolabs.rundeck.core.execution.ExecutionContext;
import com.dtolabs.rundeck.core.execution.workflow.steps.FailureReason;
import com.dtolabs.rundeck.core.execution.workflow.steps.StepException;
import com.dtolabs.rundeck.core.plugins.Plugin;
import com.dtolabs.rundeck.core.storage.ResourceMeta;
import com.dtolabs.rundeck.plugins.PluginLogger;
import com.dtolabs.rundeck.plugins.ServiceNameConstants;
import com.dtolabs.rundeck.plugins.descriptions.*;
Expand All @@ -31,8 +28,9 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableMap;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import static com.bioraft.rundeck.rancher.RancherShared.*;
Expand Down Expand Up @@ -110,21 +108,21 @@ public void executeStep(final PluginStepContext context, final Map<String, Objec
stackName = (String) configuration.get("stackName");
}
if (stackName == null || stackName.isEmpty()) {
throw new StepException("Stack Name cannot be empty", Cause.InvalidConfiguration);
throw new StepException("Stack Name cannot be empty", ErrorCause.InvalidConfiguration);
}

if (serviceName == null || serviceName.isEmpty()) {
serviceName = (String) configuration.get("serviceName");
}
if (serviceName == null || serviceName.isEmpty()) {
throw new StepException("Service Name cannot be empty", Cause.InvalidConfiguration);
throw new StepException("Service Name cannot be empty", ErrorCause.InvalidConfiguration);
}

if (imageUuid == null || imageUuid.isEmpty()) {
imageUuid = (String) configuration.get("imageUuid");
}
if (imageUuid == null || imageUuid.isEmpty()) {
throw new StepException("Image UUID cannot be empty", Cause.InvalidConfiguration);
throw new StepException("Image UUID cannot be empty", ErrorCause.InvalidConfiguration);
}

if (dataVolumes == null || dataVolumes.isEmpty()) {
Expand All @@ -147,15 +145,27 @@ public void executeStep(final PluginStepContext context, final Map<String, Objec
String project = context.getFrameworkProject();
PluginLogger logger = context.getLogger();
String endpoint = framework.getProjectProperty(project, PROJ_RANCHER_ENDPOINT);
if (endpoint == null) {
endpoint = framework.getProperty(FMWK_RANCHER_ENDPOINT);
}
String accessKeyPath = framework.getProjectProperty(project, PROJ_RANCHER_ACCESSKEY_PATH);
if (accessKeyPath == null) {
accessKeyPath = framework.getProperty(FMWK_RANCHER_ACCESSKEY_PATH);
}
String secretKeyPath = framework.getProjectProperty(project, PROJ_RANCHER_SECRETKEY_PATH);
if (secretKeyPath == null) {
secretKeyPath = framework.getProperty(FMWK_RANCHER_SECRETKEY_PATH);
}

String spec = endpoint + "/projects/" + environmentId + "/services";
String accessKey = loadStoragePathData(context.getExecutionContext(), accessKeyPath);
String secretKey = loadStoragePathData(context.getExecutionContext(), secretKeyPath);

client.setAccessKey(accessKey);
client.setSecretKey(secretKey);
try {
String accessKey = loadStoragePathData(context.getExecutionContext(), accessKeyPath);
client.setAccessKey(accessKey);
String secretKey = loadStoragePathData(context.getExecutionContext(), secretKeyPath);
client.setSecretKey(secretKey);
} catch (IOException e) {
throw new StepException("Could not get secret storage path", e, ErrorCause.IOException);
}

ImmutableMap.Builder<String, Object> mapBuilder = ImmutableMap.builder();
mapBuilder.put("type", "launchConfig");
Expand All @@ -167,13 +177,22 @@ public void executeStep(final PluginStepContext context, final Map<String, Objec
addJsonData("environment", ensureStringIsJsonObject(environment), mapBuilder);
addJsonData("labels", ensureStringIsJsonObject(labels), mapBuilder);

if (secrets != null && secrets.trim().length() > 0) {
// Add in the new or replacement secrets specified in the step.
List<String> secretsArray = new ArrayList<>();
for (String secretId : secrets.split("/[,; ]+/")) {
secretsArray.add(secretJson(secretId));
}
mapBuilder.put("secrets", "[" + String.join(",", secretsArray) + "]");
}

JsonNode check;
String stackCheck;
String stackId;
try {
// First look for a stack with the designated ID.
stackCheck = endpoint + "/projects/" + environmentId + "/stacks/" + stackName;
logger.log(INFO_LEVEL, "Looking for `" + stackCheck);
logger.log(INFO_LEVEL, "Looking for " + stackCheck);
check = client.get(stackCheck);
if (check.path("type").asText().equals("error")) {
throw new IOException();
Expand All @@ -184,7 +203,7 @@ public void executeStep(final PluginStepContext context, final Map<String, Objec
stackId = stackId(stackName, endpoint, logger);
}
if (stackId == null) {
throw new StepException("Stack does not exist: " + stackName, Cause.InvalidConfiguration);
throw new StepException("Stack does not exist: " + stackName, ErrorCause.InvalidConfiguration);
}

try {
Expand All @@ -196,24 +215,24 @@ public void executeStep(final PluginStepContext context, final Map<String, Objec
logger.log(INFO_LEVEL, "New service ID:" + serviceResult.path("id").asText());
logger.log(INFO_LEVEL, "New service name:" + serviceResult.path("name").asText());
} catch (IOException e) {
throw new StepException("Failed posting to " + spec, e, Cause.InvalidConfiguration);
throw new StepException("Failed posting to " + spec, e, ErrorCause.InvalidConfiguration);
}
}

private String stackId(String stackName, String endpoint, PluginLogger logger) throws StepException {
try {
String stackCheck = endpoint + "/projects/" + environmentId + "/stacks";
logger.log(INFO_LEVEL, "Looking for `" + stackCheck);
JsonNode check = client.get(stackCheck, ImmutableMap.<String, String>builder().put("name", stackName).build());
if (check.path("data").elements().hasNext()) {
return check.path("data").elements().next().path("id").asText();
String stackCheck = endpoint + "/projects/" + environmentId + "/stacks?name=" + stackName;
logger.log(INFO_LEVEL, "Looking for " + stackCheck);
JsonNode check = client.get(stackCheck);
if (check.path("data").has(0)) {
return check.path("data").get(0).path("id").asText();
} else {
logger.log(ERR_LEVEL, "FATAL: no stack `" + stackName + "` was found.");
throw new StepException("Stack does not exist", Cause.InvalidConfiguration);
throw new StepException("Stack does not exist", ErrorCause.InvalidConfiguration);
}
} catch (IOException ex) {
logger.log(ERR_LEVEL, "FATAL: no stack `" + stackName + "` was found.");
throw new StepException("Stack does not exist", Cause.InvalidConfiguration);
throw new StepException("Stack does not exist", ErrorCause.InvalidConfiguration);
}
}

Expand All @@ -230,34 +249,7 @@ private void addJsonData(String name, String data, ImmutableMap.Builder<String,
JsonNode map = objectMapper.readTree(data);
builder.put(name, map);
} catch (JsonProcessingException e) {
throw new StepException("Could not parse JSON for " + name + "\n" + data, e, Cause.InvalidConfiguration);
}
}

public enum Cause implements FailureReason {
InvalidConfiguration,
IOException
}

/**
* Get a (secret) value from password storage.
*
* @param context The current plugin execution context.
* @param passwordStoragePath The path to look up in storage.
* @return The requested secret or password.
* @throws StepException When there is an IO Exception writing to stream.
*/
private String loadStoragePathData(final ExecutionContext context, final String passwordStoragePath) throws StepException {
if (null == passwordStoragePath) {
return null;
}
ResourceMeta contents = context.getStorageTree().getResource(passwordStoragePath).getContents();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try {
contents.writeContent(byteArrayOutputStream);
} catch (IOException e) {
throw new StepException("Could not get " + passwordStoragePath, e, Cause.IOException);
throw new StepException("Could not parse JSON for " + name + "\n" + data, e, ErrorCause.InvalidConfiguration);
}
return new String(byteArrayOutputStream.toByteArray());
}
}
50 changes: 19 additions & 31 deletions src/main/java/com/bioraft/rundeck/rancher/RancherNewStack.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,9 @@
package com.bioraft.rundeck.rancher;

import com.dtolabs.rundeck.core.common.Framework;
import com.dtolabs.rundeck.core.execution.ExecutionContext;
import com.dtolabs.rundeck.core.execution.workflow.steps.FailureReason;
import com.dtolabs.rundeck.core.execution.workflow.steps.StepException;
import com.dtolabs.rundeck.core.plugins.Plugin;
import com.dtolabs.rundeck.core.storage.ResourceMeta;
import com.dtolabs.rundeck.plugins.PluginLogger;
import com.dtolabs.rundeck.plugins.ServiceNameConstants;
import com.dtolabs.rundeck.plugins.descriptions.PluginDescription;
Expand All @@ -31,11 +29,11 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.collect.ImmutableMap;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Map;

import static com.bioraft.rundeck.rancher.RancherShared.*;
import static com.bioraft.rundeck.rancher.RancherShared.loadStoragePathData;
import static com.dtolabs.rundeck.core.Constants.ERR_LEVEL;
import static com.dtolabs.rundeck.core.Constants.INFO_LEVEL;

Expand Down Expand Up @@ -82,19 +80,31 @@ public void executeStep(final PluginStepContext context, final Map<String, Objec
String project = context.getFrameworkProject();
PluginLogger logger = context.getLogger();
String endpoint = framework.getProjectProperty(project, PROJ_RANCHER_ENDPOINT);
if (endpoint == null) {
endpoint = framework.getProperty(FMWK_RANCHER_ENDPOINT);
}
String accessKeyPath = framework.getProjectProperty(project, PROJ_RANCHER_ACCESSKEY_PATH);
if (accessKeyPath == null) {
accessKeyPath = framework.getProperty(FMWK_RANCHER_ACCESSKEY_PATH);
}
String secretKeyPath = framework.getProjectProperty(project, PROJ_RANCHER_SECRETKEY_PATH);
if (secretKeyPath == null) {
secretKeyPath = framework.getProperty(FMWK_RANCHER_SECRETKEY_PATH);
}

String spec = endpoint + "/projects/" + environment + "/stacks/";
String accessKey = loadStoragePathData(context.getExecutionContext(), accessKeyPath);
String secretKey = loadStoragePathData(context.getExecutionContext(), secretKeyPath);

client.setAccessKey(accessKey);
client.setSecretKey(secretKey);
try {
String accessKey = loadStoragePathData(context.getExecutionContext(), accessKeyPath);
client.setAccessKey(accessKey);
String secretKey = loadStoragePathData(context.getExecutionContext(), secretKeyPath);
client.setSecretKey(secretKey);
} catch (IOException e) {
throw new StepException("Could not get secret storage path", e, ErrorCause.IOException);
}

try {
JsonNode check = client.get(spec, ImmutableMap.<String, String>builder().put("name", stackName).build());
if (check.path("data").elements().hasNext()) {
if (check.path("data").has(0)) {
logger.log(ERR_LEVEL, "FATAL: A stack with the name " + stackName + " already exists.");
throw new StepException("Stack already exists", RancherNewStackFailureReason.InvalidConfiguration);
}
Expand All @@ -121,26 +131,4 @@ public enum RancherNewStackFailureReason implements FailureReason {
InvalidEnvironmentName,
IOException
}

/**
* Get a (secret) value from password storage.
*
* @param context The current plugin execution context.
* @param passwordStoragePath The path to look up in storage.
* @return The requested secret or password.
* @throws StepException When there is an IO Exception writing to stream.
*/
private String loadStoragePathData(final ExecutionContext context, final String passwordStoragePath) throws StepException {
if (null == passwordStoragePath) {
return null;
}
ResourceMeta contents = context.getStorageTree().getResource(passwordStoragePath).getContents();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try {
contents.writeContent(byteArrayOutputStream);
} catch (IOException e) {
throw new StepException("Could not get " + passwordStoragePath, e, RancherNewStackFailureReason.IOException);
}
return new String(byteArrayOutputStream.toByteArray());
}
}
71 changes: 70 additions & 1 deletion src/main/java/com/bioraft/rundeck/rancher/RancherShared.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@

package com.bioraft.rundeck.rancher;

import com.dtolabs.rundeck.core.execution.ExecutionContext;
import com.dtolabs.rundeck.core.execution.workflow.steps.FailureReason;
import com.dtolabs.rundeck.core.execution.workflow.steps.StepException;
import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepException;
import com.dtolabs.rundeck.core.storage.ResourceMeta;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

import static com.dtolabs.rundeck.core.plugins.configuration.PropertyResolverFactory.PROJECT_PREFIX;
import static com.dtolabs.rundeck.core.plugins.configuration.PropertyResolverFactory.FRAMEWORK_PREFIX;

Expand Down Expand Up @@ -57,9 +69,15 @@ public class RancherShared {

// Step Plugins
public static final String PROJ_RANCHER_ENDPOINT = PROJECT_PREFIX + RANCHER_CONFIG_ENDPOINT;
public static final String PROJ_RANCHER_ENVIRONMENT_IDS = PROJECT_PREFIX + RANCHER_SERVICE_PROVIDER + "-" + CONFIG_ENVIRONMENT_IDS;
public static final String FMWK_RANCHER_ENDPOINT = FRAMEWORK_PREFIX + RANCHER_CONFIG_ENDPOINT;

public static final String PROJ_RANCHER_ACCESSKEY_PATH = PROJECT_PREFIX + RANCHER_SERVICE_PROVIDER + "-" + CONFIG_ACCESSKEY_PATH;
public static final String FMWK_RANCHER_ACCESSKEY_PATH = FRAMEWORK_PREFIX + RANCHER_SERVICE_PROVIDER + "-" + CONFIG_ACCESSKEY_PATH;

public static final String PROJ_RANCHER_SECRETKEY_PATH = PROJECT_PREFIX + RANCHER_SERVICE_PROVIDER + "-" + CONFIG_SECRETKEY_PATH;
public static final String FMWK_RANCHER_SECRETKEY_PATH = FRAMEWORK_PREFIX + RANCHER_SERVICE_PROVIDER + "-" + CONFIG_SECRETKEY_PATH;

public static final String PROJ_RANCHER_ENVIRONMENT_IDS = PROJECT_PREFIX + RANCHER_SERVICE_PROVIDER + "-" + CONFIG_ENVIRONMENT_IDS;

public static String ensureStringIsJsonObject(String string) {
if (string == null) {
Expand All @@ -76,4 +94,55 @@ public static String ensureStringIsJsonArray(String string) {
String trimmed = string.replaceFirst("^\\s*\\[?", "[").replaceFirst("\\s*$", "");
return trimmed + (trimmed.endsWith("]") ? "" : "]");
}

/**
* Get a (secret) value from password storage.
*
* @param context The current plugin execution context.
* @param passwordStoragePath The path to look up in storage.
* @return The requested secret or password.
* @throws IOException When there is an IO Exception writing to stream.
*/
public static String loadStoragePathData(final ExecutionContext context, final String passwordStoragePath) throws IOException {
if (null == passwordStoragePath) {
throw new IOException("Storage path is not defined.");
}
ResourceMeta contents = context.getStorageTree().getResource(passwordStoragePath).getContents();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
contents.writeContent(byteArrayOutputStream);
return new String(byteArrayOutputStream.toByteArray());
}

/**
* Builds a JsonNode object for insertion into the secrets array.
*
* @param secretId A secret ID from Rancher (like "1se1")
* @return JSON expression for secret reference.
* @throws NodeStepException when JSON is not valid.
*/
public static JsonNode buildSecret(String secretId, String nodeName) throws NodeStepException {
try {
return (new ObjectMapper()).readTree(secretJson(secretId));
} catch (JsonProcessingException e) {
throw new NodeStepException("Failed add secret", e, ErrorCause.InvalidJson, nodeName);
}
}

public static String secretJson(String secretId) {
return "{ \"type\": \"secretReference\", \"gid\": \"0\", \"mode\": \"444\", \"name\": \"\", \"secretId\": \""
+ secretId + "\", \"uid\": \"0\"}";
}

public enum ErrorCause implements FailureReason {
InvalidConfiguration,
InvalidJson,
IOException,
NoKeyStorage,
NoServiceObject,
ServiceNotRunning,
MissingUpgradeURL,
NoUpgradeData,
UpgradeFailure,
Interrupted
}
}
Loading

0 comments on commit 43eacc2

Please sign in to comment.