Overview

While the Getting Started topic provides a step by step walkthrough on creating your first Ansible Playbook Bundle (APB), this topic provides more in-depth reference material. The fundamental components that make up an APB are explained in further detail to help an experienced APB developer get a better understanding of each individual component within an APB.

For completed APB examples, you can browse APBs in the ansibleplaybookbundle organization on GitHub.

Directory Structure

The following shows an example directory structure of an APB:

example-apb/
├── Dockerfile
├── apb.yml
└── roles/
│   └── example-apb-openshift
│       ├── defaults
│       │   └── main.yml
│       └── tasks
│           └── main.yml
└── playbooks/
    └── provision.yml
    └── deprovision.yml
    └── bind.yml
    └── unbind.yml

APB Spec File

The APB spec file is located at apb.yml and is where the outline of your application is declared. The following is an example APB spec:

  version: 1.0
  name: example-apb
  description: A short description of what this APB does
  bindable: True
  async: optional
  metadata:
    documentationUrl: <link_to_documentation>
    imageUrl: <link_to_url_of_image>
    dependencies: ['<registry>/<organization>/<dependency_name_1>', '<registry>/<organization>/<dependency_name_2>']
    displayName: Example App (APB)
    longDescription: A longer description of what this APB does
    providerDisplayName: "Red Hat, Inc."
  plans:
    - name: default
      description: A short description of what this plan does
      free: true
      metadata:
        displayName: Default
        longDescription: A longer description of what this plan deploys
        cost: $0.00
      parameters:
        - name: parameter_one
          required: true
          default: foo_string
          type: string
          title: Parameter One
          maxlength: 63
        - name: parameter_two
          required: true
          default: true
          title: Parameter Two
          type: boolean

Top-level Structure

Field Description

version

Version of the APB spec. See APB Spec Versioning for details.

name

Name of the APB. Names must be valid ASCII and may contain lowercase letters, digits, underscores, periods, and dashes. See Docker’s guidelines for valid tag names.

description

Short description of this APB.

bindable

Boolean option of whether or not this APB can be bound to. Accepted fields are true or false.

metadata

Dictionary field declaring relevant metadata information.

plans

A list of plans that can be deployed. See Plans for details.

Metadata

Field Description

documentationUrl

URL to the application’s documentation.

imageUrl

URL to an image which will be displayed in the web console for the service catalog.

dependencies

List of images which are consumed from within the APB.

displayName

The name that will be displayed in the web console for this APB.

longDescription

Longer description that will be displayed when the APB is clicked in the web console.

providerDisplayName

Name of who is providing this APB for consumption.

Plans

Plans are declared as a list. This section explains what each field in a plan describes.

Field Description

name

Unique name of plan to deploy. This will be displayed when the APB is clicked from the service catalog.

description

Short description of what will be deployed from this plan.

free

Boolean field to determine if this plan is free or not. Accepted fields are true or false.

metadata

Dictionary field declaring relevant plan metadata information. See Plan Metadata for details.

parameters

List of parameter dictionaries used as input to the APB. See Parameters for details.

Plan Metadata

Field Description

displayName

Name to display for the plan in the web console.

longDescription

Longer description of what this plan deploys.

cost

How much the plan will cost to deploy. Accepted field is $x.yz.

Parameters

Each item in the parameters section can have several fields. The name field is required. The order of the parameters will be displayed in sequential order in the form in the OpenShift Origin web console.

parameters:
  - name: my_param
    title: My Parameter
    type: enum
    enum: ['X', 'Y', 'Z']
    required: True
    default: X
    display_type: select
    display_group: Group 1
Field Description

name

Unique name of the parameter passed into the APB.

title

Displayed label in the web console.

type

Data type of the parameters as specified by link json-schema, such as string, number, int, boolean, or enum. Default input field type in the web console will be assigned if no display_type is assigned.

required

Whether or not the parameter is required for APB execution. Required field in the web console.

default

Default value assigned to the parameter.

display_type

Display type for the web console. For example, you can override a string input as a password to hide it in the web console. Accepted fields include text, textarea, password, checkbox, or select.

display_group

Will cause a parameter to display in groups with adjacent parameters with matching display_group fields. In the above example, adding another field below with display_group: Group 1 will visually group them together in the web console under the heading Group 1.

When using a long list of parameters, it can be useful to use a shared parameter list. For an example of this, see the rhscl-postgresql-apb.

APB Spec Versioning

The APB spec uses semantic versioning with the format of x.y where x is a major release and y is a minor release.

The current spec version is 1.0.

Major Version

The APB spec will increment the major version whenever an API breaking change is introduced to the spec. Some examples include:

  • Introduction or deletion of a required field.

  • Changing the YAML format.

  • New features.

Minor Version

The APB spec will increment the minor version whenever a non-breaking change is introduced to the spec. Some examples include:

  • Introduction or deletion of an optional field.

  • Spelling change.

  • Introduction of new options to an existing field.

Dockerfile

The Dockerfile is what is used to actually build the APB image. As a result, sometimes you will need to customize it for your own needs. For example, if running a playbook that requires interactions with PostgreSQL, you may want to install the required packages by adding the yum install command:

FROM ansibleplaybookbundle/apb-base
MAINTAINER Ansible Playbook Bundle Community

LABEL "com.redhat.apb.spec"=\
"<------------base64-encoded-spec------------>"


COPY roles /opt/ansible/roles
COPY playbooks /opt/apb/actions
RUN chmod -R g=u /opt/{ansible,apb}


### INSTALL THE REQUIRED PACKAGES
RUN yum -y install python-boto postgresql && yum clean all

USER apb

APB Actions (Playbooks)

An action for an APB is the command that the APB is run with. The standard actions that are supported are:

  • provision

  • deprovision

  • bind

  • unbind

  • test

For an action to be valid, there must be a valid file in the playbooks/ directory named <action>.yml. These playbooks can do anything, which also means that you can technically create any action you would like. For example, the mediawiki-apb has playbook creating an update action.

Most APBs will normally have a provision action to create resources and a deprovision action to destroy the resources when deleting the service.

The bind and unbind actions are used when the coordinates of one service needs to be made available to another service. This is often the case when creating a data service and making it available to an application. Currently, the coordinates are made available during the provision.

To properly make your coordinates available to another service, use the asb_encode_binding module. This module should be called at the end of the APB’s provision role, and it will return bind credentials to the OpenShift Ansible broker (OAB):

- name: encode bind credentials
  asb_encode_binding:
    fields:
      EXAMPLE_FIELD: foo
      EXAMPLE_FIELD2: foo2

Working With Common Resources

This section describes a list of common OpenShift Origin resources that are created when developing APBs. See the Ansible Kubernetes Module for a full list of available resource modules.

Service

The following is a sample Ansible task to create a service named hello-world. The namespace variable in an APB will be provided by the OAB when launched from the web console.

Provision
- name: create hello-world service
  k8s_v1_service:
    name: hello-world
    namespace: '{{ namespace }}'
    labels:
      app: hello-world
      service: hello-world
    selector:
      app: hello-world
      service: hello-world
    ports:
      - name: web
        port: 8080
        target_port: 8080
Deprovision
- k8s_v1_service:
    name: hello-world
    namespace: '{{ namespace }}'
    state: absent

Deployment Configuration

The following is a sample Ansible task to create a deployment configuration for the image docker.io/ansibleplaybookbundle/hello-world which maps to service hello-world.

Provision
- name: create deployment config
  openshift_v1_deployment_config:
    name: hello-world
    namespace: '{{ namespace }}'
    labels:
      app: hello-world
      service: hello-world
    replicas: 1
    selector:
      app: hello-world
      service: hello-world
    spec_template_metadata_labels:
      app: hello-world
      service: hello-world
    containers:
    - env:
      image: docker.io/ansibleplaybookbundle/hello-world:latest
      name: hello-world
      ports:
      - container_port: 8080
        protocol: TCP
Deprovision
- openshift_v1_deployment_config:
    name: hello-world
    namespace: '{{ namespace }}'
    state: absent

Route

The following is an example of creating a route named hello-world which maps to the service hello-world.

Provision
- name: create hello-world route
  openshift_v1_route:
    name: hello-world
    namespace: '{{ namespace }}'
    spec_port_target_port: web
    labels:
      app: hello-world
      service: hello-world
    to_name: hello-world
Deprovision
- openshift_v1_route:
    name: hello-world
    namespace: '{{ namespace }}'
    state: absent

Persistent Volume

The following is an example of creating a persistent volume claim (PVC) resource and deployment configuration that uses it.

Provision
# Persistent volume resource
- name: create volume claim
  k8s_v1_persistent_volume_claim:
    name: hello-world-db
    namespace: '{{ namespace }}'
    state: present
    access_modes:
      - ReadWriteOnce
    resources_requests:
      storage: 1Gi

In addition to the resource, add your volume to the deployment configuration declaration:

- name: create hello-world-db deployment config
  openshift_v1_deployment_config:
    name: hello-world-db
    ---
    volumes:
    - name: hello-world-db
      persistent_volume_claim:
        claim_name: hello-world-db
      test: false
      triggers:
      - type: ConfigChange
Deprovision
- openshift_v1_deployment_config:
    name: hello-world-db
    namespace: '{{ namespace }}'
    state: absent

- k8s_v1_persistent_volume_claim:
    name: hello-world-db
    namespace: '{{ namespace }}'
    state: absent

Optional Variables

You can add optional variables to an APB by using environment variables. To pass variables into an APB, you must escape the variable substitution in your .yml files.

For example, consider the following roles/provision-etherpad-apb/tasks/main.yml file in the etherpad-apb:

- name: create mariadb deployment config
  openshift_v1_deployment_config:
    name: mariadb
    namespace: '{{ namespace }}'
    ...
    - env:
      - name: MYSQL_ROOT_PASSWORD
        value: '{{ mariadb_root_password }}'
      - name: MYSQL_DATABASE
        value: '{{ mariadb_name }}'
      - name: MYSQL_USER
        value: '{{ mariadb_user }}'
      - name: MYSQL_PASSWORD
        value: '{{ mariadb_password }}'

Variables for the APB are defined in the roles/provision-etherpad-apb/defaults/main.yml file:

playbook_debug: no
mariadb_root_password: "{{ lookup('env','MYSQL_ROOT_PASSWORD') | default('admin', true) }}"
mariadb_name: "{{ lookup('env','MYSQL_DATABASE') | default('etherpad', true) }}"
mariadb_user: "{{ lookup('env','MYSQL_USER') | default('etherpad', true) }}"
mariadb_password: "{{ lookup('env','MYSQL_PASSWORD') | default('admin', true) }}"
etherpad_admin_password: "{{ lookup('env','ETHERPAD_ADMIN_PASSWORD') | default('admin', true) }}"
etherpad_admin_user: "{{ lookup('env','ETHERPAD_ADMIN_USER') | default('etherpad', true) }}"
etherpad_db_host: "{{ lookup('env','ETHERPAD_DB_HOST') | default('mariadb', true) }}"
state: present

Working With the Restricted SCC

When building an OpenShift Origin image, it is important that you do not have your application running as the root user when at all possible. When running under the restriced security context, the application image is launched with a random UID. This causes problems if your application folder is owned by the root user.

A good way to work around this is to add a user to the root group and make the application folder owned by the root group. See OpenShift Origin-Specific Guidelines for details on supporting arbitrary user IDs.

The following is a Dockerfile example of a node application running in /usr/src. This command would be run after the application is installed in /usr/src and the associated environment variables set:

ENV USER_NAME=haste \
    USER_UID=1001 \
    HOME=/usr/src

RUN useradd -u ${USER_UID} -r -g 0 -M -d /usr/src -b /usr/src -s /sbin/nologin -c "<username> user" ${USER_NAME} \
               && chown -R ${USER_NAME}:0 /usr/src \
               && chmod -R g=u /usr/src /etc/passwd
USER 1001

Using a ConfigMap Within an APB

There is a temporary workaround for creating ConfigMaps from Ansible due to a bug in the Ansible modules.

One common use case for ConfigMaps is when the parameters of an APB will be used within a configuration file of an application or service. The ConfigMap module allows you to mount a ConfigMap into a pod as a volume, which can be used to store the configuration file. This approach allows you to also leverage the power of Ansible’s template module to create a ConfigMap out of APB paramters.

The following is an example of creating a ConfigMap from a Jinja template mounted into a pod as a volume:

- name: Create hastebin config from template
  template:
    src: config.js.j2
    dest: /tmp/config.js

- name: Create hastebin configmap
  shell: oc create configmap haste-config --from-file=haste-config=/tmp/config.js

<snip>

- name: create deployment config
  openshift_v1_deployment_config:
    name: hastebin
    namespace: '{{ namespace }}'
    labels:
      app: hastebin
      service: hastebin
    replicas: 1
    selector:
      app: hastebin
      service: hastebin
    spec_template_metadata_labels:
      app: hastebin
      service: hastebin
    containers:
    - env:
      image: docker.io/dymurray/hastebin:latest
      name: hastebin
      ports:
      - container_port: 7777
        protocol: TCP
      volumeMounts:
        - mountPath: /usr/src/haste-server/config
          name: config
    - env:
      image: docker.io/modularitycontainers/memcached:latest
      name: memcached
      ports:
      - container_port: 11211
        protocol: TCP
    volumes:
      - name: config
        configMap:
          name: haste-config
          items:
            - key: haste-config
              path: config.js