Wednesday 6 March 2024

Go Methods



In Go, methods are functions which are associated with some type; this type is called Receiver Type and methods are often called Receiver Functions. Their format is:

func (rcv RcvT) foo(arg1 TArg1, arg2 TArg2, ...) RtnT {
   ...
   // use rcv
}


This example shows some interesting properties of methods:


package main

import "fmt"

type MyInt struct {
i int
}

// associate function to a structure
// *MyInt is a Receiver type (here, it's a pointer type)
// multiply is now a Receiver function. From now on it requires a receiver in order to be called: (*MyInt).multiply
// When called on MyInt instance pointer, a copy of that pointer is passed to this function.
func (mi *MyInt) multiply(i int) {
mi.i = mi.i * i
}

// Receiver type can also be a value type.
// From now on it requires a receiver in order to be called: (MyInt).multiply
// When called on MyInt instance, a copy of that object is passed to this function.
func (mi MyInt) add(i int) {
mi.i = mi.i + i
}

func assign_struct_function_to_var(mi *MyInt) {
// Although function variable does not mention Receiver type...
var f func(i int)
// ...we can assign Receiver function to it!
f = mi.multiply

f(2)
fmt.Printf("%d\n", mi.i)

// function associated to struct can't be used in assignments on its own
// error:
// undefined: multiply
// f = multiply
}

func main() {
mi := MyInt{}
mi.i = 1
fmt.Printf("%d\n", mi.i)

// function associated to struct can't be invoked on its own
// error:
// undefined: multiply
// multiply(2)

assign_struct_function_to_var(&mi)
}

Monday 4 March 2024

HashiCorp Packer



Packer is a tool for creating (golden) images from a configurable template. 

Packer configuration file is a JSON file e.g. config.json.

config.json contains:
  • variables
  • builders
    • type
      •  amazon-ebs
  • provisioners
    • type
      • shell
      • ansible

Example of configuration file used to create AWS AMI for multiple environments, by using Ansible as a provisioner:

config.json:

{
  "variables": {
    "env_name": "{{env `env_name`}}",
    "instance_profile": "",
    "ami_name": "asb-linux-al23-arm-{{timestamp}}",
    "kms_key_id": "{{env `kms_key_id`}}",
    "sg_default": "{{env `SG_DEFAULT`}}",
    "current_user": "{{env `USER`}}"
  },
  "builders": [
    {
      "ami_name": "{{user `ami_name`}}",
      "instance_type": "t4g.medium",
      "region": "us-east-1",
      "source_ami_filter": {
        "filters": {
          "virtualization-type": "hvm",
          "name": "al2023-ami-2023.*-kernel-6.1-arm64",
          "root-device-type": "ebs"
        },
        "owners": "amazon",
        "most_recent": true
      },
      "ssh_username": "ec2-user",
      "ssh_bastion_host": "bastion.mycorp.com",
      "ssh_bastion_username": "{{user `current_user`}}",
      "ssh_bastion_agent_auth": true,
      "ssh_bastion_port": 22,
      "ssh_timeout": "2m",
      "ssh_clear_authorized_keys": "true",
      "iam_instance_profile": "{{user `instance_profile`}}",
      "type": "amazon-ebs",
      "tags": {
        "Name": "{{user `ami_name`}}",
        "Environment": "{{user `env_name`}}"
      },
      "vpc_filter": {
        "filters": {
          "tag:Environment": "{{user `env_name`}}",
          "isDefault": "false"
        }
      },
      "subnet_filter": {
        "filters": {
          "tag:Name": "vpc-{{user `env_name`}}-private-us-east-*"
        },
        "most_free": true
      },
      "security_group_ids": [
        "{{user `sg_default`}}"
      ],
      "launch_block_device_mappings": [
        {
          "device_name": "/dev/xvda",
          "encrypted": true,
          "kms_key_id": "{{user `kms_key_id`}}",
          "delete_on_termination": true,
          "volume_type": "gp3"
        }
      ]
    }
  ],
  "provisioners": [
    {
      "type": "shell",
      "inline": [
        "sudo dnf update -y",
        "sudo dnf install -y python3"
      ]
    },
    {
      "type": "ansible",
      "host_alias": "packer",
      "user": "ec2-user",
      "inventory_directory": "ansible/env/aws/{{user `env_name`}}",
      "playbook_file": "ansible/playbooks/playbook.yml",
      "extra_arguments": [
        "-D",
        "--vault-password-file",
        "./aws-{{user `env_name`}}-vault.pw",
        "--scp-extra-args", "'-O'",
        "--extra-vars",
        "'ansible_python_interpreter=/usr/bin/python3'"
      ]
    }
  ]
}


To run Packer:
 
$ packer build [-debug] config.json


AWS AMI gets created in the following way:
  • a temporary SSH keypair is created
  • new temporary EC2 instance is started. This instance is based on source AMI specified in configuration file
  • SSH tunnel is established between local/dev host and remote EC2 instance
  • provisioners are run e.g. Ansible is installing packages etc...
  • once provisioners work is completed ephemeral key is removed from authorized_keys file on temp EC2 instance
  • temp EC2 instance is terminated
  • a snapshot of the root disk of that EC2 instance is created
  • a new AMI is created based on that snapshot
  • tags are added to snapshot
  • tags are added to AMI
  • temp SSH keypair is destroyed

Once AMI is created, it is not possible to find out which AMI was used as a source (base) AMI (amazon web services - Is it possible to find the source AMI for an existing AMI? - Stack Overflow). But soon after AMI is created, we can check properties of temp EC2 instance (which is in Terminated state) and check its AMI. For source AMI filter as above, it could be e.g. al2023-ami-2023.3.20240219.0-kernel-6.1-arm64.

---