/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 io.fabric8.kubernetes.api.pipelines;
import io.fabric8.kubernetes.api.KubernetesHelper;
import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.utils.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.beans.IntrospectionException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
*/
public class PipelineConfiguration {
private static final transient Logger LOG = LoggerFactory.getLogger(PipelineConfiguration.class);
/**
* The name of the ConfigMap which stores the {@link PipelineConfiguration}
*/
public static final String FABRIC8_PIPELINES = "fabric8-pipelines";
public static final String CI_BRANCH_PATTERNS = "ci-branch-patterns";
public static final String CD_BRANCH_PATTERNS = "cd-branch-patterns";
public static final String ORGANISATION_BRANCH_PATTERNS = "organisation-branch-patterns";
public static final String JOB_NAME_TO_KIND = "job-name-to-kind";
private Map<String, PipelineKind> jobNameToKindMap = new HashMap<>();
private List<String> ciBranchPatterns = new ArrayList<>();
private List<String> cdBranchPatterns = new ArrayList<>();
private Map<String, List<String>> cdGitHostAndOrganisationToBranchPatterns = new HashMap<>();
public PipelineConfiguration() {
}
public PipelineConfiguration(Map<String, String> configMapData) {
this.ciBranchPatterns = loadYamlListOfStrings(configMapData, CI_BRANCH_PATTERNS);
this.cdBranchPatterns = loadYamlListOfStrings(configMapData, CD_BRANCH_PATTERNS);
Map<Object, Object> orgBranchMap = loadYamlMap(configMapData, ORGANISATION_BRANCH_PATTERNS);
for (Map.Entry<Object, Object> entry : orgBranchMap.entrySet()) {
Object key = entry.getKey();
Object value = entry.getValue();
if (key instanceof String) {
String keyText = (String) key;
List<String> list = null;
if (value instanceof List) {
list = (List<String>) value;
} else if (value != null) {
String valueText = value.toString();
list = new ArrayList<>();
list.add(valueText);
}
if (list != null) {
cdGitHostAndOrganisationToBranchPatterns.put(keyText, list);
} else {
LOG.warn("Could not find List for " + ORGANISATION_BRANCH_PATTERNS + " key " + key + " value: " + value);
}
}
}
Map<Object, Object> jobNameMap = loadYamlMap(configMapData, JOB_NAME_TO_KIND);
for (Map.Entry<Object, Object> entry : jobNameMap.entrySet()) {
Object key = entry.getKey();
Object value = entry.getValue();
if (key != null && value != null) {
String keyText = key.toString();
String valueText = value.toString();
try {
PipelineKind pipelineKind = PipelineKind.valueOf(valueText);
jobNameToKindMap.put(keyText, pipelineKind);
} catch (IllegalArgumentException e) {
LOG.warn("Ignoring " + JOB_NAME_TO_KIND + " key " + key + " with value: " + value
+ ". Values are: " + Arrays.asList(PipelineKind.values()) + ". " + e, e);
}
}
}
}
public static PipelineConfiguration createDefault() {
PipelineConfiguration configuration = new PipelineConfiguration();
configuration.getCiBranchPatterns().add("PR-.*");
return configuration;
}
/**
* Parses the git URL string and determines the host and organisation string
*/
public static String getGitHostOrganisationString(String gitUrl) {
if (Strings.isNullOrBlank(gitUrl)) {
return null;
}
String prefix = "git@";
if (gitUrl.startsWith(prefix)) {
String text = gitUrl.substring(prefix.length());
int idx = text.indexOf('/');
if (idx > 0) {
return text.substring(0, idx).replace(':', '/');
}
}
String schemeSuffix = "://";
int idx = gitUrl.indexOf(schemeSuffix);
if (idx > 0) {
String text = gitUrl.substring(idx + schemeSuffix.length());
idx = text.indexOf("/");
if (idx > 0) {
idx = text.indexOf("/", idx + 1);
if (idx > 0) {
return text.substring(0, idx);
}
}
}
return null;
}
/**
* Loads the pipeline configuration from the namespace in kubernetes if it is present. Otherwise a default
* configuration is loaded.
*/
public static PipelineConfiguration loadPipelineConfiguration(KubernetesClient kubernetesClient, String namespace) {
ConfigMap configMap = kubernetesClient.configMaps().inNamespace(namespace).withName(FABRIC8_PIPELINES).get();
if (configMap != null) {
return getPipelineConfiguration(configMap);
}
PipelineConfiguration configuration = createDefault();
return configuration;
}
/**
* Saves the {@link PipelineConfiguration} into a {@link ConfigMap} in the given namespace
*/
public static void savePipelineConfiguration(KubernetesClient kubernetesClient, String namespace, PipelineConfiguration configuration) {
ConfigMap configMap = configuration.createConfigMap();
kubernetesClient.configMaps().inNamespace(namespace).withName(FABRIC8_PIPELINES).createOrReplace(configMap);
}
@Override
public String toString() {
return "PipelineConfiguration{" +
"jobNameToKindMap=" + jobNameToKindMap +
", ciBranchPatterns=" + ciBranchPatterns +
", cdBranchPatterns=" + cdBranchPatterns +
", cdGitHostAndOrganisationToBranchPatterns=" + cdGitHostAndOrganisationToBranchPatterns +
'}';
}
/**
* Creates the {@link ConfigMap} for the current configuration
*/
public ConfigMap createConfigMap() {
Map<String, String> data = new HashMap<>();
data.put(JOB_NAME_TO_KIND, asYaml(jobNameToKindMap));
data.put(CI_BRANCH_PATTERNS, asYaml(ciBranchPatterns));
data.put(CD_BRANCH_PATTERNS, asYaml(cdBranchPatterns));
data.put(ORGANISATION_BRANCH_PATTERNS, asYaml(cdGitHostAndOrganisationToBranchPatterns));
return new ConfigMapBuilder().withNewMetadata().
withName(FABRIC8_PIPELINES).addToLabels("provider", "fabric8").endMetadata().
withData(data).build();
}
private String asYaml(Object value) {
if (value != null) {
try {
return KubernetesHelper.toYaml(value);
} catch (IOException e) {
LOG.warn("Error trying to convert " + value + " to YAML: " + e, e);
}
}
return "";
}
public static PipelineConfiguration getPipelineConfiguration(ConfigMap configMap) {
Map<String, String> data = configMap.getData();
if (data == null) {
data = new HashMap<>();
}
return new PipelineConfiguration(data);
}
private Map<Object, Object> loadYamlMap(Map<String, String> configMapData, String key) {
String text = configMapData.get(key);
if (Strings.isNotBlank(text)) {
try {
return KubernetesHelper.loadYaml(text, Map.class);
} catch (IOException e) {
LOG.warn("Failed to read key " + key + " with text " + text + " due to: " + e, e);
}
}
return Collections.EMPTY_MAP;
}
private List<String> loadYamlListOfStrings(Map<String, String> configMapData, String key) {
List<String> answer = new ArrayList<>();
String text = configMapData.get(key);
if (Strings.isNotBlank(text)) {
try {
List list = KubernetesHelper.loadYaml(text, List.class);
for (Object value : list) {
if (value instanceof String) {
String textValue = (String) value;
answer.add(textValue);
}
}
} catch (IOException e) {
LOG.warn("Failed to read key " + key + " with text " + text + " due to: " + e, e);
}
}
return answer;
}
public Map<String, PipelineKind> getJobNameToKindMap() {
return jobNameToKindMap;
}
public List<String> getCiBranchPatterns() {
return ciBranchPatterns;
}
public List<String> getCdBranchPatterns() {
return cdBranchPatterns;
}
public Map<String, List<String>> getCdGitHostAndOrganisationToBranchPatterns() {
return cdGitHostAndOrganisationToBranchPatterns;
}
public void setJobNameToKindMap(Map<String, PipelineKind> jobNameToKindMap) {
this.jobNameToKindMap = jobNameToKindMap;
}
public void setCiBranchPatterns(List<String> ciBranchPatterns) {
this.ciBranchPatterns = ciBranchPatterns;
}
public void setCdBranchPatterns(List<String> cdBranchPatterns) {
this.cdBranchPatterns = cdBranchPatterns;
}
public void setCdGitHostAndOrganisationToBranchPatterns(Map<String, List<String>> cdGitHostAndOrganisationToBranchPatterns) {
this.cdGitHostAndOrganisationToBranchPatterns = cdGitHostAndOrganisationToBranchPatterns;
}
public Pipeline getPipeline(Map<String, String> jobEnvironmentMap) throws IntrospectionException {
JobEnvironment jobEnvironment = JobEnvironment.create(jobEnvironmentMap);
return getPipeline(jobEnvironment);
}
public PipelineConfiguration setJobNamesCD(String... names) {
return setJobNamesKind(PipelineKind.CD, names);
}
public PipelineConfiguration setJobNamesCI(String... names) {
return setJobNamesKind(PipelineKind.CI, names);
}
public PipelineConfiguration setJobNamesDeveloper(String... names) {
return setJobNamesKind(PipelineKind.Developer, names);
}
public PipelineConfiguration setJobNamesKind(PipelineKind kind, String... names) {
for (String name : names) {
jobNameToKindMap.put(name, kind);
}
return this;
}
/**
* Adds one or more strings of the format of <code>domainName/organisationName</code> such as a String <code>github.com/fabric8io</code>
* which is used to configure the public gitub organisation as being a environment for the given list of branch patterns
*/
public PipelineConfiguration setCDGitOrganisation(String gitHostAndOrganisation, String... branchPatterns) {
return setCDGitOrganisation(gitHostAndOrganisation, Arrays.asList(branchPatterns));
}
/**
* Adds one or more strings of the format of <code>domainName/organisationName</code> such as a String <code>github.com/fabric8io</code>
* which is used to configure the public gitub organisation as being a environment for the given list of branch patterns
*/
public PipelineConfiguration setCDGitOrganisation(String gitHostAndOrganisation, List<String> branchPatterns) {
if (branchPatterns.isEmpty()) {
throw new IllegalArgumentException("You must specify at least one branch pattern for github host and organisation: " + gitHostAndOrganisation);
}
cdGitHostAndOrganisationToBranchPatterns.put(gitHostAndOrganisation, branchPatterns);
return this;
}
public Pipeline getPipeline(JobEnvironment jobEnvironment) {
String jobName = jobEnvironment.getJobName();
PipelineKind kind = jobNameToKindMap.get(jobName);
if (kind != null) {
return new Pipeline(kind, jobName);
}
// lets figure out the defaults instead
String branchName = jobEnvironment.getBranchName();
kind = PipelineKind.Developer;
if (Strings.isNullOrBlank(branchName)) {
LOG.warn("No BranchName from the environment so cannot detect CI / PR jobs!");
} else {
String gitUrl = jobEnvironment.getGitUrl();
if (Strings.isNotBlank(gitUrl)) {
String hostOrganisation = getGitHostOrganisationString(gitUrl);
if (Strings.isNotBlank(hostOrganisation)) {
List<String> branchPatterns = cdGitHostAndOrganisationToBranchPatterns.get(hostOrganisation);
if (branchPatterns != null && matchesPattern(branchName, branchPatterns)) {
return new Pipeline(PipelineKind.CD, jobName);
}
}
}
// lets use the default branch patterns
if (matchesPattern(branchName, ciBranchPatterns)) {
kind = PipelineKind.CI;
} else if (matchesPattern(branchName, cdBranchPatterns)) {
kind = PipelineKind.CD;
}
}
return new Pipeline(kind, jobName);
}
protected boolean matchesPattern(String text, List<String> listOfPatterns) {
for (String pattern : listOfPatterns) {
if (text.matches(pattern)) {
return true;
}
}
return false;
}
}