001/*
002 * Copyright 2017-2022 original authors
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package io.micronaut.maven;
017
018import com.github.dockerjava.api.command.PushImageCmd;
019import com.github.dockerjava.api.model.AuthConfig;
020import com.google.cloud.tools.jib.api.Credential;
021import com.google.cloud.tools.jib.maven.MavenProjectProperties;
022import io.micronaut.maven.jib.JibConfigurationService;
023import io.micronaut.maven.services.ApplicationConfigurationService;
024import io.micronaut.maven.services.DockerService;
025import org.apache.maven.execution.MavenSession;
026import org.apache.maven.plugin.MojoExecution;
027import org.apache.maven.plugin.MojoExecutionException;
028import org.apache.maven.plugin.MojoFailureException;
029import org.apache.maven.plugins.annotations.Mojo;
030import org.apache.maven.project.MavenProject;
031import org.slf4j.Logger;
032import org.slf4j.LoggerFactory;
033
034import javax.inject.Inject;
035import java.io.File;
036import java.util.Optional;
037
038/**
039 * <p>Implementation of the <code>deploy</code> lifecycle for pushing Docker images</p>
040 * <p><strong>WARNING</strong>: this goal is not intended to be executed directly. Instead, Execute the <code>deploy</code>
041 * phase specifying the packaging type, eg:</p>
042 *
043 * <pre>mvn deploy -Dpackaging=docker-native</pre>
044 *
045 * @author Álvaro Sánchez-Mariscal
046 * @since 1.1
047 */
048@Mojo(name = "docker-push")
049public class DockerPushMojo extends AbstractDockerMojo {
050
051    private static final Logger LOG = LoggerFactory.getLogger(DockerPushMojo.class);
052
053    @Inject
054    public DockerPushMojo(MavenProject mavenProject, JibConfigurationService jibConfigurationService,
055                          ApplicationConfigurationService applicationConfigurationService, DockerService dockerService,
056                          MavenSession mavenSession, MojoExecution mojoExecution) {
057        super(mavenProject, jibConfigurationService, applicationConfigurationService, dockerService, mavenSession, mojoExecution);
058    }
059
060    @Override
061    public void execute() throws MojoExecutionException, MojoFailureException {
062        Packaging packaging = Packaging.of(mavenProject.getPackaging());
063        if (packaging == Packaging.DOCKER || packaging == Packaging.DOCKER_NATIVE || packaging == Packaging.DOCKER_CRAC) {
064            if (packaging == Packaging.DOCKER && !shouldBuildWithDockerfile(new File(mavenProject.getBasedir(), DockerfileMojo.DOCKERFILE))) {
065                if (validateJibDeployBuildGoal()) {
066                    return;
067                }
068            }
069            var images = getTags();
070
071            // getTags() will automatically generate an image name if none is specified
072            // To maintain error compatibility, check that an image name has been
073            // manually specified.
074            if (getConfiguredToImage().isPresent()) {
075                for (String taggedImage : images) {
076                    getLog().info("Pushing image: " + taggedImage);
077                    try (PushImageCmd pushImageCmd = dockerService.pushImageCmd(taggedImage)) {
078                        Optional<Credential> toCredentials = jibConfigurationService.getToCredentials();
079                        Optional<Credential> credential = toCredentials.or(() -> jibConfigurationService.resolveCredentialForImage(taggedImage, LOG));
080                        credential.ifPresent(cred -> {
081                            var username = cred.getUsername();
082                            var password = cred.getPassword();
083                            AuthConfig authConfig = dockerService.getAuthConfigFor(taggedImage, username, password);
084                            pushImageCmd.withAuthConfig(authConfig);
085                        });
086
087                        pushImageCmd.start().awaitCompletion();
088                    } catch (InterruptedException e) {
089                        Thread.currentThread().interrupt();
090                    } catch (Exception e) {
091                        throw new MojoExecutionException(e.getMessage(), e);
092                    }
093                }
094            } else {
095                throw new MojoFailureException("The plugin " + MavenProjectProperties.PLUGIN_KEY + " is misconfigured. Missing <to> tag");
096            }
097        } else {
098            throw new MojoFailureException("The <packaging> must be set to either [" + Packaging.DOCKER.id() + "], ["
099                + Packaging.DOCKER_NATIVE.id() + "] or [" + Packaging.DOCKER_CRAC.id() + "]");
100        }
101    }
102
103    private boolean validateJibDeployBuildGoal() throws MojoExecutionException, MojoFailureException {
104        validateJibBuildGoal();
105        if (JIB_BUILD_GOAL_BUILD.equals(jibBuildGoal)) {
106            String toImage = requireConfiguredToImageForJibRegistryBuild();
107            getLog().info("Skipping Docker daemon push for " + toImage + " because jib.buildGoal=build publishes the image to the registry during the package phase.");
108            return true;
109        }
110        if (JIB_BUILD_GOAL_BUILD_TAR.equals(jibBuildGoal)) {
111            throw new MojoFailureException("jib.buildGoal=buildTar is not supported for deploy because it creates target/jib-image.tar instead of publishing to a registry. "
112                + "Use jib.buildGoal=build with jib.to.image for daemonless registry deploy, or omit jib.buildGoal/use dockerBuild for the Docker daemon push path.");
113        }
114        return false;
115    }
116
117}