/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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 org.apache.provisionr.core.activities;
import org.apache.provisionr.api.access.AdminAccess;
import org.apache.provisionr.api.pool.Machine;
import org.apache.provisionr.api.pool.Pool;
import org.apache.provisionr.core.CoreProcessVariables;
import org.apache.provisionr.core.Ssh;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.collect.ImmutableMap;
import java.util.Map;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.connection.channel.direct.Session;
import org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.delegate.JavaDelegate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Abstract activity useful for implementing activities that execute
* puppet scripts on pool machines
*/
public abstract class PuppetActivity implements JavaDelegate {
private static final Logger LOG = LoggerFactory.getLogger(PuppetActivity.class);
/**
* Puppet apply had no failures during the current transaction
* <p/>
* --detailed-exitcodes Provide transaction information via exit codes. If this is
* enabled, an exit code of '2' means there were changes, an exit code of '4' means
* there were failures during the transaction, and an exit code of '6' means there
* were both changes and failures.
*
* @see <a href="http://docs.puppetlabs.com/man/apply.html" />
*/
public static final int PUPPET_FINISHED_WITH_NO_FAILURES = 2;
private final String remoteFileName;
public PuppetActivity(String remoteFileName) {
this.remoteFileName = checkNotNull(remoteFileName);
}
/**
* This method creates a Puppet script for remote execution
*/
public abstract String createPuppetScript(Pool pool, Machine machine) throws Exception;
/**
* Override this method to change the credentials used for SSH access
*/
public AdminAccess overrideAdminAccess(Pool pool) {
return pool.getAdminAccess();
}
/**
* Map of additional files to create on the remote machine. Contains pairs of (remotePath, content)
*/
public Map<String, String> createAdditionalFiles(Pool pool, Machine machine) throws Exception {
return ImmutableMap.of();
}
@Override
public void execute(DelegateExecution execution) throws Exception {
Pool pool = (Pool) execution.getVariable(CoreProcessVariables.POOL);
checkNotNull(pool, "Please add the pool description as a process " +
"variable with the name '%s'.", CoreProcessVariables.POOL);
Machine machine = (Machine) execution.getVariable("machine");
checkNotNull(machine, "expecting a process variable named 'machine'");
LOG.info(">> Connecting to machine {} to run puppet script", machine);
SSHClient client = Ssh.newClient(machine, overrideAdminAccess(pool));
try {
for (Map.Entry<String, String> entry : createAdditionalFiles(pool, machine).entrySet()) {
Ssh.createFile(client, /* content = */ entry.getValue(), 0600, /* destination= */ entry.getKey());
}
final String destination = "/tmp/" + remoteFileName + ".pp";
Ssh.createFile(client, createPuppetScript(pool, machine), 0600, destination);
Session session = client.startSession();
try {
session.allocateDefaultPTY();
// TODO: extract this loop outside of this activity (probably using a business process error)
final String runScriptWithWaitCommand = "while ! which puppet &> /dev/null ; " +
"do echo 'Puppet command not found. Waiting for userdata.sh script to finish (10s)' " +
"&& sleep 10; " +
"done " +
"&& sudo puppet apply --detailed-exitcodes --debug --verbose " + destination;
Session.Command command = session.exec(runScriptWithWaitCommand);
Ssh.logCommandOutput(LOG, machine.getExternalId(), command);
command.join();
final Integer exitStatus = command.getExitStatus();
if (exitStatus != PUPPET_FINISHED_WITH_NO_FAILURES && exitStatus != 0) {
throw new RuntimeException(String.format("Failed to execute puppet. " +
"Exit code: %d. Exit message: %s", exitStatus, command.getExitErrorMessage()));
} else {
LOG.info("<< Command completed successfully with exit code 0");
}
} finally {
session.close();
}
} finally {
client.close();
}
}
}