Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions plugins/templates-maven-plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>${slf4j.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-testing</groupId>
<artifactId>maven-plugin-testing-harness</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
/*
* Copyright (C) 2022 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.cloud.teleport.plugin.maven;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import dev.failsafe.Failsafe;
import dev.failsafe.RetryPolicy;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

class PromoteHelper {
private static final Logger LOG = LoggerFactory.getLogger(PromoteHelper.class);
private final ArtifactRegImageSpec sourceSpec;
private final ArtifactRegImageSpec targetSpec;
// needed for adding tag
private final String targetPath;
private final String sourceDigest;
private final String token;

/**
* Promote the staged flex template image using MOSS promote API.
*
* @param sourcePath - spec for source image.
* @param targetPath - spec for target image
* @param sourceDigest - source image digest, e.g. sha256:xxxxx
*/
public PromoteHelper(String sourcePath, String targetPath, String sourceDigest)
throws IOException, InterruptedException {
this(sourcePath, targetPath, sourceDigest, accessToken());
}

Check warning on line 55 in plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java

View check run for this annotation

Codecov / codecov/patch

plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java#L54-L55

Added lines #L54 - L55 were not covered by tests

@VisibleForTesting
PromoteHelper(String sourcePath, String targetPath, String sourceDigest, String token) {
this.sourceSpec = new ArtifactRegImageSpec(sourcePath);
this.targetSpec = new ArtifactRegImageSpec(targetPath);
this.sourceDigest = sourceDigest;
this.token = token;
this.targetPath = targetPath;
}

/** Promote the artifact. */
public void promote() throws IOException, InterruptedException {
String[] promoteArtifactCmd = getPromoteFlexTemplateImageCmd();

Check warning on line 68 in plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java

View check run for this annotation

Codecov / codecov/patch

plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java#L68

Added line #L68 was not covered by tests
// promote API returns a long-running-operation
String responseRLO = TemplatesStageMojo.runCommandCapturesOutput(promoteArtifactCmd, null);
JsonElement parsed = JsonParser.parseString(responseRLO);
String operation = parsed.getAsJsonObject().get("name").getAsString();
waitForComplete(operation);
addTag();
}

Check warning on line 75 in plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java

View check run for this annotation

Codecov / codecov/patch

plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java#L70-L75

Added lines #L70 - L75 were not covered by tests

@VisibleForTesting
String[] getPromoteFlexTemplateImageCmd() {
Preconditions.checkNotNull(targetSpec.imageName, "Target image name can not be null");
String authHeader = String.format("Authorization: Bearer %s", token);
String contentTypeHeader = "Content-Type: application/json";
String sourceRepo =
String.format(
"projects/%s/locations/%s/repositories/%s",
sourceSpec.project, sourceSpec.location, sourceSpec.repository);
String sourceVersion =
String.format(
"%s/packages/%s/versions/%s",
sourceRepo,
URLEncoder.encode(targetSpec.imageName, StandardCharsets.UTF_8),
sourceDigest);
String url =
String.format(
"https://artifactregistry.googleapis.com/v1/projects/%s/locations/%s/repositories/%s:promoteArtifact",
targetSpec.project, targetSpec.location, targetSpec.repository);
ImmutableMap<String, String> postDataCollect =
ImmutableMap.<String, String>builder()
.put("source_repository", sourceRepo)
.put("source_version", sourceVersion)
.put("attachment_behavior", "EXCLUDE")
.build();
String postData = new Gson().toJson(postDataCollect);
return new String[] {
"wget",
"-O-",
"-nv",
"--header=" + authHeader,
"--header=" + contentTypeHeader,
"--post-data=" + postData,
url
};
}

/** Wait for long-running operation to complete. */
private void waitForComplete(String operation) {
String[] command =

Check warning on line 116 in plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java

View check run for this annotation

Codecov / codecov/patch

plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java#L116

Added line #L116 was not covered by tests
new String[] {
"wget",
"-O-",
"-nv",
String.format("--header=Authorization: Bearer %s", token),

Check warning on line 121 in plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java

View check run for this annotation

Codecov / codecov/patch

plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java#L121

Added line #L121 was not covered by tests
"https://artifactregistry.googleapis.com/v1/" + operation
};

RetryPolicy<?> retry =
RetryPolicy.builder()
.handleIf(throwable -> throwable instanceof QueryOperationRunnable.RetryableException)
.withBackoff(Duration.ofSeconds(5), Duration.ofSeconds(30))
.withMaxRetries(5)
.build();

Check warning on line 130 in plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java

View check run for this annotation

Codecov / codecov/patch

plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java#L126-L130

Added lines #L126 - L130 were not covered by tests

QueryOperationRunnable runnable = new QueryOperationRunnable(command);
Failsafe.with(retry).run(runnable);
}

Check warning on line 134 in plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java

View check run for this annotation

Codecov / codecov/patch

plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java#L132-L134

Added lines #L132 - L134 were not covered by tests

/** Add "latest" tag after promotion. */
private void addTag() throws IOException, InterruptedException {
// TODO: remove this once copy tag is supported by promote API
String[] command;
if (targetSpec.repository.endsWith("gcr.io")) {
// gcr.io repository needs to use `gcloud container` to add tag
command =

Check warning on line 142 in plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java

View check run for this annotation

Codecov / codecov/patch

plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java#L142

Added line #L142 was not covered by tests
new String[] {
"gcloud",
"container",
"images",
"add-tag",
"-q",
String.format("%s@%s", targetPath, sourceDigest),
String.format("%s:latest", targetPath)

Check warning on line 150 in plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java

View check run for this annotation

Codecov / codecov/patch

plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java#L149-L150

Added lines #L149 - L150 were not covered by tests
};
} else {
command =

Check warning on line 153 in plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java

View check run for this annotation

Codecov / codecov/patch

plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java#L153

Added line #L153 was not covered by tests
new String[] {
"gcloud",
"artifacts",
"docker",
"tags",
"add",
String.format("%s@%s", targetPath, sourceDigest),
String.format("%s:latest", targetPath)

Check warning on line 161 in plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java

View check run for this annotation

Codecov / codecov/patch

plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java#L160-L161

Added lines #L160 - L161 were not covered by tests
};
}
TemplatesStageMojo.runCommandCapturesOutput(command, null);
}

Check warning on line 165 in plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java

View check run for this annotation

Codecov / codecov/patch

plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java#L164-L165

Added lines #L164 - L165 were not covered by tests

private static class QueryOperationRunnable implements dev.failsafe.function.CheckedRunnable {
String[] command;

public QueryOperationRunnable(String[] command) {
this.command = command;
}

Check warning on line 172 in plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java

View check run for this annotation

Codecov / codecov/patch

plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java#L170-L172

Added lines #L170 - L172 were not covered by tests

@Override
public void run() throws RetryableException, IOException, InterruptedException {
String response = TemplatesStageMojo.runCommandCapturesOutput(command, null);
JsonObject parsed = JsonParser.parseString(response).getAsJsonObject();

Check warning on line 177 in plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java

View check run for this annotation

Codecov / codecov/patch

plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java#L176-L177

Added lines #L176 - L177 were not covered by tests
if (parsed.get("done") == null || !parsed.get("done").getAsBoolean()) {
throw new RetryableException("Operation not yet finished, will poll later.");

Check warning on line 179 in plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java

View check run for this annotation

Codecov / codecov/patch

plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java#L179

Added line #L179 was not covered by tests
}
if (parsed.get("error") != null) {
int errorCode = parsed.get("error").getAsJsonObject().get("code").getAsInt();

Check warning on line 182 in plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java

View check run for this annotation

Codecov / codecov/patch

plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java#L182

Added line #L182 was not covered by tests
if (errorCode == 6) {
// already exist
return;

Check warning on line 185 in plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java

View check run for this annotation

Codecov / codecov/patch

plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java#L185

Added line #L185 was not covered by tests
}
throw new RuntimeException("Operation failed: " + response);

Check warning on line 187 in plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java

View check run for this annotation

Codecov / codecov/patch

plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java#L187

Added line #L187 was not covered by tests
}
}

Check warning on line 189 in plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java

View check run for this annotation

Codecov / codecov/patch

plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java#L189

Added line #L189 was not covered by tests

public static class RetryableException extends Exception {
public RetryableException(String message) {
super(message);
}
}
}

private static String accessToken() throws IOException, InterruptedException {
// do not use runCommand to avoid print token to log
Process process =
Runtime.getRuntime().exec(new String[] {"gcloud", "auth", "print-access-token"});

Check warning on line 201 in plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java

View check run for this annotation

Codecov / codecov/patch

plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java#L201

Added line #L201 was not covered by tests
if (process.waitFor() != 0) {
throw new RuntimeException(

Check warning on line 203 in plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java

View check run for this annotation

Codecov / codecov/patch

plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java#L203

Added line #L203 was not covered by tests
"Error fetching access token for request: "
+ new String(process.getErrorStream().readAllBytes(), StandardCharsets.UTF_8));

Check warning on line 205 in plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java

View check run for this annotation

Codecov / codecov/patch

plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java#L205

Added line #L205 was not covered by tests
}
return new String(process.getInputStream().readAllBytes(), StandardCharsets.UTF_8).trim();

Check warning on line 207 in plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java

View check run for this annotation

Codecov / codecov/patch

plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java#L207

Added line #L207 was not covered by tests
}

/** Artifact registry image spec. */
static class ArtifactRegImageSpec {
public final String project;
public final String repository;
public final String location;

public final @Nullable String imageName;

/**
* Construct an {@code ArtifactRegImageSpec} from an image url. Supported image urls include
* [region.]gcr.io/projectId[/...] and region-docker.pkg.dev/projectId[/...]. See {@code
* PromoteHelperTest#testArtifactRegImageSpec} for example image urls.
*/
public ArtifactRegImageSpec(String imagePath) {
String[] segments = imagePath.split("/", 3);
if (segments.length < 3) {
segments = new String[] {segments[0], segments[1], null};
}
if ("google.com".equals(segments[1])) {
String[] repoSegments = segments[2].split("/", 2);
segments =
new String[] {
segments[0],
segments[1] + ":" + repoSegments[0],
repoSegments.length < 2 ? null : repoSegments[1]
};
}
this.project = segments[1];
if (segments[0].endsWith("gcr.io")) {
this.repository = segments[0];
this.imageName = segments[2];
if ("gcr.io".equals(segments[0])) {
this.location = "us";
} else {
this.location = segments[0].substring(0, segments[0].length() - ".gcr.io".length());
}
} else if (segments[0].endsWith("-docker.pkg.dev")) {
String[] repoSegments = segments[2].split("/", 2);
this.repository = repoSegments[0];
if (repoSegments.length > 1) {
this.imageName = repoSegments[1];
} else {
this.imageName = null;
}
this.location = segments[0].substring(0, segments[0].length() - "-docker.pkg.dev".length());
} else {
throw new RuntimeException("Unsupported artifact registry image path: " + imagePath);

Check warning on line 256 in plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java

View check run for this annotation

Codecov / codecov/patch

plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java#L256

Added line #L256 was not covered by tests
}
}

public ArtifactRegImageSpec(
String project, String repository, String location, @Nullable String imageName) {
this.project = project;
this.repository = repository;
this.location = location;
this.imageName = imageName;
}

@Override
public boolean equals(Object other) {
if (other instanceof ArtifactRegImageSpec t) {
return project.equals(t.project)
&& repository.equals(t.repository)
&& location.equals(t.location)
&& Objects.equal(imageName, t.imageName);
} else {
return false;

Check warning on line 276 in plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java

View check run for this annotation

Codecov / codecov/patch

plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java#L276

Added line #L276 was not covered by tests
}
}

@Override
public String toString() {
return String.format(

Check warning on line 282 in plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java

View check run for this annotation

Codecov / codecov/patch

plugins/templates-maven-plugin/src/main/java/com/google/cloud/teleport/plugin/maven/PromoteHelper.java#L282

Added line #L282 was not covered by tests
"ArtifactRegImageSpec(%s, %s, %s, %s)", project, repository, location, imageName);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,27 @@ public class TemplatesReleaseMojo extends TemplatesBaseMojo {
@Parameter(defaultValue = "${artifactRegion}", readonly = true, required = false)
protected String artifactRegion;

/**
* Artifact registry.
*
* <p>If not set, images will be built to [artifactRegion.]gcr.io/[projectId].
*
* <p>If set to "xxx.gcr.io", image will be built to xxx.gcr.io/[projectId].
*
* <p>Otherwise, image will be built to artifactRegion.
*/
@Parameter(defaultValue = "${artifactRegistry}", readonly = true, required = false)
protected String artifactRegistry;

/**
* Staging artifact registry.
*
* <p>If set, images will first build inside stagingArtifactRegistry before promote to final
* destination. Only effective when generateSBOM.
*/
@Parameter(defaultValue = "${stagingArtifactRegistry}", readonly = true, required = false)
protected String stagingArtifactRegistry;

@Parameter(defaultValue = "${gcpTempLocation}", readonly = true, required = false)
protected String gcpTempLocation;

Expand Down Expand Up @@ -185,6 +206,8 @@ public void execute() throws MojoExecutionException {
javaTemplateLauncherEntryPoint,
pythonVersion,
beamVersion,
artifactRegistry,
stagingArtifactRegistry,
unifiedWorker,
generateSBOM);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,27 @@ public class TemplatesRunMojo extends TemplatesBaseMojo {
@Parameter(defaultValue = "${artifactRegion}", readonly = true, required = false)
protected String artifactRegion;

/**
* Artifact registry.
*
* <p>If not set, images will be built to [artifactRegion.]gcr.io/[projectId].
*
* <p>If set to "xxx.gcr.io", image will be built to xxx.gcr.io/[projectId].
*
* <p>Otherwise, image will be built to artifactRegion.
*/
@Parameter(defaultValue = "${artifactRegistry}", readonly = true, required = false)
protected String artifactRegistry;

/**
* Staging artifact registry.
*
* <p>If set, images will first build inside stagingArtifactRegistry before promote to final
* destination. Only effective when generateSBOM.
*/
@Parameter(defaultValue = "${stagingArtifactRegistry}", readonly = true, required = false)
protected String stagingArtifactRegistry;

@Parameter(defaultValue = "${gcpTempLocation}", readonly = true, required = false)
protected String gcpTempLocation;

Expand Down Expand Up @@ -194,6 +215,8 @@ public void execute() throws MojoExecutionException {
javaTemplateLauncherEntryPoint,
pythonVersion,
beamVersion,
artifactRegistry,
stagingArtifactRegistry,
unifiedWorker,
generateSBOM);

Expand Down
Loading