Upload artifacts to Nexus, without Maven
I have a non-Java project that produces a versioned build artifact, and I want to upload this to a Nexus repository. Because the project isn't Java, it doesn't use Maven for builds开发者_如何学C. And I'd rather not introduce Maven/POM files just to get files into Nexus.
The links on blogs to the Nexus REST API all end up at a sign-in wall, with no "create user" link that I can see.
So, what's the best (or any reasonable) way to upload build artifacts to a Nexus repository without Maven? "bash + curl" would be great, or even a Python script.
Have you considering using the Maven command-line to upload files?
mvn deploy:deploy-file \
-Durl=$REPO_URL \
-DrepositoryId=$REPO_ID \
-DgroupId=org.myorg \
-DartifactId=myproj \
-Dversion=1.2.3 \
-Dpackaging=zip \
-Dfile=myproj.zip
This will automatically generate the Maven POM for the artifact.
Update
The following Sonatype article states that the "deploy-file" maven plugin is the easiest solution, but it also provides some examples using curl:
https://support.sonatype.com/entries/22189106-How-can-I-programatically-upload-an-artifact-into-Nexus-
Using curl:
curl -v \
-F "r=releases" \
-F "g=com.acme.widgets" \
-F "a=widget" \
-F "v=0.1-1" \
-F "p=tar.gz" \
-F "file=@./widget-0.1-1.tar.gz" \
-u myuser:mypassword \
http://localhost:8081/nexus/service/local/artifact/maven/content
You can see what the parameters mean here: https://support.sonatype.com/entries/22189106-How-can-I-programatically-upload-an-artifact-into-Nexus-
To make the permissions for this work, I created a new role in the admin GUI and I added two privileges to that role: Artifact Download and Artifact Upload. The standard "Repo: All Maven Repositories (Full Control)"-role is not enough. You won't find this in the REST API documentation that comes bundled with the Nexus server, so these parameters might change in the future.
On a Sonatype JIRA issue, it was mentioned that they "are going to overhaul the REST API (and the way it's documentation is generated) in an upcoming release, most likely later this year".
You can ABSOLUTELY do this without using anything MAVEN related. I personally use the NING HttpClient (v1.8.16, to support java6).
For whatever reason, Sonatype makes it incredibly difficulty to figure out what the correct URLs, headers, and payloads are supposed to be; and I had to sniff the traffic and guess... There are some barely useful blogs/documentation there, however it is either irrelevant to oss.sonatype.org
, or it's XML based (and I found out it doesn't even work). Crap documentation on their part, IMHO, and hopefully future seekers can find this answer useful. Many thanks to https://stackoverflow.com/a/33414423/2101812 for their post, as it helped a lot.
If you release somewhere other than oss.sonatype.org
, just replace it with whatever the correct host is.
Here is the (CC0 licensed) code I wrote to accomplish this. Where profile
is your sonatype/nexus profileID (such as 4364f3bbaf163
) and repo
(such as comdorkbox-1003
) are parsed from the response when you upload your initial POM/Jar.
Close repo:
/**
* Closes the repo and (the server) will verify everything is correct.
* @throws IOException
*/
private static
String closeRepo(final String authInfo, final String profile, final String repo, final String nameAndVersion) throws IOException {
String repoInfo = "{'data':{'stagedRepositoryId':'" + repo + "','description':'Closing " + nameAndVersion + "'}}";
RequestBuilder builder = new RequestBuilder("POST");
Request request = builder.setUrl("https://oss.sonatype.org/service/local/staging/profiles/" + profile + "/finish")
.addHeader("Content-Type", "application/json")
.addHeader("Authorization", "Basic " + authInfo)
.setBody(repoInfo.getBytes(OS.UTF_8))
.build();
return sendHttpRequest(request);
}
Promote repo:
/**
* Promotes (ie: release) the repo. Make sure to drop when done
* @throws IOException
*/
private static
String promoteRepo(final String authInfo, final String profile, final String repo, final String nameAndVersion) throws IOException {
String repoInfo = "{'data':{'stagedRepositoryId':'" + repo + "','description':'Promoting " + nameAndVersion + "'}}";
RequestBuilder builder = new RequestBuilder("POST");
Request request = builder.setUrl("https://oss.sonatype.org/service/local/staging/profiles/" + profile + "/promote")
.addHeader("Content-Type", "application/json")
.addHeader("Authorization", "Basic " + authInfo)
.setBody(repoInfo.getBytes(OS.UTF_8))
.build();
return sendHttpRequest(request);
}
Drop repo:
/**
* Drops the repo
* @throws IOException
*/
private static
String dropRepo(final String authInfo, final String profile, final String repo, final String nameAndVersion) throws IOException {
String repoInfo = "{'data':{'stagedRepositoryId':'" + repo + "','description':'Dropping " + nameAndVersion + "'}}";
RequestBuilder builder = new RequestBuilder("POST");
Request request = builder.setUrl("https://oss.sonatype.org/service/local/staging/profiles/" + profile + "/drop")
.addHeader("Content-Type", "application/json")
.addHeader("Authorization", "Basic " + authInfo)
.setBody(repoInfo.getBytes(OS.UTF_8))
.build();
return sendHttpRequest(request);
}
Delete signature turds:
/**
* Deletes the extra .asc.md5 and .asc.sh1 'turds' that show-up when you upload the signature file. And yes, 'turds' is from sonatype
* themselves. See: https://issues.sonatype.org/browse/NEXUS-4906
* @throws IOException
*/
private static
void deleteSignatureTurds(final String authInfo, final String repo, final String groupId_asPath, final String name,
final String version, final File signatureFile)
throws IOException {
String delURL = "https://oss.sonatype.org/service/local/repositories/" + repo + "/content/" +
groupId_asPath + "/" + name + "/" + version + "/" + signatureFile.getName();
RequestBuilder builder;
Request request;
builder = new RequestBuilder("DELETE");
request = builder.setUrl(delURL + ".sha1")
.addHeader("Authorization", "Basic " + authInfo)
.build();
sendHttpRequest(request);
builder = new RequestBuilder("DELETE");
request = builder.setUrl(delURL + ".md5")
.addHeader("Authorization", "Basic " + authInfo)
.build();
sendHttpRequest(request);
}
File uploads:
public
String upload(final File file, final String extension, String classification) throws IOException {
final RequestBuilder builder = new RequestBuilder("POST");
final RequestBuilder requestBuilder = builder.setUrl(uploadURL);
requestBuilder.addHeader("Authorization", "Basic " + authInfo)
.addBodyPart(new StringPart("r", repo))
.addBodyPart(new StringPart("g", groupId))
.addBodyPart(new StringPart("a", name))
.addBodyPart(new StringPart("v", version))
.addBodyPart(new StringPart("p", "jar"))
.addBodyPart(new StringPart("e", extension))
.addBodyPart(new StringPart("desc", description));
if (classification != null) {
requestBuilder.addBodyPart(new StringPart("c", classification));
}
requestBuilder.addBodyPart(new FilePart("file", file));
final Request request = requestBuilder.build();
return sendHttpRequest(request);
}
EDIT1:
How to get the activity/status for a repo
/**
* Gets the activity information for a repo. If there is a failure during verification/finish -- this will provide what it was.
* @throws IOException
*/
private static
String activityForRepo(final String authInfo, final String repo) throws IOException {
RequestBuilder builder = new RequestBuilder("GET");
Request request = builder.setUrl("https://oss.sonatype.org/service/local/staging/repository/" + repo + "/activity")
.addHeader("Content-Type", "application/json")
.addHeader("Authorization", "Basic " + authInfo)
.build();
return sendHttpRequest(request);
}
No need to use these commands .. you can directly use the nexus web Interface in order to upload your JAR using GAV parameters.
So it is very simple.
The calls that you need to make against Nexus are REST api calls.
The maven-nexus-plugin is a Maven plugin that you can use to make these calls. You could create a dummy pom with the necessary properties and make those calls through the Maven plugin.
Something like:
mvn -DserverAuthId=sonatype-nexus-staging -Dauto=true nexus:staging-close
Assumed things:
- You have defined a server in your ~/.m2/settings.xml named sonatype-nexus-staging with your sonatype user and password set up - you will probably already have done this if you are deploying snapshots. But you can find more info here.
- Your local settings.xml includes the nexus plugins as specified here.
- The pom.xml sitting in your current directory has the correct Maven coordinates in its definition. If not, you can specify the groupId, artifactId, and version on the command line.
- The -Dauto=true will turn off the interactive prompts so you can script this.
Ultimately, all this is doing is creating REST calls into Nexus. There is a full Nexus REST api but I have had little luck finding documentation for it that's not behind a paywall. You can turn on the debug mode for the plugin above and figure it out however by using -Dnexus.verboseDebug=true -X
.
You could also theoretically go into the UI, turn on the Firebug Net panel, and watch for /service POSTs and deduce a path there as well.
In ruby https://github.com/RiotGames/nexus_cli A CLI wrapper around Sonatype Nexus REST calls.
Usage Example:
nexus-cli push_artifact com.mycompany.artifacts:myartifact:tgz:1.0.0 ~/path/to/file/to/push/myartifact.tgz
Configuration is done via the .nexus_cli
file.
url: "http://my-nexus-server/nexus/"
repository: "my-repository-id"
username: "username"
password: "password"
You can also use the direct deploy method using curl. You don't need a pom for your file for it but it will not be generated as well so if you want one, you will have to upload it separately.
Here is the command:
version=1.2.3
artifact="myartifact"
repoId=yourrepository
groupId=org.myorg
REPO_URL=http://localhost:8081/nexus
curl -u nexususername:nexuspassword --upload-file filename.tgz $REPO_URL/content/repositories/$repoId/$groupId/$artifact/$version/$artifact-$version.tgz
for those who need it in Java, using apache httpcomponents 4.0:
public class PostFile {
protected HttpPost httppost ;
protected MultipartEntity mpEntity;
protected File filePath;
public PostFile(final String fullUrl, final String filePath){
this.httppost = new HttpPost(fullUrl);
this.filePath = new File(filePath);
this.mpEntity = new MultipartEntity();
}
public void authenticate(String user, String password){
String encoding = new String(Base64.encodeBase64((user+":"+password).getBytes()));
httppost.setHeader("Authorization", "Basic " + encoding);
}
private void addParts() throws UnsupportedEncodingException{
mpEntity.addPart("r", new StringBody("repository id"));
mpEntity.addPart("g", new StringBody("group id"));
mpEntity.addPart("a", new StringBody("artifact id"));
mpEntity.addPart("v", new StringBody("version"));
mpEntity.addPart("p", new StringBody("packaging"));
mpEntity.addPart("e", new StringBody("extension"));
mpEntity.addPart("file", new FileBody(this.filePath));
}
public String post() throws ClientProtocolException, IOException {
HttpClient httpclient = new DefaultHttpClient();
httpclient.getParams().setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_1);
addParts();
httppost.setEntity(mpEntity);
HttpResponse response = httpclient.execute(httppost);
System.out.println("executing request " + httppost.getRequestLine());
System.out.println(httppost.getEntity().getContentLength());
HttpEntity resEntity = response.getEntity();
String statusLine = response.getStatusLine().toString();
System.out.println(statusLine);
if (resEntity != null) {
System.out.println(EntityUtils.toString(resEntity));
}
if (resEntity != null) {
resEntity.consumeContent();
}
return statusLine;
}
}
If you need a convenient command line interface or python API, look at repositorytools
Using it, you can upload artifact to nexus with command
artifact upload foo-1.2.3.ext releases com.fooware
To make it work, you will also need to set some environment variables
export REPOSITORY_URL=https://repo.example.com
export REPOSITORY_USER=admin
export REPOSITORY_PASSWORD=mysecretpassword
For recent versions of Nexus OSS (>= 3.9.0)
https://support.sonatype.com/hc/en-us/articles/115006744008-How-can-I-programmatically-upload-files-into-Nexus-3-
Example for versions 3.9.0 to 3.13.0:
curl -D - -u user:pass -X POST "https://nexus.domain/nexus/service/rest/beta/components?repository=somerepo" -H "accept: application/json" -H "Content-Type: multipart/form-data" -F "raw.directory=/test/" -F "raw.asset1=@test.txt;type=application/json" -F "raw.asset1.filename=test.txt"
You can manually upload the artifact's by clicking on upload artifacts button in the Nexus server and provide the necessary GAV properties for uploading(it's generally the file structure for storing the artifact)
@Adam Vandenberg For Java code to POST to Nexus. https://github.com/manbalagan/nexusuploader
public class NexusRepository implements RepoTargetFactory {
String DIRECTORY_KEY= "raw.directory";
String ASSET_KEY= "raw.asset1";
String FILENAME_KEY= "raw.asset1.filename";
String repoUrl;
String userName;
String password;
@Override
public void setRepoConfigurations(String repoUrl, String userName, String password) {
this.repoUrl = repoUrl;
this.userName = userName;
this.password = password;
}
public String pushToRepository() {
HttpClient httpclient = HttpClientBuilder.create().build();
HttpPost postRequest = new HttpPost(repoUrl) ;
String auth = userName + ":" + password;
byte[] encodedAuth = Base64.encodeBase64(
auth.getBytes(StandardCharsets.ISO_8859_1));
String authHeader = "Basic " + new String(encodedAuth);
postRequest.setHeader(HttpHeaders.AUTHORIZATION, authHeader);
try
{
byte[] packageBytes = "Hello. This is my file content".getBytes();
MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create();
InputStream packageStream = new ByteArrayInputStream(packageBytes);
InputStreamBody inputStreamBody = new InputStreamBody(packageStream, ContentType.APPLICATION_OCTET_STREAM);
multipartEntityBuilder.addPart(DIRECTORY_KEY, new StringBody("DIRECTORY"));
multipartEntityBuilder.addPart(FILENAME_KEY, new StringBody("MyFile.txt"));
multipartEntityBuilder.addPart(ASSET_KEY, inputStreamBody);
HttpEntity entity = multipartEntityBuilder.build();
postRequest.setEntity(entity); ;
HttpResponse response = httpclient.execute(postRequest) ;
if (response != null)
{
System.out.println(response.getStatusLine().getStatusCode());
}
}
catch (Exception ex)
{
ex.printStackTrace() ;
}
return null;
}
}
You can use curl instead.
version=1.2.3
artifact="artifact"
repoId=repositoryId
groupId=org/myorg
REPO_URL=http://localhost:8081/nexus
curl -u username:password --upload-file filename.tgz $REPO_URL/content/repositories/$repoId/$groupId/$artifact/$version/$artifact-$version.tgz
精彩评论