One place for hosting & domains

      Module

      How to use the Linode Ansible Module to Deploy Linodes


      Updated by Linode Contributed by Linode

      Ansible is a popular open-source tool that can be used to automate common IT tasks, like cloud provisioning and configuration management. With Ansible’s 2.8 release, you can deploy Linode instances using our latest API (v4). Ansible’s linode_v4 module adds the functionality needed to deploy and manage Linodes via the command line or in your Ansible Playbooks. While the dynamic inventory plugin for Linode helps you source your Ansible inventory directly from the Linode API (v4).

      In this guide you will learn how to:

      • Deploy and manage Linodes using Ansible and the linode_v4 module.
      • Create an Ansible inventory for your Linode infrastructure using the dynamic inventory plugin for Linode.

      Caution

      This guide’s example instructions will create a 1 GB Nanode billable resource on your Linode account. If you do not want to keep using the Nanode that you create, be sure to delete the resource when you have finished the guide.

      If you remove the resource afterward, you will only be billed for the hour(s) that the resources were present on your account.

      Before You Begin

      Note

      Configure Ansible

      The Ansible configuration file is used to adjust Ansible’s default system settings. Ansible will search for a configuration file in the directories listed below, in the order specified, and apply the first configuration values it finds:

      • ANSIBLE_CONFIG environment variable pointing to a configuration file location. If passed, it will override the default Ansible configuration file.
      • ansible.cfg file in the current directory
      • ~/.ansible.cfg in the home directory
      • /etc/ansible/ansible.cfg

      In this section, you will create an Ansible configuration file and add options to disable host key checking, and to whitelist the Linode inventory plugin. The Ansible configuration file will be located in a development directory that you create, however, it could exist in any of the locations listed above. See Ansible’s official documentation for a full list of available configuration settings.

      Caution

      When storing your Ansible configuration file, ensure that its corresponding directory does not have world-writable permissions. This could pose a security risk that allows malicious users to use Ansible to exploit your local system and remote infrastructure. At minimum, the directory should restrict access to particular users and groups. For example, you can create an ansible group, only add privileged users to the ansible group, and update the Ansible configuration file’s directory to have 764 permissions. See the Linux Users and Groups guide for more information on permissions.
      1. In your home directory, create a directory to hold all of your Ansible related files and move into the directory:

        mkdir development && cd development
        
      2. Create the Ansible configuration file, ansible.cfg in the development directory and add the host_key_checking and enable_plugins options.

        ~/development/ansible.cfg
        1
        2
        3
        4
        5
        6
        
        [defaults]
        host_key_checking = False
        VAULT_PASSWORD_FILE = ./vault-pass
        [inventory]
        enable_plugins = linode
              
        • host_key_checking = False will allow Ansible to SSH into hosts without having to accept the remote server’s host key. This will disable host key checking globally.
        • VAULT_PASSWORD_FILE = ./vault-pass is used to specify a Vault password file to use whenever Ansible Vault requires a password. Ansible Vault offers several options for password management. To learn more password management, read Ansible’s Providing Vault Passwords documentation.
        • enable_plugins = linode enables the Linode dynamic inventory plugin.

      Create a Linode Instance

      You can now begin creating Linode instances using Ansible. In this section, you will create an Ansible Playbook that can deploy Linodes.

      Create your Linode Playbook

      1. Ensure you are in the development directory that you created in the Configure Ansible section:

        cd ~/development
        
      2. Using your preferred text editor, create the Create Linode Playbook file and include the following values:

        ~/development/linode_create.yml
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        
        - name: Create Linode
          hosts: localhost
          vars_files:
              - ./group_vars/example_group/vars
          tasks:
          - name: Create a new Linode.
            linode_v4:
              label: "{{ label }}{{ 100 |random }}"
              access_token: "{{ token }}"
              type: g6-nanode-1
              region: us-east
              image: linode/debian9
              root_pass: "{{ password }}"
              authorized_keys: "{{ ssh_keys }}"
              group: example_group
              tags: example_group
              state: present
            register: my_linode
            
        • The Playbook my_linode contains the Create Linode play, which will be executed on hosts: localhost. This means the Ansible playbook will execute on the local system and use it as a vehicle to deploy the remote Linode instances.
        • The vars_files key provides the location of a local file that contains variable values to populate in the play. The value of any variables defined in the vars file will substitute any Jinja template variables used in the Playbook. Jinja template variables are any variables between curly brackets, like: {{ my_var }}.
        • The Create a new Linode task calls the linode_v4 module and provides all required module parameters as arguments, plus additional arguments to configure the Linode’s deployment. For details on each parameter, see the linode_v4 Module Parameters section.

          Note

          Usage of groups is deprecated, but still supported by Linode’s API v4. The Linode dynamic inventory module requires groups to generate an Ansible inventory and will be used later in this guide.
        • Theregister keyword defines a variable name, my_linode that will store linode_v4 module return data. For instance, you could reference the my_linode variable later in your Playbook to complete other actions using data about your Linode. This keyword is not required to deploy a Linode instance, but represents a common way to declare and use variables in Ansible Playbooks. The task in the snippet below will use Ansible’s debug module and the my_linode variable to print out a message with the Linode instance’s ID and IPv4 address during Playbook execution.

          1
          2
          3
          4
          5
          
          ...
          - name: Print info about my Linode instance
              debug:
                msg: "ID is {{ my_linode.instance.id }} IP is {{ my_linode.instance.ipv4 }}"
                  

      Create the Variables File

      In the previous section, you created the Create Linode Playbook to deploy Linode instances and made use of Jinja template variables. In this section, you will create the variables file to provide values to those template variables.

      1. Create the directory to store your Playbook’s variable files. The directory is structured to group your variable files by inventory group. This directory structure supports the use of file level encryption that Ansible Vault can detect and parse. Although it is not relevant to this guide’s example, it will be used as a best practice.

        mkdir -p ~/development/group_vars/example_group/
        
      2. Create the variables file and populate it with the example variables. You can replace the values with your own.

        ~/development/group_vars/example_group/vars
        1
        2
        3
        4
        
        ssh_keys: >
                ['ssh-rsa AAAAB3N..5bYqyRaQ== user@mycomputer', '~/.ssh/id_rsa.pub']
        label: simple-linode-
            
        • The ssh_keys example passes a list of two public SSH keys. The first provides the string value of the key, while the second provides a local public key file location.

          Configure your SSH Agent

          If your SSH Keys are passphrase-protected, you should add the keys to your SSH agent so that Ansible does not hang when running Playbooks on the remote Linode. The following instructions are for Linux systems:

          1. Run the following command; if you stored your private key in another location, update the path that’s passed to ssh-add accordingly:

            eval $(ssh-agent) && ssh-add ~/.ssh/id_rsa
            

            If you start a new terminal, you will need to run the commands in this step again before having access to the keys stored in your SSH agent.

        • label provides a label prefix that will be concatenated with a random number. This occurs when the Create Linode Playbook’s Jinja templating for the label argument is parsed (label: "{{ label }}{{ 100 |random }}").

      Encrypt Sensitive Variables with Ansible Vault

      Ansible Vault allows you to encrypt sensitive data, like passwords or tokens, to keep them from being exposed in your Ansible Playbooks or Roles. You will take advantage of this functionality to keep your Linode instance’s password and access_token encrypted within the variables file.

      Note

      Ansible Vault can also encrypt entire files containing sensitive values. View Ansible’s documentation on Vault for more information.
      1. Create your Ansible Vault password file and add your password to the file. Remember the location of the password file was configured in the ansible.cfg file in the Configure Ansible section of this guide.

        ~/development/vault-pass
        1
        2
        
        My.ANS1BLEvault-c00lPassw0rd
            
      2. Encrypt the value of your Linode’s root user password using Ansible Vault. Replace My.c00lPassw0rd with your own strong password that conforms to the root_pass parameter’s constraints.

        ansible-vault encrypt_string 'My.c00lPassw0rd' --name 'password'
        

        You will see a similar output:

          
              password: !vault |
                  $ANSIBLE_VAULT;1.1;AES256
                  30376134633639613832373335313062366536313334316465303462656664333064373933393831
                  3432313261613532346134633761316363363535326333360a626431376265373133653535373238
                  38323166666665376366663964343830633462623537623065356364343831316439396462343935
                  6233646239363434380a383433643763373066633535366137346638613261353064353466303734
                  3833
        Encryption successful
      3. Copy the generated output and add it to your vars file.

      4. Encrypt the value of your access token. Replace the value of 86210...1e1c6bd with your own access token.

        ansible-vault encrypt_string '86210...1e1c6bd' --name 'token'
        
      5. Copy the generated output and append it to the bottom of your vars file.

        The final vars file should resemble the example below:

        ~/development/group_vars/example_group/vars
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        
        ssh_keys: >
                ['ssh-rsa AAAAB3N..5bYqyRaQ== user@mycomputer', '~/.ssh/id_rsa.pub']
        label: simple-linode-
        password: !vault |
                  $ANSIBLE_VAULT;1.1;AES256
                  30376134633639613832373335313062366536313334316465303462656664333064373933393831
                  3432313261613532346134633761316363363535326333360a626431376265373133653535373238
                  38323166666665376366663964343830633462623537623065356364343831316439396462343935
                  6233646239363434380a383433643763373066633535366137346638613261353064353466303734
                  3833
        token: !vault |
                  $ANSIBLE_VAULT;1.1;AES256
                  65363565316233613963653465613661316134333164623962643834383632646439306566623061
                  3938393939373039373135663239633162336530373738300a316661373731623538306164363434
                  31656434356431353734666633656534343237333662613036653137396235353833313430626534
                  3330323437653835660a303865636365303532373864613632323930343265343665393432326231
                  61313635653463333630636631336539643430326662373137303166303739616262643338373834
                  34613532353031333731336339396233623533326130376431346462633832353432316163373833
                  35316333626530643736636332323161353139306533633961376432623161626132353933373661
                  36663135323664663130
            

      Run the Ansible Playbook

      You are now ready to run the Create Linode Playbook. When you run the Playbook, a 1 GB Nanode will be deployed in the Newark data center. Note: you want to run Ansible commands from the directory where your ansible.cfg file is located.

      1. Run your playbook to create your Linode instances.

        ansible-playbook ~/development/linode_create.yml
        

        You will see a similar output:

        PLAY [Create Linode] *********************************************************************
        
        TASK [Gathering Facts] *******************************************************************
        ok: [localhost]
        
        TASK [Create a new Linode.] **************************************************************
        changed: [localhost]
        
        PLAY RECAP *******************************************************************************
        localhost                  : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
        

      linode_v4 Module Parameters

      Parameter Data type/Status Usage
      access_token string, required Your Linode API v4 access token. The token should have permission to read and write Linodes. The token can also be specified by exposing the LINODE_ACCESS_TOKEN environment variable.
      authorized_keys list A list of SSH public keys or SSH public key file locations on your local system, for example, ['averylongstring','~/.ssh/id_rsa.pub']. The public key will be stored in the /root/.ssh/authorized_keys file on your Linode. Ansible will use the public key to SSH into your Linodes as the root user and execute your Playbooks.
      group string, deprecated The Linode instance’s group. Please note, group labelling is deprecated but still supported. The encouraged method for marking instances is to use tags. This parameter must be provided to use the Linode dynamic inventory module.
      image string The Image ID to deploy the Linode disk from. Official Linode Images start with linode/, while your private images start with private/. For example, use linode/ubuntu18.04 to deploy a Linode instance with the Ubuntu 18.04 image. This is a required parameter only when creating Linode instances.

      To view a list of all available Linode images, issue the following command:

      curl https://api.linode.com/v4/images.

      label string, required The Linode instance label. The label is used by the module as the main determiner for idempotence and must be a unique value.

      Linode labels have the following constraints:

      • Must start with an alpha character.
      • May only consist of alphanumeric characters, dashes (-), underscores (_) or periods (.).
      • Cannot have two dashes (–), underscores (__) or periods (..) in a row.

      region string The region where the Linode will be located. This is a required parameter only when creating Linode instances.

      To view a list of all available regions, issue the following command:

      curl https://api.linode.com/v4/regions.

      root_pass string The password for the root user. If not specified, will be generated. This generated password will be available in the task success JSON.

      The root password must conform to the following constraints:

      • May only use alphanumerics, punctuation, spaces, and tabs.
      • Must contain at least two of the following characters classes: upper-case letters, lower-case letters, digits, punctuation.

      state string, required The desired instance state. The accepted values are absent and present.
      tags list The user-defined labels attached to Linodes. Tags are used for grouping Linodes in a way that is relevant to the user.
      type string, The Linode instance’s plan type. The plan type determines your Linode’s hardware resources and its pricing.

      To view a list of all available Linode types including pricing and specifications for each type, issue the following command:

      curl https://api.linode.com/v4/linode/types.

      The Linode Dynamic Inventory Plugin

      Ansible uses inventories to manage different hosts that make up your infrastructure. This allows you to execute tasks on specific parts of your infrastructure. By default, Ansible will look in /etc/ansible/hosts for an inventory, however, you can designate a different location for your inventory file and use multiple inventory files that represent your infrastructure. To support infrastructures that shift over time, Ansible offers the ability to track inventory from dynamic sources, like cloud providers. The Ansible dynamic inventory plugin for Linode can be used to source your inventory from Linode’s API v4. In this section, you will use the Linode plugin to source your Ansible deployed Linode inventory.

      Note

      The dynamic inventory plugin for Linode was enabled in the Ansible configuration file created in the Configure Ansible section of this guide.

      Configure the Plugin

      1. Configure the Ansible dynamic inventory plugin for Linode by creating a file named linode.yml.

        ~/development/linode.yml
        1
        2
        3
        4
        5
        6
        7
        
        plugin: linode
        regions:
          - us-east
        groups:
          - example_group
        types:
          - g6-nanode-1
        • The configuration file will create an inventory for any Linodes on your account that are in the us-east region, part of the example_group group and of type g6-nanode-1. Any Linodes that are not part of the example_group group, but that fulfill the us-east region and g6-nanode-type type will be displayed as ungrouped. All other Linodes will be excluded from the dynamic inventory. For more information on all supported parameters, see the Plugin Parameters section.

      Run the Inventory Plugin

      1. Export your Linode API v4 access token to the shell environment. LINODE_ACCESS_TOKEN must be used as the environment variable name. Replace mytoken with your own access token.

        export LINODE_ACCESS_TOKEN='mytoken'
        
      2. Run the Linode dynamic inventory plugin.

        ansible-inventory -i ~/development/linode.yml --graph
        

        You should see a similar output. The output may vary depending on the Linodes already deployed to your account and the parameter values you pass.

        @all:
        |--@example_group:
        |  |--simple-linode-29
        

        For a more detailed output including all Linode instance configurations, issue the following command:

        ansible-inventory -i ~/development/linode.yml --graph --vars
        
      3. Before you can communicate with your Linode instances using the dynamic inventory plugin, you will need to add your Linode’s IPv4 address and label to your /etc/hosts file.

        The Linode Dynamic Inventory Plugin assumes that the Linodes in your account have labels that correspond to hostnames that are in your resolver search path, /etc/hosts. This means you will have to create an entry in your /etc/hosts file to map the Linode’s IPv4 address to its hostname.

        Note

        A pull request currently exists to support using a public IP, private IP or hostname. This change will enable the inventory plugin to be used with infrastructure that does not have DNS hostnames or hostnames that match Linode labels.

        To add your deployed Linode instance to the /etc/hosts file:

        • Retrieve your Linode instance’s IPv4 address:

          ansible-inventory -i ~/development/linode.yml --graph --vars | grep 'ipv4|simple-linode'
          

          Your output will resemble the following:

          |  |--simple-linode-36
          |  |  |--{ipv4 = [u'192.0.2.0']}
          |  |  |--{label = simple-linode-36}
          
        • Open the /etc/hosts file and add your Linode’s IPv4 address and label:

          /etc/hosts
          1
          2
          3
          
          127.0.0.1       localhost
          192.0.2.0 simple-linode-29
                    
      4. Verify that you can communicate with your grouped inventory by pinging the Linodes. The ping command will use the dynamic inventory plugin configuration file to target example_group. The u root option will run the command as root on the Linode hosts.

        ansible -m ping example_group -i ~/development/linode.yml -u root
        

        You should see a similar output:

        simple-linode-29 | SUCCESS => {
            "ansible_facts": {
                "discovered_interpreter_python": "/usr/bin/python"
            },
            "changed": false,
            "ping": "pong"
        }
        

      Plugin Parameters

      Parameter Data type/Status Usage
      access_token string, required Your Linode API v4 access token. The token should have permission to read and write Linodes. The token can also be specified by exposing the LINODE_ACCESS_TOKEN environment variable.
      plugin string, required The plugin name. The value must always be linode in order to use the dynamic inventory plugin for Linode.
      regions list The Linode region with which to populate the inventory. For example, us-east is possible value for this parameter.

      To view a list of all available Linode images, issue the following command:

      curl https://api.linode.com/v4/images.

      types list The Linode type with which to populate the inventory. For example, g6-nanode-1 is a possible value for this parameter.

      To view a list of all available Linode types including pricing and specifications for each type, issue the following command:

      curl https://api.linode.com/v4/linode/types.

      groups list The Linode group with which to populate the inventory. Please note, group labelling is deprecated but still supported. The encouraged method for marking instances is to use tags. This parameter must be provided to use the Linode dynamic inventory module.

      Delete Your Resources

      1. To delete the Linode instance created in this guide, create a Delete Linode Playbook with the following content in the example. Replace the value of label with your Linode’s label:

        ~/development/linode_delete.yml
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        
        - name: Delete Linode
          hosts: localhost
          vars_files:
            - ./group_vars/example_group/vars
          tasks:
          - name: Delete your Linode Instance.
            linode_v4:
              label: simple-linode-29
              state: absent
              
      2. Run the Delete Linode Playbook:

        ansible-playbook ~/development/linode_delete.yml
        

      More Information

      You may wish to consult the following resources for additional information on this topic. While these are provided in the hope that they will be useful, please note that we cannot vouch for the accuracy or timeliness of externally hosted materials.

      Find answers, ask questions, and help others.

      This guide is published under a CC BY-ND 4.0 license.



      Source link

      Create a Terraform Module


      Updated by Linode Contributed by Linode

      Terraform modules allow you to better organize your configuration code and make the code reusable. You can host your Terraform modules on remote version control services, like GitHub, for others to use. The Terraform Module Registry hosts community modules that you can reuse for your own Terraform configurations, or you can publish your own modules for consumption by the Terraform community.

      In this guide you will create a Linode StackScripts module. This module will deploy a Linode instance from a StackScript you will create. This module will include nested modules that split up the required resources between the root module, a linode_instance module, and a stackscripts module.

      Before You Begin

      1. Install Terraform on your local computer using the steps found in the Install Terraform section of the Use Terraform to Provision Linode Environments guide. Your Terraform project directory should be named linode_stackscripts.

      2. Terraform requires an API access token. Follow the Getting Started with the Linode API guide to obtain a token.

      3. Complete the steps in the Configure Git section of the Getting Started with Git guide.

      4. Review Deploy a WordPress Site using Terraform and StackScripts to familiarize yourself with the Linode provider’s StackScript resource.

      Standard Module Structure

      Terraform’s standard module structure provides guidance on file and directory layouts for reusable modules. If you would like to make your module public to the Terraform community, the recommended layout allows Terraform to generate documentation and index modules for the Terraform Module Registry.

      • The primary module structure requirement is that a root module must exist. The root module is the directory that holds the Terraform configuration files that are applied to build your desired infrastructure. These files provide an entry point into any nested modules you might utilize.

      • Any module should include, at minimum, a main.tf, a variables.tf, and an outputs.tf file. This naming convention is recommended, but not enforced.

        • If using nested modules to split up your infrastructure’s required resources, the main.tf file holds all your module blocks and any needed resources not contained within your nested modules. A simple module’s main.tf file, without any nested modules, declares all resources within this file.

        • The variables.tf and outputs.tf files contain input variable and output variable declarations. All variables and outputs should include descriptions.

      • If using nested modules, they should be located in a root module’s subdirectory named modules/.

      • If your modules will be hosted on Terraform’s Module Registry, root modules and any nested modules should contain a README.MD file with a description that explains the module’s intended use.

      • You can provide examples in a root module’s subdirectory named examples.

      Create the Linode StackScripts Module

      The Linode Stackscripts module will included two nested modules that split up the required resources between the root module, a linodes module, and a stackscripts module. When you are done creating all required Terraform files your directory structure will look as follows:

        
      linode_stackscripts/
      ├── main.tf
      ├── outputs.tf
      ├── secrets.tfvars
      ├── terraform
      ├── terraform.tfvars
      ├── variables.tf
      └── modules/
          ├── linodes/
          │   ├── main.tf
          │   ├── variables.tf
          │   └── outputs.tf
          └── stackscripts/
              ├── main.tf
              ├── variables.tf
              └── outputs.tf
      
      

      Note

      Your linode_stackscripts directory will likely contain other files related to the Terraform installation you completed prior to beginning the steps in this guide.

      Create the Linodes Module

      In this section, you will create the linodes module which will be in charge of creating your Linode instance. This module contains a main.tf file and corresponding variables.tf and outputs.tf files.

      1. If your Terraform project directory is not named linode_stackscripts, rename it before beginning and move into that directory:

        mv terraform linode_stackscripts
        cd linode_stackscripts
        
      2. Create the modules and linodes subdirectories:

        mkdir -p modules/linodes
        
      3. Using your preferred text editor, create a main.tf file in modules/linodes/ with the following resources:

        linode_stackscripts/modules/linodes/main.tf
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        
        locals {
            key ="${var.key}"
        }
        
        resource "linode_sshkey" "main_key" {
            label = "${var.key_label}"
            ssh_key = "${chomp(file(local.key))}"
        }
        
        resource "linode_instance" "linode_id" {
            image = "${var.image}"
            label = "${var.label}"
            region = "${var.region}"
            type = "${var.type}"
            authorized_keys = [ "${linode_sshkey.main_key.ssh_key}" ]
            root_pass = "${var.root_pass}"
            stackscript_id = "${var.stackscript_id}"
            stackscript_data = "${var.stackscript_data}"
        }

        The main.tf file declares a linode_instance resource that deploys a Linode using a StackScript. Notice that all argument values use interpolation syntax to access variable values. You will declare the variables next and provide the variable values in the root module’s terraform.tfvars file. Using separate files for variable declaration and assignment parameterizes your configurations and allows them to be reused as modules.

        Let’s take a closer look at each block in the main.tf configuration file.

        1
        2
        3
        4
        5
        6
        7
        8
        
        locals {
            key ="${var.key}"
        }
        
        resource "linode_sshkey" "main_key" {
            label = "${var.key_label}"
            ssh_key = "${chomp(file(local.key))}"
        }
        • The locals stanza declares a local variable key whose value will be provided by an input variable.

        • The linode_sshkey resource will create Linode SSH Keys tied to your Linode account. These keys can be reused for future Linode deployments once the resource has been created. ssh_key = "${chomp(file(local.key))}" uses Terraform’s built-in function file() to provide a local file path to the public SSH key’s location. The location of the file path is the value of the local variable key. The chomp() built-in function removes trailing new lines from the SSH key.

          Note

          If you do not already have SSH keys, follow the steps in the Create an Authentication Key-pair section of the Securing Your Server Guide.
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        
        resource "linode_instance" "linode_id" {
            image = "${var.image}"
            label = "${var.label}"
            region = "${var.region}"
            type = "${var.type}"
            authorized_keys = [ "${linode_sshkey.main_key.ssh_key}" ]
            root_pass = "${var.root_pass}"
            stackscript_id = "${var.stackscript_id}"
            stackscript_data = "${var.stackscript_data}"
        }

        The linode_instance resource creates a Linode instance with the listed arguments. Please note the following information:

        • The authorized_keys argument uses the SSH public key provided by the linode_sshkey resource in the previous stanza. This argument expects a value of type list, so the value must be wrapped in brackets.

        • To use an existing Linode StackScript you must use the stackscript_id argument and provide a valid ID as a value. Every StackScript is assigned a unique ID upon creation. Later on in the guide, you will create your own StackScript and expose its ID as an output variable in order to use its ID to deploy your Linode instance.

        • StackScripts support user defined data. This means a StackScript can use the UDF tag to create a variable whose value must be provided by the user of the script. This allows users to customize the behavior of a StackScript on a per-deployment basis. Any required UDF variable can be defined using the stackscript_data argument.

      4. Create the variables.tf file to define your resource’s required variables:

        linode_stackscripts/modules/linodes/variables.tf
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        
        variable "key" {
          description = "Public SSH Key's path."
        }
        
        variable "key_label" {
          description = "new SSH key label"
        }
        
        variable "image" {
          description = "Image to use for Linode instance"
          default = "linode/ubuntu18.04"
        }
        
        variable "label" {
          description = "The Linode's label is for display purposes only, but must be unique."
          default = "default-linode"
        }
        
        variable "region" {
          description = "The region where your Linode will be located."
          default = "us-east"
        }
        
        variable "type" {
          description = "Your Linode's plan type."
          default = "g6-standard-1"
        }
        
        variable "authorized_keys" {
          description = "SSH Keys to use for the Linode."
          type = "list"
        }
        
        variable "root_pass" {
          description = "Your Linode's root user's password."
        }
        
        variable "stackscript_id" {
          description = "Stackscript ID."
        }
        
        variable "stackscript_data" {
          description = "Map of required StackScript UDF data."
          type = "map"
        }
        • Modules must include a description for each input variable to help document your configuration’s usage. This will make it easier for anyone else to use this module.

        • Every variable can contain a default value. The default value is only used if no other value is provided. For example, if you have a favorite Linux distribution, you may want to provide it as your image variable’s default value. In this case, linode/ubuntu18.04 is set as the default value.

        • You can declare a type for each variable. If no type is provided, the variable will default to type = "string".

        • Notice that the stackscript_data variable is of type = "map". This will allow you to provide values for as many UDF variables as your StackScript requires.

      5. Create the outputs.tf file:

        ~/linode_stackscripts/modules/linodes/outputs.tf
        1
        2
        3
        
        output "sshkey_linode" {
          value = "${linode_sshkey.main_key.ssh_key}"
        }

        The outputs.tf file exposes any values from the resources you declared in the main.tf file. Any exposed values can be used by any other module within the root module. The sshkey_linode output variable exposes the linode_sshkey resource’s public key.

      Now that the linodes module is complete, in the next section, you will create the stackscripts module.

      Create the StackScripts Module

      In this section you will create the StackScripts module. This module creates a linode_stackscripts resource which you can use to create and modify your own Linode StackScript.

      1. Ensure you are in the linode_stackscripts directory and create the stackscripts subdirectory:

        mkdir modules/stackscripts
        
      2. Using your preferred text editor, create a main.tf file in modules/stackscripts/ with the following resource:

        ~/linode_stackscripts/modules/stackscripts/main.tf
        1
        2
        3
        4
        5
        6
        7
        
        resource "linode_stackscript" "default" {
          label = "${var.stackscript_label}"
          description = "${var.description}"
          script = "${var.stackscript}"
          images = [ "${var.stackscript_image}" ]
          rev_note = "${var.rev_note}"
        }

        The main.tf file creates the linode_stackscript resource and provides the required configurations. All argument values use interpolation syntax to access input variable values. You will declare the input variables next and provide the variable values in the root module’s terraform.tfvars file. For more information on StackScripts see the Automate Deployments with StackScripts guide and the Linode APIv4 documentation.

      3. Create the variables.tf file to define your resource’s required variables:

        ~/linode_stackscripts/modules/stackscripts/variables.tf
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        
        variable "stackscript_label" {
          description = "The StackScript's label is for display purposes only."
        }
        
        variable "description" {
          description = "A description for the StackScript."
        }
        
        variable "stackscript" {
          description = "The script to execute when provisioning a new Linode with this StackScript."
        }
        variable "stackscript_image" {
          description = " A list of Image IDs representing the Images that this StackScript is compatible for deploying with."
          type = "list"
        }
        variable "rev_note" {
          description = "This field allows you to add notes for the set of revisions made to this StackScript."
        }
      4. Create the outputs.tf file:

        ~/linode_stackscripts/modules/stackscripts/output.tf
        1
        2
        3
        
        output "stackscript_id" {
          value = "${linode_stackscript.default.id}"
        }

        The outputs.tf file exposes the value of the linode_stackscript resource’s ID. Every StackScript is assigned a unique ID upon creation. You will need this ID when creating your root module.

      You have now created the StackScripts module and are ready to use both modules within the root module. You will complete this work in the next section.

      Create the Root Module

      The root module will call the linode and stackscripts modules, satisfy their required variables and then apply those configurations to build your desired infrastructure. These configurations deploy a Linode based on a StackScript you will define in this section. When using nested modules, the modules will be hidden from your root configuration, so you’ll have to re-expose any variables and outputs you require.

      1. Ensure you are in the linode_stackscripts directory and create the main.tf file:

        linode_stackscripts/main.tf
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        
        provider "linode" {
            token = "${var.token}"
        }
        
        module "stackscripts" {
            source = "./modules/stackscripts"
            stackscript_label = "${var.stackscript_label}"
            description = "${var.description}"
            stackscript = "${var.stackscript}"
            stackscript_image = [ "${var.stackscript_image}" ]
            rev_note = "${var.rev_note}"
        }
        
        module "linodes" {
            source = "./modules/linodes"
            key = "${var.key}"
            key_label = "${var.key_label}"
            image = "${var.image}"
            label = "${var.label}"
            region = "${var.region}"
            type = "${var.type}"
            root_pass = "${var.root_pass}"
            authorized_keys = [ "${module.linodes.sshkey_linode}" ]
            stackscript_id = "${module.stackscripts.stackscript_id}"
            stackscript_data = "${var.stackscript_data}"
        }

        The main.tf file uses the linodes and stackscripts modules that were created in the previous sections and provides the required arguments. All argument values use interpolation syntax to access variable values, which you will declare in a variables.tf file and then provide corresponding values for in a terraform.tfvars file.

        Let’s review each block:

        1
        2
        3
        
        provider "linode" {
            token = "${var.token}"
        }

        The first stanza declares Linode as the provider that will manage the lifecycle of any resources declared throughout the configuration file. The Linode provider requires your Linode APIv4 token for authentication.

        1
        2
        3
        4
        5
        6
        7
        8
        
        module "stackscripts" {
            source = "./modules/stackscripts"
            stackscript_label = "${var.stackscript_label}"
            description = "${var.description}"
            stackscript = "${var.stackscript}"
            stackscript_image = [ "${var.stackscript_image}" ]
            rev_note = "${var.rev_note}"
        }

        The next stanza instructs Terraform to create an instance of the stackscripts module and instantiate any of the resources defined within the module. The source attribute provides the location of the child module’s source code and is required whenever you create an instance of a module. All other attributes are determined by the module. Notice that all the attributes included in the module block correspond to the linode_stackscript resource’s arguments declared in the main.tf file of the stackscripts module.

         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11
        12
        13
        14
        
        module "linodes" {
            source = "./modules/linodes"
            key = "${var.key}"
            key_label = "${var.key_label}"
            image = "${var.image}"
            label = "${var.label}"
            group = "${var.group}"
            region = "${var.region}"
            type = "${var.type}"
            root_pass = "${var.root_pass}"
            authorized_keys = [ "${module.linodes.sshkey_linode}" ]
            stackscript_id = "${module.stackscripts.stackscript_id}"
            stackscript_data = "${var.stackscript_data}"
        }

        This stanza creates an instance of the linodes module and then instantiates the resources you defined in the module. Notice that authorized_keys = [ "${module.linodes.sshkey_id}" ] and stackscript_id = "${module.stackscripts.stackscript_id}" both access values exposed as output variables by the linodes and stackscripts modules. Any module’s exposed output variables can be referenced in your root module’s main.tf file.

      2. Create the variables.tf file to declare the input variables required by the module instances:

        ~/linode_stackscripts/variables.tf
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        60
        
        variable "token" {
          description = " Linode API token"
        }
        
        variable "stackscript_label" {
          description = "The StackScript's label is for display purposes only."
        }
        
        variable "description" {
          description = "A description for the StackScript."
        }
        
        variable "stackscript" {
          description = "The script to execute when provisioning a new Linode with this StackScript."
        }
        
        variable "stackscript_image" {
          description = "A list of Image IDs representing the Images that this StackScript is compatible for deploying with."
        }
        
        variable "rev_note" {
          description = "This field allows you to add notes for the set of revisions made to this StackScript."
        }
        
        variable "key" {
          description = "Public SSH Key's path."
        }
        
        variable "key_label" {
          description = "New SSH key label."
        }
        
        variable "image" {
          description = "Image to use for Linode instance."
          default = "linode/ubuntu18.04"
        }
        
        variable "label" {
          description = "The Linode's label is for display purposes only, but must be unique."
          default = "default-linode"
        }
        
        variable "region" {
          description = "The region where your Linode will be located."
          default = "us-east"
        }
        
        variable "type" {
          description = "Your Linode's plan type."
          default = "g6-standard-1"
        }
        
        variable "root_pass" {
          description = "Your Linode's root user's password."
        }
        
        variable "stackscript_data" {
          description = "Map of required StackScript UDF data."
          type = "map"
        }
      3. Create the outputs.tf file:

        ~/linode_stackscripts/outputs.tf
        1
        2
        3
        
        output "stackscript_id" {
          value = "${module.stackscripts.stackscript_id}"
        }

        In the outputs.tf file you will re-expose the output variables exposed by the stackscripts module.

      4. Create the terraform.tfvars file to provide values for all input variables defined in the variables.tf file. This file will exclude any values that provide sensitive data, like passwords and API tokens. A file containing sensitive values will be created in the next step:

        ~/linode_stackscripts/terraform.tfvars
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        
        key = "~/.ssh/id_rsa.pub"
        key_label = "my-ssh-key"
        label = "my-linode"
        stackscript_data {
          my_username = "username"
          my_hostname = "linode-hostname"
        }
        stackscript_id = "base-ubuntu-deployment"
        stackscript_label = "base-ubuntu-deployment"
        description = "A base deployment for Ubuntu 18.04 that creates a limited user account."
        stackscript = <<EOF
        #!/bin/bash
        # <UDF name="my_hostname" Label="Linode's Hostname" />
        # <UDF name="my_username" Label="Limited user account" />
        # <UDF name="my_password" Label="Limited user account's password" />
        # <UDF name="my_userpubkey" Label="Limited user account's public key" />
        
        source <ssinclude StackScriptID="1">
        
        set -x
        
        MY_IP=system_primary_ip
        system_set_hostname "$MY_HOSTNAME"
        system_add_host_entry "$MY_IP" "$MY_HOSTNAME"
        user_add_sudo "$MY_USERNAME" "$MY_PASSWORD"
        user_add_pubkey "$MY_USERNAME" "$MY_USERPUBKEY"
        ssh_disable_root
        goodstuff
        EOF
        stackscript_image = "linode/ubuntu18.04"
        rev_note = "First revision of my StackScript created with the Linode Terraform provider."

        The terraform.tfvars file supplies all values required by the linodes and stackscripts modules. Ensure you replace any values with your own values when using this example file.

        The stackscript variable provides the actual contents of the StackScript you create. This example StackScript requires four UDF values: my_hostname, my_username, my_password, and my_userpubkey. The my_hostname and my_username values are supplied by the stackscript_data map. The my_password and my_userpubkey values will be provided in the next step.

        The StackScript will then use these values to create a limited user account; set a hostname; add a host entry; add the created user to the sudo group; disable SSH access for the root user; and install vim, wget, and less. This StackScript uses bash functions defined in the Linode Community StackScript Bash Library.

      5. Create a file named secrets.tfvars to hold any sensitive values:

        ~/linode_stackscripts/secrets.tfvars
        1
        2
        3
        4
        5
        6
        
        token = "my-linode-api-token"
        root_pass = "my-secure-root-password"
        stackscript_data {
          my_password = "my-limited-users-password"
          my_userpubkey = "my-public-ssh-key"
        }

        This file contains all sensitive data needed for your Linode deployment. Ensure you replace all values with your own secure passwords and your Linode account’s APIv4 token. This file should never be tracked in version control software and should be listed in your .gitignore file if using GitHub.

        Note

      You are now ready to apply your linode_stackscripts module’s Terraform configuration. These steps will be completed in the next section.

      Initialize, Plan and Apply the Terraform Configuration

      Whenever a new provider is used in a Terraform configuration, it must first be initialized. The initialization process downloads and installs the provider’s plugin and performs any other steps needed for its use. Before applying your configuration, it is also useful to view your configuration’s execution plan before making any actual changes to your infrastructure. In this section, you will complete all these steps.

      1. Initialize the Linode provider. Ensure you are in the linode_stackscripts directory before running this command:

        terraform init
        

        You will see a message that confirms that the provider plugins have been successfully initialized.

      2. Run the Terraform plan command:

        terraform plan -var-file="secrets.tfvars" -var-file="terraform.tfvars"
        

        Terraform plan won’t take any action or make any changes on your Linode account. Instead, an analysis is done to determine which actions (i.e. Linode instance creations, deletions, or modifications) are required to achieve the state described in your configuration.

      3. You are now ready to create the infrastructure defined in your root module’s main.tf configuration file:

        terraform apply -var-file="secrets.tfvars" -var-file="terraform.tfvars"
        

        Since you are using multiple variable value files, you must call each file individually using the var-file argument. You will be prompted to confirm the apply action. Type yes and hit enter. Terraform will begin to create the resources you’ve defined throughout this guide. This process will take a couple of minutes to complete. Once the infrastructure has been successfully built you will see a similar output:

          
          Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
            
        
      4. To verify the deployment, retrieve your Linode instance’s IP address:

        terraform show | grep 'ip_address'
        

        You should see a similar output:

          
                ip_address = 192.0.2.0
              
        
      5. Open a new shell session and SSH into your Linode using the IP address you retrieved in the previous step and the username you defined in the terraform.tfvars file’s my_username variable:

        ssh username@192.0.2.0
        

        You should be able to access your Linode and then verify that what you defined in the StackScript was executed.

      Version Control Your Terraform Module

      To make the linode_stackscripts module available to other team members, you can version control it using GitHub. Before completing the steps in this section, ensure you have completed the steps in the Configure Git section of the Getting Started with Git guide.

      1. In the linode_stackscripts directory create a .gitignore file:

        ~/linode_stackscripts/.gitignore
        1
        2
        3
        4
        
        secrets.tfvars
        .terraform/
        terraform/
        terraform.tfstate

        Note

        If there are any files related to the Terraform installation steps completed before beginning this guide (i.e zip files and checksum files), you can remove these files from the linode_stackscripts directory, since you should not track them in version control and they are no longer necessary.

      2. Initialize the git repository:

        git init
        

        Stage all the files you’ve created so far for your first commit:

        git add -A
        
      3. Commit all the linode_stackscripts files:

        git commit -m "Initial commit"
        
      4. Navigate to your GitHub account and create a new repository. Ensure you name the repository the same name as that of your Terraform module. In this example, the GitHub repository will be named linode_stackscripts.

      5. At the top of your GitHub repository’s Quick Setup page, copy the remote repository URL.

      6. Return to your local computer’s linode_stackscripts directory and add the URL for the remote repository:

        git remote add origin https://github.com/my-github/linode_stackscripts.git
        
      7. Push your local linode_stackscripts repository to your remote GitHub repository:

        git push -u origin master
        

      Your Terraform module is now tracked via GitHub and can be used, shared and modified by anyone who has access to your GitHub account.

      Invoking Your GitHub-Hosted Module

      In the future, you can source this module from GitHub within your Terraform module declarations. You would write your module block like the following:

      1
      2
      3
      4
      5
      6
      
      module "linode_stackscripts" {
          source = "github.com/username/linode_stackscripts"
      
          VARIABLES HERE
          . . .
      }

      More Information

      You may wish to consult the following resources for additional information on this topic. While these are provided in the hope that they will be useful, please note that we cannot vouch for the accuracy or timeliness of externally hosted materials.

      Find answers, ask questions, and help others.

      This guide is published under a CC BY-ND 4.0 license.



      Source link

      Create a Salt Execution Module


      Updated by Linode Written by Linode

      Use promo code DOCS10 for $10 credit on a new account.

      A Salt execution module is a Python module that runs on a Salt minion. It perform tasks and returns data to the Salt master. In this tutorial you will create and install an execution module that will call the US National Weather Service API and return the current temperature at a specified weather station. This example could easily be adapted to access any API.

      Before You Begin

      If you haven’t already, set up a Salt master and at least one Salt minion. You can follow the first few steps of our Getting Started with Salt – Basic Installation and Setup guide.

      Note

      The steps in this guide require root privileges. Be sure to run the steps below with the sudo prefix. For more information on privileges, see our Users and Groups guide.

      Prepare Salt

      The files created in the following steps will be located in the /srv/salt directory. If you have changed Salt’s default file_roots configuration, use that directory location instead.

      1. Begin by creating the /srv/salt directory if it does not already exist. This is where you will place your top file and your Salt state file:

        mkdir /srv/salt
        
      2. Create a top file in /srv/salt which will be Salt’s point of entry for our Salt configuration:

        /srv/salt/top.sls
        1
        2
        3
        
        base:
          '*':
            - weather
      3. Create a state file named weather.sls and instruct Salt to make sure our minions have PIP installed, as well as the required Python library.

        /srv/salt/weather.sls
        1
        2
        3
        4
        5
        6
        7
        
        python-pip:
          pkg.installed
        
        requests:
          pip.installed:
            - require:
              - pkg: python-pip
      4. Apply these state changes:

        salt '*' state.apply
        
      5. Finally, create the /srv/salt/_modules directory which will contain our execution module:

        mkdir /srv/salt/_modules
        

      Create the Execution Module

      1. Create a file called weather.py in the /srv/salt/_modules directory, and add the following lines to set up Salt logging and import the requests module.

        /srv/salt/_modules/weather.py
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        
        import logging
        try:
            import requests
            HAS_REQUESTS = True
        except ImportError:
            HAS_REQUESTS = False
        
        log = logging.getLogger(__name__)
        
        . . .
      2. Add the __virtualname__ variable and the __virtual__ function.

        /srv/salt/_modules/weather.py
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11
        12
        13
        14
        
        . . .
        
        __virtualname__ = 'weather'
        
        def __virtual__():
            '''
            Only load weather if requests is available
            '''
            if HAS_REQUESTS:
                return __virtualname__
            else:
                return False, 'The weather module cannot be loaded: requests package unavailable.'
        
        . . .

        The __virtual__ function either returns the module’s virtual name and loads the module, or returns False with an error string and the module is not loaded. The if HAS_REQUESTS conditional is tied to the try/except block created in the previous step through the use of the HAS_REQUESTS variable.

      3. Add the public get() function and the private _make_request() function:

        /srv/salt/_modules/weather.py
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        
        . . .
        
        def get(signs=None):
            '''
            Gets the Current Weather
        
            CLI Example::
        
                salt minion weather.get KPHL
        
            This module also accepts multiple values in a comma separated list::
        
                salt minion weather.get KPHL,KACY
            '''
            log.debug(signs)
            return_value = {}
            signs = signs.split(',')
            for sign in signs:
                return_value[sign] = _make_request(sign)
            return return_value
        
        def _make_request(sign):
            '''
            The function that makes the request for weather data from the National Weather Service.
            '''
            request = requests.get('https://api.weather.gov/stations/{}/observations/current'.format(sign))
            conditions = {
                "description:": request.json()["properties"]["textDescription"],
                "temperature": round(request.json()["properties"]["temperature"]["value"], 1)
            }
            return conditions

        There are two functions in this step. The get() function accepts one or more weather station call signs as a comma separated list. It calls _make_request() to make the HTTP request and returns a text description of the current weather and the temperature.

        It’s important to note that by adding an underscore to the beginning of the _make_request() function it becomes a private function, which means it is not directly accessible through the Salt command line or a state file.

        The complete file looks like this:

        /srv/salt/_modules/weather.py
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        
        import logging
        try:
            import requests
            HAS_REQUESTS = True
        except ImportError:
            HAS_REQUESTS = False
        
        log = logging.getLogger(__name__)
        
        __virtual_name__ = 'weather'
        
        def __virtual__():
            '''
            Only load weather if requests is available
            '''
            if HAS_REQUESTS:
                return __virtual_name__
            else:
                return False, 'The weather module cannot be loaded: requests package unavailable.'
        
        
        def get(signs=None):
            '''
            Gets the Current Weather
        
            CLI Example::
        
                salt minion weather.get KPHL
        
            This module also accepts multiple values in a comma seperated list::
        
                salt minion weather.get KPHL,KACY
            '''
            log.debug(signs)
            return_value = {}
            signs = signs.split(',')
            for sign in signs:
                return_value[sign] = _make_request(sign)
            return return_value
        
        def _make_request(sign):
            '''
            The function that makes the request for weather data from the National Weather Service.
            '''
            request = requests.get('https://api.weather.gov/stations/{}/observations/current'.format(sign))
            conditions = {
                "description:": request.json()["properties"]["textDescription"],
                "temperature": round(request.json()["properties"]["temperature"]["value"], 1)
            }
            return conditions

      Run the Execution Module

      1. To run the execution module, you need to first sync it to your minions. To do this, you can call a highstate with state.apply, which will also try to apply the state changes you specified earlier in the weather.sls state file. Since the weather.sls state was already applied in the Preparing Salt section, use the saltutil.sync_modules function:

        salt '*' saltutil.sync_modules
        
      2. Run the execution module on your Salt master:

        salt '*' weather.get KPHL
        

        You should see an output like the following:

          
        salt-minion:
        ----------
        KPHL:
            ----------
            description::
                Cloudy
            temperature:
                17.2
        
        
      3. Alternatively, you can run the Salt execution module locally on your Salt minion by entering the following:

        salt-call weather.get KVAY,KACY
        

        You should get an output like the following:

          
        local:
            ----------
            KACY:
                ----------
                description::
                    Cloudy
                temperature:
                    18.9
            KVAY:
                ----------
                description::
                    Cloudy
                temperature:
                    16.7
        
        

      You have now successfully created and installed a Salt execution module.

      More Information

      You may wish to consult the following resources for additional information on this topic. While these are provided in the hope that they will be useful, please note that we cannot vouch for the accuracy or timeliness of externally hosted materials.

      Join our Community

      Find answers, ask questions, and help others.

      This guide is published under a CC BY-ND 4.0 license.



      Source link