/*
* Copyright 2005-2016 Red Hat, Inc.
*
* Red Hat licenses this file to you under the Apache License, version
* 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package io.fabric8.maven;
import com.jcraft.jsch.*;
import com.jcraft.jsch.agentproxy.*;
import io.fabric8.kubernetes.api.KubernetesHelper;
import io.fabric8.maven.helm.Chart;
import io.fabric8.utils.Files;
import io.fabric8.utils.Strings;
import org.apache.maven.model.Developer;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectHelper;
import org.eclipse.jgit.api.CloneCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.TransportConfigCallback;
import org.eclipse.jgit.transport.JschConfigSessionFactory;
import org.eclipse.jgit.transport.OpenSshConfig;
import org.eclipse.jgit.transport.SshTransport;
import org.eclipse.jgit.transport.Transport;
import org.eclipse.jgit.util.FS;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* Generates a Helm chart for the kubernetes.yml file
*/
@Mojo(name = "helm", defaultPhase = LifecyclePhase.PACKAGE)
public class HelmMojo extends AbstractFabric8Mojo {
// TODO when Helm supports this we should probably switch to ".yml"
public static final String HELM_YAML_EXTENSION = ".yaml";
public static final String PROPERTY_HELM_GIT_URL = "fabric8.helm.gitUrl";
public static final String PROPERTY_HELM_CHART_NAME = "fabric8.helm.chart";
@Component
private MavenProjectHelper projectHelper;
/**
* The artifact type for attaching the generated kubernetes json file to the project
*/
@Parameter(property = PROPERTY_HELM_GIT_URL, defaultValue = "git@github.com:fabric8io/charts.git")
private String helmGitUrl;
/**
* The kubernetes YAML file
*/
@Parameter(property = "fabric8.yaml.target", defaultValue = "${basedir}/target/classes/kubernetes.yml")
private File kubernetesYaml;
/**
* The kubernetes YAML file
*/
@Parameter(property = "fabric8.helm.cloneDir")
private File helmCloneDir;
@Parameter(property = "fabric8.helm.privateKeyPath")
private String privateKeyPath;
@Parameter(property = "fabric8.helm.privateKeyPassphrase")
private String privateKeyPassphrase;
/**
* The kubernetes YAML file
*/
@Parameter(property = PROPERTY_HELM_CHART_NAME, defaultValue = "${project.artifactId}")
private String chartName;
/**
* The name of the git remote repo
*/
@Parameter(property = "fabric8.helm.gitRemote", defaultValue = "origin")
protected String remoteRepoName;
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
File yaml = getKubernetesYaml();
if (Files.isFile(yaml)) {
getLog().info("Creating Helm Chart for kubernetes yaml file: " + yaml);
File outputDir = getOutputDir();
if (outputDir != null) {
File manifestsDir = new File(outputDir, "manifests");
// lets delete all the manifests in case there are some existing ones with different names
if (Files.isDirectory(manifestsDir)) {
Files.recursiveDelete(manifestsDir);
}
manifestsDir.mkdirs();
File outputYamlFile = new File(manifestsDir, chartName + HELM_YAML_EXTENSION);
try {
Files.copy(yaml, outputYamlFile);
} catch (IOException e) {
throw new MojoExecutionException("Failed to copy file " + yaml + " to chart manifest file: " + outputYamlFile + ". Reason: " + e, e);
}
File outputChartFile = new File(outputDir, "Chart" + HELM_YAML_EXTENSION);
Chart chart = createChart();
try {
KubernetesHelper.saveYaml(chart, outputChartFile);
} catch (IOException e) {
throw new MojoExecutionException("Failed to save chart " + outputChartFile + ". Reason: " + e, e);
}
MavenProject project = getProject();
if (project != null) {
File basedir = project.getBasedir();
if (basedir != null) {
String outputReadMeFileName = "README.md";
try {
copyReadMe(basedir, outputDir, outputReadMeFileName);
} catch (IOException e) {
throw new MojoExecutionException("Failed to save " + outputReadMeFileName + ". Reason: " + e, e);
}
}
}
getLog().info("Generated Helm Chart " + chartName + " at " + outputDir);
}
}
}
public String getHelmGitUrl() {
return helmGitUrl;
}
protected Chart createChart() {
Chart answer = new Chart();
answer.setName(chartName);
MavenProject project = getProject();
if (project != null) {
answer.setVersion(project.getVersion());
answer.setDescription(project.getDescription());
answer.setHome(project.getUrl());
List<Developer> developers = project.getDevelopers();
if (developers != null) {
List<String> maintainers = new ArrayList<>();
for (Developer developer : developers) {
String email = developer.getEmail();
String name = developer.getName();
String text = Strings.defaultIfEmpty(name, "");
if (Strings.isNotBlank(email)) {
if (Strings.isNotBlank(text)) {
text = text + " <" + email + ">";
} else {
text = email;
}
}
if (Strings.isNotBlank(text)) {
maintainers.add(text);
}
}
answer.setMaintainers(maintainers);
}
}
return answer;
}
protected File getOutputDir() throws MojoExecutionException {
File helmRepoDir = getHelmRepoFolder();
if (helmRepoDir == null) {
return null;
}
if (Strings.isNullOrBlank(helmGitUrl)) {
getLog().warn("No git url so cannot clone a Helm repository. Please specify the `" + PROPERTY_HELM_GIT_URL + "` property");
} else {
cloneGitRepository(helmRepoDir, helmGitUrl);
}
if (Strings.isNullOrBlank(chartName)) {
throw new MojoExecutionException("No Chart name defined! Please specify the `" + PROPERTY_HELM_CHART_NAME + "` property");
}
return new File(helmRepoDir, chartName);
}
protected File getHelmRepoFolder() {
if (helmCloneDir == null) {
File rootProjectFolder = getRootProjectFolder();
if (rootProjectFolder != null) {
helmCloneDir = new File(rootProjectFolder, "target/helm-repo");
}
}
return helmCloneDir;
}
protected void cloneGitRepository(File outputFolder, String gitUrl) {
File gitFolder = new File(outputFolder, ".git");
if (Files.isDirectory(gitFolder)) {
// we could do a pull here but then we'd be doing a pull per maven module
// so maybe its better to just use maven clean as a way to force a clean updated pull?
} else {
CloneCommand command = Git.cloneRepository();
command = command.setURI(gitUrl).setDirectory(outputFolder).setRemote(remoteRepoName);
setupCredentials(command);
try {
Git git = command.call();
} catch (Throwable e) {
throw new RuntimeException("Failed to clone chart repo " + gitUrl + " due: ", e);
}
}
}
private void setupCredentials(CloneCommand command) {
command.setTransportConfigCallback(new TransportConfigCallback() {
@Override
public void configure(Transport transport) {
SshTransport sshTransport = (SshTransport) transport;
sshTransport.setSshSessionFactory(new JschConfigSessionFactory() {
@Override
protected void configure(OpenSshConfig.Host host, Session session) { }
@Override
protected JSch createDefaultJSch(FS fs) throws JSchException {
JSch jsch = super.createDefaultJSch(fs);
// If private key path is set, use this
if (!Strings.isNullOrBlank(privateKeyPath)) {
getLog().debug("helm: Using SSH private key from " + privateKeyPath);
jsch.removeAllIdentity();
if (!Strings.isNullOrBlank(privateKeyPassphrase)) {
jsch.addIdentity(privateKeyPath, privateKeyPassphrase);
} else {
jsch.addIdentity(privateKeyPath);
}
} else {
try {
// Try using an ssh-agent first
ConnectorFactory cf = ConnectorFactory.getDefault();
Connector con = cf.createConnector();
IdentityRepository irepo = new RemoteIdentityRepository(con);
jsch.setIdentityRepository(irepo);
getLog().debug("helm: Using ssh-agent");
} catch (AgentProxyException e) {
// No special handling
getLog().debug("helm: No ssh-agent available");
}
}
return jsch;
}
});
}
});
}
public File getKubernetesYaml() {
return kubernetesYaml;
}
}