Sube artefactos a Nexus, sin Maven

102

Tengo un proyecto que no es Java que produce un artefacto de compilación versionado y quiero cargarlo en un repositorio de Nexus. Debido a que el proyecto no es Java, no usa Maven para compilaciones. Y prefiero no introducir archivos Maven / POM solo para introducir archivos en Nexus.

Todos los enlaces de los blogs a la API de REST de Nexus terminan en un muro de inicio de sesión, sin ningún enlace "crear usuario" que pueda ver.

Entonces, ¿cuál es la mejor (o razonable) forma de cargar artefactos de compilación en un repositorio de Nexus sin Maven? "bash + curl" sería genial, o incluso un script de Python.

Adam Vandenberg
fuente
Tenga en cuenta que asegúrese de tener un settings.xml en ~ / .m2 con los servidores y la autenticación adecuados definidos.
Adam Vandenberg

Respuestas:

98

¿Está considerando usar la línea de comandos de Maven para cargar archivos?

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

Esto generará automáticamente el Maven POM para el artefacto.

Actualizar

El siguiente artículo de Sonatype afirma que el complemento de maven "deploy-file" es la solución más sencilla, pero también proporciona algunos ejemplos con curl:

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

Mark O'Connor
fuente
Si tan solo esto nos permitiera descargar archivos desde este zip directamente, pero parece que no es posible si lo carga de esta manera.
sorin
@sorin No es posible descargar archivos desde dentro en un zip usando Maven. Es un requisito inusual y el único administrador de dependencias que sé que puede hacerlo es ivy (y no es simple) vea el siguiente ejemplo: stackoverflow.com/questions/3445696/…
Mark O'Connor
Instalé Nexus para hacerlo todo más simple, pero ¿qué diablos es esto? .. ¿Qué pasa si tengo un JAR casero sin conocimiento de sus dependencias? Mi IDE sigue quejándose de que falta * .pom. Esperaba que Nexus ya manejara eso por mí, pero NOOOOO sh ...
vintproykt
66

Usando 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

Puede ver lo que significan los parámetros aquí: https://support.sonatype.com/entries/22189106-How-can-I-programatic-upload-an-artifact-into-Nexus-

Para que los permisos de este trabajo funcionen, creé un nuevo rol en la GUI de administrador y agregué dos privilegios a ese rol: Descarga de artefactos y Carga de artefactos. El rol estándar "Repo: todos los repositorios Maven (control total)" no es suficiente. No encontrará esto en la documentación de la API REST que viene incluida con el servidor Nexus, por lo que estos parámetros pueden cambiar en el futuro.

En un problema de Sonatype JIRA , se mencionó que "van a revisar la API REST (y la forma en que se genera su documentación) en una próxima versión, probablemente a finales de este año".

Ed I
fuente
digamos que publicamos desde Jenkins, y solo permitimos que los usuarios de compilación publiquen en Nexus, ¿cómo administra el problema de la contraseña simple? ¿Jenkins tiene un complemento para la carga para que podamos usar las credenciales de Jenkins?
Jirong Hu
8

No es necesario utilizar estos comandos. Puede utilizar directamente la interfaz web nexus para cargar su JAR utilizando los parámetros GAV.

ingrese la descripción de la imagen aquí

Entonces es muy simple.

Praneel PIDIKITI
fuente
24
Una GUI no ayuda; Necesito poder cargar a través de un script de línea de comandos utilizado como parte de un proceso de construcción.
Adam Vandenberg
Bueno, se traduce en una solicitud HTTP POST, ¿no crees?
Yngve Sneen Lindal
5
@YngveSneenLindal Seguro, pero eso no significa que esos argumentos POST sean una API bien definida para usar públicamente.
Ken Williams
@KenWilliams Claro, yo tampoco dije eso. Pero funcionarán y representarán una solución, ese es mi punto.
Yngve Sneen Lindal
Al menos, para nuestro Sonatype Nexus ™ 2.11.1-01 tuve que otorgarle el privilegio al usuario Artifact Upload. Desafortunadamente, no pude encontrar nada en los documentos que mencionen esto ... (Edición: Ya veo, Ed, ya lo he señalado )
Alberto
8

ABSOLUTAMENTE puede hacer esto sin usar nada relacionado con MAVEN. Yo personalmente uso el NING HttpClient (v1.8.16, para admitir java6).

Por alguna razón, Sonatype hace que sea increíblemente difícil averiguar cuáles se supone que son las URL, los encabezados y las cargas útiles correctas; y tuve que oler el tráfico y adivinar ... Hay algunos blogs / documentación apenas útiles allí, sin embargo, o es irrelevante oss.sonatype.orgo está basado en XML (y descubrí que ni siquiera funciona). La documentación de mierda de su parte, en mi humilde opinión, y espero que los futuros buscadores puedan encontrar esta respuesta útil. Muchas gracias a https://stackoverflow.com/a/33414423/2101812 por su publicación, ya que ayudó mucho.

Si lo libera en otro lugar que no sea oss.sonatype.org, simplemente reemplácelo con el host correcto.

Aquí está el código (con licencia CC0) que escribí para lograr esto. ¿Dónde profileestá su sonatype / nexus profileID (como 4364f3bbaf163) y repo(como comdorkbox-1003) se analizan a partir de la respuesta cuando carga su POM / Jar inicial.

Cerrar repositorio:

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

Promocionar repositorio:

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

Dejar repositorio:

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

Eliminar turds de firma:

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

Cargas de archivos:

    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:

Cómo obtener la actividad / estado de un repositorio

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

Las llamadas que debe realizar contra Nexus son llamadas de API REST.

El plugin maven-nexus-plugin es un plugin de Maven que puede utilizar para realizar estas llamadas. Puede crear un pom ficticio con las propiedades necesarias y realizar esas llamadas a través del complemento Maven.

Algo como:

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

Cosas asumidas:

  1. Ha definido un servidor en su ~ / .m2 / settings.xml llamado sonatype-nexus-staging con su usuario y contraseña de sonatype configurados; probablemente ya lo haya hecho si está implementando instantáneas. Pero puedes encontrar más información aquí .
  2. Su settings.xml local incluye los complementos nexus como se especifica aquí .
  3. El pom.xml que se encuentra en su directorio actual tiene las coordenadas Maven correctas en su definición. De lo contrario, puede especificar groupId, artifactId y la versión en la línea de comando.
  4. -Dauto = true desactivará las indicaciones interactivas para que pueda escribir esto.

En última instancia, todo lo que está haciendo es crear llamadas REST en Nexus. Hay una API REST completa de Nexus, pero he tenido poca suerte para encontrar documentación que no esté detrás de un muro de pago. Puede activar el modo de depuración para el complemento anterior y, sin embargo, averiguarlo usando -Dnexus.verboseDebug=true -X.

En teoría, también podría ir a la interfaz de usuario, activar el panel de Firebug Net y observar las POST / service y deducir una ruta allí también.

Alex Miller
fuente
3

para aquellos que lo necesiten en Java, usando 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;
    }
}
McMosfet
fuente
Primer comentario. Intenté agregar resaltado para Java, pero no pude obtenerlo.
McMosfet
3

En ruby https://github.com/RiotGames/nexus_cli Un envoltorio CLI alrededor de las llamadas REST de Sonatype Nexus.

Ejemplo de uso:

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

La configuración se realiza a través del .nexus_cliarchivo.

url:            "http://my-nexus-server/nexus/"
repository:     "my-repository-id"
username:       "username"
password:       "password"
Francois
fuente
2

También puede utilizar el método de implementación directa mediante curl. No necesita un pom para su archivo, pero no se generará tan bien, por lo que si desea uno, tendrá que cargarlo por separado.

Aquí está el comando:

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
Djidiouf
fuente
"artefacto", no artefacto
Ram
1

Si necesita una interfaz de línea de comandos conveniente o una API de Python, consulte repositorytools

Usándolo, puede cargar artefactos a nexus con el comando

artifact upload foo-1.2.3.ext releases com.fooware

Para que funcione, también deberá establecer algunas variables de entorno

export REPOSITORY_URL=https://repo.example.com
export REPOSITORY_USER=admin
export REPOSITORY_PASSWORD=mysecretpassword
Michel Samia
fuente
0

Puede cargar manualmente los artefactos haciendo clic en el botón cargar artefactos en el servidor Nexus y proporcionar las propiedades GAV necesarias para la carga (generalmente es la estructura de archivo para almacenar el artefacto)

jijendiran
fuente
0

Para versiones recientes de Nexus OSS (> = 3.9.0)

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

Ejemplo para las versiones 3.9.0 a 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 "[email protected];type=application/json" -F "raw.asset1.filename=test.txt"
Adrianlzt
fuente
-1

@Adam Vandenberg Para código Java para POST en 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;
    }

}
Mano Anbalagan
fuente
-2

Puedes usar curl en su lugar.

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
Scott Jones
fuente
esta respuesta no es correcta. Con curl, el ID de grupo debe representarse como org / myorg (sustituya el punto "." Por barra diagonal "/")
madduci