Lesson 8 - Input and Output Parameters

Goal

In this lesson we’ll learn how to change the way inputs and outputs behave using input and output properties.

Get Started

It’s time to create a new operation. Create a new file in the tutorials/hiring folder called generate_user_email.sl. In here we’ll create an operation that takes in some user information and produces an email address for that user. We’ll write it so that it will take in which attempt this is at creating an email address for this user. That way we can use it in conjunction with our check_availability operation. Eventually, we’ll generate an address, check its availability, and if it’s unavailable we’ll do it all over again. The following code does not present any new concepts. We will use it as a starting point for a discussion on input properties.

namespace: tutorials.hiring

operation:
  name: generate_user_email

  inputs:
    - first_name
    - middle_name
    - last_name
    - domain
    - attempt

  python_action:
    script: |
      attempt = int(attempt)
      if attempt == 1:
        address = first_name[0:1] + '.' + last_name + '@' + domain
      elif attempt == 2:
        address = first_name + '.' + last_name[0:1] + '@' + domain
      elif attempt == 3 and middle_name != '':
        address = first_name + '.' + middle_name[0:1] + '.' + last_name + '@' + domain
      else:
        address = ''
      # print address

  outputs:
    - email_address: ${address}

  results:
    - FAILURE: ${address == ''}
    - SUCCESS

Test

You can save the file and test that the operation is working as expected by using the following command:

run --f <folder path>/tutorials/hiring/generate_user_email.sl --i first_name=john,middle_name=e,last_name=doe,domain=somecompany,attempt=1

It may help to uncomment the print line to see what is being output. Change the value for attempt in the run command and see what happens.

Add to Flow

Let’s add a step in the new_hire flow to call our new operation. That will allow us to demonstrate how input properties affect the way variables are passed to operations.

Between the print_start step and check_address step we’ll put our new step named generate_address.

- generate_address:
    do:
      generate_user_email:
        - first_name
        - middle_name
        - last_name
        - domain
        - attempt
    publish:
      - address: ${email_address}

We’ll also have to change the inputs of the flow to reflect our new addition. We can remove the address from the flow inputs since we’ll now be getting the address from the generate_user_email operation and publishing it in the generate_address step. Instead, we need to add the inputs necessary for the generate_user_email operation to the flow’s inputs section.

inputs:
  - first_name
  - middle_name
  - last_name
  - domain
  - attempt

We also have to fix the navigation of the print_start step.

- print_start:
    do:
      base.print:
        - text: "Starting new hire process"
    navigate:
      - SUCCESS: generate_address

One last thing to tidy up is the failure message, which no longer receives an address that was not created.

- on_failure:
  - print_fail:
      do:
        base.print:
          - text: "Failed to create address"

At this point everything is set up to go. We can save the file and run the flow as long as we pass all the necessary arguments.

run --f <folder path>/tutorials/hiring/new_hire.sl --cp <folder path>/tutorials --i first_name=john,middle_name=e,last_name=doe,domain=somecompany.com,attempt=1

Required

By default all flow and operation inputs are required. We can change that behavior by setting the required property of an input to false. Let’s make the middle_name optional. We’ll have to set its required property to false in both the flow’s inputs and the generate_user_email operation’s inputs.

flow:
  name: new_hire

  inputs:
    - first_name
    - middle_name:
        required: false
    - last_name
    - domain
    - attempt
operation:
  name: generate_user_email

  inputs:
    - first_name
    - middle_name:
        required: false
    - last_name
    - domain
    - attempt

Note

YAML Note: Don’t forget to add a colon (:) to the input name before adding its properties.

For more information, see required in the DSL reference.

Default

We can also make an input optional by providing a default value. If no value is passed for an input that declares the default property, the default value is used instead. In our case, we can set the generate_user_email operation’s middle_name to default to the empty string.

operation:
  name: generate_user_email

  inputs:
    - first_name
    - middle_name:
        required: false
        default: ""
    - last_name
    - domain
    - attempt

Now the flow can be run after saving the files without providing a value for the middle name.

run --f <folder path>/tutorials/hiring/new_hire.sl --cp <folder path>/tutorials --i first_name=john,last_name=doe,domain=somecompany.com,attempt=1

For more information, see default in the DSL reference.

Private

The default value is only used if another value is not passed to the operation. But sometimes we want to force the default value to be the one used, even if a different value is passed from a flow. Let’s do that to the domain input of the generate_user_email operation. To do so, we set the input’s private parameter to true. We’ll also have to set a default value for the input.

operation:
  name: generate_user_email

  inputs:
    - first_name
    - middle_name:
        required: false
        default: ""
    - last_name
    - domain:
        default: "acompany.com"
        private: true
    - attempt

We can save the file and then run the flow using the same command as above. You’ll notice that no matter what is passed to the domain input, acompany.com is what ends up in the email address. That’s exactly what we want, but obviously there is no reason to pass values to the domain variable anymore. So let’s just remove it from the flow inputs and the generate_address step.

flow:
  name: new_hire

  inputs:
    - first_name
    - middle_name:
        required: false
    - last_name
    - attempt
- generate_address:
    do:
      generate_user_email:
        - first_name
        - middle_name
        - last_name
        - attempt
    publish:
      - address: ${email_address}

For more information, see private in the DSL reference.

Sensitive

Finally, we can mark both inputs and outputs as sensitive. When a variable is marked as sensitive, its value will not be printed in logs, events or in outputs of the CLI and Build Tool.

In the check_availability operation, let’s create a temporary password if an email address is available. We’ll just add a few lines to our script to randomly generate a short password if the address is available. In the outputs section, we’ll mark that password as sensitive. Notice, that when we add a sensitive property to an output we have to add a value property as well.

python_action:
  script: |
    import random
    rand = random.randint(0, 2)
    vacant = rand != 0
    # print vacant
    if vacant == True:
      password = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(6))
    else:
      password = ''

outputs:
  - available: ${str(vacant)}
  - password:
      value: ${password}
      sensitive: true

You can now run the check_availability operation and see how the password output is not printed to the screen.

run --f <folder path>/tutorials/hiring/check_availability.sl --i address=john.doe@somecompany.com

In the new_hire flow we’ll add password to the publish section of the check_address step to be used later on.

- check_address:
    do:
      check_availability:
        - address
    publish:
      - availability: ${available}
      - password
    navigate:
      - UNAVAILABLE: print_fail
      - AVAILABLE: print_finish

For more information, see sensitive in the DSL reference.

Run It

Now we can save the file and run the flow without passing the domain. We can also leave out the middle name if we want, but we can also leave it in.

run --f <folder path>/tutorials/hiring/new_hire.sl --cp <folder path>/tutorials --i first_name=john,last_name=doe,attempt=1

Download the Code

Lesson 8 - Complete code

Up Next

In the next lesson we’ll see how to use subflows.

New Code - Complete

new_hire.sl

namespace: tutorials.hiring

imports:
  base: tutorials.base

flow:
  name: new_hire

  inputs:
    - first_name
    - middle_name:
        required: false
    - last_name
    - attempt

  workflow:
    - print_start:
        do:
          base.print:
            - text: "Starting new hire process"
        navigate:
          - SUCCESS: generate_address

    - generate_address:
        do:
          generate_user_email:
            - first_name
            - middle_name
            - last_name
            - attempt
        publish:
          - address: ${email_address}

    - check_address:
        do:
          check_availability:
            - address
        publish:
          - availability: ${available}
          - password
        navigate:
          - UNAVAILABLE: print_fail
          - AVAILABLE: print_finish

    - print_finish:
        do:
          base.print:
            - text: "${'Availability for address ' + address + ' is: ' + availability}"
        navigate:
          - SUCCESS: SUCCESS

    - on_failure:
      - print_fail:
          do:
            base.print:
              - text: "Failed to create address"

generate_user_email.sl

namespace: tutorials.hiring

operation:
  name: generate_user_email

  inputs:
    - first_name
    - middle_name:
        required: false
        default: ""
    - last_name
    - domain:
        default: "acompany.com"
        private: true
    - attempt

  python_action:
    script: |
      attempt = int(attempt)
      if attempt == 1:
        address = first_name[0:1] + '.' + last_name + '@' + domain
      elif attempt == 2:
        address = first_name + '.' + last_name[0:1] + '@' + domain
      elif attempt == 3 and middle_name != '':
        address = first_name + '.' + middle_name[0:1] + '.' + last_name + '@' + domain
      else:
        address = ''
      # print address

  outputs:
    - email_address: ${address}

  results:
    - FAILURE: ${address == ''}
    - SUCCESS

check_availability.sl

namespace: tutorials_08.hiring

operation:
  name: check_availability

  inputs:
    - address

  python_action:
    script: |
      import random
      import string
      rand = random.randint(0, 2)
      vacant = rand != 0
      # print vacant
      if vacant == True:
        password = ''.join(random.choice(string.letters) for _ in range(6))
      else:
        password = ''

  outputs:
    - available: ${str(vacant)}
    - password:
        value: ${password}
        sensitive: true
  results:
    - UNAVAILABLE: ${rand == 0}
    - AVAILABLE