package com.kiwigrid.helm.maven.plugin; import java.io.BufferedInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.PasswordAuthentication; import java.net.URL; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.attribute.AclEntry; import java.nio.file.attribute.AclEntryPermission; import java.nio.file.attribute.AclEntryType; import java.nio.file.attribute.AclFileAttributeView; import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.UserPrincipal; import java.util.List; import java.util.Locale; import java.util.Set; import org.apache.commons.compress.archivers.ArchiveEntry; import org.apache.commons.compress.archivers.ArchiveException; import org.apache.commons.compress.archivers.ArchiveInputStream; import org.apache.commons.compress.archivers.ArchiveStreamFactory; import org.apache.commons.compress.compressors.CompressorException; import org.apache.commons.compress.compressors.CompressorStreamFactory; import org.apache.commons.compress.utils.IOUtils; import org.apache.commons.lang3.SystemUtils; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.codehaus.plexus.util.Os; import org.codehaus.plexus.util.StringUtils; import com.kiwigrid.helm.maven.plugin.pojo.HelmRepository; /** * Mojo for initializing helm * * @author Fabian Schlegel * @since 06.11.17 */ @Mojo(name = "init", defaultPhase = LifecyclePhase.INITIALIZE) public class InitMojo extends AbstractHelmMojo { @Parameter(property = "helm.init.skip", defaultValue = "false") private boolean skipInit; @Parameter(property = "helm.init.add-default-repo", defaultValue = "true") private boolean addDefaultRepo; public void execute() throws MojoExecutionException { if (skip || skipInit) { getLog().info("Skip init"); return; } getLog().info("Initializing Helm..."); Path outputDirectory = Paths.get(getOutputDirectory()).toAbsolutePath(); if (!outputDirectory.toFile().exists()) { getLog().info("Creating output directory..."); try { Files.createDirectories(outputDirectory); } catch (IOException e) { throw new MojoExecutionException("Unable to create output directory at " + outputDirectory, e); } } if(isUseLocalHelmBinary()) { verifyLocalHelmBinary(); getLog().info("Using local HELM binary ["+ getHelmExecuteablePath() +"]"); } else { downloadAndUnpackHelm(); } if (addDefaultRepo) { getLog().info("Adding default repo [stable]"); callCli(getHelmExecuteablePath() + " repo add stable https://kubernetes-charts.storage.googleapis.com" + " " + (StringUtils.isNotEmpty(getRegistryConfig()) ? " --registry-config " + getRegistryConfig() : "") + (StringUtils.isNotEmpty(getRepositoryCache()) ? " --repository-cache " + getRepositoryCache() : "") + (StringUtils.isNotEmpty(getRepositoryConfig()) ? " --repository-config " + getRepositoryConfig() : ""), "Unable add repo", false); } if (getHelmExtraRepos() != null) { for (HelmRepository repository : getHelmExtraRepos()) { getLog().info("Adding repo [" + repository +"]"); PasswordAuthentication auth = getAuthentication(repository); callCli(getHelmExecuteablePath() + " repo add " + repository.getName() + " " + repository.getUrl() + (StringUtils.isNotEmpty(getRegistryConfig()) ? " --registry-config=" + getRegistryConfig() : "") + (StringUtils.isNotEmpty(getRepositoryCache()) ? " --repository-cache=" + getRepositoryCache() : "") + (StringUtils.isNotEmpty(getRepositoryConfig()) ? " --repository-config=" + getRepositoryConfig() : "") + (auth != null ? " --username=" + auth.getUserName() + " --password=" + String.valueOf(auth.getPassword()) : ""), "Unable add repo", false); } } } protected void downloadAndUnpackHelm() throws MojoExecutionException { Path directory = Paths.get(getHelmExecutableDirectory()); if (Files.exists(directory.resolve(SystemUtils.IS_OS_WINDOWS ? "helm.exe" : "helm"))) { getLog().info("Found helm executable, skip init."); return; } String url = getHelmDownloadUrl(); if (StringUtils.isBlank(url)) { String os = getOperatingSystem(); String architecture = getArchitecture(); String extension = getExtension(); url = String.format("https://get.helm.sh/helm-v%s-%s-%s.%s", getHelmVersion(), os, architecture, extension); } getLog().debug("Downloading Helm: " + url); boolean found = false; try (InputStream dis = new URL(url).openStream(); InputStream cis = createCompressorInputStream(dis); ArchiveInputStream is = createArchiverInputStream(cis)) { // create directory if not present Files.createDirectories(directory); // get helm executable entry ArchiveEntry entry = null; while ((entry = is.getNextEntry()) != null) { String name = entry.getName(); if (entry.isDirectory() || (!name.endsWith("helm.exe") && !name.endsWith("helm"))) { getLog().debug("Skip archive entry with name: " + name); continue; } getLog().debug("Use archive entry with name: " + name); Path helm = directory.resolve(name.endsWith(".exe") ? "helm.exe" : "helm"); try (FileOutputStream output = new FileOutputStream(helm.toFile())) { IOUtils.copy(is, output); } addExecPermission(helm); found = true; break; } } catch (IOException e) { throw new MojoExecutionException("Unable to download and extract helm executable.", e); } if (!found) { throw new MojoExecutionException("Unable to find helm executable in tar file."); } } public boolean isAddDefaultRepo() { return addDefaultRepo; } public void setAddDefaultRepo(boolean addDefaultRepo) { this.addDefaultRepo = addDefaultRepo; } private void addExecPermission(final Path helm) throws IOException { Set<String> fileAttributeView = FileSystems.getDefault().supportedFileAttributeViews(); if (fileAttributeView.contains("posix")) { final Set<PosixFilePermission> permissions; try { permissions = Files.getPosixFilePermissions(helm); } catch (UnsupportedOperationException e) { getLog().debug("Exec file permission is not set", e); return; } permissions.add(PosixFilePermission.OWNER_EXECUTE); Files.setPosixFilePermissions(helm, permissions); } else if (fileAttributeView.contains("acl")) { String username = System.getProperty("user.name"); UserPrincipal userPrincipal = FileSystems.getDefault().getUserPrincipalLookupService().lookupPrincipalByName(username); AclEntry aclEntry = AclEntry.newBuilder().setPermissions(AclEntryPermission.EXECUTE).setType(AclEntryType.ALLOW).setPrincipal(userPrincipal).build(); AclFileAttributeView acl = Files.getFileAttributeView(helm, AclFileAttributeView.class, LinkOption.NOFOLLOW_LINKS); List<AclEntry> aclEntries = acl.getAcl(); aclEntries.add(aclEntry); acl.setAcl(aclEntries); } } private void verifyLocalHelmBinary() throws MojoExecutionException { callCli(getHelmExecuteablePath() + " version", "Unable to verify local HELM binary", false); } private ArchiveInputStream createArchiverInputStream(InputStream is) throws MojoExecutionException { // Stream must support mark to allow for auto detection of archiver if (!is.markSupported()) { is = new BufferedInputStream(is); } try { ArchiveStreamFactory archiveStreamFactory = new ArchiveStreamFactory(); return archiveStreamFactory.createArchiveInputStream(is); } catch (ArchiveException e) { throw new MojoExecutionException("Unsupported archive type downloaded", e); } } private InputStream createCompressorInputStream(InputStream is) throws MojoExecutionException { // Stream must support mark to allow for auto detection of compressor if (!is.markSupported()) { is = new BufferedInputStream(is); } // Detect if stream is compressed String compressorType = null; try { compressorType = CompressorStreamFactory.detect(is); } catch (CompressorException e) { getLog().debug("Unknown type of compressed stream", e); } // If compressed then wrap with compressor stream if (compressorType != null) { try { CompressorStreamFactory compressorFactory = new CompressorStreamFactory(); return compressorFactory.createCompressorInputStream(compressorType, is); } catch (CompressorException e) { throw new MojoExecutionException("Unsupported compressor type: " + compressorType); } } return is; } private String getArchitecture() { String architecture = System.getProperty("os.arch").toLowerCase(Locale.US); if (architecture.equals("x86_64") || architecture.equals("amd64")) { return "amd64"; } else if (architecture.equals("x86") || architecture.equals("i386")) { return "386"; } else if (architecture.contains("arm64")) { return "arm64"; } else if (architecture.equals("aarch32") || architecture.startsWith("arm")) { return "arm"; } else if (architecture.contains("ppc64le") || (architecture.contains("ppc64") && System.getProperty("sun.cpu.endian").equals("little"))) { return "ppc64le"; } throw new IllegalStateException("Unsupported architecture: " + architecture); } private String getExtension() { return Os.OS_FAMILY.equals(Os.FAMILY_WINDOWS) ? "zip" : "tar.gz"; } private String getOperatingSystem() { switch (Os.OS_FAMILY) { case Os.FAMILY_UNIX: return "linux"; case Os.FAMILY_MAC: return "darwin"; case Os.FAMILY_WINDOWS: return "windows"; default: throw new IllegalStateException("Unsupported OS: " + Os.OS_FAMILY); } } }