001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- 002 * 003 * Copyright © 2017-2018 microBean. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 014 * implied. See the License for the specific language governing 015 * permissions and limitations under the License. 016 */ 017package org.microbean.helm.maven; 018 019import java.io.BufferedOutputStream; 020import java.io.Closeable; 021import java.io.IOException; 022import java.io.OutputStream; 023 024import java.net.UnknownServiceException; 025import java.net.URI; 026import java.net.URISyntaxException; 027import java.net.URL; 028import java.net.URLConnection; 029 030import java.nio.file.Files; 031import java.nio.file.Path; 032import java.nio.file.Paths; 033 034import java.util.Objects; 035 036import java.util.zip.GZIPOutputStream; 037 038import javax.inject.Inject; 039 040import hapi.chart.ChartOuterClass.Chart; 041import hapi.chart.MetadataOuterClass.MetadataOrBuilder; 042 043import org.apache.maven.model.Build; 044 045import org.apache.maven.project.MavenProject; 046 047import org.apache.maven.plugin.MojoExecutionException; 048import org.apache.maven.plugin.MojoFailureException; 049 050import org.apache.maven.plugin.logging.Log; 051 052import org.apache.maven.plugins.annotations.Mojo; 053import org.apache.maven.plugins.annotations.Parameter; 054 055import org.codehaus.plexus.personality.plexus.lifecycle.phase.Disposable; 056 057import org.microbean.helm.chart.AbstractChartLoader; 058import org.microbean.helm.chart.AbstractChartWriter; 059import org.microbean.helm.chart.URLChartLoader; 060import org.microbean.helm.chart.TapeArchiveChartWriter; 061 062/** 063 * Packages a Helm chart from its raw materials. 064 * 065 * @author <a href="https://about.me/lairdnelson" 066 * target="_parent">Laird Nelson</a> 067 * 068 * @see #execute() 069 */ 070@Mojo(name = "package") 071public class PackageMojo extends AbstractHelmMojo implements Disposable { 072 073 074 /* 075 * Instance fields. 076 */ 077 078 079 /** 080 * The {@link MavenProject} in effect. 081 * 082 * <p>This field is never {@code null}.</p> 083 * 084 * @see #PackageMojo(MavenProject) 085 */ 086 private final MavenProject project; 087 088 /** 089 * Whether to skip execution. 090 */ 091 @Parameter(defaultValue = "false") 092 private boolean skip; 093 094 /** 095 * A {@link String} in {@link URI} form pointing to the Helm chart 096 * contents to load. 097 */ 098 @Parameter(required = true, defaultValue = "file:${project.basedir}/src/helm/charts/${project.artifactId}/") 099 private String chartContentsUri; 100 101 @Parameter 102 private AbstractChartLoader<URL> chartLoader; 103 104 @Parameter 105 private AbstractChartWriter chartWriter; 106 107 @Parameter 108 private URI chartTargetUri; 109 110 111 /* 112 * Constructors. 113 */ 114 115 116 @Inject 117 public PackageMojo(final MavenProject project) { 118 super(); 119 this.project = project; 120 } 121 122 123 /* 124 * Public instance methods. 125 */ 126 127 128 @Override 129 public void execute() throws MojoExecutionException, MojoFailureException { 130 final Log log = this.getLog(); 131 assert log != null; 132 133 if (this.getSkip()) { 134 if (log.isDebugEnabled()) { 135 log.debug("Skipping execution by request."); 136 } 137 return; 138 } 139 140 final URI chartContentsUri = this.getChartContentsUri(); 141 if (chartContentsUri == null) { 142 throw new IllegalStateException("getChartContentsUri() == null"); 143 } 144 URL chartContentsUrl = null; 145 try { 146 chartContentsUrl = chartContentsUri.toURL(); 147 } catch (final IOException ioException) { 148 throw new MojoExecutionException(ioException.getMessage(), ioException); 149 } 150 assert chartContentsUrl != null; 151 152 AbstractChartLoader<URL> chartLoader = this.getChartLoader(); 153 if (chartLoader == null) { 154 chartLoader = new URLChartLoader(); 155 } 156 157 Throwable throwable = null; 158 159 Chart.Builder chart = null; 160 try { 161 chart = chartLoader.load(chartContentsUrl); 162 } catch (final RuntimeException runtimeException) { 163 throwable = runtimeException; 164 throw runtimeException; 165 } catch (final IOException exception) { 166 final MojoExecutionException e = new MojoExecutionException(exception.getMessage(), exception); 167 throwable = e; 168 throw e; 169 } finally { 170 try { 171 chartLoader.close(); 172 } catch (final IOException suppressMe) { 173 if (throwable == null) { 174 throw new MojoExecutionException(suppressMe.getMessage(), suppressMe); 175 } else { 176 throwable.addSuppressed(suppressMe); 177 } 178 } 179 } 180 throwable = null; 181 182 final MetadataOrBuilder metadata = chart.getMetadata(); 183 if (metadata == null) { 184 throw new IllegalStateException("chart.getMetadata() == null"); 185 } 186 final String chartName = metadata.getName(); 187 if (chartName == null) { 188 throw new IllegalStateException("metadata.getName() == null"); 189 } else if (chartName.isEmpty()) { 190 throw new IllegalStateException("metadata.getName().isEmpty()"); 191 } 192 193 AbstractChartWriter chartWriter = this.getChartWriter(); 194 if (chartWriter == null) { 195 URI chartTargetUri = this.getChartTargetUri(); 196 if (chartTargetUri == null) { 197 final Build build = this.project.getBuild(); 198 assert build != null; 199 final String targetDirectory = build.getDirectory(); 200 assert targetDirectory != null; 201 final Path targetDirectoryPath = Paths.get(targetDirectory); 202 assert targetDirectoryPath != null; 203 final Path helmChartsDirectoryPath = targetDirectoryPath.resolve("generated-sources/helm/charts"); 204 assert helmChartsDirectoryPath != null; 205 assert helmChartsDirectoryPath.isAbsolute(); 206 final Path chartDirectoryPath = helmChartsDirectoryPath.resolve(chartName + ".tgz"); 207 assert chartDirectoryPath != null; 208 chartTargetUri = chartDirectoryPath.toUri(); 209 } 210 assert chartTargetUri != null; 211 if ("file".equals(chartTargetUri.getScheme())) { 212 final String chartTargetUriPath = chartTargetUri.getPath(); 213 if (chartTargetUriPath != null) { 214 final Path chartTargetPath = Paths.get(chartTargetUriPath).normalize(); 215 assert chartTargetPath != null; 216 final Path parent = chartTargetPath.getParent(); 217 if (parent != null) { 218 try { 219 Files.createDirectories(parent); 220 } catch (final IOException ioException) { 221 throw new MojoExecutionException(ioException.getMessage(), ioException); 222 } 223 } 224 } 225 } 226 URL chartTargetURL = null; 227 try { 228 chartTargetURL = chartTargetUri.toURL(); 229 } catch (final IOException ioException) { 230 throw new MojoExecutionException(ioException.getMessage(), ioException); 231 } 232 assert chartTargetURL != null; 233 URLConnection connection = null; 234 try { 235 connection = chartTargetURL.openConnection(); 236 } catch (final IOException ioException) { 237 throw new MojoExecutionException(ioException.getMessage(), ioException); 238 } 239 assert connection != null; 240 connection.setDoOutput(true); 241 OutputStream outputStream = null; 242 try { 243 outputStream = connection.getOutputStream(); 244 } catch (final UnknownServiceException outputNotSupported) { 245 // http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4191800 246 if ("file".equals(chartTargetURL.getProtocol())) { 247 final Path chartTargetPath = Paths.get(chartTargetURL.getPath()); 248 assert chartTargetPath != null; 249 try { 250 outputStream = Files.newOutputStream(chartTargetPath); 251 } catch (final IOException ioException) { 252 ioException.addSuppressed(outputNotSupported); 253 throw new MojoExecutionException(ioException.getMessage(), ioException); 254 } 255 } else { 256 throw new MojoExecutionException(outputNotSupported.getMessage(), outputNotSupported); 257 } 258 } catch (final IOException ioException) { 259 throw new MojoExecutionException(ioException.getMessage(), ioException); 260 } 261 try { 262 chartWriter = new TapeArchiveChartWriter(new BufferedOutputStream(new GZIPOutputStream(outputStream))); 263 } catch (final IOException ioException) { 264 throw new MojoExecutionException(ioException.getMessage(), ioException); 265 } 266 } 267 assert chartWriter != null; 268 269 throwable = null; 270 try { 271 chartWriter.write(chart); 272 } catch (final RuntimeException runtimeException) { 273 throwable = runtimeException; 274 throw runtimeException; 275 } catch (final IOException ioException) { 276 final MojoExecutionException e = new MojoExecutionException(ioException.getMessage(), ioException); 277 throwable = e; 278 throw e; 279 } finally { 280 try { 281 chartWriter.close(); 282 } catch (final IOException suppressMe) { 283 if (throwable == null) { 284 throw new MojoExecutionException(suppressMe.getMessage(), suppressMe); 285 } else { 286 throwable.addSuppressed(suppressMe); 287 } 288 } 289 } 290 291 } 292 293 /** 294 * Implements the {@link Disposable} interface by {@linkplain 295 * AbstractChartLoader#close() closing} the {@link 296 * AbstractChartLoader} returned by the {@link #getChartLoader()} 297 * method and {@linkplain AbstractChartWriter#close() closing} the 298 * {@link AbstractChartWriter} returned by the {@link 299 * #getChartWriter()} method. 300 * 301 * <p>Any {@link IOException}s encountered are {@linkplain 302 * Log#error(Throwable) logged}.</p> 303 */ 304 @Override 305 public void dispose() { 306 final Log log = this.getLog(); 307 assert log != null; 308 309 final Closeable chartLoader = this.getChartLoader(); 310 if (chartLoader != null) { 311 try { 312 chartLoader.close(); 313 } catch (final IOException logMe) { 314 if (log.isErrorEnabled()) { 315 log.error(logMe); 316 } 317 } 318 } 319 320 final Closeable chartWriter = this.getChartWriter(); 321 if (chartWriter != null) { 322 try { 323 chartWriter.close(); 324 } catch (final IOException logMe) { 325 if (log.isErrorEnabled()) { 326 log.error(logMe); 327 } 328 } 329 } 330 } 331 332 /** 333 * Returns {@code true} if this {@link PackageMojo} should not 334 * execute. 335 * 336 * @return {@code true} if this {@link PackageMojo} should not 337 * execute; {@code false} otherwise 338 * 339 * @see #setSkip(boolean) 340 */ 341 public boolean getSkip() { 342 return this.skip; 343 } 344 345 /** 346 * Controls whether this {@link PackageMojo} should execute. 347 * 348 * @param skip if {@code true}, this {@link PackageMojo} will not 349 * execute 350 * 351 * @see #getSkip() 352 */ 353 public void setSkip(final boolean skip) { 354 this.skip = skip; 355 } 356 357 public AbstractChartLoader<URL> getChartLoader() { 358 return this.chartLoader; 359 } 360 361 public void setChartLoader(final AbstractChartLoader<URL> chartLoader) { 362 this.chartLoader = chartLoader; 363 } 364 365 public AbstractChartWriter getChartWriter() { 366 return this.chartWriter; 367 } 368 369 public void setChartWriter(final AbstractChartWriter chartWriter) { 370 this.chartWriter = chartWriter; 371 } 372 373 public URI getChartContentsUri() { 374 final URI returnValue; 375 if (this.chartContentsUri == null) { 376 returnValue = null; 377 } else { 378 URI temp = null; 379 try { 380 temp = new URI(this.chartContentsUri); 381 } catch (final URISyntaxException uriSyntaxException) { 382 try { 383 temp = new URI(this.chartContentsUri.replace('\\', '/')); 384 } catch (final URISyntaxException backslashReplacementFailed) { 385 throw new IllegalStateException(backslashReplacementFailed.getMessage(), backslashReplacementFailed); 386 } 387 } finally { 388 returnValue = temp; 389 } 390 } 391 return returnValue; 392 } 393 394 public void setChartContentsUri(final URI chartContentsUri) { 395 if (chartContentsUri == null) { 396 this.chartContentsUri = null; 397 } else { 398 this.chartContentsUri = chartContentsUri.toString(); 399 } 400 } 401 402 public URI getChartTargetUri() { 403 return this.chartTargetUri; 404 } 405 406 public void setChartTargetUri(final URI chartTargetUri) { 407 this.chartTargetUri = chartTargetUri; 408 } 409 410 411}