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 <[email protected]>
Authored: Fri Feb 5 10:05:41 2016 +0200
Committer: Yavor Yanchev <[email protected]>
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();
+    }
+    
+}

Reply via email to