package cd.go.plugin.config.yaml.transforms; import cd.go.plugin.config.yaml.YamlConfigException; import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.gson.internal.LinkedTreeMap; import java.text.NumberFormat; import java.text.ParseException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import static cd.go.plugin.config.yaml.JSONUtils.*; import static cd.go.plugin.config.yaml.YamlUtils.*; import static cd.go.plugin.config.yaml.transforms.EnvironmentVariablesTransform.JSON_ENV_VAR_FIELD; public class JobTransform extends ConfigurationTransform { private static final String YAML_JOB_TIMEOUT_FIELD = "timeout"; private static final String JSON_JOB_TIMEOUT_FIELD = "timeout"; private static final String YAML_JOB_TASKS_FIELD = "tasks"; private static final String JSON_JOB_TASKS_FIELD = "tasks"; private static final String YAML_JOB_RUN_INSTANCES_FIELD = "run_instances"; private static final String JSON_JOB_RUN_INSTANCES_FIELD = "run_instance_count"; private static final String JSON_JOB_NAME_FIELD = "name"; private static final String YAML_JOB_TABS_FIELD = "tabs"; private static final String JSON_JOB_TAB_NAME_FIELD = "name"; private static final String JSON_JOB_TAB_PATH_FIELD = "path"; private static final String JSON_JOB_TABS_FIELD = "tabs"; private static final String JSON_JOB_RESOURCES_FIELD = "resources"; private static final String YAML_JOB_RESOURCES_FIELD = "resources"; private static final String JSON_JOB_ELASTIC_PROFILE_FIELD = "elastic_profile_id"; private static final String YAML_JOB_ELASTIC_PROFILE_FIELD = "elastic_profile_id"; private static final String YAML_JOB_ARTIFACTS_FIELD = "artifacts"; private static final String JSON_JOB_ARTIFACTS_FIELD = "artifacts"; private static final String JSON_JOB_ARTIFACT_SOURCE_FIELD = "source"; private static final String YAML_JOB_ARTIFACT_SOURCE_FIELD = "source"; private static final String JSON_JOB_ARTIFACT_DEST_FIELD = "destination"; private static final String YAML_JOB_ARTIFACT_DEST_FIELD = "destination"; private static final String JSON_JOB_ARTIFACT_ARTIFACT_ID_FIELD = "id"; private static final String YAML_JOB_ARTIFACT_ARTIFACT_ID_FIELD = "id"; private static final String JSON_JOB_ARTIFACT_STORE_ID_FIELD = "store_id"; private static final String YAML_JOB_ARTIFACT_STORE_ID_FIELD = "store_id"; private static final String YAML_JOB_PROPS_FIELD = "properties"; private static final String JSON_JOB_PROPS_FIELD = "properties"; private static final String JSON_JOB_PROP_NAME_FIELD = "name"; private static final String JSON_JOB_PROP_SOURCE_FIELD = "source"; private static final String YAML_JOB_PROP_SOURCE_FIELD = "source"; private static final String JSON_JOB_PROP_XPATH_FIELD = "xpath"; private static final String YAML_JOB_PROP_XPATH_FIELD = "xpath"; private static final String EXTERNAL_ARTIFACT_TYPE_FIELD = "external"; private EnvironmentVariablesTransform environmentTransform; private TaskTransform taskTransform; public JobTransform(EnvironmentVariablesTransform environmentTransform, TaskTransform taskTransform) { this.environmentTransform = environmentTransform; this.taskTransform = taskTransform; } public JsonObject transform(Object yamlObject) { Map<String, Object> map = (Map<String, Object>) yamlObject; for (Map.Entry<String, Object> entry : map.entrySet()) { return transform(entry); } throw new RuntimeException("expected job hash to have 1 item"); } public JsonObject transform(Map.Entry<String, Object> entry) { return transform(entry.getKey(), (Map<String, Object>) entry.getValue()); } public JsonObject transform(String jobName, Map<String, Object> jobMap) { JsonObject jobJson = new JsonObject(); jobJson.addProperty(JSON_JOB_NAME_FIELD, jobName); addOptionalInteger(jobJson, jobMap, JSON_JOB_TIMEOUT_FIELD, YAML_JOB_TIMEOUT_FIELD); addRunInstances(jobMap, jobJson); JsonArray jsonEnvVariables = environmentTransform.transform(jobMap); if (jsonEnvVariables != null && jsonEnvVariables.size() > 0) jobJson.add(JSON_ENV_VAR_FIELD, jsonEnvVariables); addTabs(jobJson, jobMap); addOptionalStringList(jobJson, jobMap, JSON_JOB_RESOURCES_FIELD, YAML_JOB_RESOURCES_FIELD); addOptionalString(jobJson, jobMap, JSON_JOB_ELASTIC_PROFILE_FIELD, YAML_JOB_ELASTIC_PROFILE_FIELD); addArtifacts(jobJson, jobMap); addProperties(jobJson, jobMap); addTasks(jobJson, jobMap); return jobJson; } public Map<String, Object> inverseTransform(Map<String, Object> job) { if (job == null) return null; String jobName = (String) job.get(JSON_JOB_NAME_FIELD); Map<String, Object> inverseJob = new LinkedTreeMap<>(); Map<String, Object> jobData = new LinkedTreeMap<>(); addOptionalInt(jobData, job, JSON_JOB_TIMEOUT_FIELD, YAML_JOB_TIMEOUT_FIELD); addInverseRunInstances(jobData, job); Map<String, Object> yamlEnvVariables = environmentTransform.inverseTransform((List<Map<String, Object>>) job.get(JSON_ENV_VAR_FIELD)); if (yamlEnvVariables != null && yamlEnvVariables.size() > 0) jobData.putAll(yamlEnvVariables); addInverseTabs(jobData, job); addOptionalList(jobData, job, JSON_JOB_RESOURCES_FIELD, YAML_JOB_RESOURCES_FIELD); addOptionalValue(jobData, job, JSON_JOB_ELASTIC_PROFILE_FIELD, YAML_JOB_ELASTIC_PROFILE_FIELD); addInverseArtifacts(jobData, job); addInverseProperties(jobData, job); addInverseTasks(jobData, job); inverseJob.put(jobName, jobData); return inverseJob; } private void addInverseRunInstances(Map<String, Object> jobData, Map<String, Object> job) { Object run = job.get(JSON_JOB_RUN_INSTANCES_FIELD); if (run == null || run.equals(0) || run.equals(0.0) || run.equals("0")) return; if (run instanceof String) { addOptionalValue(jobData, job, JSON_JOB_RUN_INSTANCES_FIELD, YAML_JOB_RUN_INSTANCES_FIELD); } else { addOptionalInt(jobData, job, JSON_JOB_RUN_INSTANCES_FIELD, YAML_JOB_RUN_INSTANCES_FIELD); } } private void addInverseTabs(Map<String, Object> jobData, Map<String, Object> job) { List<Map<String, Object>> tabs = (List<Map<String, Object>>) job.get(JSON_JOB_TABS_FIELD); if (tabs == null || tabs.isEmpty()) return; Map<String, Object> inverseTabs = new LinkedTreeMap<>(); for (Map<String, Object> tab : tabs) { inverseTabs.put((String) tab.get(JSON_JOB_TAB_NAME_FIELD), tab.get(JSON_JOB_TAB_PATH_FIELD)); } jobData.put(YAML_JOB_TABS_FIELD, inverseTabs); } private void addInverseTasks(Map<String, Object> jobData, Map<String, Object> job) { List<Map<String, Object>> tasks = (List<Map<String, Object>>) job.get(JSON_JOB_TASKS_FIELD); if (tasks == null) return; List<Map<String, Object>> inverseTasks = new ArrayList<>(); for (Map<String, Object> task : tasks) { inverseTasks.add(null == task ? null : taskTransform.inverseTransform(task)); } jobData.put(YAML_JOB_TASKS_FIELD, inverseTasks); } private void addInverseProperties(Map<String, Object> jobData, Map<String, Object> job) { List<Map<String, Object>> properties = (List<Map<String, Object>>) job.get(JSON_JOB_PROPS_FIELD); if (properties == null || properties.isEmpty()) return; Map<String, Object> inverseProperties = new LinkedTreeMap<>(); for (Map<String, Object> prop : properties) { String name = (String) prop.remove(JSON_JOB_PROP_NAME_FIELD); inverseProperties.put(name, prop); } jobData.put(YAML_JOB_PROPS_FIELD, inverseProperties); } private void addProperties(JsonObject jobJson, Map<String, Object> jobMap) { Object props = jobMap.get(YAML_JOB_PROPS_FIELD); if (props == null) return; if (!(props instanceof Map)) throw new YamlConfigException("properties should be a hash"); JsonArray propsJson = new JsonArray(); Map<String, Object> propsMap = (Map<String, Object>) props; for (Map.Entry<String, Object> propEntry : propsMap.entrySet()) { String propName = propEntry.getKey(); Object propObj = propEntry.getValue(); if (!(propObj instanceof Map)) throw new YamlConfigException("property " + propName + " should be a hash"); Map<String, Object> propMap = (Map<String, Object>) propObj; JsonObject propJson = new JsonObject(); propJson.addProperty(JSON_JOB_PROP_NAME_FIELD, propName); addRequiredString(propJson, propMap, JSON_JOB_PROP_SOURCE_FIELD, YAML_JOB_PROP_SOURCE_FIELD); addRequiredString(propJson, propMap, JSON_JOB_PROP_XPATH_FIELD, YAML_JOB_PROP_XPATH_FIELD); propsJson.add(propJson); } jobJson.add(JSON_JOB_PROPS_FIELD, propsJson); } private void addInverseArtifacts(Map<String, Object> jobData, Map<String, Object> job) { List<Map<String, Object>> artifacts = (List<Map<String, Object>>) job.get(JSON_JOB_ARTIFACTS_FIELD); if (artifacts == null || artifacts.isEmpty()) return; List<Map<String, Object>> inverseArtifacts = new ArrayList<>(); for (Map<String, Object> artifact : artifacts) { Map<String, Object> inverseArtifact = new LinkedTreeMap<>(); String type = (String) artifact.remove("type"); inverseArtifact.put(type, artifact); inverseArtifacts.add(inverseArtifact); handleExternalArtifactConfiguration(type, inverseArtifact); } jobData.put(YAML_JOB_ARTIFACTS_FIELD, inverseArtifacts); } private void handleExternalArtifactConfiguration(String type, Map<String, Object> inverseArtifact) { if (!EXTERNAL_ARTIFACT_TYPE_FIELD.equals(type)) { return; } final Map<String, Object> artifactMap = (Map<String, Object>) inverseArtifact.get(EXTERNAL_ARTIFACT_TYPE_FIELD); final List<Map<String, String>> configuration = (List<Map<String, String>>) artifactMap.get(JSON_PLUGIN_CONFIGURATION_FIELD); final Map<String, Map<String, String>> result = new HashMap<>(); for (Map<String, String> configProperty : configuration) { final boolean isEncryptedProperty = configProperty.containsKey(JSON_PLUGIN_CONFIG_ENCRYPTED_VALUE_FIELD); String sourceFieldToUse = isEncryptedProperty ? JSON_PLUGIN_CONFIG_ENCRYPTED_VALUE_FIELD : JSON_PLUGIN_CONFIG_VALUE_FIELD; String mapToPutIntoInResult = isEncryptedProperty ? YAML_PLUGIN_SEC_CONFIG_FIELD : YAML_PLUGIN_STD_CONFIG_FIELD; result.putIfAbsent(mapToPutIntoInResult, new HashMap<>()); result.get(mapToPutIntoInResult).put(configProperty.get(JSON_PLUGIN_CONFIG_KEY_FIELD), configProperty.get(sourceFieldToUse)); } artifactMap.put(JSON_PLUGIN_CONFIGURATION_FIELD, result); } private void addArtifacts(JsonObject jobJson, Map<String, Object> jobMap) { Object artifacts = jobMap.get(YAML_JOB_ARTIFACTS_FIELD); if (artifacts == null) return; if (!(artifacts instanceof List)) throw new YamlConfigException("artifacts should be a list of hashes"); JsonArray artifactArrayJson = new JsonArray(); List<Object> artifactsList = (List<Object>) artifacts; for (Object artifactObj : artifactsList) { if (!(artifactObj instanceof Map)) throw new YamlConfigException("artifact should be a hash - build:, test: or external:"); Map<String, Object> artifactMap = (Map<String, Object>) artifactObj; for (Map.Entry<String, Object> artMap : artifactMap.entrySet()) { JsonObject artifactJson = new JsonObject(); if ("build".equalsIgnoreCase(artMap.getKey())) artifactJson.addProperty("type", "build"); else if ("test".equalsIgnoreCase(artMap.getKey())) artifactJson.addProperty("type", "test"); else if (EXTERNAL_ARTIFACT_TYPE_FIELD.equalsIgnoreCase(artMap.getKey())) { artifactJson.addProperty("type", EXTERNAL_ARTIFACT_TYPE_FIELD); } else throw new YamlConfigException("expected build:, test:, or external: in artifact, got " + artMap.getKey()); Map<String, Object> artMapValue = (Map<String, Object>) artMap.getValue(); if (EXTERNAL_ARTIFACT_TYPE_FIELD.equalsIgnoreCase(artMap.getKey())) { addRequiredString(artifactJson, artMapValue, JSON_JOB_ARTIFACT_ARTIFACT_ID_FIELD, YAML_JOB_ARTIFACT_ARTIFACT_ID_FIELD); addRequiredString(artifactJson, artMapValue, JSON_JOB_ARTIFACT_STORE_ID_FIELD, YAML_JOB_ARTIFACT_STORE_ID_FIELD); super.addConfiguration(artifactJson, (Map<String, Object>) artMapValue.get("configuration")); } else { addRequiredString(artifactJson, artMapValue, JSON_JOB_ARTIFACT_SOURCE_FIELD, YAML_JOB_ARTIFACT_SOURCE_FIELD); addOptionalString(artifactJson, artMapValue, JSON_JOB_ARTIFACT_DEST_FIELD, YAML_JOB_ARTIFACT_DEST_FIELD); } artifactArrayJson.add(artifactJson); break;// we read first hash and exit } } jobJson.add(JSON_JOB_ARTIFACTS_FIELD, artifactArrayJson); } private void addTabs(JsonObject jobJson, Map<String, Object> jobMap) { Object tabs = jobMap.get(YAML_JOB_TABS_FIELD); if (tabs == null) return; if (!(tabs instanceof Map)) throw new YamlConfigException("tabs should be a hash"); JsonArray tabsJson = new JsonArray(); Map<String, String> tabsMap = (Map<String, String>) tabs; for (Map.Entry<String, String> tab : tabsMap.entrySet()) { String tabName = tab.getKey(); String tabPath = tab.getValue(); JsonObject tabJson = new JsonObject(); tabJson.addProperty(JSON_JOB_TAB_NAME_FIELD, tabName); tabJson.addProperty(JSON_JOB_TAB_PATH_FIELD, tabPath); tabsJson.add(tabJson); } jobJson.add(JSON_JOB_TABS_FIELD, tabsJson); } private void addRunInstances(Map<String, Object> jobMap, JsonObject jobJson) { String runInstancesText = getOptionalString(jobMap, YAML_JOB_RUN_INSTANCES_FIELD); if (runInstancesText != null) { if ("all".equalsIgnoreCase(runInstancesText)) jobJson.addProperty(JSON_JOB_RUN_INSTANCES_FIELD, "all"); else { try { jobJson.addProperty(JSON_JOB_RUN_INSTANCES_FIELD, NumberFormat.getInstance().parse(runInstancesText)); } catch (ParseException e) { throw new YamlConfigException(YAML_JOB_RUN_INSTANCES_FIELD + " must be 'all' or a number", e); } } } } private void addTasks(JsonObject jobJson, Map<String, Object> jobMap) { Object tasksObj = jobMap.get(YAML_JOB_TASKS_FIELD); if (tasksObj == null) throw new YamlConfigException("tasks are required in a job"); JsonArray tasksJson = new JsonArray(); List<Object> taskList = (List<Object>) tasksObj; addTasks(taskList, tasksJson); jobJson.add(JSON_JOB_TASKS_FIELD, tasksJson); } private void addTasks(List<Object> taskList, JsonArray tasksJson) { for (Object maybeTask : taskList) { if (maybeTask instanceof List) { List<Object> taskNestedList = (List<Object>) maybeTask; addTasks(taskNestedList, tasksJson); } else { JsonObject task = taskTransform.transform(maybeTask); tasksJson.add(task); } } } }