You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2016/02/19 11:07:40 UTC
[3/7] brooklyn-library git commit: Initial Ansible support
Initial Ansible support
- Ansible install for Ubuntu and CentOS
- Playbooks can be specified via URL or directly as a YAML
- Support for custom start/stop commands
- Support for command to support setting a useful value for the
service.isUp sensor
- Generic check for a running service based on the provided serviceName
Project: http://git-wip-us.apache.org/repos/asf/brooklyn-library/repo
Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-library/commit/373c598a
Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-library/tree/373c598a
Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-library/diff/373c598a
Branch: refs/heads/master
Commit: 373c598afe05a81adbc317088b166b734a04472a
Parents: 9fdf1db
Author: Yavor Yanchev <ya...@yanchev.com>
Authored: Fri Feb 5 10:05:41 2016 +0200
Committer: Yavor Yanchev <ya...@yanchev.com>
Committed: Wed Feb 17 11:43:03 2016 +0200
----------------------------------------------------------------------
software/cm/pom.xml | 51 ++++++
.../entity/cm/ansible/AnsibleBashCommands.java | 38 ++++
.../entity/cm/ansible/AnsibleConfig.java | 62 +++++++
.../entity/cm/ansible/AnsibleEntity.java | 26 +++
.../entity/cm/ansible/AnsibleEntityImpl.java | 41 +++++
.../ansible/AnsibleLifecycleEffectorTasks.java | 180 +++++++++++++++++++
.../entity/cm/ansible/AnsiblePlaybookTasks.java | 72 ++++++++
7 files changed, 470 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/brooklyn-library/blob/373c598a/software/cm/pom.xml
----------------------------------------------------------------------
diff --git a/software/cm/pom.xml b/software/cm/pom.xml
index 90af5b4..ea17774 100644
--- a/software/cm/pom.xml
+++ b/software/cm/pom.xml
@@ -71,6 +71,57 @@
</mailingLists>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.brooklyn</groupId>
+ <artifactId>brooklyn-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.brooklyn</groupId>
+ <artifactId>brooklyn-software-base</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.brooklyn</groupId>
+ <artifactId>brooklyn-software-database</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.brooklyn</groupId>
+ <artifactId>brooklyn-camp</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.brooklyn</groupId>
+ <artifactId>brooklyn-test-support</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.brooklyn</groupId>
+ <artifactId>brooklyn-core</artifactId>
+ <version>${project.version}</version>
+ <classifier>tests</classifier>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.brooklyn</groupId>
+ <artifactId>brooklyn-software-base</artifactId>
+ <version>${project.version}</version>
+ <classifier>tests</classifier>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <version>${assertj.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
<modules>
<module>salt</module>
http://git-wip-us.apache.org/repos/asf/brooklyn-library/blob/373c598a/software/cm/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleBashCommands.java
----------------------------------------------------------------------
diff --git a/software/cm/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleBashCommands.java b/software/cm/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleBashCommands.java
new file mode 100644
index 0000000..e22ee34
--- /dev/null
+++ b/software/cm/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleBashCommands.java
@@ -0,0 +1,38 @@
+/*
+ * 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.brooklyn.entity.cm.ansible;
+
+import static org.apache.brooklyn.util.ssh.BashCommands.INSTALL_CURL;
+import static org.apache.brooklyn.util.ssh.BashCommands.INSTALL_TAR;
+import static org.apache.brooklyn.util.ssh.BashCommands.INSTALL_UNZIP;
+import static org.apache.brooklyn.util.ssh.BashCommands.installExecutable;
+import static org.apache.brooklyn.util.ssh.BashCommands.sudo;
+
+import org.apache.brooklyn.util.ssh.BashCommands;
+
+public class AnsibleBashCommands {
+
+ public static final String INSTALL_ANSIBLE =
+ BashCommands.chain(
+ sudo("apt-add-repository -y ppa:ansible/ansible"),
+ INSTALL_CURL,
+ INSTALL_TAR,
+ INSTALL_UNZIP,
+ installExecutable("ansible"));
+}
http://git-wip-us.apache.org/repos/asf/brooklyn-library/blob/373c598a/software/cm/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleConfig.java
----------------------------------------------------------------------
diff --git a/software/cm/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleConfig.java b/software/cm/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleConfig.java
new file mode 100644
index 0000000..b201294
--- /dev/null
+++ b/software/cm/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleConfig.java
@@ -0,0 +1,62 @@
+/*
+ * 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.brooklyn.entity.cm.ansible;
+
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.util.core.flags.SetFromFlag;
+
+import com.google.common.annotations.Beta;
+
+/** {@link ConfigKey}s used to configure Ansible */
+@Beta
+public interface AnsibleConfig {
+
+ public static enum AnsibleModes {
+ PLAYBOOK
+ };
+
+ @SetFromFlag("playbook")
+ public static final ConfigKey<String> ANSIBLE_PLAYBOOK = ConfigKeys.newStringConfigKey("brooklyn.ansible.playbook",
+ "Playbook to be execute by Ansible");
+
+ @SetFromFlag("playbook.yaml")
+ public static final ConfigKey<String> ANSIBLE_PLAYBOOK_YAML = ConfigKeys.newStringConfigKey("brooklyn.ansible.playbookYaml",
+ "Playbook to be execute by Ansible");
+
+ @SetFromFlag("playbook.url")
+ public static final ConfigKey<String> ANSIBLE_PLAYBOOK_URL = ConfigKeys.newStringConfigKey("brooklyn.ansible.playbookUrl");
+
+ @SetFromFlag("ansible.service.start")
+ public static final ConfigKey<String> ANSIBLE_SERVICE_START = ConfigKeys.newStringConfigKey("ansible.service.start",
+ "Default start command used with conjunction with the Ansible's service module",
+ "sudo ansible localhost -c local -m service -a \"name=%s state=started\"");
+
+ @SetFromFlag("ansible.service.stop")
+ public static final ConfigKey<String> ANSIBLE_SERVICE_STOP = ConfigKeys.newStringConfigKey("ansible.service.stop",
+ "Default stop command used with conjunction with the Ansible's service module",
+ "sudo ansible localhost -c local -m service -a \"name=%s state=stopped\"");
+
+ @SetFromFlag("ansible.service.checkPort")
+ public static final ConfigKey<Integer> ANSIBLE_SERVICE_CHECK_PORT = ConfigKeys.newIntegerConfigKey("ansible.service.check.port");
+
+ @SetFromFlag("service.name")
+ public static final ConfigKey<String> SERVICE_NAME = ConfigKeys.newStringConfigKey("brooklyn.ansible.serviceName",
+ "Name of OS service this will run as, for use in checking running and stopping");
+}
http://git-wip-us.apache.org/repos/asf/brooklyn-library/blob/373c598a/software/cm/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleEntity.java
----------------------------------------------------------------------
diff --git a/software/cm/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleEntity.java b/software/cm/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleEntity.java
new file mode 100644
index 0000000..0124854
--- /dev/null
+++ b/software/cm/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleEntity.java
@@ -0,0 +1,26 @@
+/*
+ * 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.brooklyn.entity.cm.ansible;
+
+import org.apache.brooklyn.api.entity.ImplementedBy;
+import org.apache.brooklyn.entity.software.base.SoftwareProcess;
+
+@ImplementedBy(AnsibleEntityImpl.class)
+public interface AnsibleEntity extends SoftwareProcess, AnsibleConfig {
+}
http://git-wip-us.apache.org/repos/asf/brooklyn-library/blob/373c598a/software/cm/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleEntityImpl.java
----------------------------------------------------------------------
diff --git a/software/cm/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleEntityImpl.java b/software/cm/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleEntityImpl.java
new file mode 100644
index 0000000..bd797e6
--- /dev/null
+++ b/software/cm/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleEntityImpl.java
@@ -0,0 +1,41 @@
+/*
+ * 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.brooklyn.entity.cm.ansible;
+
+import org.apache.brooklyn.entity.stock.EffectorStartableImpl;
+import org.apache.brooklyn.util.text.Strings;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+public class AnsibleEntityImpl extends EffectorStartableImpl implements AnsibleEntity {
+
+ public void init() {
+ checkNotNull(getConfig(SERVICE_NAME), "service name is missing. it has to be provided by the user");
+ String playbookName = getConfig(ANSIBLE_PLAYBOOK);
+ if (!Strings.isBlank(playbookName)) setDefaultDisplayName(playbookName + " (ansible)");
+
+ super.init();
+ new AnsibleLifecycleEffectorTasks().attachLifecycleEffectors(this);
+ }
+
+ @Override
+ public void populateServiceNotUpDiagnostics() {
+ // TODO no-op currently; should check ssh'able etc
+ }
+}
http://git-wip-us.apache.org/repos/asf/brooklyn-library/blob/373c598a/software/cm/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleLifecycleEffectorTasks.java
----------------------------------------------------------------------
diff --git a/software/cm/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleLifecycleEffectorTasks.java b/software/cm/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleLifecycleEffectorTasks.java
new file mode 100644
index 0000000..0262af3
--- /dev/null
+++ b/software/cm/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleLifecycleEffectorTasks.java
@@ -0,0 +1,180 @@
+/*
+ * 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.brooklyn.entity.cm.ansible;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.location.MachineLocation;
+import org.apache.brooklyn.core.effector.ssh.SshEffectorTasks;
+import org.apache.brooklyn.core.entity.Attributes;
+import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
+import org.apache.brooklyn.core.entity.trait.Startable;
+import org.apache.brooklyn.core.location.Locations;
+import org.apache.brooklyn.core.location.Machines;
+import org.apache.brooklyn.entity.software.base.SoftwareProcess;
+import org.apache.brooklyn.entity.software.base.lifecycle.MachineLifecycleEffectorTasks;
+import org.apache.brooklyn.feed.ssh.SshFeed;
+import org.apache.brooklyn.feed.ssh.SshPollConfig;
+import org.apache.brooklyn.location.ssh.SshMachineLocation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.brooklyn.util.core.task.DynamicTasks;
+import org.apache.brooklyn.util.guava.Maybe;
+import org.apache.brooklyn.util.net.Urls;
+import org.apache.brooklyn.util.text.Strings;
+import org.apache.brooklyn.util.time.Duration;
+import org.apache.brooklyn.util.time.Time;
+
+import com.google.common.base.Supplier;
+
+public class AnsibleLifecycleEffectorTasks extends MachineLifecycleEffectorTasks implements AnsibleConfig {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AnsibleLifecycleEffectorTasks.class);
+
+ protected String serviceName;
+ protected SshFeed serviceSshFeed;
+
+ public AnsibleLifecycleEffectorTasks() {
+ }
+
+ public String getServiceName() {
+ if (serviceName!=null) return serviceName;
+ return serviceName = entity().config().get(AnsibleConfig.SERVICE_NAME);
+ }
+
+ @Override
+ public void attachLifecycleEffectors(Entity entity) {
+ if (getServiceName()==null && getClass().equals(AnsibleLifecycleEffectorTasks.class)) {
+ // warn on incorrect usage
+ LOG.warn("Uses of "+getClass()+" must define a PID file or a service name (or subclass and override {start,stop} methods as per javadoc) " +
+ "in order for check-running and stop to work");
+ }
+ super.attachLifecycleEffectors(entity);
+ }
+
+ @Override
+ protected String startProcessesAtMachine(Supplier<MachineLocation> machineS) {
+ startWithAnsibleAsync();
+
+ return "ansible start tasks submitted";
+ }
+
+ protected String getPlaybookName() {
+ return entity().config().get(ANSIBLE_PLAYBOOK);
+ }
+
+ protected void startWithAnsibleAsync() {
+ String baseDir = MachineLifecycleEffectorTasks.resolveOnBoxDir(entity(), Machines.findUniqueMachineLocation(entity().getLocations(), SshMachineLocation.class).get());
+ String installDir = Urls.mergePaths(baseDir, "installs/ansible");
+
+ String playbookUrl = entity().config().get(ANSIBLE_PLAYBOOK_URL);
+ String playbookYaml = entity().config().get(ANSIBLE_PLAYBOOK_YAML);
+
+ if (Strings.isNonBlank(playbookUrl) && Strings.isNonBlank(playbookYaml)) {
+ throw new IllegalArgumentException("You can specify " + AnsibleConfig.ANSIBLE_PLAYBOOK_URL.getName() + " or " + AnsibleConfig.ANSIBLE_PLAYBOOK_YAML.getName() + " but not both of them!");
+ }
+
+ DynamicTasks.queue(AnsiblePlaybookTasks.installAnsible(installDir, false));
+
+ String runDir = Urls.mergePaths(baseDir, "apps/"+entity().getApplicationId()+"/ansible/playbooks/"+entity().getEntityType().getSimpleName()+"_"+entity().getId());
+
+ if (Strings.isNonBlank(playbookUrl)) {
+ DynamicTasks.queue(AnsiblePlaybookTasks.installPlaybook(runDir, getPlaybookName(), playbookUrl));
+ }
+
+ if (Strings.isNonBlank(playbookYaml)) {
+ DynamicTasks.queue(AnsiblePlaybookTasks.buildPlaybookFile(runDir, getPlaybookName()));
+ }
+ DynamicTasks.queue(AnsiblePlaybookTasks.runAnsible(runDir, getPlaybookName()));
+ }
+
+ protected void postStartCustom() {
+ boolean result = false;
+ result |= tryCheckStartService();
+
+ if (!result) {
+ LOG.warn("No way to check whether "+entity()+" is running; assuming yes");
+ }
+ entity().sensors().set(SoftwareProcess.SERVICE_UP, true);
+
+ Maybe<SshMachineLocation> machine = Locations.findUniqueSshMachineLocation(entity().getLocations());
+
+ if (machine.isPresent()) {
+
+ String serviceName = String.format("[%s]%s", entity().config().get(SERVICE_NAME).substring(0, 1),
+ entity().config().get(SERVICE_NAME).substring(1));
+ String checkCmd = String.format("ps -ef | grep %s", serviceName);
+
+ Integer serviceCheckPort = entity().config().get(ANSIBLE_SERVICE_CHECK_PORT);
+
+ if (serviceCheckPort != null) {
+ checkCmd = String.format("sudo ansible localhost -c local -m wait_for -a \"host=0.0.0.0 port=%d\"", serviceCheckPort);
+ }
+ serviceSshFeed = SshFeed.builder()
+ .entity(entity())
+ .period(Duration.ONE_MINUTE)
+ .machine(machine.get())
+ .poll(new SshPollConfig<Boolean>(Startable.SERVICE_UP)
+ .command(checkCmd)
+ .setOnSuccess(true)
+ .setOnFailureOrException(false))
+ .build();
+
+ entity().feeds().addFeed(serviceSshFeed);
+ } else {
+ LOG.warn("Location(s) {} not an ssh-machine location, so not polling for status; setting serviceUp immediately", entity().getLocations());
+ }
+ }
+
+ protected boolean tryCheckStartService() {
+ if (getServiceName()==null) return false;
+
+ // if it's still up after 5s assume we are good (default behaviour)
+ Time.sleep(Duration.FIVE_SECONDS);
+ if (!((Integer)0).equals(DynamicTasks.queue(SshEffectorTasks.ssh(String.format(entity().config().get(AnsibleConfig.ANSIBLE_SERVICE_START), getServiceName()))).get())) {
+ throw new IllegalStateException("The process for "+entity()+" appears not to be running (service "+getServiceName()+")");
+ }
+
+ return true;
+ }
+
+ @Override
+ protected String stopProcessesAtMachine() {
+ boolean result = false;
+ result |= tryStopService();
+ if (!result) {
+ throw new IllegalStateException("The process for "+entity()+" could not be stopped (no impl!)");
+ }
+ return "stopped";
+ }
+
+ @Override
+ protected StopMachineDetails<Integer> stopAnyProvisionedMachines() {
+ return super.stopAnyProvisionedMachines();
+ }
+
+ protected boolean tryStopService() {
+ if (getServiceName()==null) return false;
+ int result = DynamicTasks.queue(SshEffectorTasks.ssh(String.format(entity().config().get(AnsibleConfig.ANSIBLE_SERVICE_STOP), getServiceName()))).get();
+ if (0 == result) return true;
+ if (entity().getAttribute(Attributes.SERVICE_STATE_ACTUAL) != Lifecycle.RUNNING)
+ return true;
+
+ throw new IllegalStateException("The process for "+entity()+" appears could not be stopped (exit code "+result+" to service stop)");
+ }
+}
http://git-wip-us.apache.org/repos/asf/brooklyn-library/blob/373c598a/software/cm/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsiblePlaybookTasks.java
----------------------------------------------------------------------
diff --git a/software/cm/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsiblePlaybookTasks.java b/software/cm/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsiblePlaybookTasks.java
new file mode 100644
index 0000000..1579e2d
--- /dev/null
+++ b/software/cm/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsiblePlaybookTasks.java
@@ -0,0 +1,72 @@
+/*
+ * 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.brooklyn.entity.cm.ansible;
+
+import org.apache.brooklyn.api.entity.Entity;
+
+import org.apache.brooklyn.api.mgmt.TaskFactory;
+import org.apache.brooklyn.core.effector.EffectorTasks;
+import org.apache.brooklyn.core.effector.ssh.SshEffectorTasks;
+import org.apache.brooklyn.util.core.ResourceUtils;
+import org.apache.brooklyn.util.core.task.Tasks;
+import org.apache.brooklyn.util.net.Urls;
+import org.apache.brooklyn.util.ssh.BashCommands;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class AnsiblePlaybookTasks {
+ private static final Logger LOG = LoggerFactory.getLogger(AnsiblePlaybookTasks.class);
+
+ public static TaskFactory<?> installAnsible(String ansibleDirectory, boolean force) {
+ String installCmd = cdAndRun(ansibleDirectory, AnsibleBashCommands.INSTALL_ANSIBLE);
+ if (!force) installCmd = BashCommands.alternatives("which ansible", installCmd);
+ return SshEffectorTasks.ssh(installCmd).summary("install ansible");
+ }
+
+ public static TaskFactory<?> installPlaybook(final String ansibleDirectory, final String playbookName, final String playbookUrl) {
+ return Tasks.sequential("build ansible playbook file for "+playbookName,
+ SshEffectorTasks.put(ansibleDirectory + "/" + playbookName + ".yaml").contents(ResourceUtils.create().getResourceFromUrl(playbookUrl)).createDirectory());
+ }
+
+ protected static String cdAndRun(String targetDirectory, String command) {
+ return BashCommands.chain("mkdir -p "+targetDirectory,
+ "cd "+targetDirectory,
+ command);
+ }
+
+ public static TaskFactory<?> buildPlaybookFile(final String ansibleDirectory, String playbook) {
+ Entity entity = EffectorTasks.findEntity();
+ String yaml = entity.config().get(AnsibleConfig.ANSIBLE_PLAYBOOK_YAML);
+
+ return Tasks.sequential("build ansible playbook file for "+ playbook,
+ SshEffectorTasks.put(Urls.mergePaths(ansibleDirectory) + "/" + playbook + ".yaml").contents(yaml).createDirectory());
+ }
+
+ public static TaskFactory<?> runAnsible(final String ansibleDirectory, String playbookName) {
+ String cmd = String.format("sudo ansible-playbook -i \"localhost,\" -c local -s %s.yaml", playbookName);
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Ansible command: {}", cmd);
+ }
+
+ return SshEffectorTasks.ssh(cdAndRun(ansibleDirectory, cmd)).
+ summary("run ansible playbook for " + playbookName).requiringExitCodeZero();
+ }
+
+}