Puppet Enterprise 2019.0

Configure Bolt to use the orchestrator API and perform actions on PE-managed nodes.

Bolt is an open source task runner that automates the manual work that you do to maintain your infrastructure. Use Bolt to automate tasks that you perform on your infrastructure on an as-needed basis, for example, when you troubleshoot a system, deploy an application, or stop and restart services. Bolt connects directly to remote nodes with SSH or WinRM, so you are not required to install any agent software.

Note: Bolt is a pre-1.0 release in development. For details on frequent changes and feature iteration, see the Bolt release notes.

You can configure Bolt to use the orchestrator API and perform actions on Puppet Enterprise-managed nodes. For example, you can run a remote command:

bolt command run hostname --nodes pcp://<master> 

When you run Bolt plans, the plan logic is processed locally while the corresponding commands, scripts, tasks, and file uploads run remotely via the API.

To set up Bolt to use the orchestrator API you must do the following:

  • Install the bolt_shim module in a PE environment.

  • Assign task permissions to a user role.

  • Adjust the orchestrator configuration files, as needed.

  • View available tasks.

Install the Bolt module in a PE environment

Bolt uses a task to execute commands, upload files, and run scripts over orchestrator. To install this task, install the puppetlabs-bolt_shim module from the Forge. Install the code in the same environment as the other tasks you want to run. Use the following Puppetfile line:

mod 'puppetlabs-bolt_shim', '0.1.1'

Assign task permissions to a user role

Warning: By granting users access to Bolt tasks, you give them permission to run arbitrary commands and upload files as a super-user.
  1. In the PE console, click Access control > User roles.

  2. From the list of user roles, click the one you want to have task permissions.

  3. On the Permissions tab, in the Type box, select Tasks.

  4. For Permission, select Run tasks, and then select All from the Instance drop-down list.

  5. Click Add permission, and then commit the change.

Adjust the orchestrator configuration files

Set up the orchestrator API for Bolt in the same user-specified configuration file that is used for PE client tools:

  • *nix systems /etc/puppetlabs/client-tools/orchestrator.conf

  • Windows C:/ProgramData/PuppetLabs/client-tools/orchestrator.conf

Note: If you use a global configuration file stored at /etc/puppetlabs/client-tools/orchestrator.conf (or C:\ProgramData\PuppetLabs\client-tools\orchestrator.conf for Windows), copy the file to your home directory.
Tip: You can also configure orchestrator in the Bolt configuration file (~/.puppetlabs/bolt.yaml) or the configuration section of the inventory file (~/.puppetlabs/bolt/inventory.yaml).

Bolt can be configured to connect to Orchestrator in the pcp section of the bolt config file as well. This configuration will not be shared with puppet task.

By default Bolt uses the production environment in PE when running tasks. To use a different environment change the task-environment setting in bolt config.

pcp:
  task-environment: development

View available tasks

To view a list of available tasks from the orchestrator API, run the command puppet task show (instead of the command bolt task show).

Bolt Plan example

View a plan that combine multiple tasks with one command.

Plan that deploys an application 

In this example, the plan my_app runs the tasks necessary to deploy an application to multiple nodes. It uses node information from an inventory file and tasks written in Python and stored at my_app/tasks/.

You run the plan with this command:

bolt plan run my_app::deploy version=1.0.2 app_servers=app db_server=db lb_server=lb --inventoryfile ./inventory.yaml --modulepath=./modules

Using the sample code below, the plan validates that there is a single load balancer server; queries the server load to determine availability, installs the application, migrates the database, makes the new code available on each application server and, finally, cleans up old versions of the application.

Note: This is sample code. To set up these tasks and run this plan in your environment, try the Puppet Tasks Hands-on Lab. This GitHub repository contains sample files, code examples, and exercises to help you interact with Bolt in a safe environment. For more information, see the puppetlabs/tasks-hands-on-lab repository.
plan my_app::deploy(
  Pattern[/\d+\.\d+\.\d+/] $version,
  TargetSpec $app_servers,
  TargetSpec $db_server,
  TargetSpec $lb_server,
  String[1] $instance = 'my_app',
  Boolean $force = false
) {
  # Validate that there is only a single load balancer server to check
  if get_targets($lb_server).length > 1 {
    fail_plan("${lb_server} did not resolve to a single target")
  }

  # First query the load balancer and make sure the app isn't under too much load to do a deploy.
  unless $force {
    $conns = run_task('my_app::lb', $lb_server,
       "Check load before starting deploy",
       action => 'stats',
       backend => $instance,
       server => 'FRONTEND',
    ).first['connections']
    if ($conns > 8) {
      fail_plan("The application has too many open connections: ${conns}")
    } else {
      # Info messages will be displayed when the --verbose flag is used.
      info("Application has ${conns} open connections.")
    }
  }

  # Install the new version of the application and check what version was previously
  # installed so it can be deleted after the deploy.
  $old_versions = run_task('my_app::install', [$app_servers, $db_server],
    "Install ${version} of the application",
    version => $version
  ).map |$r| { $r['previous_version'] }

  run_task('my_app::migrate', $db_server)

  # Don't log every action on each node, only log important messages
  without_default_logging() || {
    # Expand group references or globs before iterating
    get_targets($app_servers).each |$server| {

      # Check stats and print a message to the user
      $stats = run_task('my_app::lb', $lb_server,
        action => 'stats',
        backend => $instance,
        server => $server.name,
        _catch_errors => $force
      ).first
      notice("Deploying to ${server.name}, currently ${stats["status"]} with ${stats["connections"]} open connections.")

      run_task('my_app::lb', $lb_server,
        "Drain connections from ${server.name}",
        action => 'drain',
        backend => $instance,
        server => $server.name,
        _catch_errors => $force
      )

      run_task('my_app::deploy', [$server],
        "Update application for new version",
      )

      # Verify the app server is healthy before returning it to the load
      # balancer.
      $health = run_task('my_app::health_check', $lb_server,
        "Run Healthcheck for ${server.name}",
        target => "http://${server.name}:5000/",
        '_catch_errors' => true).first

      if $health['status'] == 'success' {
        info("Upgrade Healthy, Returning ${server.name} to load balancer")
      } else {
        # Fail the plan unless the app server is healthy or this is a forced deploy
        unless $force {
          fail_plan("Deploy failed on app server ${server.name}: ${health.result}")
        }
      }

      run_task('my_app::lb', $lb_server,
        action => 'add',
        backend => $instance,
        server => $server.name,
        _catch_errors => $force
      )
      notice("Deploy complete on ${server}.")
    }
  }

  run_task('my_app::uninstall', [$db_server, $app_servers],
    "Clean up old versions",
    live_versions => $old_versions + $version,
  )
}
Back to top