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 2022/10/22 00:01:38 UTC

[brooklyn-library] 02/02: ansible workflow step, and tidies to ansible

This is an automated email from the ASF dual-hosted git repository.

heneveld pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/brooklyn-library.git

commit d2fda1781f1db4619cb7dc4311234707813ea203
Author: Alex Heneveld <al...@cloudsoft.io>
AuthorDate: Tue Oct 18 14:08:19 2022 +0100

    ansible workflow step, and tidies to ansible
---
 .../entity/cm/ansible/AnsibleBashCommands.java     |  33 ++---
 .../brooklyn/entity/cm/ansible/AnsibleConfig.java  |   4 +-
 .../entity/cm/ansible/AnsibleEntitySshDriver.java  |   4 +-
 .../entity/cm/ansible/AnsiblePlaybookTasks.java    |  17 ++-
 .../entity/cm/ansible/AnsibleSshWorkflowStep.java  | 139 +++++++++++++++++++++
 software/cm/ansible/src/main/resources/catalog.bom |   5 +
 6 files changed, 179 insertions(+), 23 deletions(-)

diff --git a/software/cm/ansible/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleBashCommands.java b/software/cm/ansible/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleBashCommands.java
index ffe74e44b..57ab0b7be 100644
--- a/software/cm/ansible/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleBashCommands.java
+++ b/software/cm/ansible/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleBashCommands.java
@@ -18,22 +18,27 @@
  */
 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.ifExecutableElse0;
-import static org.apache.brooklyn.util.ssh.BashCommands.sudo;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.util.core.file.BrooklynOsCommands;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
 
-import org.apache.brooklyn.util.ssh.BashCommands;
+import static org.apache.brooklyn.util.ssh.BashCommands.sudo;
 
 public class AnsibleBashCommands {
 
-    public static final String INSTALL_ANSIBLE =
-            BashCommands.chain(
-                    ifExecutableElse0("apt-add-repository",sudo("apt-add-repository -y ppa:ansible/ansible")),
-                    INSTALL_CURL,
-                    INSTALL_TAR,
-                    INSTALL_UNZIP,
-                    installExecutable("ansible"));
+    public static final String INSTALL_ANSIBLE(Entity entity) {
+        return INSTALL_ANSIBLE(BrooklynOsCommands.bash(entity, true));
+    }
+
+    static final String INSTALL_ANSIBLE(BashCommandsConfigurable cmds) {
+        return cmds.chain(
+                cmds.ifExecutableElse0("apt-add-repository",sudo("apt-add-repository -y ppa:ansible/ansible")),
+                cmds.INSTALL_CURL,
+                cmds.INSTALL_TAR,
+                cmds.INSTALL_UNZIP,
+                cmds.installExecutable("ansible"));
+    }
+
+    public static final String INSTALL_ANSIBLE = INSTALL_ANSIBLE(BashCommandsConfigurable.newInstance());
+
 }
diff --git a/software/cm/ansible/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleConfig.java b/software/cm/ansible/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleConfig.java
index a47ffebfa..a52f11971 100644
--- a/software/cm/ansible/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleConfig.java
+++ b/software/cm/ansible/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleConfig.java
@@ -34,11 +34,11 @@ public interface AnsibleConfig {
 
     @SetFromFlag("playbook")
     ConfigKey<String> ANSIBLE_PLAYBOOK = ConfigKeys.newStringConfigKey("brooklyn.ansible.playbook",
-        "Playbook to be execute by Ansible");
+        "Name to be used for a playbook to be execute by Ansible");
 
     @SetFromFlag("playbook.yaml")
     ConfigKey<String> ANSIBLE_PLAYBOOK_YAML = ConfigKeys.newStringConfigKey("brooklyn.ansible.playbookYaml",
-        "Playbook to be execute by Ansible");
+        "Playbook contents as YAML in a string, to be execute by Ansible");
 
     @SetFromFlag("playbook.url")
     ConfigKey<String> ANSIBLE_PLAYBOOK_URL = ConfigKeys.newStringConfigKey("brooklyn.ansible.playbookUrl");
diff --git a/software/cm/ansible/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleEntitySshDriver.java b/software/cm/ansible/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleEntitySshDriver.java
index 8846670dc..41948a264 100644
--- a/software/cm/ansible/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleEntitySshDriver.java
+++ b/software/cm/ansible/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleEntitySshDriver.java
@@ -58,7 +58,7 @@ public class AnsibleEntitySshDriver extends AbstractSoftwareProcessSshDriver imp
         String playbookYaml = getEntity().config().get(AnsibleConfig.ANSIBLE_PLAYBOOK_YAML);
 
         if (playbookUrl != null && playbookYaml != null) {
-            throw new IllegalArgumentException( "You can not specify both "+  AnsibleConfig.ANSIBLE_PLAYBOOK_URL.getName() +
+            throw new IllegalArgumentException( "You cannot specify both "+  AnsibleConfig.ANSIBLE_PLAYBOOK_URL.getName() +
                     " and " + AnsibleConfig.ANSIBLE_PLAYBOOK_YAML.getName() + " as arguments.");
         }
 
@@ -79,7 +79,7 @@ public class AnsibleEntitySshDriver extends AbstractSoftwareProcessSshDriver imp
         }
 
         if (Strings.isNonBlank(playbookYaml)) {
-            DynamicTasks.queue(AnsiblePlaybookTasks.buildPlaybookFile(getRunDir(), playbookName));
+            DynamicTasks.queue(AnsiblePlaybookTasks.buildPlaybookFile(getRunDir(), playbookName, playbookYaml));
         }
         DynamicTasks.queue(AnsiblePlaybookTasks.runAnsible(getRunDir(), extraVars, playbookName));
     }
diff --git a/software/cm/ansible/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsiblePlaybookTasks.java b/software/cm/ansible/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsiblePlaybookTasks.java
index fffbcea25..84d2bada9 100644
--- a/software/cm/ansible/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsiblePlaybookTasks.java
+++ b/software/cm/ansible/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsiblePlaybookTasks.java
@@ -22,6 +22,7 @@ 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.core.mgmt.BrooklynTaskTags;
 import org.apache.brooklyn.util.core.ResourceUtils;
 import org.apache.brooklyn.util.core.task.Tasks;
 import org.apache.brooklyn.util.core.task.system.ProcessTaskFactory;
@@ -41,7 +42,9 @@ public class AnsiblePlaybookTasks {
     private static final String EXTRA_VARS_FILENAME = "extra_vars.yaml";
 
     public static TaskFactory<?> installAnsible(String ansibleDirectory, boolean force) {
-        String installCmd = cdAndRun(ansibleDirectory, AnsibleBashCommands.INSTALL_ANSIBLE);
+        Entity entity = BrooklynTaskTags.getContextEntity(Tasks.current());
+        String installCmd = cdAndRun(ansibleDirectory, entity!=null ? AnsibleBashCommands.INSTALL_ANSIBLE(entity) : AnsibleBashCommands.INSTALL_ANSIBLE);
+
         if (!force) installCmd = BashCommands.alternatives("which ansible", installCmd);
         return ssh(installCmd).summary("install ansible");
     }
@@ -59,19 +62,23 @@ public class AnsiblePlaybookTasks {
                 command);
     }
 
+    @Deprecated /** @deprecated since 1.1, not used; pass the yaml manually */
     public static TaskFactory<?> buildPlaybookFile(final String ansibleDirectory, String playbook) {
         Entity entity = EffectorTasks.findEntity();
         String yaml = entity.config().get(AnsibleConfig.ANSIBLE_PLAYBOOK_YAML);
+        return buildPlaybookFile(ansibleDirectory, playbook, yaml);
+    }
 
+    public static TaskFactory<?> buildPlaybookFile(final String ansibleDirectory, String playbook, String playbookYaml) {
         return Tasks.sequential("build ansible playbook file for "+ playbook,
             SshEffectorTasks.put(Urls.mergePaths(ansibleDirectory) + "/" + playbook + ".yaml")
-                .contents(yaml).createDirectory());
+                .contents(playbookYaml).createDirectory());
     }
 
-    public static TaskFactory<?> runAnsible(final String dir, Object extraVars, String playbookName) {
-        String cmd = sudo(String.format("ansible-playbook "
+    public static SshEffectorTasks.SshEffectorTaskFactory<Integer> runAnsible(final String dir, Object extraVars, String playbookName) {
+        String cmd = sudo("ansible-playbook "
             + optionalExtraVarsParameter(extraVars)
-            + " -b %s.yaml", playbookName));
+            + String.format(" -b %s.yaml", playbookName));
 
         if (LOG.isDebugEnabled()) {
             LOG.debug("Ansible command: {}", cmd);
diff --git a/software/cm/ansible/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleSshWorkflowStep.java b/software/cm/ansible/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleSshWorkflowStep.java
new file mode 100644
index 000000000..4a6596858
--- /dev/null
+++ b/software/cm/ansible/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleSshWorkflowStep.java
@@ -0,0 +1,139 @@
+/*
+ * 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 com.fasterxml.jackson.core.JsonProcessingException;
+import org.apache.brooklyn.api.entity.drivers.DriverDependentEntity;
+import org.apache.brooklyn.api.entity.drivers.EntityDriver;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.location.Locations;
+import org.apache.brooklyn.core.resolve.jackson.BeanWithTypeUtils;
+import org.apache.brooklyn.core.resolve.jackson.BrooklynJacksonSerializationUtils;
+import org.apache.brooklyn.core.workflow.WorkflowStepDefinition;
+import org.apache.brooklyn.core.workflow.WorkflowStepInstanceExecutionContext;
+import org.apache.brooklyn.core.workflow.steps.SshWorkflowStep;
+import org.apache.brooklyn.entity.software.base.AbstractSoftwareProcessDriver;
+import org.apache.brooklyn.location.ssh.SshMachineLocation;
+import org.apache.brooklyn.util.core.task.DynamicTasks;
+import org.apache.brooklyn.util.core.task.system.ProcessTaskFactory;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.text.Strings;
+import org.apache.brooklyn.util.yaml.Yamls;
+
+import java.util.Map;
+
+public class AnsibleSshWorkflowStep extends WorkflowStepDefinition {
+
+    public static final String SHORTHAND = "${playbook_url} [ \" named \" ${playbook_name} ]";
+
+    public static final ConfigKey<String> RUN_DIR = ConfigKeys.newStringConfigKey("run_dir");
+    public static final ConfigKey<String> INSTALL_DIR = ConfigKeys.newStringConfigKey("install_dir");
+
+    public static final ConfigKey<Boolean> INSTALL_ANSIBLE = ConfigKeys.newBooleanConfigKey("install");
+
+    public static final ConfigKey<String> ANSIBLE_PLAYBOOK = ConfigKeys.newStringConfigKey("playbook_name", "Local filename to use when installing the playbook");
+    public static final ConfigKey<Object> ANSIBLE_PLAYBOOK_YAML = ConfigKeys.newConfigKey(Object.class, "playbook_yaml");
+    public static final ConfigKey<String> ANSIBLE_PLAYBOOK_URL = ConfigKeys.newStringConfigKey("playbook_url");
+    public static final ConfigKey<Object> ANSIBLE_VARS = ConfigKeys.newConfigKey(Object.class, "vars");
+
+    @Override
+    public void populateFromShorthand(String expression) {
+        populateFromShorthandTemplate(SHORTHAND, expression);
+    }
+
+    @Override
+    protected Object doTaskBody(WorkflowStepInstanceExecutionContext context) {
+        SshMachineLocation machine = Locations.findUniqueSshMachineLocation(context.getEntity().getLocations()).orThrow("No SSH location available for workflow at " + context.getEntity());
+
+        Object extraVars = context.getInput(ANSIBLE_VARS);
+        String playbookName = context.getInput(ANSIBLE_PLAYBOOK);
+
+        String playbookUrl = context.getInput(ANSIBLE_PLAYBOOK_URL);
+        Object playbookYamlO = context.getInput(ANSIBLE_PLAYBOOK_YAML);
+
+        if (playbookUrl != null && playbookYamlO != null) {
+            throw new IllegalArgumentException( "You cannot specify both "+  AnsibleConfig.ANSIBLE_PLAYBOOK_URL.getName() +
+                    " and " + AnsibleConfig.ANSIBLE_PLAYBOOK_YAML.getName() + " as arguments.");
+        }
+
+        if (playbookUrl == null && playbookYamlO == null) {
+            throw new IllegalArgumentException("You have to specify either " + AnsibleConfig.ANSIBLE_PLAYBOOK_URL.getName() +
+                    " or " + AnsibleConfig.ANSIBLE_PLAYBOOK_YAML.getName() + " as arguments.");
+        }
+
+        String playbookYaml;
+        try {
+            playbookYaml = playbookYamlO==null ? null : playbookYamlO instanceof String ? (String) playbookYamlO :
+                    BeanWithTypeUtils.newYamlMapper(context.getManagementContext(), false, null, false).writeValueAsString(playbookYamlO);
+        } catch (JsonProcessingException e) {
+            throw Exceptions.propagateAnnotated("Invalid YAML supplied for playbook", e);
+        }
+        if (playbookName==null) playbookName = "playbook-"+Strings.firstNonNull(playbookUrl, playbookYaml).hashCode();
+
+        if (!Boolean.FALSE.equals(context.getInput(INSTALL_ANSIBLE))) {
+            DynamicTasks.queue(AnsiblePlaybookTasks.installAnsible(getInstallDir(context), false));
+            DynamicTasks.queue(AnsiblePlaybookTasks.setUpHostsFile(false));
+        }
+
+        if (extraVars != null) {
+            DynamicTasks.queue(AnsiblePlaybookTasks.configureExtraVars(getRunDir(context), extraVars, false));
+        }
+
+        if (Strings.isNonBlank(playbookUrl)) {
+            DynamicTasks.queue(AnsiblePlaybookTasks.installPlaybook(getRunDir(context), playbookName, playbookUrl));
+        }
+
+        if (Strings.isNonBlank(playbookYaml)) {
+            DynamicTasks.queue(AnsiblePlaybookTasks.buildPlaybookFile(getRunDir(context), playbookName, playbookYaml));
+        }
+
+        ProcessTaskFactory<Map<?,?>> tf = SshWorkflowStep.customizeProcessTaskFactory(context, AnsiblePlaybookTasks.runAnsible(getRunDir(context), extraVars, playbookName));
+        return DynamicTasks.queue(tf).asTask().getUnchecked();
+    }
+
+    private String getRunDir(WorkflowStepInstanceExecutionContext context) {
+        String candidate = context.getInput(RUN_DIR);
+        if (candidate!=null) return candidate;
+
+        if (context.getEntity() instanceof DriverDependentEntity) {
+            EntityDriver driver = ((DriverDependentEntity) context.getEntity()).getDriver();
+            if (driver instanceof AbstractSoftwareProcessDriver) {
+                return ((AbstractSoftwareProcessDriver)driver).getRunDir();
+            }
+        }
+
+        return "./brooklyn-managed-ansible/install/";
+    }
+
+    private String getInstallDir(WorkflowStepInstanceExecutionContext context) {
+        String candidate = context.getInput(INSTALL_DIR);
+        if (candidate!=null) return candidate;
+
+        if (context.getEntity() instanceof DriverDependentEntity) {
+            EntityDriver driver = ((DriverDependentEntity) context.getEntity()).getDriver();
+            if (driver instanceof AbstractSoftwareProcessDriver) {
+                return ((AbstractSoftwareProcessDriver)driver).getInstallDir();
+            }
+        }
+
+        return "./brooklyn-managed-ansible/run-"+context.getEntity().getApplicationId()+"-"+context.getEntity().getId()+"/";
+    }
+
+}
diff --git a/software/cm/ansible/src/main/resources/catalog.bom b/software/cm/ansible/src/main/resources/catalog.bom
index 8ebb5f75a..26a834539 100644
--- a/software/cm/ansible/src/main/resources/catalog.bom
+++ b/software/cm/ansible/src/main/resources/catalog.bom
@@ -25,3 +25,8 @@ brooklyn.catalog:
         type: org.apache.brooklyn.entity.cm.ansible.AnsibleEntity
         name: AnsibleEntity
         description: Software managed by Ansible CM
+    - id: ansible-ssh
+      format: java-type-name
+      itemType: bean
+      item:
+        type: org.apache.brooklyn.entity.cm.ansible.AnsibleSshWorkflowStep