Package: ansible-taskrunner
Table of Contentsgenerated withDocTocOverviewTL;DRUse case and exampleGivenThe ChallengeInvestigationAssessmentProposed SolutionTechnical DetailsCreating a task manifest fileAdd the hosts blockAdd the vars blockPopulate the vars block - defaultsPopulate the vars block - define global optionsPopulate the vars block - define sub-commandsPopulate the vars block - cli options - mapped variablesPopulate the vars block - help/messagePopulate the vars block - embedded shell functionsMore about embedded shell functionsBash example:Python example:Ruby example:Populate the vars block - dynamic inventory expressionPopulate the vars block - inventory fileAdd tasksUsage ExamplesInstallationMore ExamplesAppendixThe Options SeparatorBastion ModeSpecial Variablesansible_playbook_commandcli_provideransible_extra_optionstasks_filecommandMutually Exclusive OptionsSimple TemplatingSingle-Executable ReleasesUnit TestingTODO - Add more tests!License and CreditsOverviewansible-taskrunneris a cli app that is essentially an ansible wrapper.It reads an ansible playbook file as its input, which serves as atask manifest.If no task manifest is specified, the app will search for 'Taskfile.yaml' in the current working directory.The inspiration for the tool comes from the gnu make command, which operates in similar fashion, i.e.A Makefile defines available build stepsThe make command consumes the Makefile at runtime and exposes these steps as command-line optionsIf you are running this tool from Windows, please read the section onBastion ModeTL;DREver wanted to add custom switches to theansible-playbookcommand? Something like this:ansible-playbook -i myinventory.txt -d dbhost1 -w webhost1 -t value1 myplaybook.yamlWell, you can through the use of an ansible-playbook wrapperThat's wheretaskscomes in:tasks -s -b bar -f foo1translates to:ansible-playbook -i /tmp/ansible-inventory16xdkrjd.tmp.ini \ -e "{'some_foo_variable':'foo1'}" -e "{'some_bar_variable':'bar'}" -e "{'playbook_targets':'local'}" Taskfile.yamlJump down to theusage examplesto see this in actionReview theinstallationinstructions if you want to test-drive itRead on if you want to dig deeper into the toolUse case and exampleGivenAn enterprise-grade application named contoso-appMultiple teams:DevelopmentEngineeringDBAOperationsQAAnsible is the primary means of invoking business and operational processes across the numerous environmentsThe ChallengeYou must ensure all teams adopt a standardized approach to running ansible workloadsInvestigationUpon investigating the current approach, you observe the following:Users tend to create wrapper scripts that call the ansible-playbook commandThese scripts don't follow any naming convention, as you've noted:run.shstart.shplaybook.shThese shell scripts have common attributes:Dynamically populate ansible-playbook variables via the --extra-vars optionDynamically creating ansible inventoriesPerforming pre/post-flight tasksProviding a command-line interfaceAssessmentAdvantages to the above approach:Quick-n-dirty, anyone can get started relatively quickly with writing ansible automationDisadvantages:Lack of standards:Leads to difficulty in collaboration and code refactoringDecreased re-usability of codebaseThis design encourages standalone playbooksMakes it more difficult to package actions as rolesDuplicate efforts across codebaseProposed SolutionEmploy a pre-execution script that operates at a layer above theansible-playbookcommand:Accomplishes the same as the above, but in more uniform mannerSupport for custom command-line parameters/flagsDynamic inventory expressionEmbedded shell functionsAdvantages to this approach:Easier to manageIf you know YAML and Ansible, you can get started relatively quickly with writing ansible automationSingle executable (/usr/local/bin/tasks)Disadvantages:Target ansible controller needs to have thetaskscommand installedBack To TopTechnical DetailsAs stated in theoverview, this tool functions much like themakecommand in that
it accepts an input file that essentially extends its cli options.We create a specially formatted ansible-playbook that serves as a task manifest file (by default, Taskfile.yaml).This task manifest file:Extends thetaskscommandIs a valid ansible playbook (Taskfile.yaml), and can thus be launched with theansible-playbookcommandVariables available to the pre-execution phase are also available to the ansible execution phaseCreating a task manifest fileIn the following sections, we'll be building a sample manifest/playbook.Start by opening up your favorite text/IDE/editor and creating
a newtask manifest filenamedTaskfile.yaml.Add the hosts blockAdd hosts, gather_facts, etc:Click to ExpandTaskfile.yaml### The hosts block
- hosts: myhosts
  gather_facts: true
  become: trueAdd the vars blockRemember, the task runner will ultimately be calling theansible-playbookcommand against
this very same file,so it must be a valid ansible playbook.Let's add the 'vars' block, which allows us to populate some default values:Click to ExpandTaskfile.yaml### The hosts block
- hosts: myhosts
  gather_facts: true
  become: true
### The vars block
  vars:Populate the vars block - defaultsLet's add some default variables to the playbook:Click to ExpandTaskfile.yaml### The hosts block
- hosts: myhosts
  gather_facts: true
  become: true
  ### The vars block  
  vars:
    var1: value1
    var2: value2
    var3: value3
    var4: |-
      This is a multi-line value
      of type string
    var5:
      - listvalue1
      - listvalue2
      - listvalue3
      - listvalue4
    var6:
      some_key:
        some_child_key: dictvalue1
    var7: $(echo some_value)
    var8: 8000
    dbhosts:
      - dbhost1
      - dbhost2
      - dbhost3
    webhosts:
      - webhost1
      - webhost2
      - webhost3As you can see, we've defined a number of variables holding different values.The rules for evaluation of these are as follows:Variable                                     | Ansible Evaluation      | Shell Function Evaluation
-------------------------------------------- | ----------------------- | -----------------------
str_var: value1                              | String                  | String
bool_var: True                               | Boolean                 | String
num_var: 3                                   | Integer                 | Integer
multiline_var: |                             | Multiline String        | String (heredoc)
  This is a multi-line value                 |                         |
  of type string                             |                         |
list_var:                                    | List Object             | String (heredoc)
  - item1                                    |                         |
  - item2                                    |                         |
dict_var:                                    | Dictionary Object       | JSON String (heredoc)
  key1: somevalue1                           |                         |
  key2: somevalue2                           |                         |
shell_var: $(grep somestring /some/file.txt) | String                  | Depends on outputAdditionally, arguments supplied from the command-line conformto the type specified in the options definition, with "string" being the default type.Back To TopPopulate the vars block - define global optionsGlobal options are available to all sub-commands.These are defined under thevars.globals.optionskey.Let's add a simple example:Click to ExpandTaskfile.yaml### The hosts block
- hosts: myhosts
  gather_facts: true
  become: true
  ### The vars block  
  vars:
    var1: value1
    var2: value2
    var3: value3
    var4: |-
      This is a multi-line value
      of type string
    var5:
      - listvalue1
      - listvalue2
      - listvalue3
      - listvalue4
    var6:
      some_key:
        some_child_key: dictvalue1
    var7: $(echo some_value)
    var8: 8000
    dbhosts:
      - dbhost1
      - dbhost2
      - dbhost3
    webhosts:
      - webhost1
      - webhost2
      - webhost3
    globals:
      options:
          my_global_option:
            help: "This is my global option"
            short: -g
            long: --global
            var: some_global_variablePopulate the vars block - define sub-commandsNext, we define the available sub-commands and their options.Let's add a sub-command namedrunalong with its command-line options:Click to ExpandTaskfile.yaml### The hosts block
- hosts: myhosts
  gather_facts: true
  become: true
  ### The vars block  
  vars:
    var1: value1
    var2: value2
    var3: value3
    var4: |-
      This is a multi-line value
      of type string
    var5:
      - listvalue1
      - listvalue2
      - listvalue3
      - listvalue4
    var6:
      some_key:
        some_child_key: dictvalue1
    var7: $(echo some_value)
    var8: 8000
    dbhosts:
      - dbhost1
      - dbhost2
      - dbhost3
    webhosts:
      - webhost1
      - webhost2
      - webhost3
    ### Global Options Block
    globals:
      options:
          my_global_option:
            help: "This is my global option"
            short: -g
            long: --global
            var: some_global_variable    
    ### The commands block
    commands:
      run:
        options:
          foo:
            help: "This is some foo option"
            short: -f
            long: --foo
            type: choice
            var: some_foo_variable
            required: True
            not_required_if: 
              - some_bar_variable
            options:
              - foo1
              - foo2
          bar:
            help: "This is some bar option"
            short: -b
            long: --bar 
            type: str
            var: some_bar_variable
            required: False
            required_if: 
              - hello
              - some_baz_variable
          baz:
            help: "This is some baz option"
            short: -z
            long: --baz
            type: str
            var: some_baz_variable
            required: False
            mutually_exclusive_with: 
              - some_bar_variable
              - some_foo_variable
          envvar:
            help: "The value for this argument can be derived from an Environmental Variable"
            short: -E
            long: --env-var
            type: str
            var: env_var
            env_var: SOME_ENVIRONMENT_VARIABLE
            env_var_show: True
          num:
            help: "This is a numeric argument"
            short: -n
            long: --number
            var: some_num_variable
            type: int
            required: False 
            env_var_show: True
          targets:
            help: "Playbook targets"
            short: -t
            long: --targets
            type: str
            var: playbook_targets
            required: True
          multiple:
            help: |-
              This option can be specified multiple times
            short: -m
            long: --multiple
            type: str
            var: multiple_arg
            allow_multiple: True
          some_switch:
            help: |-
              This is some boolean option, behaves like Click's switches,
              holds the value of True if specified
              see: https://github.com/pallets/click
            short: -s
            long: --some-switch
            is_flag: true
            var: some_switch
            required: True
          say_hello:
            help: "Invoke the 'hello' embedded shell function"
            short: -hello
            long: --say-hello
            type: str
            var: hello
            is_flag: True
          say_goodbye:
            help: "Invoke the 'goodbye' embedded shell function"
            short: -goodbye
            long: --say-goodbye
            type: str
            var: goodbye
            is_flag: True
          hidden_option:
            help: "This is a hidden option"
            short: -O
            long: --hidden-option
            is_hidden: True
            type: str
            var: hidden
            is_flag: True            
          verbose:
            help: |-
              This is a sample paramter that supports counting, as with:
              -v, -vv, -vvv, which would evaluate to 1, 2, and 3, respectively
            short: -v
            allow_counting: True
            var: verbosityAs you can see, commands are defined via YAML, and the syntax is mostly self-explanatory.Currently, the parameters available to any given option areconsistent with click version 8.1.x, seeAPI — Click Documentation (8.1.x)Important Notes:An option'svarkey:In the case of standard options, this variable holds the value of the arguments passed to the optionIn the case of flags/switches, this variable is a BooleanThe variable is available during the entire runtimeIn the above example, the-helloand-goodbyeoptions point to special mappedvariables that themselves map to corresponding shell functions defined in the subcommand'sfunctions directive. We'll discuss this more in sectionembedded-shell-functions.Populate the vars block - cli options - mapped variablesAs I mentioned before, the above mapped variables can be usedduring runtime.That is, they can be referenced in any defined shell functions,dynamic inventory expression logic, as well as during ansible execution.Consider the-f|-foofrom the example.Whatever argument you pass to this option becomes the value for the mapped variablesome_foo_variable.Again, this variable is made available to the underlying subprocess call, and thus to ansible.So when we call the tasks command like sotasks run -f foo1, the value for thesome_foo_variablebecomesfoo.Populate the vars block - help/messageNext, we add the help/message section:Click to ExpandTaskfile.yaml### The hosts block
- hosts: myhosts
  gather_facts: true
  become: true
  ### The vars block  
  vars:
    var1: value1
    var2: value2
    var3: value3
    var4: |-
      This is a multi-line value
      of type string
    var5:
      - listvalue1
      - listvalue2
      - listvalue3
      - listvalue4
    var6:
      some_key:
        some_child_key: dictvalue1
    var7: $(echo some_value)
    var8: 8000
    dbhosts:
      - dbhost1
      - dbhost2
      - dbhost3
    webhosts:
      - webhost1
      - webhost2
      - webhost3
    ### Global Options Block
    globals:
      options:
          my_global_option:
            help: "This is my global option"
            short: -g
            long: --global
            var: some_global_variable 
    ### The commands block
    commands:
      run:
        options:
          foo:
            help: "This is some foo option"
            short: -f
            long: --foo
            type: choice
            var: some_foo_variable
            required: True
            not_required_if: 
              - some_bar_variable
            options:
              - foo1
              - foo2
          bar:
            help: "This is some bar option"
            short: -b
            long: --bar 
            type: str
            var: some_bar_variable
            required: False
            required_if: 
              - hello
              - some_baz_variable
          baz:
            help: "This is some baz option"
            short: -z
            long: --baz
            type: str
            var: some_baz_variable
            required: False
            mutually_exclusive_with: 
              - some_bar_variable
              - some_foo_variable
          envvar:
            help: "The value for this argument can be derived from an Environmental Variable"
            short: -E
            long: --env-var
            type: str
            var: env_var
            env_var: SOME_ENVIRONMENT_VARIABLE
            env_var_show: True
          num:
            help: "This is a numeric argument"
            short: -n
            long: --number
            var: some_num_variable
            type: int
            required: False 
            env_var_show: True
          targets:
            help: "Playbook targets"
            short: -t
            long: --targets
            type: str
            var: playbook_targets
            required: True
          multiple:
            help: |-
              This option can be specified multiple times
            short: -m
            long: --multiple
            type: str
            var: multiple_arg
            allow_multiple: True
          some_switch:
            help: |-
              This is some boolean option, behaves like Click's switches,
              holds the value of True if specified
              see: https://github.com/pallets/click
            short: -s
            long: --some-switch
            is_flag: true
            var: some_switch
            required: True
          say_hello:
            help: "Invoke the 'hello' embedded shell function"
            short: -hello
            long: --say-hello
            type: str
            var: hello
            is_flag: True
          say_goodbye:
            help: "Invoke the 'goodbye' embedded shell function"
            short: -goodbye
            long: --say-goodbye
            type: str
            var: goodbye
            is_flag: True
          hidden_option:
            help: "This is a hidden option"
            short: -O
            long: --hidden-option
            is_hidden: True
            type: str
            var: hidden
            is_flag: True            
          verbose:
            help: |-
              This is a sample paramter that supports counting, as with:
              -v, -vv, -vvv, which would evaluate to 1, 2, and 3, respectively
            short: -v
            allow_counting: True
            var: verbosity
        help:
          message: |
            Invoke the 'run' command 
          epilog: |
            This line will be displayed at the end of the help text message
          examples:
            - example1: |
                tasks $command
            - example2: |
                Usage example 2Runningtasks run --helpshould return the list of parameters along with the help message you defined.Populate the vars block - embedded shell functionsAdd embedded shell functions:Taskfile.yaml### The hosts block
- hosts: myhosts
  gather_facts: true
  become: true
  ### The vars block  
  vars:
    var1: value1
    var2: value2
    var3: value3
    var4: |-
      This is a multi-line value
      of type string
    var5:
      - listvalue1
      - listvalue2
      - listvalue3
      - listvalue4
    var6:
      some_key:
        some_child_key: dictvalue1
    var7: $(echo some_value)
    var8: 8000
    dbhosts:
      - dbhost1
      - dbhost2
      - dbhost3
    webhosts:
      - webhost1
      - webhost2
      - webhost3
    ### Global Options Block
    globals:
      options:
          my_global_option:
            help: "This is my global option"
            short: -g
            long: --global
            var: some_global_variable 
    ### The commands block
    commands:
      run:
        options:
          foo:
            help: "This is some foo option"
            short: -f
            long: --foo
            type: choice
            var: some_foo_variable
            required: True
            not_required_if: 
              - some_bar_variable
            options:
              - foo1
              - foo2
          bar:
            help: "This is some bar option"
            short: -b
            long: --bar 
            type: str
            var: some_bar_variable
            required: False
            required_if: 
              - hello
              - some_baz_variable
          baz:
            help: "This is some baz option"
            short: -z
            long: --baz
            type: str
            var: some_baz_variable
            required: False
            mutually_exclusive_with: 
              - some_bar_variable
              - some_foo_variable
          envvar:
            help: "The value for this argument can be derived from an Environmental Variable"
            short: -E
            long: --env-var
            type: str
            var: env_var
            env_var: SOME_ENVIRONMENT_VARIABLE
            env_var_show: True
          num:
            help: "This is a numeric argument"
            short: -n
            long: --number
            var: some_num_variable
            type: int
            required: False 
            env_var_show: True
          targets:
            help: "Playbook targets"
            short: -t
            long: --targets
            type: str
            var: playbook_targets
            required: True
          multiple:
            help: |-
              This option can be specified multiple times
            short: -m
            long: --multiple
            type: str
            var: multiple_arg
            allow_multiple: True
          some_switch:
            help: |-
              This is some boolean option, behaves like Click's switches,
              holds the value of True if specified
              see: https://github.com/pallets/click
            short: -s
            long: --some-switch
            is_flag: true
            var: some_switch
            required: True
          say_hello:
            help: "Invoke the 'hello' embedded shell function"
            short: -hello
            long: --say-hello
            type: str
            var: hello
            is_flag: True
          say_goodbye:
            help: "Invoke the 'goodbye' embedded shell function"
            short: -goodbye
            long: --say-goodbye
            type: str
            var: goodbye
            is_flag: True
          hidden_option:
            help: "This is a hidden option"
            short: -O
            long: --hidden-option
            is_hidden: True
            type: str
            var: hidden
            is_flag: True            
          verbose:
            help: |-
              This is a sample paramter that supports counting, as with:
              -v, -vv, -vvv, which would evaluate to 1, 2, and 3, respectively
            short: -v
            allow_counting: True
            var: verbosity
        ### The help message
        help:
          message: |
            Invoke the 'run' command 
          epilog: |
            This line will be displayed at the end of the help text message
          examples:
            - example1: |
                tasks $command
            - example2: |
                Usage example 2
        ### Embedded shell functions
        functions:
          hello:
            shell: bash
            help: Say hello
            source: |-
              echo Hello! The value for var1 is $var1
          goodbye:
            shell: bash
            help: Say goodbye
            source: |-
              echo The value for var1 is $var1. Goodbye!Again, notice the two switches-helloand-goodbye, with mapped variableshelloandgoodbye, respectively.These mapped variables correspond to keys in thefunctionsblock with matching names.As such, specifying either or both-helloand-goodbyein yourtasks runinvocationwill short-circuit normal operation and execute the corresponding functionsin the order in which you call them.Try it yourself by running:tasks run -t local -s -b bar -m one -m two -vvv -O -hello -goodbyetasks run -t local -s -b bar -m one -m two -vvv -O -goodbye -helloThere is also a special flag---invoke-functionthat is globally available to all subcommands.Invocation is as follows:tasks <subcommand> ---invoke-function <function_name>.In our example, we would run:tasks run -t local -s -b bar -m one -m two -vvv -O ---invoke-function helloFor more usage examples, see theappendix.More about embedded shell functionsLet's briefly side-step into embedded shell functions.The syntax for nesting these under thefunctionskey is as follows:name_of_function:
            shell: bash, ruby, or python
            help: Help Text to Display
            hidden: false/true
            source: |-
              {{ code }}Back To TopBash example:hello:
            shell: bash
            help: Hello World in Bash
            hidden: false
            source: |-
              echo 'Hello World!'Python example:hello:
            shell: python
            help: Hello World in Python
            hidden: false
            source: |-
              print('Hello World!')Ruby example:hello:
            shell: ruby
            help: Hello World in Ruby
            hidden: false
            source: |-
              puts 'Hello World!'Back To TopPopulate the vars block - dynamic inventory expressionA useful feature of this tool is the ability to define your ansibleinventory as a dynamic expression in the Taskfile itself.To do so, we populate the with theinventory_expressionkey.When the inventory is defined in this manner, the logic is as follows:The inventory expression is evaluatedAn ephemeral inventory file is created with thecontents of this file being the output, or result, of that expressionThe fully qualified path to the ephemeral inventory file is specified as theargument to theansible-playbookinventory parameter-iLet's define our inventory expression:Click to ExpandTaskfile.yaml### The hosts block
- hosts: myhosts
  gather_facts: true
  become: true
  ### The vars block  
  vars:
    var1: value1
    var2: value2
    var3: value3
    var4: |-
      This is a multi-line value
      of type string
    var5:
      - listvalue1
      - listvalue2
      - listvalue3
      - listvalue4
    var6:
      some_key:
        some_child_key: dictvalue1
    var7: $(echo some_value)
    var8: 8000
    dbhosts:
      - dbhost1
      - dbhost2
      - dbhost3
    webhosts:
      - webhost1
      - webhost2
      - webhost3
    ### Global Options Block
    globals:
      options:
          my_global_option:
            help: "This is my global option"
            short: -g
            long: --global
            var: some_global_variable 
    ### The commands block
    commands:
      run:
        options:
          foo:
            help: "This is some foo option"
            short: -f
            long: --foo
            type: choice
            var: some_foo_variable
            required: True
            not_required_if: 
              - some_bar_variable
            options:
              - foo1
              - foo2
          bar:
            help: "This is some bar option"
            short: -b
            long: --bar 
            type: str
            var: some_bar_variable
            required: False
            required_if: 
              - hello
              - some_baz_variable
          baz:
            help: "This is some baz option"
            short: -z
            long: --baz
            type: str
            var: some_baz_variable
            required: False
            mutually_exclusive_with: 
              - some_bar_variable
              - some_foo_variable
          envvar:
            help: "The value for this argument can be derived from an Environmental Variable"
            short: -E
            long: --env-var
            type: str
            var: env_var
            env_var: SOME_ENVIRONMENT_VARIABLE
            env_var_show: True
          num:
            help: "This is a numeric argument"
            short: -n
            long: --number
            var: some_num_variable
            type: int
            required: False 
            env_var_show: True
          targets:
            help: "Playbook targets"
            short: -t
            long: --targets
            type: str
            var: playbook_targets
            required: True
          multiple:
            help: |-
              This option can be specified multiple times
            short: -m
            long: --multiple
            type: str
            var: multiple_arg
            allow_multiple: True
          some_switch:
            help: |-
              This is some boolean option, behaves like Click's switches,
              holds the value of True if specified
              see: https://github.com/pallets/click
            short: -s
            long: --some-switch
            is_flag: true
            var: some_switch
            required: True
          say_hello:
            help: "Invoke the 'hello' embedded shell function"
            short: -hello
            long: --say-hello
            type: str
            var: hello
            is_flag: True
          say_goodbye:
            help: "Invoke the 'goodbye' embedded shell function"
            short: -goodbye
            long: --say-goodbye
            type: str
            var: goodbye
            is_flag: True
          hidden_option:
            help: "This is a hidden option"
            short: -O
            long: --hidden-option
            is_hidden: True
            type: str
            var: hidden
            is_flag: True            
          verbose:
            help: |-
              This is a sample paramter that supports counting, as with:
              -v, -vv, -vvv, which would evaluate to 1, 2, and 3, respectively
            short: -v
            allow_counting: True
            var: verbosity
        ### The help message
        help:
          message: |
            Invoke the 'run' command 
          epilog: |
            This line will be displayed at the end of the help text message
          examples:
            - example1: |
                tasks $command
            - example2: |
                Usage example 2
        ### Embedded shell functions
        functions:
          hello:
            shell: bash
            help: Say hello
            source: |-
              echo Hello! The value for var1 is $var1
          goodbye:
            shell: bash
            help: Say goodbye
            source: |-
              echo The value for var1 is $var1. Goodbye!
    ### The inventory expression              
    inventory_expression: |
      [local]
      localhost ansible_connection=local
      [web_hosts]
      $(echo -e "${webhosts}" | tr ',' '\n')
      [db_hosts]
      $(echo -e "${dbhosts}" | tr ',' '\n')
      [myhosts:children]
      web_hosts
      db_hostsAs you can see, the inventory expression is dynamic, asit gets evaluated based on the output of inline shell commands.Let's focus on the variable$webhosts.As per the logic describedabove, the variable $webhosts is a heredoc with a value of:webhosts='
webhost1
webhost2
webhost3
'As such, theweb_hostsgroup in the inventory expression ...[web_hosts]
      $(echo -e "${webhosts}" | tr ',' '\n')... will evaluate to:[web_hosts]
webhost1
webhost2
webhost3Also, notice how the inline shell command transforms commas into newline characters by way of the transform (tr) command.This makes it so that if we were to have defined thewebhostsvariablein the Tasksfile aswebhosts: webhost1,webhost2,webhost3, it would have had the same outcomeas defining it as a list object in thevarsblock.Populate the vars block - inventory fileWe can specify an inventory file instead of an inventory expression with theinventory_filekey.Click to ExpandTaskfile.yaml### The hosts block
- hosts: myhosts
  gather_facts: true
  become: true
  ### The vars block  
  vars:
    var1: value1
    var2: value2
    var3: value3
    var4: |-
      This is a multi-line value
      of type string
    var5:
      - listvalue1
      - listvalue2
      - listvalue3
      - listvalue4
    var6:
      some_key:
        some_child_key: dictvalue1
    var7: $(echo some_value)
    var8: 8000
    dbhosts:
      - dbhost1
      - dbhost2
      - dbhost3
    webhosts:
      - webhost1
      - webhost2
      - webhost3
    ### Global Options Block
    globals:
      options:
          my_global_option:
            help: "This is my global option"
            short: -g
            long: --global
            var: some_global_variable 
    ### The commands block
    commands:
      run:
        options:
          foo:
            help: "This is some foo option"
            short: -f
            long: --foo
            type: choice
            var: some_foo_variable
            required: True
            not_required_if: 
              - some_bar_variable
            options:
              - foo1
              - foo2
          bar:
            help: "This is some bar option"
            short: -b
            long: --bar 
            type: str
            var: some_bar_variable
            required: False
            required_if: 
              - hello
              - some_baz_variable
          baz:
            help: "This is some baz option"
            short: -z
            long: --baz
            type: str
            var: some_baz_variable
            required: False
            mutually_exclusive_with: 
              - some_bar_variable
              - some_foo_variable
          envvar:
            help: "The value for this argument can be derived from an Environmental Variable"
            short: -E
            long: --env-var
            type: str
            var: env_var
            env_var: SOME_ENVIRONMENT_VARIABLE
            env_var_show: True
          num:
            help: "This is a numeric argument"
            short: -n
            long: --number
            var: some_num_variable
            type: int
            required: False 
            env_var_show: True
          targets:
            help: "Playbook targets"
            short: -t
            long: --targets
            type: str
            var: playbook_targets
            required: True
          multiple:
            help: |-
              This option can be specified multiple times
            short: -m
            long: --multiple
            type: str
            var: multiple_arg
            allow_multiple: True
          some_switch:
            help: |-
              This is some boolean option, behaves like Click's switches,
              holds the value of True if specified
              see: https://github.com/pallets/click
            short: -s
            long: --some-switch
            is_flag: true
            var: some_switch
            required: True
          say_hello:
            help: "Invoke the 'hello' embedded shell function"
            short: -hello
            long: --say-hello
            type: str
            var: hello
            is_flag: True
          say_goodbye:
            help: "Invoke the 'goodbye' embedded shell function"
            short: -goodbye
            long: --say-goodbye
            type: str
            var: goodbye
            is_flag: True
          hidden_option:
            help: "This is a hidden option"
            short: -O
            long: --hidden-option
            is_hidden: True
            type: str
            var: hidden
            is_flag: True            
          verbose:
            help: |-
              This is a sample paramter that supports counting, as with:
              -v, -vv, -vvv, which would evaluate to 1, 2, and 3, respectively
            short: -v
            allow_counting: True
            var: verbosity
        ### The help message
        help:
          message: |
            Invoke the 'run' command 
          epilog: |
            This line will be displayed at the end of the help text message
          examples:
            - example1: |
                tasks $command
            - example2: |
                Usage example 2
        ### Embedded shell functions
        functions:
          hello:
            shell: bash
            help: Say hello
            source: |-
              echo Hello! The value for var1 is $var1
          goodbye:
            shell: bash
            help: Say goodbye
            source: |-
              echo The value for var1 is $var1. Goodbye!
    ### Inventory file
    inventory_file: '/some/path/some/inventory.yaml'Notes of Importance:The value you provide to theinventory_filekey supports templatingThat is, any of the variables available runtime variables can be used, for example:inventory_file: '/some/path/some/inventory_$foo_variable.yaml'inventory_file: '/some/path/some/inventory_$var1.yaml'You should not be specifying both aninventory_fileand aninventory_expression, as you get unexpected results.Back To TopAdd tasksFinally, let's add some proper ansible tasks!Click to ExpandTaskfile.yaml### The hosts block
- hosts: myhosts
  gather_facts: true
  become: true
  ### The vars block  
  vars:
    var1: value1
    var2: value2
    var3: value3
    var4: |-
      This is a multi-line value
      of type string
    var5:
      - listvalue1
      - listvalue2
      - listvalue3
      - listvalue4
    var6:
      some_key:
        some_child_key: dictvalue1
    var7: $(echo some_value)
    var8: 8000
    dbhosts:
      - dbhost1
      - dbhost2
      - dbhost3
    webhosts:
      - webhost1
      - webhost2
      - webhost3
    ### Global Options Block
    globals:
      options:
          my_global_option:
            help: "This is my global option"
            short: -g
            long: --global
            var: some_global_variable 
    ### The commands block
    commands:
      run:
        options:
          foo:
            help: "This is some foo option"
            short: -f
            long: --foo
            type: choice
            var: some_foo_variable
            required: True
            not_required_if: 
              - some_bar_variable
            options:
              - foo1
              - foo2
          bar:
            help: "This is some bar option"
            short: -b
            long: --bar 
            type: str
            var: some_bar_variable
            required: False
            required_if: 
              - hello
              - some_baz_variable
          baz:
            help: "This is some baz option"
            short: -z
            long: --baz
            type: str
            var: some_baz_variable
            required: False
            mutually_exclusive_with: 
              - some_bar_variable
              - some_foo_variable
          envvar:
            help: "The value for this argument can be derived from an Environmental Variable"
            short: -E
            long: --env-var
            type: str
            var: env_var
            env_var: SOME_ENVIRONMENT_VARIABLE
            env_var_show: True
          num:
            help: "This is a numeric argument"
            short: -n
            long: --number
            var: some_num_variable
            type: int
            required: False 
            env_var_show: True
          targets:
            help: "Playbook targets"
            short: -t
            long: --targets
            type: str
            var: playbook_targets
            required: True
          multiple:
            help: |-
              This option can be specified multiple times
            short: -m
            long: --multiple
            type: str
            var: multiple_arg
            allow_multiple: True
          some_switch:
            help: |-
              This is some boolean option, behaves like Click's switches,
              holds the value of True if specified
              see: https://github.com/pallets/click
            short: -s
            long: --some-switch
            is_flag: true
            var: some_switch
            required: True
          say_hello:
            help: "Invoke the 'hello' embedded shell function"
            short: -hello
            long: --say-hello
            type: str
            var: hello
            is_flag: True
          say_goodbye:
            help: "Invoke the 'goodbye' embedded shell function"
            short: -goodbye
            long: --say-goodbye
            type: str
            var: goodbye
            is_flag: True
          hidden_option:
            help: "This is a hidden option"
            short: -O
            long: --hidden-option
            is_hidden: True
            type: str
            var: hidden
            is_flag: True            
          verbose:
            help: |-
              This is a sample paramter that supports counting, as with:
              -v, -vv, -vvv, which would evaluate to 1, 2, and 3, respectively
            short: -v
            allow_counting: True
            var: verbosity
        ### The help message
        help:
          message: |
            Invoke the 'run' command 
          epilog: |
            This line will be displayed at the end of the help text message
          examples:
            - example1: |
                tasks $command
            - example2: |
                Usage example 2
        ### Embedded shell functions
        functions:
          hello:
            shell: bash
            help: Say hello
            source: |-
              echo Hello! The value for var1 is $var1
          goodbye:
            shell: bash
            help: Say goodbye
            source: |-
              echo The value for var1 is $var1. Goodbye!
    ### The inventory expression
    inventory_expression: |
      [local]
      localhost ansible_connection=local
      [web_hosts]
      $(echo -e "${webhosts}" | tr ',' '\n')
      [db_hosts]
      $(echo -e "${dbhosts}" | tr ',' '\n')
      [myhosts:children]
      web_hosts
      db_hosts            
  tasks:
    - name: Show Variables
      debug:
        msg: |-
          {{ hostvars[inventory_hostname] | to_nice_json }}The task above will display all available host variables.Usage ExamplesQuick usage examples:Display help for main commandtasks --helpDisplay help for therunsubcommandtasks run --helpInitialize your workspacetasks initRun the Taskfile.yaml playbook, passing in additional options to the underlying subprocesstasks run -t local -s -b bar -m one -m twoDon't do anything, just echo the underlying shell commandtasks run -t local -s -b bar -m one -m two -O ---echoResult should be similar to:ansible-playbook -i /var/folders/5f/4g4xnnv958q52108qxd2rj_r0000gn/T/ansible-inventorytlmz2hpz.tmp.ini \ -e "{'var1':'${var1}'}" ... Taskfile.yamlRun the embedded functionhellotasks run -t local -s -b bar -m one -m two -helloRun the embedded functionshelloandgoodbyerun -t local -s -b bar -m one -m two -hello -goodbyeBack To TopInstallationAnsible-taskrunner consists of thetaskscommand.It can be installed in a few ways:pip install ansible-taskrunnerpip install git+https://github.com/berttejeda/ansible-taskrunner.gitObtaining arelease(these lag behind the pip distributions)Note: You'll need to pre-install a python distribution for the Windows MSI release.
Not yet sure if I am doing something wrong or if that's by design.
I lean toward the former :|More ExamplesReview theexamplesdirectory for more hands-on usage samples.AppendixThe Options SeparatorWhen you pass the---options separator to any subcommand, anything
after the separator is passed directly to the ansible subprocess.Bastion ModeIf you're launching thetaskscommand from a Windows host, this tool will automatically execute inBastion ModeUnder Bastion Mode, thetaskscommand will:Execute theansible-playbooksubprocess via abastion host, i.e. a remote machine thatshould haveansibleinstalledThis is done via ssh using theparamikomoduleRunning in Bastion Mode requires a configuration file containing the ssh connection settings.To initialize this configuration file, you can simply runtasks init.For full usage options, enter intasks init --help.Once you've initialized the configuration file, you should seesftp-config.jsonin your workspace.This configuration file is fashioned after thesftpplugin forSublime Textand is thus compatible.Special Variablesansible_playbook_commandIf you define the playbook variableansible_playbook_command, this will override the underlying ansible-playbook command invocation.As an example, suppose I define this variable in the aboveTaskfile.yaml, as follows:- hosts: myhosts
  gather_facts: true
  become: true
  vars:
    ansible_playbook_command: 'python ${HOME}/ansible_2.7.8/ansible-playbook'
    var1: value1
    var2: value2
    var3: value3
    # ...Upon invoking thetaskscommand with the---echoflag:The temporary inventory would be revealed as:inventory_is_ephemeral=True
if [[ "$inventory_is_ephemeral" == "True" ]];then
echo -e """${inventory_expression}"""| while read line;do
 eval "echo -e ${line}" >> "/var/folders/some/path/ansible-inventoryo4fw4ttc.tmp.ini";
done
fi;*The above inventory file path will differ, of courseAnd the underlying shell command would be revealed as:python ${HOME}/ansible_2.7.8/ansible-playbook \
-i /var/folders/some/path/ansible-inventoryo4fw4ttc.tmp.ini \
-e "{'var1':'${var1}'}" \
-e "{'var2':'${var2}'}" \
-e "{'var3':'${var3}'}" \
... Taskfile.yamlBack To Toppre_executionAnything defined under thepre_executionvariable will be evaluatedbeforeall other statements in the underlying shell expression.As an example, suppose I define the pre_execution variable in the aboveTaskfile.yaml, as follows:- hosts: myhosts
  gather_facts: true
  become: true
  vars:
    pre_execution: |-
      export pxe_var=some_value
      touch /tmp/.run.lockUpon invoking thetaskscommand with the---echoflag:The underlying shell expression would be revealed as:...
export pxe_var=some_value
touch /tmp/.run.lock
...The commands above are always placedbeforeall variables declarations in the underlying shell expresison.post_executionThis is similar topre_execution, except that anything defined under thepost_executionvariable will be evaluatedafterall other statements in the underlying shell expression.environment_varsBy defining the playbook dictionary variableenvironment_vars,the following occurs:For each dictionarykey: valuepair:A correspondingexportstatement is defined in the underlying shell expressionAs an example, suppose I define this variable in the aboveTaskfile.yaml, as follows:- hosts: myhosts
  gather_facts: true
  become: true
  vars:
    ansible_playbook_command: 'python ${HOME}/ansible_2.7.8/ansible-playbook'
    var1: value1
    var2: value2
    var3: value3
    some_path: /some/path
    environment_vars:
      MY_ENV_VAR1: "${some_path}/${var1}"    
      MY_ENV_VAR2: "${some_path}/${var2}"    
    # ...Upon invoking thetaskscommand with the---echoflag:The underlying shell expression would be revealed as:var1="value1"
var2="value2"
export MY_ENV_VAR1="${some_path}/${var1}"
export MY_ENV_VAR2="${some_path}/${var2}"These export statements are always placedafterall variables declarations in the underlying shell expresison.ANSIBLE_ VariablesAny variables matching ANSIBLE_.* will automatically be expressed as export statements.As an example, suppose I define such variables in the aboveTaskfile.yaml, as follows:- hosts: myhosts
  gather_facts: true
  become: true
  vars:
    ANSIBLE_VAULT_PASSWORD_FILE: /some/path/some/password_file
    ANSIBLE_CALLBACK_PLUGINS: /some/other/path/some/plugins
    # ...Upon invoking thetaskscommand with the---echoflag:The underlying shell expression would be revealed as:var1="value1"
var2="value2"
export ANSIBLE_VAULT_PASSWORD_FILE="/some/path/some/password_file"
export ANSIBLE_CALLBACK_PLUGINS="/some/other/path/some/plugins"cli_providerYou can override the underlying command-line provider in two ways:Via the tasks config file (seeexamples)By defining the variablecli_providerin the specified TaskfileAs an example, suppose I define this variable in the aboveTaskfile.yaml, as follows:- hosts: myhosts
  gather_facts: true
  become: true
  vars:
    cli_provider: bash
    # ...Upon invoking thetaskscommand, you will note that the app no longer operates in anansible-playbookmode, but rather as yaml-abstracted bash-script.There are three cli-providers built in to the tasks command:ansiblebashvagrantansible_extra_optionsApart from utilizing the---options separator, you can specify additional options to pass to the underlyingansible-playbooksubprocess by setting an appropriate value for the__ansible_extra_options__Environmental variable.tasks_fileThe__tasks_file__variable points to the current Taskfile.It is available to the underlying subprocess shell.commandThe__command__variable points to the name of the invoked subcommand.It is available to the underlying subprocess shell.Back To TopMutually Exclusive OptionsThis tool supports the following advanced options:Mutually Exclusive, seeMutually exclusive option groups in python Click - Stack Overflow.Mutually InclusiveConditionally requiredSuppose you want a set of options such that:You want to accept one option but only if another, related option has not been specifiedYou can accomplish this by defining your option with the following parameters:- required: False
  - mutually_exclusive_with: 
    - some_bar_variable
    - some_foo_variableIn the above configuration, calling this option along with options-f|-fooand-b|-barwill trigger an illegal usage error, since you'vemarked the option as mutually exclusive with either of these two options.Feel free to review theTaskfile.yaml, as you'll find an example of:mutually exclusivemutually inclusiveconditionally requiredSimple TemplatingAs of version 1.1.5, simple templating is available to the following objects:Help messagesExamplesOptionsOptions valuesWhat this means is that we expose a limited set of internal variables to the above.As an example:help:
          message: |
            Invoke the 'run' command 
          epilog: |
            This line will be displayed at the end of the help text message
          examples:
            - example1: |
                tasks -f $tf_path --foo foo --bar bar
            - example2: |
                tasks -f $tf_path --foo foo --baz bazIn the above strings,$tf_pathwill expand to the internal variable tf_path,
which holds the relative path to the current tasks file.Below is a list of available variables for your convenience:Variable        | Description
-------------   | -------------
exe_path        | The absolute path to the tasks executable
cli_args        | The current command-line invocation
cli_args_short  | The current command-line invocation, minus the executable
sys_platform    | The OS Platform as detected by Python
tf_path         | The relative path to the specified TaskfileAdditionally, allcurrently set environmental variablesare also available for templating.Back To TopSingle-Executable ReleasesThis script also ships as a zipapp executable (similar to a windows .exe).Head over to thereleases pagefor release downloads.You can also build your own single-executable zipapp, as follows:Make sure you have themake-zipappexecutable in your pathInvoking build tasksBuild zipapp:python ansible_taskrunner/cli.py -f Makefile.yaml run ---make zipappRead More on zipapps:zipapp — Manage executable Python zip archives — Python 3.7.4rc2 documentationUnit TestingTo run all tests, simply call the test script, as with:python tests/test_ansible_taskrunner.pyTODO - Add more tests!Back To TopLicense and CreditsThis project adopts the the MIT distribution License.Releasescome bundled with the following opensource python packages:click, licensed under BSD-3-ClausepyYaml, licensed under MITLastly, this package was created with Cookiecutter and theaudreyr/cookiecutter-pypackageproject template.Cookiecutter:https://github.com/audreyr/cookiecutteraudreyr/cookiecutter-pypackage:https://github.com/audreyr/cookiecutter-pypackage