Ansible – Default Command module

The Ansible command module is the default module that ansible. It is used to execute Binary Commands.

The command module will execute if we do not specific module name in Ansible Ad-Hoc commands.

ansible localhost -m command -a "uptime"

In the above example, we are passing the module as a command, if we skip the module name, ansible will still execute the command and will pick the command as the default module, the output will be the same for both ad-hoc commands.

ansible localhost -a "uptime"

Ansible – Install Packages

Ansible can be used to install application packages like Git, apache, Mysql, etc. These packages can be installed using Ansible Ad-Hoc commands or through ansible Playbooks.

Syntax – To install the git package on the Ubuntu system.

ansible localhost -m apt -a "name=git state=present" -b

  • localhost – inventory, the host to be targeted.
  • -m module name, here we are using apt for Ubuntu/Debian systems. For the Linux systems using yum.
  • -a arguments passed for installation of the package.
    • name – package name to be installed.
    • state – Ansible provides multiple state options
      • present/installed – to install package
      • latest – to update the existing package
      • absent/removed – to uninstall package
  • -b argument to change the owner, like to grant sudo privileges

Output –

Dynamic Terraform State Configuration

Terraform doesn’t support passing dynamic variables for state files or backend blocks.

For example, let us consider we have a backend defined like the below,

Now, we have a scenario where we do not want to pass the values in the backend block in a static format, instead, we want the value to be dynamic.

We will rewrite the code and create two variables to pass the bucket name and bucket object (key) and pass these values in the backend block as variables instead of static values.

The above terraform configuration when we run the terraform init command will throw errors, as variables not allowed in the backend block.

E:\Projects\terraform>terraform init -migrate-state

Initializing the backend...
Backend configuration changed!

Terraform has detected that the configuration specified for the backend
has changed. Terraform will now check for existing state in the backends.

╷
│ Error: Variables not allowed
│
│   on main.tf line 9, in terraform:
│    9:     bucket = var.backend_bucket #"s3-backend-demo"
│
│ Variables may not be used here.
╵

╷
│ Error: Variables not allowed
│
│   on main.tf line 10, in terraform:
│   10:     key = var.backend_key #"backend"
│
│ Variables may not be used here.
╵
Error while using dynamic variables for backend block

To resolve the issue, terraform allows us to pass the backend values dynamically through backend-config arguments via command prompt.

In your backend configuration block, remove the details which you want to pass dynamically. Like in our example we are using s3 as the backend, passing bucket name and object (key) values.

We will remove those attributes from the backend configuration block and instead we will pass them through backend-config arguments.

Passing backend configuration arguments values through a command prompt,

terraform init -backend-config="bucket=s3-backend-demo" -backend-config="key=backend"

In case we have an existing state file and we want to update the new backend arguments, you will need to run the init command with -migrate-state arguments.

terraform init -migrate-state -backend-config="bucket=s3-backend-demo" -backend-config="key=backend"

Ansible – Modules

Ansible Modules are pre-defined, reusable scripts, Ansible ships the default modules with the install package, and also allows the creation of custom modules.

AnsibleModules can be used with Ansible Ad-hoc commands or with Ansible-playbook.

To list all modules available with your Ansible installation run the below command,

> ansible-doc -l

Ansible – Ad-hoc Command Structure

Ansible provides us the option to either run playbooks or ad-hoc commands on the local and remote nodes through ansible engines.

Let us look at an Ansible ad-hoc command example

> ansible localhost -m shell -a "uptime"

The above command can be divided into the following parts,

  • localhost – You can specify the target host/groups here where you want the command to be executed.
  • -m shell – You define the Ansible module, in our example shell.
  • -a “uptime” – Pass the command arguments in double quotes string.

To run the commands on custom inventory host files,

> ansible localhost -m shell -a "uptime" -i custom_inventory_file

On a similar line, we can create our own ad-hoc commands.

ansible.cfg – file priority

We can have the ansible.cfg file at various locations, the default location shipped with ansible installation is at,

/etc/ansible/ansible.cfg

The default ansible.cfg has the least priority when you have multiple ansible.cfg defined in your system/server.

The priority is set to ansible.cfg file is based on how it is defined or stored,

  • ANSIBLE_CONF – Environment variable, defined in your server/system.
  • ./ansible.cfg – Ansible config file defined in your current working directory.
  • ~/.ansible.cfg – Ansible config file defined in your home directory.
  • /etc/ansible/ansible.cfg – Ansible config file defined in default location.

grep – Linux search text in the file

To find a string in a text file on your Linux system/server, we can use the grep command.

For example, you are looking for certain strings like “generate” and “host” in the file /etc/hosts

To search text generate run the below command,

cat /etc/hosts | grep -i generate

To search text host run the below command, as the host string is at multiple locations, the result will display all occurrences of string host in file /etc/hosts

cat /etc/hosts | grep -i host

Ubuntu/Linux WSL – Access Windows Files

Many of us may have faced issues in accessing the Windows directory/drives in Ubuntu/Linux WSL installed on your local Windows machine/Laptop.

The easiest way of accessing your local Windows drive/folders etc through the Ubuntu/Linux WSL Bash interface is by accessing the /mnt (mount).

For example, you have your project folder on your local Windows system at,
f:/ubuntu

You can access the f:/ubuntu folder in your Ubuntu WSL using the below command,

cd /mnt/f/ubuntu

Terraform – Security Group

The Security Group acts as a virtual firewall, controlling the traffic flowing in and out of your EC2 resource associated with it.

VPS when created have default security groups, we can add additional security groups as per our requirements.

Security Groups operate at the instance level, whereas ACL acts over VPCs. Security Group supports allow rules only and are Stateful.

Security Groups consist of rules, which control traffic based upon protocols and port numbers, there are separate sets of rules for inbound and outbound traffic.

Security Groups allow you to restrict those rules to a certain IP CIDR range or you can allow traffic to all, 0.0.0.0/0 (IP4) and ::/0 (IP6)

We can create a Security Group through various methods, including AWS console as well as using IaaC (Infrastructure as Code).

Create Security Group through AWS Console

  • Login to your AWS Console, and navigate to VPC or EC2 service.
  • Click on the Security Group on the left navigation bar. All existing Security Groups will be listed.
  • Click on Create new Security Group, and enter the Security Group Name and Description.
  • Add the Protocol, Port, CIDR Range, etc., and click on save.

That’s it and your security group is created through GUI, now we will learn how we can use IaaC to create a Security Group in AWS using Terraform.

Create Security Group through Terraform (IaaC)

In our example, we will create a Security Group for the LAMP server and will allow traffic for ports 80 (HTTP), 443 (HTTPS), 22 (SSH), and 3306 (MySQL).

We will be creating a Security Group using different methods,

Method 1

In Method one let us go in the simplest way, we will have multiple blocks of ingress rules.

Files to be created

  • data.tf
  • variable.tf
  • provider.tf
  • securitygroup.tf

Provider.tf, Let’s update the Terraform provider information, so Terraform knows which cloud provider we are using. The provider files contain two blocks Terraform and Provider.

// Terraform block,define the provider source and version
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.0"
    }
  }
}

// Define provider 
provider "aws" {
  shared_credentials_file = var.shared_credentials_files
  region                  = var.region
  profile                 = var.profile

  default_tags {
    tags = {
      "Resource" = "Security Group TF"
    }
  }
}

// local variables, can be referenced 
locals {
  common_tags = {
    "Project"   = "TF Modules"
    "Owner"     = "CubLeaf"
    "WorkSpace" = "${terraform.workspace}"
  }
}

Variables.tf, declare the variables used in the configuration. These variables are also named input variables.

variable "region" {
  description = "Enter the region"
  default     = "us-east-1"
}

// update the path of credential file 
// replace the below path with your credential file
variable "shared_credentials_files" {
  description = "Enter the credential file"
  default     = "C:\\Users\\username\\.aws\\credentials"
}

// Enter the profile of aws you want to use
variable "profile" {
  description = "Enter the profile name"
  default     = "myprofile"
}

variable "port" {
  description = "Enter the ports to be configured in SG Ingress rule"
  default     = [80, 22, 443, 3306]
}

variable "protocol" {
  description = "Ports for LAMP Server"
  type        = map(any)
  default = { "80" = "HTTP"
    "443" = "HTTPS"
    "22"  = "SSH"
  "3306" = "MYSQL" }
}

Data.tf, These are return values, in our case we want the default VPC id, to reference it in our security group resource, instead of hard coding we can use the data block to reference the existing resource. It’s very similar to traditional function return values.

// Get the default VPC ID
data "aws_vpc" "get_vpc_id" {
  default = true
}

Securitygroup.tf, The actual resource “aws_security_group” that we are going to create will be declared here,

resource "aws_security_group" "lamp_securitygroup_basic" {
  name        = "LAMP Security Group Basic"
  description = "LAMP Server Security Group Basic ${terraform.workspace}" // call the workspace 
  vpc_id      = data.aws_vpc.get_vpc_id.id  // calling from the data block
  tags        = local.common_tags

  // in-bound rules
  ingress {
    from_port        = 22
    to_port          = 22
    protocol         = "tcp"
    cidr_blocks      = ["0.0.0.0/0"]
    ipv6_cidr_blocks = ["::/0"]
  }

  ingress {
    from_port        = 443
    to_port          = 443
    protocol         = "tcp"
    cidr_blocks      = ["0.0.0.0/0"]
    ipv6_cidr_blocks = ["::/0"]
  }

  ingress {
    from_port        = 22
    to_port          = 22
    protocol         = "tcp"
    cidr_blocks      = ["0.0.0.0/0"]
    ipv6_cidr_blocks = ["::/0"]
  }

  ingress {
    from_port        = 3306
    to_port          = 3306
    protocol         = "tcp"
    cidr_blocks      = ["0.0.0.0/0"]
    ipv6_cidr_blocks = ["::/0"]
  }

  // outbound rules
  egress {

    from_port        = 0
    to_port          = 0
    protocol         = -1
    cidr_blocks      = ["0.0.0.0/0"]
    ipv6_cidr_blocks = ["::/0"]
  }
}

Now, as we have the code/configuration ready, let us initiate and create the Security Groupby executing the terraform plan and applying the plan command.

Terraform – Dynamic Block

A Dynamic block is similar to for expression but creates nested blocks instead of a complex typed value. It iterates over a given complex value and generates a nested block for each element of that complex value. Unlike the count block, which will iterate over the resource, the dynamic will reside inside the resource block.

Dynamic blocks are supported inside the following blocks,

  • Resource
  • Data
  • Provider
  • Provisioner

Dynamic blocks can be used for resources like Setting, Security Group, etc.

Let us consider the example of creating a security group with multiple ingress rules for ports (80, 443, 3306, and 22). In an ideal scenario, we will end up copy-pasting the ingress rule for each port in the security group resource block.

Security Group (Without Dynamic Block)

// Security Group without Dynamic Block
resource "aws_security_group" "lamp_securitygroup_basic" {
  name        = "LAMP Security Group Basic"
  description = "LAMP Server Security Group Basic ${terraform.workspace}" // call the workspace 
  vpc_id      = data.aws_vpc.get_vpc_id.id
  tags        = local.common_tags
  // in-bound rules
  ingress {
    from_port        = 22
    to_port          = 22
    protocol         = "tcp"
    cidr_blocks      = ["0.0.0.0/0"]
    ipv6_cidr_blocks = ["::/0"]
  }
  ingress {
    from_port        = 443
    to_port          = 443
    protocol         = "tcp"
    cidr_blocks      = ["0.0.0.0/0"]
    ipv6_cidr_blocks = ["::/0"]
  }
  ingress {
    from_port        = 22
    to_port          = 22
    protocol         = "tcp"
    cidr_blocks      = ["0.0.0.0/0"]
    ipv6_cidr_blocks = ["::/0"]
  }
  ingress {
    from_port        = 3306
    to_port          = 3306
    protocol         = "tcp"
    cidr_blocks      = ["0.0.0.0/0"]
    ipv6_cidr_blocks = ["::/0"]
  }
  // outbound rules
  egress {
    from_port        = 0
    to_port          = 0
    protocol         = -1
    cidr_blocks      = ["0.0.0.0/0"]
    ipv6_cidr_blocks = ["::/0"]
  }
}

The above code will work perfectly fine, but as your security group rules go on increasing, you will need to copy-paste the ingress block for each rule, which will make the code hard to maintain and update.

To overcome this issue, Terraform provides us with a dynamic block. We can write the same code using dynamic block,

Security Group (Dynamic Block)

// Data block for VPC ID
data "aws_vpc" "get_vpc_id" {
  default = true
}
// variable declaration 
variable "port" {
  description = "Enter the ports to be configured in SG Ingress rule"
  default     = [80, 22, 443, 3306]
}
// Create Security Group uisng Dynamic Block
resource "aws_security_group" "lamp_sg" {
  name        = "LAMP Security Group"
  description = "LAMP Server Security Group ${terraform.workspace}" // call the workspace 
  vpc_id      = data.aws_vpc.get_vpc_id.id
  tags        = local.common_tags
  dynamic "ingress" {
    for_each = var.port
    content {
      description      = "Security rule for inbound ${ingress.value}"
      from_port        = ingress.value // The block should use block name to fetch the value and key
      to_port          = ingress.value
      protocol         = "tcp"
      cidr_blocks      = ["0.0.0.0/0"]
      ipv6_cidr_blocks = ["::/0"]
    }
  }
  // outbound rules
  egress {
    from_port        = 0
    to_port          = 0
    protocol         = -1
    cidr_blocks      = ["0.0.0.0/0"]
    ipv6_cidr_blocks = ["::/0"]
  }
}

In our example, we are producing a nested block of ingress rules,

  • The label of our dynamic block is “ingress”, you can change the label, as you per the requirement, like if you want to generate “setting” nested blocks we can name the dynamic block as “setting”.
  • For_each – Argument will provide the complex argument to iterate over.
  • Content Block – defines the body of each generated block. In our example it will be the ingress block, iterating over multiple ports.

In multi-level nested block, we can have multi-level nested dynamic blocks, ie the nested dynamic bock within the dynamic block.