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.jib;
017
018import com.fasterxml.jackson.core.JsonProcessingException;
019import com.fasterxml.jackson.databind.DeserializationFeature;
020import com.fasterxml.jackson.dataformat.xml.XmlMapper;
021import com.google.cloud.tools.jib.api.Credential;
022import com.google.cloud.tools.jib.maven.MavenProjectProperties;
023import com.google.cloud.tools.jib.plugins.common.PropertyNames;
024import org.apache.maven.model.Plugin;
025import org.apache.maven.project.MavenProject;
026
027import javax.inject.Inject;
028import javax.inject.Singleton;
029import java.util.Collections;
030import java.util.HashSet;
031import java.util.List;
032import java.util.Optional;
033import java.util.Set;
034
035import static io.micronaut.maven.jib.JibConfiguration.*;
036
037/**
038 * Exposes the Jib plugin configuration so that it can be read by other mojos.
039 *
040 * @author Álvaro Sánchez-Mariscal
041 * @since 1.1
042 */
043@Singleton
044public class JibConfigurationService {
045
046    private final Optional<JibConfiguration> configuration;
047
048    @Inject
049    public JibConfigurationService(MavenProject mavenProject) {
050        final Plugin plugin = mavenProject.getPlugin(MavenProjectProperties.PLUGIN_KEY);
051        if (plugin != null && plugin.getConfiguration() != null) {
052            final XmlMapper mapper = XmlMapper.builder()
053                    .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
054                    .findAndAddModules()
055                    .build();
056            try {
057                configuration = Optional.ofNullable(mapper.readValue(plugin.getConfiguration().toString(), JibConfiguration.class));
058            } catch (JsonProcessingException e) {
059                throw new IllegalArgumentException("Error parsing Jib plugin configuration", e);
060            }
061        } else {
062            configuration = Optional.empty();
063        }
064    }
065
066    /**
067     * @return the <code>to.image</code> configuration.
068     */
069    public Optional<String> getToImage() {
070        final String value = configuration.flatMap(c -> c.to().flatMap(ToConfiguration::image))
071                .orElse(null);
072        return Optional.ofNullable(System.getProperties().getProperty(PropertyNames.TO_IMAGE, value));
073    }
074
075    /**
076     * @return the <code>from.image</code> configuration.
077     */
078    public Optional<String> getFromImage() {
079        final String value = configuration.flatMap(c -> c.from().flatMap(FromConfiguration::image))
080                .orElse(null);
081        return Optional.ofNullable(System.getProperties().getProperty(PropertyNames.FROM_IMAGE, value));
082    }
083
084    /**
085     * @return the <code>to.tags</code> configuration.
086     */
087    public Set<String> getTags() {
088        final Set<String> tags = configuration.flatMap(c -> c.to().map(ToConfiguration::tags))
089                .orElse(Collections.emptySet());
090        return Optional.ofNullable(System.getProperties().getProperty(PropertyNames.TO_TAGS))
091                .map(JibConfigurationService::parseCommaSeparatedList)
092                .orElse(tags);
093    }
094
095    /**
096     * @return the <code>to.auth.username</code> and <code>to.auth.password</code> configuration.
097     */
098    public Optional<Credential> getToCredentials() {
099        String usernameProperty = System.getProperties().getProperty(PropertyNames.TO_AUTH_USERNAME);
100        String passwordProperty = System.getProperties().getProperty(PropertyNames.TO_AUTH_PASSWORD);
101        if (usernameProperty != null || passwordProperty != null) {
102            return Optional.of(Credential.from(usernameProperty, passwordProperty));
103        } else {
104            return configuration
105                    .flatMap(c -> c.to().flatMap(ToConfiguration::auth))
106                    .map(this::getCredentials);
107        }
108    }
109
110    /**
111     * @return the <code>from.auth.username</code> and <code>from.auth.password</code> configuration.
112     */
113    public Optional<Credential> getFromCredentials() {
114        String usernameProperty = System.getProperties().getProperty(PropertyNames.FROM_AUTH_USERNAME);
115        String passwordProperty = System.getProperties().getProperty(PropertyNames.FROM_AUTH_PASSWORD);
116        if (usernameProperty != null || passwordProperty != null) {
117            return Optional.of(Credential.from(usernameProperty, passwordProperty));
118        } else {
119            return configuration
120                    .flatMap(c -> c.from().flatMap(FromConfiguration::auth))
121                    .map(this::getCredentials);
122        }
123
124    }
125
126    private Credential getCredentials(AuthConfiguration authConfiguration) {
127        return Credential.from(
128                authConfiguration.username().orElse(null),
129                authConfiguration.password().orElse(null)
130        );
131    }
132
133    /**
134     * @return the <code>container.workingDirectory</code> configuration.
135     */
136    public Optional<String> getWorkingDirectory() {
137        final String value = configuration.flatMap(c -> c.container().flatMap(ContainerConfiguration::workingDirectory))
138                .orElse(null);
139        return Optional.ofNullable(System.getProperties().getProperty(PropertyNames.CONTAINER_WORKING_DIRECTORY, value));
140    }
141
142    /**
143     * @return the <code>container.args</code> configuration.
144     */
145    public List<String> getArgs() {
146        final List<String> args = configuration.flatMap(c -> c.container().map(ContainerConfiguration::args))
147                .orElse(Collections.emptyList());
148        return Optional.ofNullable(System.getProperties().getProperty(PropertyNames.CONTAINER_ARGS))
149                .map(JibConfigurationService::parseCommaSeparatedList)
150                .map(List::copyOf)
151                .orElse(args);
152    }
153
154    /**
155     * @return the <code>container.ports</code> configuration.
156     */
157    public Optional<String> getPorts() {
158        final Set<String> ports = configuration.flatMap(c -> c.container().map(ContainerConfiguration::ports))
159                .orElse(Collections.emptySet());
160        return Optional.ofNullable(System.getProperties().getProperty(PropertyNames.CONTAINER_PORTS))
161                .map(s -> s.replace(",", " "))
162                .or(() -> ports.isEmpty() ? Optional.empty() : Optional.of(String.join(" ", ports)));
163    }
164
165    private static Set<String> parseCommaSeparatedList(String list) {
166        String[] parts = list.split(",");
167        var items = new HashSet<String>(parts.length);
168        for (String part : parts) {
169            items.add(part.trim());
170        }
171        return items;
172    }
173}