在没有Maven的情况下将构件上传到Nexus

113

我有一个非Java项目,它会生成一个带版本的构建产物,并且我想将其上传到Nexus仓库。因为这个项目不是用Maven进行构建的,所以也不想引入Maven/POM文件就能把文件放到Nexus中。

博客中关于Nexus REST API的链接最后都会出现一个登录墙,我没有看到“创建用户”的链接。

那么,有没有一种最好(或任何合理的)方法可以在没有Maven的情况下将构建产物上传到Nexus仓库呢?使用 “bash + curl” 或者 Python脚本都可以。


请注意,在~/.m2中有一个带有适当服务器和身份验证定义的settings.xml文件。 - Adam Vandenberg
14个回答

109

如果只是希望能够直接从这个压缩包中下载文件,但似乎如果像这样上传它是不可能的。 - sorin
@sorin 在Maven中无法从zip文件中下载文件。这是一个不寻常的要求,我知道唯一能够做到的依赖管理器是ivy(而且它并不简单),请参考以下示例:http://stackoverflow.com/questions/3445696/gradle-how-to-declare-a-dependency-of-a-jar-in-a-jar/ - Mark O'Connor
我安装了Nexus来使一切变得更简单,但这是什么鬼?如果我有一些自制的JAR文件而没有它的依赖关系的知识怎么办?我的集成开发环境不断抱怨缺少*.pom文件。我希望Nexus已经为我处理了这个问题,但是NOOOOO嗨…… - vintprox
有没有办法在不指定版本等信息的情况下上传文件?比如,如果你只有url、repo、file和packaging属性? - dev4life
1
我不这么认为。三个最重要的元数据是Group、ArtifactId和Version,也被称为GAV坐标。 - Mark O'Connor

74

使用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

您可以在此处查看参数的含义:https://support.sonatype.com/entries/22189106-How-can-I-programatically-upload-an-artifact-into-Nexus-

为了使权限生效,我在管理员GUI中创建了一个新角色,并向该角色添加了两个权限:Artifact Download和Artifact Upload。标准的“Repo: All Maven Repositories (Full Control)”角色不足以满足需求。这些参数可能会在未来发生改变,但它们并没有包含在Nexus服务器捆绑的REST API文档中。

Sonatype JIRA问题页面上提到他们“计划在即将发布的版本中彻底改进REST API(以及文档生成方式)”,预计在今年晚些时候发布。


假设我们从Jenkins发布,并且仅允许构建用户发布到Nexus,那么如何处理明文密码问题?Jenkins是否有一个上传插件,以便我们可以使用Jenkins凭据? - Jirong Hu
1
Jenkins有一个插件可以管理密码并将它们隐藏起来,不会直接显示在屏幕上。但是这些密码最终必须以某种方式出现在命令行中。 - Sven

9
你可以绝对不使用任何MAVEN相关内容来完成这个任务。我个人使用NING HttpClient(v1.8.16,以支持java6)。
由于某种原因,Sonatype使得确定正确的URL、头和负载变得极其困难;我不得不嗅探流量并猜测……有一些几乎没有用的博客/文档,但它们要么与oss.sonatype.org无关,要么基于XML(我发现它甚至不起作用)。在我看来,他们的文档很烂,希望未来的搜索者能够找到这个答案有用。非常感谢https://dev59.com/FW865IYBdhLWcg3wBJ0q#33414423的帖子,因为它帮了我很多忙。
如果您发布到的地方不是oss.sonatype.org,请将其替换为正确的主机名。
这是我编写的代码(采用 CC0 许可),用于实现此目的。其中 profile 是您的 sonatype/nexus 个人资料 ID(例如 4364f3bbaf163),repo(例如 comdorkbox-1003)从您上传初始 POM/Jar 时的响应中解析出来。
关闭 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);
}

推广代码库:

/**
 * 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);
}

删除仓库:

/**
 * 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);
}

删除签名档垃圾:

/**
 * 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);
}

文件上传:
    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);
    }

编辑1:

如何获取存储库的活动/状态

/**
 * 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);
}

8

不需要使用这些命令... 您可以直接使用nexus web界面,使用GAV参数上传您的JAR文件。

enter image description here

所以非常简单。


28
GUI无法满足我的需求;我需要能够通过命令行脚本上传文件,作为构建过程的一部分。 - Adam Vandenberg
这意味着它转换为一个HTTP POST请求,你不觉得吗? - Yngve Sneen Lindal
5
可以,但这并不意味着那些POST参数就是一个明确定义的公共API。 - Ken Williams
@KenWilliams 当然,我也没有这样说过。但它们能够运行并代表一种解决方案,这是我的观点。 - Yngve Sneen Lindal
至少对于我们的_Sonatype Nexus™ 2.11.1-01_,我必须授予用户Artifact Upload权限。不幸的是,在文档中我找不到任何提及此事的内容...(编辑:我看到了,Ed I已经指出了这一点) - Alberto

6

你需要对 Nexus 进行的调用是 REST API 调用。

maven-nexus-plugin 是一个可以用来进行这些调用的 Maven 插件。你可以创建一个带有必要属性的虚拟 pom 文件,并通过 Maven 插件来进行这些调用。

例如:

mvn -DserverAuthId=sonatype-nexus-staging -Dauto=true nexus:staging-close

假设以下事项:

  1. 您在 ~/.m2/settings.xml 中定义了一个名为 sonatype-nexus-staging 的服务器,并设置了您的 sonatype 用户和密码 - 如果您正在部署快照,那么您可能已经完成了这个步骤。但是您可以在此处找到更多信息。
  2. 您的本地 settings.xml 包括 nexus 插件,如此处所指定的。
  3. 位于当前目录中的 pom.xml 在其定义中具有正确的 Maven 坐标。如果没有,请在命令行上指定 groupId、artifactId 和 version。
  4. -Dauto=true 将关闭交互式提示,以便您可以脚本化此过程。

最终,所有这些都只是在 Nexus 中创建 REST 调用。Nexus 有一个完整的 REST api,但我很难找到不需要付费才能查看的文档。您可以使用 -Dnexus.verboseDebug=true -X 打开插件的调试模式并自己找出来。

您也可以理论上进入 UI,打开 Firebug Net 面板,并观察 /service POST,从而推断出路径。


4

您也可以使用curl直接部署的方法。您的文件不需要pom,但是它也不会被生成,所以如果您需要一个,您必须单独上传它。

以下是命令:

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

"artifact" 不是 "artefact" - Ram

4

在Ruby中,https://github.com/RiotGames/nexus_cli 是一个围绕Sonatype Nexus REST调用的CLI包装器。

使用示例:

nexus-cli push_artifact com.mycompany.artifacts:myartifact:tgz:1.0.0 ~/path/to/file/to/push/myartifact.tgz

配置是通过.nexus_cli文件完成的。
url:            "http://my-nexus-server/nexus/"
repository:     "my-repository-id"
username:       "username"
password:       "password"

3

对于需要在Java中使用的人,可以使用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;
    }
}

第一篇帖子。我尝试为Java添加高亮显示,但无法实现。 - McMosfet

1
如果您需要方便的命令行界面或Python API,请查看repositorytools
使用它,您可以使用命令将构件上传到Nexus。
artifact upload foo-1.2.3.ext releases com.fooware

为了使其正常工作,您还需要设置一些环境变量。
export REPOSITORY_URL=https://repo.example.com
export REPOSITORY_USER=admin
export REPOSITORY_PASSWORD=mysecretpassword

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接