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.api.ImageReference; 023import com.google.cloud.tools.jib.api.InvalidImageReferenceException; 024import com.google.cloud.tools.jib.api.LogEvent; 025import com.google.cloud.tools.jib.frontend.CredentialRetrieverFactory; 026import com.google.cloud.tools.jib.maven.MavenProjectProperties; 027import com.google.cloud.tools.jib.plugins.common.PropertyNames; 028import com.google.cloud.tools.jib.registry.credentials.CredentialRetrievalException; 029import java.util.function.Consumer; 030import java.util.stream.Stream; 031import org.apache.maven.model.Plugin; 032import org.apache.maven.project.MavenProject; 033import org.slf4j.Logger; 034 035import javax.inject.Inject; 036import javax.inject.Singleton; 037import java.util.Collections; 038import java.util.HashSet; 039import java.util.List; 040import java.util.Optional; 041import java.util.Set; 042 043import static io.micronaut.maven.jib.JibConfiguration.*; 044 045/** 046 * Exposes the Jib plugin configuration so that it can be read by other mojos. 047 * 048 * @author Álvaro Sánchez-Mariscal 049 * @since 1.1 050 */ 051@Singleton 052public class JibConfigurationService { 053 private final Optional<JibConfiguration> configuration; 054 055 @Inject 056 public JibConfigurationService(MavenProject mavenProject) { 057 final Plugin plugin = mavenProject.getPlugin(MavenProjectProperties.PLUGIN_KEY); 058 if (plugin != null && plugin.getConfiguration() != null) { 059 final XmlMapper mapper = XmlMapper.builder() 060 .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) 061 .findAndAddModules() 062 .build(); 063 try { 064 configuration = Optional.ofNullable(mapper.readValue(plugin.getConfiguration().toString(), JibConfiguration.class)); 065 } catch (JsonProcessingException e) { 066 throw new IllegalArgumentException("Error parsing Jib plugin configuration", e); 067 } 068 } else { 069 configuration = Optional.empty(); 070 } 071 } 072 073 /** 074 * @return the <code>to.image</code> configuration. 075 */ 076 public Optional<String> getToImage() { 077 final String value = configuration.flatMap(c -> c.to().flatMap(ToConfiguration::image)) 078 .orElse(null); 079 return Optional.ofNullable(System.getProperties().getProperty(PropertyNames.TO_IMAGE, value)); 080 } 081 082 /** 083 * @return the <code>from.image</code> configuration. 084 */ 085 public Optional<String> getFromImage() { 086 final String value = configuration.flatMap(c -> c.from().flatMap(FromConfiguration::image)) 087 .orElse(null); 088 return Optional.ofNullable(System.getProperties().getProperty(PropertyNames.FROM_IMAGE, value)); 089 } 090 091 /** 092 * @return the <code>to.tags</code> configuration. 093 */ 094 public Set<String> getTags() { 095 final Set<String> tags = configuration.flatMap(c -> c.to().map(ToConfiguration::tags)) 096 .orElse(Collections.emptySet()); 097 return Optional.ofNullable(System.getProperties().getProperty(PropertyNames.TO_TAGS)) 098 .map(JibConfigurationService::parseCommaSeparatedList) 099 .orElse(tags); 100 } 101 102 /** 103 * @return the <code>to.auth.username</code> and <code>to.auth.password</code> configuration. 104 */ 105 public Optional<Credential> getToCredentials() { 106 String usernameProperty = System.getProperties().getProperty(PropertyNames.TO_AUTH_USERNAME); 107 String passwordProperty = System.getProperties().getProperty(PropertyNames.TO_AUTH_PASSWORD); 108 if (usernameProperty != null || passwordProperty != null) { 109 return Optional.of(Credential.from(usernameProperty, passwordProperty)); 110 } else { 111 return configuration 112 .flatMap(c -> c.to().flatMap(ToConfiguration::auth)) 113 .map(this::getCredentials); 114 } 115 } 116 117 /** 118 * @return the <code>from.auth.username</code> and <code>from.auth.password</code> configuration. 119 */ 120 public Optional<Credential> getFromCredentials() { 121 String usernameProperty = System.getProperties().getProperty(PropertyNames.FROM_AUTH_USERNAME); 122 String passwordProperty = System.getProperties().getProperty(PropertyNames.FROM_AUTH_PASSWORD); 123 if (usernameProperty != null || passwordProperty != null) { 124 return Optional.of(Credential.from(usernameProperty, passwordProperty)); 125 } else { 126 return configuration 127 .flatMap(c -> c.from().flatMap(FromConfiguration::auth)) 128 .map(this::getCredentials); 129 } 130 131 } 132 133 /** 134 * Resolves effective credentials for a registry hosting the provided image reference. 135 * Precedence: explicit credentials -> well-known credential helpers -> Google ADC -> docker config. 136 * 137 * @param image the image reference (e.g., gcr.io/project/image:tag) 138 * @param logger the logger to use for logging events 139 * @return a Credential if one could be resolved 140 */ 141 public Optional<Credential> resolveCredentialForImage(String image, Logger logger) { 142 try { 143 ImageReference imageReference = ImageReference.parse(image); 144 Consumer<LogEvent> logConsumer = logEvent -> logEvent(logEvent, logger); 145 CredentialRetrieverFactory factory = CredentialRetrieverFactory.forImage(imageReference, logConsumer); 146 return Stream.of( 147 factory.wellKnownCredentialHelpers(), 148 factory.googleApplicationDefaultCredentials(), 149 factory.dockerConfig() 150 ) 151 .map(retriever -> { 152 try { 153 return retriever.retrieve(); 154 } catch (CredentialRetrievalException e) { 155 return Optional.<Credential>empty(); 156 } 157 }) 158 .filter(Optional::isPresent) 159 .map(Optional::get) 160 .findFirst(); 161 } catch (InvalidImageReferenceException e) { 162 logger.warn("Invalid image reference '{}': {}", image, e.getMessage()); 163 return Optional.empty(); 164 } 165 } 166 167 private Credential getCredentials(AuthConfiguration authConfiguration) { 168 return Credential.from( 169 authConfiguration.username().orElse(null), 170 authConfiguration.password().orElse(null) 171 ); 172 } 173 174 /** 175 * @return the <code>container.workingDirectory</code> configuration. 176 */ 177 public Optional<String> getWorkingDirectory() { 178 final String value = configuration.flatMap(c -> c.container().flatMap(ContainerConfiguration::workingDirectory)) 179 .orElse(null); 180 return Optional.ofNullable(System.getProperties().getProperty(PropertyNames.CONTAINER_WORKING_DIRECTORY, value)); 181 } 182 183 /** 184 * @return the <code>container.args</code> configuration. 185 */ 186 public List<String> getArgs() { 187 final List<String> args = configuration.flatMap(c -> c.container().map(ContainerConfiguration::args)) 188 .orElse(Collections.emptyList()); 189 return Optional.ofNullable(System.getProperties().getProperty(PropertyNames.CONTAINER_ARGS)) 190 .map(JibConfigurationService::parseCommaSeparatedList) 191 .map(List::copyOf) 192 .orElse(args); 193 } 194 195 /** 196 * @return the <code>container.ports</code> configuration. 197 */ 198 public Optional<String> getPorts() { 199 final Set<String> ports = configuration.flatMap(c -> c.container().map(ContainerConfiguration::ports)) 200 .orElse(Collections.emptySet()); 201 return Optional.ofNullable(System.getProperties().getProperty(PropertyNames.CONTAINER_PORTS)) 202 .map(s -> s.replace(",", " ")) 203 .or(() -> ports.isEmpty() ? Optional.empty() : Optional.of(String.join(" ", ports))); 204 } 205 206 private static Set<String> parseCommaSeparatedList(String list) { 207 String[] parts = list.split(","); 208 var items = new HashSet<String>(parts.length); 209 for (String part : parts) { 210 items.add(part.trim()); 211 } 212 return items; 213 } 214 215 private void logEvent(LogEvent logEvent, Logger logger) { 216 if (logEvent.getLevel().equals(LogEvent.Level.DEBUG)) { 217 logger.debug(logEvent.getMessage()); 218 } else { 219 logger.info(logEvent.getMessage()); 220 } 221 } 222}