Skip to content

Commit

Permalink
SKETCH: add step for getting properties
Browse files Browse the repository at this point in the history
Add a groovy script step to fetch custom properties from Artifactory.

This provides Artifactory users with access to their additional
metadata associated with artifacts stored in Artifactory, and allows
pipelines to make decisions based on that metadata.

This is called roughly as:
```
artifactory = newArtifactoryServer( ARGS )
def props = artifactoryGetProps(
    server: artifactory,
    relativePath: "PATH/TO/ARTIFACT"
    propertyKeys: ['PROPA', 'PROPB']
)
```

Signed-off-by: Eero Aaltonen <[email protected]>
  • Loading branch information
eaaltonen committed Jun 19, 2024
1 parent b6506c2 commit 2a3a3ca
Show file tree
Hide file tree
Showing 8 changed files with 352 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.jfrog.build.extractor.clientConfiguration.client;

import com.google.common.collect.ArrayListMultimap;
import org.jfrog.build.extractor.clientConfiguration.client.artifactory.ArtifactoryManager;
import org.jfrog.build.extractor.clientConfiguration.util.GetProperties;

import java.io.IOException;
import java.util.List;

public class GetPropertyCapableArtifactoryManagerWrapper {
private final ArtifactoryManager wrappedManager;

public GetPropertyCapableArtifactoryManagerWrapper(ArtifactoryManager wrappedManager) {
this.wrappedManager = wrappedManager;
}

public ArtifactoryManager getWrappedManager() {
return wrappedManager;
}

public ArrayListMultimap<String, String> getProperties(String relativePath, List<String> propertyKeys) throws IOException {
GetProperties getPropertiesService = new GetProperties(relativePath, propertyKeys, wrappedManager.log);
return getPropertiesService.execute(wrappedManager.jfrogHttpClient);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package org.jfrog.build.extractor.clientConfiguration.util;

import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.collect.ArrayListMultimap;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpRequestBase;
import org.jfrog.build.api.util.Log;
import org.jfrog.build.extractor.clientConfiguration.client.JFrogService;

import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

import static org.apache.commons.lang3.StringUtils.stripEnd;
import static org.jfrog.build.extractor.UrlUtils.*;

public class GetProperties extends JFrogService<ArrayListMultimap<String, String>> {
public static final String GET_PROPERTIES_ENDPOINT = "api/storage/";
private final String relativePath;
private final List<String> propertyKeys;

public GetProperties(String relativePath, List<String> propertyKeys, Log log) {
super(log);
this.relativePath = relativePath;
this.propertyKeys = propertyKeys;
}

public GetProperties(String relativePath, Log log) {
this(relativePath, null, log);
}

@Override
public HttpRequestBase createRequest() throws IOException {
String requestBase = GET_PROPERTIES_ENDPOINT + encodeUrl(stripEnd(relativePath, "/")) + "?properties=";
String requestUrl = requestBase;
if (propertyKeys != null && !propertyKeys.isEmpty()) {
ListIterator<String> properties = propertyKeys.listIterator();
String s = properties.next();
StringBuilder sb = new StringBuilder("?properties="+s);
while (properties.hasNext()) {
s = properties.next();
sb.append(",");
sb.append(s);
}
requestUrl = requestBase + sb.toString();
}
return new HttpGet(requestUrl);
}

@Override
protected void setResponse(InputStream stream) throws IOException {
ArrayListMultimap<String, String> propsMap = ArrayListMultimap.create();
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = objectMapper.readTree(stream);
JsonNode properties = jsonNode.get("properties");
for (Iterator<String> it = properties.fieldNames(); it.hasNext(); ) {
String property = it.next();
JsonNode propList = properties.get(property);
if (!propList.isArray()) {
throw new RuntimeException("Api property " + property + " is not an array");
}
for (JsonNode keyNode : propList) {
propsMap.put(property, keyNode.asText());
}
}
result = propsMap;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.jfrog.build.extractor.clientConfiguration.util;

import com.google.common.collect.ArrayListMultimap;
import org.jfrog.build.api.search.AqlSearchResult;
import org.jfrog.build.api.util.Log;
import org.jfrog.build.extractor.clientConfiguration.client.GetPropertyCapableArtifactoryManagerWrapper;
import org.jfrog.build.extractor.clientConfiguration.client.artifactory.ArtifactoryManager;

import java.io.IOException;
import java.util.List;

public class GetPropertiesHelper {

private final GetPropertyCapableArtifactoryManagerWrapper artifactoryManager;
private final Log log;

public GetPropertiesHelper(ArtifactoryManager artifactoryManager, Log log) {
this.artifactoryManager = new GetPropertyCapableArtifactoryManagerWrapper(artifactoryManager);
this.log = log;
}

public ArrayListMultimap<String, String> getProperties(String relativePath, List<String> properties) throws IOException {
return artifactoryManager.getProperties(relativePath, properties);
}

private ArrayListMultimap<String, String> getPropertiesOnResults(List<AqlSearchResult.SearchEntry> searchResults, List<String> properties) throws IOException {
ArrayListMultimap<String, String> resultMap = ArrayListMultimap.create();
log.info("Getting properties...");
String relativePath = buildEntryUrl(searchResults.get(0));
return artifactoryManager.getProperties(relativePath, properties);
}

private String buildEntryUrl(AqlSearchResult.SearchEntry result) {
String path = result.getPath().equals(".") ? "" : result.getPath() + "/";
return result.getRepo() + "/" + path + result.getName();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.jfrog.build.extractor.clientConfiguration.util;

import com.google.common.collect.ArrayListMultimap;

public class GetPropertiesResponse {
private ArrayListMultimap<String, String> propertiesMap;

public ArrayListMultimap<String, String> getPropertiesMap() { return propertiesMap; };

public void setPropertiesMap(ArrayListMultimap<String, String> propertiesMap) { this.propertiesMap = propertiesMap; }
}
50 changes: 50 additions & 0 deletions src/main/java/org/jfrog/hudson/generic/GetPropertiesCallable.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package org.jfrog.hudson.generic;

import com.google.common.collect.ArrayListMultimap;
import hudson.remoting.VirtualChannel;
import jenkins.MasterToSlaveFileCallable;
import org.apache.commons.lang3.StringUtils;
import org.jfrog.build.api.util.Log;
import org.jfrog.build.client.ProxyConfiguration;
import org.jfrog.build.extractor.clientConfiguration.client.artifactory.ArtifactoryManager;
import org.jfrog.hudson.generic.relocate.PropsHelper;
import org.jfrog.hudson.util.Credentials;

import java.io.File;
import java.io.IOException;
import java.util.List;

public class GetPropertiesCallable extends MasterToSlaveFileCallable<ArrayListMultimap<String, String>> {
private Log log;
private String username;
private String password;
private String accessToken;
private String serverUrl;
private ProxyConfiguration proxyConfig;
private String relativePath;
private List<String> properties;

public GetPropertiesCallable(Log log, Credentials credentials, String serverUrl, String relativePath, ProxyConfiguration proxyConfig, List<String> properties) {
this.log = log;
this.username = credentials.getUsername();
this.password = credentials.getPassword();
this.accessToken = credentials.getAccessToken();
this.serverUrl = serverUrl;
this.proxyConfig = proxyConfig;
this.relativePath = relativePath;
this.properties = properties;
}

public ArrayListMultimap<String, String> invoke(File file, VirtualChannel channel) throws IOException, InterruptedException {
if (StringUtils.isEmpty(relativePath)) {
return ArrayListMultimap.create();
}
PropsHelper propsHelper = new PropsHelper(log);
try (ArtifactoryManager artifactoryManager = new ArtifactoryManager(serverUrl, username, password, accessToken, log)) {
if (proxyConfig != null) {
artifactoryManager.setProxyConfiguration(proxyConfig);
}
return propsHelper.getPropertiesByPathAndKeyNames(relativePath, artifactoryManager, properties);
}
}
}
25 changes: 25 additions & 0 deletions src/main/java/org/jfrog/hudson/generic/relocate/PropsHelper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.jfrog.hudson.generic.relocate;

import com.google.common.collect.ArrayListMultimap;
import org.jfrog.build.api.util.Log;
import org.jfrog.build.extractor.clientConfiguration.client.artifactory.ArtifactoryManager;
import org.jfrog.build.extractor.clientConfiguration.util.GetPropertiesHelper;

import java.io.IOException;
import java.util.List;

public class PropsHelper {

private final Log log;

public PropsHelper(Log log) {
this.log = log;
}

public ArrayListMultimap<String, String> getPropertiesByPathAndKeyNames(String relativePath, ArtifactoryManager artifactoryManager, List<String> properties) throws IOException {
GetPropertiesHelper helper = new GetPropertiesHelper(artifactoryManager, log);
/* TODO: do we want any validation on the relativepath?
*/
return helper.getProperties(relativePath, properties);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package org.jfrog.hudson.pipeline.common.executors;

import com.google.common.collect.ArrayListMultimap;
import hudson.FilePath;
import hudson.model.Run;
import hudson.model.TaskListener;
import org.jfrog.hudson.ArtifactoryServer;
import org.jfrog.hudson.CredentialsConfig;
import org.jfrog.hudson.generic.GetPropertiesCallable;
import org.jfrog.hudson.pipeline.common.Utils;
import org.jfrog.hudson.util.JenkinsBuildInfoLog;

import java.io.IOException;
import java.util.List;

public class GetPropsExecutor implements Executor {
private final Run build;
private transient FilePath ws;
private ArtifactoryServer server;
private TaskListener listener;
private String relativePath;
private List<String> propertyKeys;
private ArrayListMultimap<String, String> properties;

public GetPropsExecutor(ArtifactoryServer server, TaskListener listener, Run build, FilePath ws, String relativePath, List<String> propertyKeys) {
this.build = build;
this.server = server;
this.listener = listener;
this.relativePath = relativePath;
this.propertyKeys = propertyKeys;
this.ws = ws;
}

public void execute() throws IOException, InterruptedException {
CredentialsConfig preferredDeployer = server.getDeployerCredentialsConfig();
properties = ws.act(new GetPropertiesCallable(new JenkinsBuildInfoLog(listener),
preferredDeployer.provideCredentials(build.getParent()),
server.getArtifactoryUrl(), relativePath, Utils.getProxyConfiguration(server), propertyKeys));
}

public ArrayListMultimap<String, String> getProperties() {
return properties;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package org.jfrog.hudson.pipeline.scripted.steps;

import com.google.common.collect.ArrayListMultimap;
import com.google.inject.Inject;
import hudson.Extension;
import org.jenkinsci.plugins.workflow.steps.AbstractStepDescriptorImpl;
import org.jenkinsci.plugins.workflow.steps.AbstractStepImpl;
import org.jenkinsci.plugins.workflow.steps.StepContext;
import org.jfrog.hudson.pipeline.ArtifactorySynchronousNonBlockingStepExecution;
import org.jfrog.hudson.pipeline.common.Utils;
import org.jfrog.hudson.pipeline.common.executors.GetPropsExecutor;
import org.jfrog.hudson.pipeline.common.types.ArtifactoryServer;
import org.kohsuke.stapler.DataBoundConstructor;

import java.io.IOException;
import java.util.List;

public class GetPropsStep extends AbstractStepImpl {
static final String STEP_NAME = "artifactoryGetProps";
private ArtifactoryServer server;
private final String relativePath;
private final List<String> propertyKeys;

@DataBoundConstructor
public GetPropsStep(StepContext context, ArtifactoryServer server, String relativePath, List<String> propertyKeys) {
this.server = server;
this.relativePath = relativePath;
this.propertyKeys = propertyKeys;
}

public ArtifactoryServer getServer() { return server; }

public String getRelativePath() { return relativePath; }

public List<String> getPropertyKeys() { return propertyKeys; }

public static class Execution extends ArtifactorySynchronousNonBlockingStepExecution<ArrayListMultimap<String, String>> {
protected static final long serialVersionUID = 1L;

private transient GetPropsStep step;
@Inject
public Execution(GetPropsStep step, StepContext context) throws IOException, InterruptedException {
super(context);
this.step = step;
}

@Override
protected ArrayListMultimap<String, String> runStep() throws Exception {
GetPropsExecutor executor = new GetPropsExecutor(Utils.prepareArtifactoryServer(null, step.getServer()),
this.listener, this.build, this.ws, step.getRelativePath(), step.getPropertyKeys());
executor.execute();
return executor.getProperties();
}

@Override
public org.jfrog.hudson.ArtifactoryServer getUsageReportServer() {
return Utils.prepareArtifactoryServer(null, step.getServer());
}

@Override
public String getUsageReportFeatureName() {
return STEP_NAME;
}
}

@Extension
public static final class DescriptorImpl extends AbstractStepDescriptorImpl {

public DescriptorImpl() {
super(GetPropsStep.Execution.class);
}

@Override
// The step is invoked by ArtifactoryServer by the step name
public String getFunctionName() {
return STEP_NAME;
}

@Override
public String getDisplayName() {
return "Get properties";
}

@Override
public boolean isAdvanced() {
return true;
}
}
}

0 comments on commit 2a3a3ca

Please sign in to comment.