/* * Copyright 2015 Jean-Christophe Sirot <sirot@chelonix.com> * * Licensed 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 org.jenkinsci.plugins.ansible; import java.io.IOException; import java.util.HashMap; import java.util.Map; import com.cloudbees.jenkins.plugins.sshcredentials.SSHUserPrivateKey; import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials; import com.cloudbees.plugins.credentials.common.UsernamePasswordCredentials; import hudson.EnvVars; import hudson.FilePath; import hudson.model.AbstractBuild; import hudson.model.BuildListener; import hudson.model.Run; import hudson.model.TaskListener; import hudson.util.ArgumentListBuilder; import hudson.util.Secret; import org.apache.commons.lang.StringUtils; /** * Ansible command invocation */ abstract class AbstractAnsibleInvocation<T extends AbstractAnsibleInvocation<T>> { protected final EnvVars envVars; protected final TaskListener listener; protected final Run<?, ?> build; protected final Map<String, String> environment = new HashMap<String, String>(); protected String exe; protected int forks; protected boolean sudo; protected String sudoUser; protected StandardUsernameCredentials credentials; protected String additionalParameters; private FilePath key = null; private FilePath script = null; private Inventory inventory; private boolean copyCredentialsInWorkspace = false; private final FilePath ws; protected AbstractAnsibleInvocation(String exe, Run<?, ?> build, FilePath ws, TaskListener listener) throws IOException, InterruptedException, AnsibleInvocationException { this.build = build; this.ws = ws; this.envVars = build.getEnvironment(listener); this.listener = listener; this.exe = exe; if (exe == null) { throw new AnsibleInvocationException("Ansible executable not found, check your installation."); } } protected ArgumentListBuilder appendExecutable(ArgumentListBuilder args) { args.add(exe); return args; } public T setInventory(Inventory inventory) { this.inventory = inventory; return (T) this; } protected ArgumentListBuilder appendInventory(ArgumentListBuilder args) throws IOException, InterruptedException, AnsibleInvocationException { if (inventory == null) { // throw new AnsibleInvocationException( // "The inventory of hosts and groups is not defined. Check the job configuration."); return args; } inventory.addArgument(args, ws, envVars, listener); return args; } public T setForks(int forks) { this.forks = forks; return (T) this; } public ArgumentListBuilder appendForks(ArgumentListBuilder args) { args.add("-f").add(forks); return args; } public T setAdditionalParameters(String additionalParameters) { this.additionalParameters = additionalParameters; return (T) this; } public ArgumentListBuilder appendAdditionalParameters(ArgumentListBuilder args) { args.addTokenized(envVars.expand(additionalParameters)); return args; } public T setSudo(boolean sudo, String sudoUser) { this.sudo = sudo; this.sudoUser = sudoUser; return (T) this; } protected ArgumentListBuilder appendSudo(ArgumentListBuilder args) { if (sudo) { args.add("-s"); if (StringUtils.isNotBlank(sudoUser)) { args.add("-U").add(envVars.expand(sudoUser)); } } return args; } public T setCredentials(StandardUsernameCredentials credentials) { this.credentials = credentials; return (T) this; } public T setCredentials(StandardUsernameCredentials credentials, boolean copyCredentialsInWorkspace) { this.copyCredentialsInWorkspace = copyCredentialsInWorkspace; return setCredentials(credentials); } protected ArgumentListBuilder prependPasswordCredentials(ArgumentListBuilder args) { if (credentials instanceof UsernamePasswordCredentials) { UsernamePasswordCredentials passwordCredentials = (UsernamePasswordCredentials)credentials; args.add("sshpass").addMasked("-p" + Secret.toString(passwordCredentials.getPassword())); } return args; } protected ArgumentListBuilder appendCredentials(ArgumentListBuilder args) throws IOException, InterruptedException { if (credentials instanceof SSHUserPrivateKey) { SSHUserPrivateKey privateKeyCredentials = (SSHUserPrivateKey)credentials; key = Utils.createSshKeyFile(key, ws, privateKeyCredentials, copyCredentialsInWorkspace); args.add("--private-key").add(key); args.add("-u").add(privateKeyCredentials.getUsername()); if (privateKeyCredentials.getPassphrase() != null) { script = Utils.createSshAskPassFile(script, ws, privateKeyCredentials, copyCredentialsInWorkspace); environment.put("SSH_ASKPASS", script.getRemote()); // inspired from https://github.com/jenkinsci/git-client-plugin/pull/168 // but does not work with MacOSX if (! environment.containsKey("DISPLAY")) { environment.put("DISPLAY", ":123.456"); } } } else if (credentials instanceof UsernamePasswordCredentials) { args.add("-u").add(credentials.getUsername()); args.add("-k"); } return args; } public T setUnbufferedOutput(boolean unbufferedOutput) { if (unbufferedOutput) { environment.put("PYTHONUNBUFFERED", "1"); } return (T) this; } public T setColorizedOutput(boolean colorizedOutput) { if (colorizedOutput) { environment.put("ANSIBLE_FORCE_COLOR", "true"); } return (T) this; } public T setHostKeyCheck(boolean hostKeyChecking) { if (! hostKeyChecking) { environment.put("ANSIBLE_HOST_KEY_CHECKING", "False"); } return (T) this; } abstract protected ArgumentListBuilder buildCommandLine() throws InterruptedException, AnsibleInvocationException, IOException; public boolean execute(CLIRunner runner) throws IOException, InterruptedException, AnsibleInvocationException { try { return runner.execute(buildCommandLine(), environment); } finally { if (inventory != null) { inventory.tearDown(listener); } Utils.deleteTempFile(key, listener); Utils.deleteTempFile(script, listener); } } }