/* * Copyright 2016 ThoughtWorks, 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.thoughtworks.go.config.materials; import com.google.gson.annotations.Expose; import com.google.gson.annotations.SerializedName; import com.thoughtworks.go.config.CaseInsensitiveString; import com.thoughtworks.go.config.PipelineConfig; import com.thoughtworks.go.domain.MaterialInstance; import com.thoughtworks.go.domain.MaterialRevision; import com.thoughtworks.go.domain.config.ConfigurationProperty; import com.thoughtworks.go.domain.materials.*; import com.thoughtworks.go.domain.materials.scm.PluggableSCMMaterialInstance; import com.thoughtworks.go.domain.materials.scm.PluggableSCMMaterialRevision; import com.thoughtworks.go.domain.scm.SCM; import com.thoughtworks.go.plugin.access.scm.SCMMetadataStore; import com.thoughtworks.go.util.StringUtil; import com.thoughtworks.go.util.command.ConsoleOutputStreamConsumer; import com.thoughtworks.go.util.command.EnvironmentVariableContext; import com.thoughtworks.go.util.json.JsonHelper; import java.io.File; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.HashMap; import java.util.Map; import java.util.UUID; import static com.thoughtworks.go.util.command.EnvironmentVariableContext.escapeEnvironmentVariable; import static java.lang.String.format; import static org.apache.commons.lang.StringUtils.isEmpty; import static org.apache.commons.lang.StringUtils.upperCase; public class PluggableSCMMaterial extends AbstractMaterial { public static final String TYPE = "PluggableSCMMaterial"; private String scmId; @Expose @SerializedName("scm") private SCM scmConfig; private String folder; private Filter filter; public PluggableSCMMaterial() { super(TYPE); } public PluggableSCMMaterial(String scmId) { this(); this.scmId = scmId; } public PluggableSCMMaterial(PluggableSCMMaterialConfig config) { this(); this.name = config.getName(); this.scmId = config.getScmId(); this.scmConfig = config.getSCMConfig(); this.folder = config.getFolder(); this.filter = config.filter(); } @Override public MaterialConfig config() { return new PluggableSCMMaterialConfig(name, scmConfig, folder, filter); } @Override public Class getInstanceType() { return PluggableSCMMaterialInstance.class; } @Override public MaterialInstance createMaterialInstance() { return new PluggableSCMMaterialInstance(JsonHelper.toJsonString(this), UUID.randomUUID().toString()); } public SCM getScmConfig() { return scmConfig; } public void setSCMConfig(SCM scmConfig) { this.scmConfig = scmConfig; } @Override public String getFolder() { return folder; } public void setFolder(String folder) { this.folder = folder; } public File workingDirectory(File baseFolder) { if (folder == null) { return baseFolder; } return new File(baseFolder, folder); } public Filter getFilter() { return filter; } public void setFilter(Filter filter) { this.filter = filter; } public String getPluginId() { return getScmConfig().getPluginConfiguration().getId(); } @Override public String getFingerprint() { if (isEmpty(fingerprint)) { return scmConfig == null ? null : scmConfig.getFingerprint(); } return fingerprint; } @Override protected void appendCriteria(Map<String, Object> parameters) { parameters.put("fingerprint", getFingerprint()); } @Override protected void appendAttributes(Map<String, Object> parameters) { parameters.put("scmName", CaseInsensitiveString.str(getName())); } public void setFingerprint(String fingerprint) { this.fingerprint = fingerprint; } @Override protected void appendPipelineUniqueCriteria(Map<String, Object> basicCriteria) { basicCriteria.put("dest", folder); } @Override public void updateTo(ConsoleOutputStreamConsumer outputStreamConsumer, File baseDir, RevisionContext revisionContext, SubprocessExecutionContext execCtx) { // do nothing. used in tests. } @Override public void toJson(Map jsonMap, Revision revision) { jsonMap.put("scmType", getTypeForDisplay()); jsonMap.put("materialName", getDisplayName()); jsonMap.put("location", getUriForDisplay()); jsonMap.put("folder", getFolder()); jsonMap.put("action", "Modified"); } // most of the material such as git, hg, p4 all print the file from the root without '/'. but svn print it with '/', we standardize it here. @Override public boolean matches(String name, String regex) { if (regex.startsWith("/")) { regex = regex.substring(1); } return name.matches(regex); } @Override public void emailContent(StringBuilder content, Modification modification) { String scmDetails = getTypeForDisplay() + " : " + getDisplayName(); String revisionDetails = format("revision: %s, completed on %s\n%s", modification.getRevision(), modification.getModifiedTime(), modification.getComment()); content.append(scmDetails).append('\n').append(revisionDetails); } private boolean nameIsEmpty() { return name == null || name.isBlank(); } private boolean scmNameIsEmpty() { return (scmConfig == null || scmConfig.getName() == null || scmConfig.getName().isEmpty()); } @Override public CaseInsensitiveString getName() { if (nameIsEmpty() && !scmNameIsEmpty()) { return new CaseInsensitiveString(scmConfig.getName()); } else { return name; } } @Override public String getDescription() { return getDisplayName(); } @Override public String getTypeForDisplay() { String type = scmConfig == null ? null : SCMMetadataStore.getInstance().displayValue(scmConfig.getPluginConfiguration().getId()); return type == null ? "SCM" : type; } @Override public String getDisplayName() { CaseInsensitiveString name = getName(); return (name == null || name.isBlank()) ? getUriForDisplay() : name.toString(); } @Override public String getUriForDisplay() { return scmConfig.getConfigForDisplay(); } @Override public Map<String, Object> getAttributes(boolean addSecureFields) { Map<String, Object> materialMap = new HashMap<>(); materialMap.put("type", "scm"); materialMap.put("plugin-id", getPluginId()); Map<String, String> configurationMap = scmConfig.getConfiguration().getConfigurationAsMap(addSecureFields); materialMap.put("scm-configuration", configurationMap); return materialMap; } @Override public boolean isAutoUpdate() { return scmConfig.isAutoUpdate(); } @Override public MatchedRevision createMatchedRevision(Modification modification, String searchString) { return new MatchedRevision(searchString, modification.getRevision(), modification.getRevision(), modification.getUserName(), modification.getModifiedTime(), modification.getComment()); } @Override public Boolean isUsedInFetchArtifact(PipelineConfig pipelineConfig) { return Boolean.FALSE; } @Override public Revision oldestRevision(Modifications modifications) { if (modifications.isEmpty()) { return new NullRevision(); } Modification modification = modifications.get(modifications.size() - 1); return new PluggableSCMMaterialRevision(modification.getRevision(), modification.getModifiedTime()); } @Override public String getLongDescription() { return getUriForDisplay(); } @Override public void populateEnvironmentContext(EnvironmentVariableContext context, MaterialRevision materialRevision, File workingDir) { context.setProperty(getEnvironmentVariableKey("GO_SCM_%s_%s", "LABEL"), materialRevision.getRevision().getRevision(), false); for (ConfigurationProperty configurationProperty : scmConfig.getConfiguration()) { context.setProperty(getEnvironmentVariableKey("GO_SCM_%s_%s", configurationProperty.getConfigurationKey().getName()), configurationProperty.getValue(), configurationProperty.isSecure()); } HashMap<String, String> additionalData = materialRevision.getLatestModification().getAdditionalDataMap(); if (additionalData != null) { for (Map.Entry<String, String> entry : additionalData.entrySet()) { boolean isSecure = false; for (EnvironmentVariableContext.EnvironmentVariable secureEnvironmentVariable : context.getSecureEnvironmentVariables()) { String urlEncodedValue = null; try { urlEncodedValue = URLEncoder.encode(secureEnvironmentVariable.value(), "UTF-8"); } catch (UnsupportedEncodingException e) { } boolean isSecureEnvironmentVariableEncoded = !StringUtil.isBlank(urlEncodedValue) && !secureEnvironmentVariable.value().equals(urlEncodedValue); if (isSecureEnvironmentVariableEncoded && entry.getValue().contains(urlEncodedValue)) { isSecure = true; break; } } String key = entry.getKey(); String value = entry.getValue(); context.setProperty(getEnvironmentVariableKey("GO_SCM_%s_%s", key), value, isSecure); } } } private String getEnvironmentVariableKey(String keyPattern, String givenKey) { return escapeEnvironmentVariable(upperCase(format(keyPattern, getName().toString(), givenKey))); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; PluggableSCMMaterial that = (PluggableSCMMaterial) o; if (this.getFingerprint() != null ? !this.getFingerprint().equals(that.getFingerprint()) : that.getFingerprint() != null) { return false; } return true; } @Override public int hashCode() { int result = super.hashCode(); result = 31 * result + (this.getFingerprint() != null ? this.getFingerprint().hashCode() : 0); return result; } @Override public String toString() { return String.format("'PluggableSCMMaterial{%s}'", getLongDescription()); } @Override public boolean supportsDestinationFolder() { return true; } @Override public void updateFromConfig(MaterialConfig materialConfig) { super.updateFromConfig(materialConfig); this.getScmConfig().setConfiguration(((PluggableSCMMaterialConfig)materialConfig).getSCMConfig().getConfiguration()); } }