将工件上传到 Nexus,无需 Maven

Posted

技术标签:

【中文标题】将工件上传到 Nexus,无需 Maven【英文标题】:Upload artifacts to Nexus, without Maven 【发布时间】:2011-05-01 01:57:01 【问题描述】:

我有一个生成版本化构建工件的非 Java 项目,我想将它上传到 Nexus 存储库。因为该项目不是 Java,所以它不使用 Maven 进行构建。而且我宁愿不引入 Maven/POM 文件只是为了将文件导入 Nexus。

博客上指向 Nexus REST API 的链接最终都出现在登录墙上,我看不到“创建用户”链接。

那么,在没有 Maven 的情况下,将构建工件上传到 Nexus 存储库的最佳(或任何合理)方法是什么? “bash + curl”会很棒,甚至是 Python 脚本。

【问题讨论】:

注意,确保您在 ~/.m2 中有一个 settings.xml 并定义了适当的服务器和身份验证。 【参考方案1】:

您是否考虑使用 Maven 命令行来上传文件?

mvn deploy:deploy-file \
    -Durl=$REPO_URL \
    -DrepositoryId=$REPO_ID \
    -DgroupId=org.myorg \
    -DartifactId=myproj \
    -Dversion=1.2.3  \
    -Dpackaging=zip \
    -Dfile=myproj.zip

这将自动为工件生成 Maven POM。

更新

以下 Sonatype 文章指出“deploy-file”maven 插件是最简单的解决方案,但它也提供了一些使用 curl 的示例:

https://support.sonatype.com/entries/22189106-How-can-I-programatically-upload-an-artifact-into-Nexus-

【讨论】:

如果只有这样可以让我们直接从这个 zip 中下载文件,但如果你像这样上传它似乎是不可能的。 @sorin 无法使用 Maven 从 zip 文件中下载文件。这是一个不寻常的要求,我知道唯一可以做到的依赖管理器是常春藤(这并不简单),请参见以下示例:***.com/questions/3445696/… 我安装 Nexus 是为了让一切变得更简单,但这到底是什么?.. 如果我有一些自制的 JAR 却不知道它的依赖关系怎么办?我的 IDE 一直抱怨缺少 *.pom。我希望 Nexus 已经为我处理了这个问题,但是 NOOOOO sh... 是否有上传文件而不指定版本等?比如,如果你只有 url、repo、file 和 packaging 属性? 我不这么认为。 3 个最重要的元数据是 Group、ArtifactId 和 Version。所谓的 GAV 坐标。【参考方案2】:

使用卷曲:

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:所有 Maven 存储库(完全控制)”角色是不够的。 您不会在 Nexus 服务器随附的 REST API 文档中找到此内容,因此这些参数将来可能会更改。

在a Sonatype JIRA issue 上,有人提到他们“将在即将发布的版本中彻底检查 REST API(以及生成文档的方式),很可能在今年晚些时候发布”。

【讨论】:

假设我们从 Jenkins 发布,并且只允许构建用户发布到 Nexus,您如何管理纯密码问题? Jenkins 是否有用于上传的插件,以便我们可以使用 Jenkins 凭据? Jenkins 有一个插件可以管理密码并将它们隐藏起来。但他们必须以某种方式最终出现在命令行上。【参考方案3】:

您可以绝对做到这一点,而无需使用任何与 MAVEN 相关的东西。我个人使用的是NING HttpClient(v1.8.16,支持java6)。

无论出于何种原因,Sonatype 都难以置信地难以确定正确的 URL、标头和有效负载应该是什么;我不得不嗅探流量并猜测......那里有一些几乎有用的博客/文档,但它要么与oss.sonatype.org无关,要么它是基于XML的(我发现它没有'甚至不工作)。恕我直言,他们的废话文档,希望未来的寻求者可以发现这个答案很有用。非常感谢 https://***.com/a/33414423/2101812 的帖子,因为它帮助了很多。

如果您在 oss.sonatype.org 以外的地方发布,只需将其替换为正确的主机即可。

这是我为实现此目的而编写的(CC0 许可)代码。其中profile 是您的 sonatype/nexus profileID(例如4364f3bbaf163)和repo(例如comdorkbox-1003)是在您上传初始 POM/Jar 时从响应中解析出来的。

关闭回购:

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

EDIT1:

如何获取 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);

【讨论】:

【参考方案4】:

无需使用这些命令..您可以直接使用nexus Web界面来使用GAV参数上传您的JAR。

所以很简单。

【讨论】:

GUI 没有帮助;我需要能够通过用作构建过程的一部分的命令行脚本进行上传。 嗯,它转换为 HTTP POST 请求,你不觉得吗? @YngveSneenLindal 当然,但这并不意味着这些 POST 参数是一个定义明确的 API,可以公开使用。 @KenWilliams 当然,我也没有声称。但它们会起作用并代表一个解决方案,这就是我的观点。 至少,对于我们的 Sonatype Nexus™ 2.11.1-01,我必须授予用户特权 Artifact Upload。不幸的是,我在docs 中找不到任何提到这一点的东西...(编辑:我明白了,Ed I has already pointed this out)【参考方案5】:

您需要对 Nexus 进行的调用是 REST api 调用。

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

类似:

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

假设的事情:

    您已经在 ~/.m2/settings.xml 中定义了一个名为 sonatype-nexus-staging 的服务器,并设置了您的 sonatype 用户和密码 - 如果您正在部署快照,您可能已经这样做了。但是您可以找到更多信息here。 您的本地 settings.xml 包含指定 here 的 nexus 插件。 当前目录中的 pom.xml 在其定义中具有正确的 Maven 坐标。如果没有,您可以在命令行中指定 groupId、artifactId 和 version。 -Dauto=true 将关闭交互式提示,以便您可以编写脚本。

最终,所有这些都是在 Nexus 中创建 REST 调用。有一个完整的 Nexus REST api,但我很难找到不在付费墙后面的文档。您可以打开上面插件的调试模式并通过使用-Dnexus.verboseDebug=true -X 来解决它。

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

【讨论】:

【参考方案6】:

对于那些在 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 添加高亮显示,但没有得到它。【参考方案7】:

在 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"

【讨论】:

【参考方案8】:

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

命令如下:

version=1.2.3
artefact="myartefact"
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/$artefact/$version/$artefact-$version.tgz

【讨论】:

“神器”不是神器【参考方案9】:

如果您需要方便的命令行界面或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

【讨论】:

【参考方案10】:

对于最新版本的 Nexus OSS (>= 3.9.0)

https://support.sonatype.com/hc/en-us/articles/115006744008-How-can-I-programmatically-upload-files-into-Nexus-3-

3.9.0 至 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"

【讨论】:

【参考方案11】:

您可以通过单击 Nexus 服务器中的上传工件按钮手动上传工件,并提供上传所需的 GAV 属性(通常是用于存储工件的文件结构)

【讨论】:

【参考方案12】:

@Adam Vandenberg 用于将 Java 代码发布到 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;
    


【讨论】:

【参考方案13】:

你可以用 curl 代替。

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/$artefact/$version/$artifact-$version.tgz

【讨论】:

这个答案不正确。使用 curl,groupId 应该表示为 org/myorg(用斜杠“/”替换点“.”)

以上是关于将工件上传到 Nexus,无需 Maven的主要内容,如果未能解决你的问题,请参考以下文章

Nexus 支持批量上传工件吗?

Android Gradle 插件将自定义 Gradle 插件上传到自建 Maven 仓库 ③ ( 配置上传工件 | 将 Gradle 插件 jar 包源码文档上传到本地Maven 仓库 )

Nexus 接受上传但表示失败

通过 Maven 将整个目录上传/下载到 Nexus

sh 通过curl上传Nexus工件

Maven 将本地JAR包上传到Nexus