Category: Terraform

Dec 02

Terraform AWS Param Store with json

Since it took me a while to get this syntax sorted I am noting this for future use.

NOTE: This is non-sensitive parameters not secrets. You are probably better of using secrets manager for secrets. And I did not try but the Param Store SecureString type will probably not work for below.

I stored this json in Param Store as StringList with Value

{"email":"riaan@email.com","engine_version":"3.11.20","host_instance_type":"mq.t3.micro"}

test

❯ aws ssm get-parameter --name "/microservice/blah/development" --region us-east-1 | jq
{
  "Parameter": {
    "Name": "/microservice/blah/development",
    "Type": "StringList",
    "Value": "{\"email\":\"riaan@email.com\",\"engine_version\":\"3.11.20\",\"host_instance_type\":\"mq.t3.micro\"}",
    "Version": 6,
    "LastModifiedDate": "2023-12-01T08:53:33.920000-06:00",
    "ARN": "arn:aws:ssm:us-east-1:xxx:parameter/microservice/blah/development",
    "DataType": "text"
  }
}

get it to a local var

data "aws_ssm_parameter" "cfg" {
  provider = aws.target1
  name = "/microservice/blah/development"
}

locals {
  cfg = jsondecode(data.aws_ssm_parameter.cfg.value)
}

in terraform reference like this

  #engine_version = "3.11.20"
  engine_version = local.cfg.engine_version

Comments Off on Terraform AWS Param Store with json
comments

Aug 14

traefik in kubernetes using terraform + helm and AWS ALB

If you are using Traefik in kubernetes but you want to use an AWS ALB (application load balancer) this recipe may work for you. You will note a few important things:

  1. Traefik relies on the underlying kubernetes provider to create an Ingress. If not specified this will be a loadbalancer CLB (classic load balancer). There is a way to make this a NLB (network load balancer) but the AWS provider is not doing an ALB so Traefik can't do an ALB. This recipe therefore relies on a NodePort service and ties the Ingress (ALB) to the NodePort service via the ingressclass annotation. If you do not like or want to use NodePort this is not for you.
  2. Yet to confirm can this recipe work if you did not intentionally install the AWS LBC (load balancer controller). And does this work on non AWS EKS or self-managed kubernetes on AWS.
  3. Still looking at why the AWS Target group health check is not able to use the /ping or /dashboard. This may be an issue with my security groups but for now I just created manually a IngressRoute /<>-health on the Traefik web entrypoint and updated the Target Group health check either programatically or in the AWS console.
  4. I did not want to complicate this with kubernetes so I am using the simplest way for helm to communicate with the cluster and point to the environment kube config to get to the cluster.
  5. I did some minimal templating to change the helm release name and corresponding kubernetes objects but for this post I just hard coded for simplicity.
  6. I commented out deployment as Daemonset for my testing. You need to decide what is better in your environment Deployment or Daemonset.

providers.tf

provider "helm" {
  kubernetes {
    config_path = "~/.kube/config-eks"
  }
}

versions.tf

terraform {
  required_providers {
    helm = {
      source  = "hashicorp/helm"
      version = ">= 2.0.1"
    }
  }
  required_version = ">= 0.15"
}

helm values

additionalArguments:
- --providers.kubernetescrd.ingressclass=traefik-pub

#deployment:
#  kind: DaemonSet

service:
  enabled: true
  type: NodePort

service:
  enabled: true
  type: NodePort
extraObjects:
  - apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: traefik-pub
      annotations:
        kubernetes.io/ingress.class: alb
        alb.ingress.kubernetes.io/scheme: internet-facing
        alb.ingress.kubernetes.io/security-groups: sg-,
        #alb.ingress.kubernetes.io/actions.ssl-redirect: '{"Type": "redirect", "RedirectConfig":
        #  { "Protocol": "HTTPS", "Port": "443", "StatusCode": "HTTP_301"}}'
        alb.ingress.kubernetes.io/backend-protocol: HTTP
        alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:us-east-1::certificate/b6ead273-66e9-4768-ad25-0924dca35cdb
        alb.ingress.kubernetes.io/healthcheck-path: "/traefik-pub-health"
        alb.ingress.kubernetes.io/healthcheck-port: "traffic-port"
        #alb.ingress.kubernetes.io/healthcheck-protocol: HTTP
        alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS":443}]'
    spec:
      defaultBackend:
        service:
          name: traefik-pub
          port:
            number: 80  

ingressClass:
  enabled: true
  isDefaultClass: false

ingressRoute:
  dashboard:
    enabled: true
    # Additional ingressRoute annotations (e.g. for kubernetes.io/ingress.class)
    annotations:
      kubernetes.io/ingress.class: traefik-pub
    # Additional ingressRoute labels (e.g. for filtering IngressRoute by custom labels)
    labels: {}
    entryPoints:
    - traefik
    labels: {}
    matchRule: PathPrefix(/dashboard) || PathPrefix(/api)
    middlewares: []
    tls: {}

rollingUpdate:
  maxUnavailable: 1
  maxSurge: 1

variables.tf (shortened for documentation)

variable "traefik_name" {
  description = "helm release name"
  type        = string
  default     = "traefik-pub"
}

variable "namespace" {
  description = "Namespace to install traefik chart into"
  type        = string
  default     = "test"
}

variable "traefik_chart_version" {
  description = "Version of Traefik chart to install"
  type        = string
  default     = "21.2.1"
}

chart.tf

resource "helm_release" "traefik" {
  namespace        = var.namespace
  create_namespace = true
  name             = var.traefik_name
  repository       = "https://traefik.github.io/charts"
  chart            = "traefik"
  version          = var.traefik_chart_version
  timeout = var.timeout_seconds

  values = [
    file("values.yml")
  ]

  set {
    name  = "deployment.replicas"
    value = var.replica_count
  }

}

Comments Off on traefik in kubernetes using terraform + helm and AWS ALB
comments

Jul 02

Terraform with a Makefile

To streamline Terraform use you can use the typical make command. Below is a very simple Makefile and of course it can be built out for testing steps etc...

Makefile

➜ cat Makefile 

init:
        terraform init

validate:
        terraform fmt -recursive
        terraform validate

plan:
        terraform validate
        terraform plan -var-file="variables.tfvars"

apply:
        terraform apply -var-file="variables.tfvars" --auto-approve

destroy:
        terraform destroy -var-file="variables.tfvars"

all: validate plan apply

Example command

❯ make init
terraform init

Initializing the backend...

Initializing provider plugins...
- Reusing previous version of oracle/oci from the dependency lock file
- Using previously-installed oracle/oci v5.2.1

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

Comments Off on Terraform with a Makefile
comments

Mar 24

Go Import JSON File Into MongoDB Unstructured

During a recent POC I imported a Terraform state file(JSON) into MongoDB using golang. This may not exactly fit any use case since it is simpler and use actual file(s) and not MongoDB to store a Terraform state file, but this helps record my effort as well as the unstructured nature of the JSON ie I am not defining a structure just using an interface{}.

Insert From File

package main

import (
    "context"
    "fmt"
    "os"
    "io/ioutil"
    "encoding/json"
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
)

func main() {
    clientOptions := options.Client().ApplyURI("mongodb://localhost:27017")
    client, e := mongo.Connect(context.TODO(), clientOptions)
    CheckError(e)
    collection := client.Database("tfstatedb").Collection("states")
    jsonFile, err := os.Open("terraform.tfstate")
    if err != nil {
        fmt.Println(err)
    }
    defer jsonFile.Close()

    byteValue, _ := ioutil.ReadAll(jsonFile)
    var result map[string]interface{}
    json.Unmarshal([]byte(byteValue), &result) 
    _, e = collection.InsertOne(context.TODO(), result)
    CheckError(e)
}

func CheckError(e error) {
    if e != nil {
        fmt.Println(e)
    }
}

FindOne From DB

package main

import (
    "flag"
    "log"
    "context"
    "fmt"
    "encoding/json"
    "go.mongodb.org/mongo-driver/bson"
    "go.mongodb.org/mongo-driver/bson/primitive"
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
)

var (
     flagId string
)

func init() {
     flag.StringVar(&flagId, "id", "", "mongodb ObjectId")
}

func PrettyPrint(v interface{}) (err error) {
      b, err := json.MarshalIndent(v, "", "  ")
      if err == nil {
         fmt.Println(string(b))
      }
      return
}

func main() {
    if !flag.Parsed() {
    flag.Parse()
    }
    if flagId != "" {
          fmt.Println("finding id: " + flagId)
    } else {
          log.Fatal("no _id specified")
    }

    clientOptions := options.Client().ApplyURI("mongodb://localhost:27017")
    client, e := mongo.Connect(context.TODO(), clientOptions)
    CheckError(e)

    e = client.Ping(context.TODO(), nil)
    CheckError(e)

    collection := client.Database("tfstatedb").Collection("states")

    var res interface{}

    docID, e := primitive.ObjectIDFromHex(flagId)
    CheckError(e)

    filter := bson.M{"_id": docID}

    tempResult := bson.M{}
    e = collection.FindOne(context.TODO(), filter).Decode(&tempResult)
    if e == nil {   
      obj, _ := json.Marshal(tempResult)
      e = json.Unmarshal(obj, &res)
    }
    PrettyPrint(res)
}

func CheckError(e error) {
    if e != nil {
        fmt.Println(e)
    }
}

Comments Off on Go Import JSON File Into MongoDB Unstructured
comments

Mar 22

Terraform Stateserver Using Python

Terraform can utilize a http backend for maintaining state. This is a test of a Terraform http backend using a server implemented with python.

NOTE: checked source into https://github.com/rrossouw01/terraform-stateserver-py/

recipe and components

Using Virtualbox Ubuntu 20.10 and followed links:

setup

$ mkdir tf-state-server
$ cd tf-state-server

$ virtualenv -p python3 venv
created virtual environment CPython3.8.6.final.0-64 in 174ms
  creator CPython3Posix(dest=/home/rrosso/tf-state-server/venv, clear=False, global=False)
  seeder FromAppData(download=False, pip=bundle, setuptools=bundle, wheel=bundle, via=copy, app_data_dir=/home/rrosso/.local/share/virtualenv)
    added seed packages: pip==20.1.1, pkg_resources==0.0.0, setuptools==44.0.0, wheel==0.34.2
  activators BashActivator,CShellActivator,FishActivator,PowerShellActivator,PythonActivator,XonshActivator

$ source venv/bin/activate

(venv) $ pip install -U -r requirements.txt
Collecting flask
  Using cached Flask-1.1.2-py2.py3-none-any.whl (94 kB)
Collecting flask_restful
  Downloading Flask_RESTful-0.3.8-py2.py3-none-any.whl (25 kB)
Collecting itsdangerous>=0.24
  Using cached itsdangerous-1.1.0-py2.py3-none-any.whl (16 kB)
Collecting Werkzeug>=0.15
  Using cached Werkzeug-1.0.1-py2.py3-none-any.whl (298 kB)
Collecting Jinja2>=2.10.1
  Using cached Jinja2-2.11.3-py2.py3-none-any.whl (125 kB)
Collecting click>=5.1
  Using cached click-7.1.2-py2.py3-none-any.whl (82 kB)
Collecting pytz
  Downloading pytz-2021.1-py2.py3-none-any.whl (510 kB)
     |████████████████████████████████| 510 kB 3.0 MB/s 
Collecting six>=1.3.0
  Using cached six-1.15.0-py2.py3-none-any.whl (10 kB)
Collecting aniso8601>=0.82
  Downloading aniso8601-9.0.1-py2.py3-none-any.whl (52 kB)
     |████████████████████████████████| 52 kB 524 kB/s 
Collecting MarkupSafe>=0.23
  Using cached MarkupSafe-1.1.1-cp38-cp38-manylinux2010_x86_64.whl (32 kB)
Installing collected packages: itsdangerous, Werkzeug, MarkupSafe, Jinja2, click, flask, pytz, six, aniso8601, flask-restful
Successfully installed Jinja2-2.11.3 MarkupSafe-1.1.1 Werkzeug-1.0.1 aniso8601-9.0.1 click-7.1.2 flask-1.1.2 flask-restful-0.3.8 itsdangerous-1.1.0 pytz-2021.1 six-1.15.0

(venv) $ python3 stateserver.py
 * Serving Flask app "stateserver" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://192.168.1.235:5000/ (Press CTRL+C to quit)
...

terraform point to remote http

➜  cat main.tf 
terraform {
  backend "http" {
    address = "http://192.168.1.235:5000/terraform_state/4cdd0c76-d78b-11e9-9bea-db9cd8374f3a"
    lock_address = "http://192.168.1.235:5000/terraform_lock/4cdd0c76-d78b-11e9-9bea-db9cd8374f3a"
    lock_method = "PUT"
    unlock_address = "http://192.168.1.235:5000/terraform_lock/4cdd0c76-d78b-11e9-9bea-db9cd8374f3a"
    unlock_method = "DELETE"
  }
}

➜  source ../env-vars 

➜  terraform init    

Initializing the backend...
Do you want to copy existing state to the new backend?
  Pre-existing state was found while migrating the previous "local" backend to the
  newly configured "http" backend. No existing state was found in the newly
  configured "http" backend. Do you want to copy this state to the new "http"
  backend? Enter "yes" to copy and "no" to start with an empty state.

  Enter a value: yes

Successfully configured the backend "http"! Terraform will automatically
use this backend unless the backend configuration changes.

Initializing provider plugins...

The following providers do not have any version constraints in configuration,
so the latest version was installed.

To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.

* provider.oci: version = "~> 4.17"

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

server shows
---
````bash
...
192.168.1.111 - - [16/Mar/2021 10:50:00] "POST /terraform_state/4cdd0c76-d78b-11e9-9bea-db9cd8374f3a?ID=84916e49-1b44-1b32-2058-62f28e1e8ee7 HTTP/1.1" 200 -
192.168.1.111 - - [16/Mar/2021 10:50:00] "DELETE /terraform_lock/4cdd0c76-d78b-11e9-9bea-db9cd8374f3a HTTP/1.1" 200 -
192.168.1.111 - - [16/Mar/2021 10:50:00] "GET /terraform_state/4cdd0c76-d78b-11e9-9bea-db9cd8374f3a HTTP/1.1" 200 -
...

$ ls -la .stateserver/
total 24
drwxrwxr-x 2 rrosso rrosso 4096 Mar 16 10:50 .
drwxrwxr-x 4 rrosso rrosso 4096 Mar 16 10:43 ..
-rw-rw-r-- 1 rrosso rrosso 4407 Mar 16 10:50 4cdd0c76-d78b-11e9-9bea-db9cd8374f3a
-rw-rw-r-- 1 rrosso rrosso 5420 Mar 16 10:50 4cdd0c76-d78b-11e9-9bea-db9cd8374f3a.log

$ more .stateserver/4cdd0c76-d78b-11e9-9bea-db9cd8374f3a*
::::::::::::::
.stateserver/4cdd0c76-d78b-11e9-9bea-db9cd8374f3a
::::::::::::::
{
    "lineage": "9b756fb7-e41a-7cd6-d195-d794f377e7be",
    "outputs": {},
    "resources": [
{
"instances": [
{
"attributes": {
"compartment_id": null,
"id": "ObjectStorageNamespaceDataSource-0",
"namespace": "axwscg6apasa"
},
"schema_version": 0
}
],
...
    "serial": 0,
    "terraform_version": "0.12.28",
    "version": 4
}
::::::::::::::
.stateserver/4cdd0c76-d78b-11e9-9bea-db9cd8374f3a.log
::::::::::::::
lock: {
    "Created": "2021-03-16T15:49:34.567178267Z",
    "ID": "7eea0d70-5f53-e475-041b-bcc393f4a92d",
    "Info": "",
    "Operation": "migration destination state",
    "Path": "",
    "Version": "0.12.28",
    "Who": "rrosso@desktop01"
}
unlock: {
    "Created": "2021-03-16T15:49:34.567178267Z",
    "ID": "7eea0d70-5f53-e475-041b-bcc393f4a92d",
    "Info": "",
    "Operation": "migration destination state",
    "Path": "",
    "Version": "0.12.28",
    "Who": "rrosso@desktop01"
}
lock: {
    "Created": "2021-03-16T15:49:45.760917508Z",
    "ID": "84916e49-1b44-1b32-2058-62f28e1e8ee7",
    "Info": "",
    "Operation": "migration destination state",
    "Path": "",
    "Version": "0.12.28",
    "Who": "rrosso@desktop01"
}
state_write: {
    "lineage": "9b756fb7-e41a-7cd6-d195-d794f377e7be",
    "outputs": {},
    "resources": [
{
"instances": [
{
"attributes": {
"compartment_id": null,
"id": "ObjectStorageNamespaceDataSource-0",
"namespace": "axwscg6apasa"
},
"schema_version": 0
}
...

stateserver shows during plan

...
192.168.1.111 - - [16/Mar/2021 10:54:12] "PUT /terraform_lock/4cdd0c76-d78b-11e9-9bea-db9cd8374f3a HTTP/1.1" 200 -
192.168.1.111 - - [16/Mar/2021 10:54:12] "GET /terraform_state/4cdd0c76-d78b-11e9-9bea-db9cd8374f3a HTTP/1.1" 200 -
192.168.1.111 - - [16/Mar/2021 10:54:15] "DELETE /terraform_lock/4cdd0c76-d78b-11e9-9bea-db9cd8374f3a HTTP/1.1" 200 -
...

source

$ more requirements.txt stateserver.py 
::::::::::::::
requirements.txt
::::::::::::::
flask
flask_restful
::::::::::::::
stateserver.py
::::::::::::::
#!/usr/bin/python3

import flask
import flask_restful
import json
import logging
import os

app = flask.Flask(__name__)
api = flask_restful.Api(app)

@app.before_request
def log_request_info():
    headers = []
    for header in flask.request.headers:
        headers.append('%s = %s' % (header[0], header[1]))

    body = flask.request.get_data().decode('utf-8').split('\n')

    app.logger.debug(('%(method)s for %(url)s...\n'
                      '    Header -- %(headers)s\n'
                      '    Body -- %(body)s\n')
                     % {
        'method': flask.request.method,
        'url': flask.request.url,
        'headers': '\n    Header -- '.join(headers),
        'body': '\n           '.join(body)
    })

class Root(flask_restful.Resource):
    def get(self):
        resp = flask.Response(
            'Oh, hello',
            mimetype='text/html')
        resp.status_code = 200
        return resp

class StateStore(object):
    def __init__(self, path):
        self.path = path
        os.makedirs(self.path, exist_ok=True)

    def _log(self, id, op, data):
        log_file = os.path.join(self.path, id) + '.log'
        with open(log_file, 'a') as f:
            f.write('%s: %s\n' %(op, data))

    def get(self, id):
        file = os.path.join(self.path, id)
        if os.path.exists(file):
            with open(file) as f:
                d = f.read()
                self._log(id, 'state_read', {})
                return json.loads(d)
        return None

    def put(self, id, info):
        file = os.path.join(self.path, id)
        data = json.dumps(info, indent=4, sort_keys=True)
        with open(file, 'w') as f:
            f.write(data)
            self._log(id, 'state_write', data)

    def lock(self, id, info):
        # NOTE(mikal): this is racy, but just a demo
        lock_file = os.path.join(self.path, id) + '.lock'
        if os.path.exists(lock_file):
            # If the lock exists, it should be a JSON dump of information about
            # the lock holder
            with open(lock_file) as f:
                l = json.loads(f.read())
            return False, l

        data = json.dumps(info, indent=4, sort_keys=True)
        with open(lock_file, 'w') as f:
            f.write(data)
        self._log(id, 'lock', data)
        return True, {}

    def unlock(self, id, info):
        lock_file = os.path.join(self.path, id) + '.lock'
        if os.path.exists(lock_file):
            os.unlink(lock_file)
            self._log(id, 'unlock', json.dumps(info, indent=4, sort_keys=True))
            return True
        return False

state = StateStore('.stateserver')

class TerraformState(flask_restful.Resource):
    def get(self, tf_id):
        s = state.get(tf_id)
        if not s:
            flask.abort(404)
        return s

    def post(self, tf_id):
        print(flask.request.form)
        s = state.put(tf_id, flask.request.json)
        return {}

class TerraformLock(flask_restful.Resource):
    def put(self, tf_id):
        success, info = state.lock(tf_id, flask.request.json)
        if not success:
            flask.abort(423, info)
        return info

    def delete(self, tf_id):
        if not state.unlock(tf_id, flask.request.json):
            flask.abort(404)
        return {}

api.add_resource(Root, '/')
api.add_resource(TerraformState, '/terraform_state/')
api.add_resource(TerraformLock, '/terraform_lock/')

if __name__ == '__main__':
    # Note this is not run with the flask task runner...
    app.log = logging.getLogger('werkzeug')
    app.log.setLevel(logging.DEBUG)
    #app.run(host='0.0.0.0', debug=True)

Comments Off on Terraform Stateserver Using Python
comments

Mar 22

Terraform Stateserver Using Go

Terraform can utilize a http backend for maintaining state. This is a test of a Terraform http backend using a server implemented with go.

recipe and components

• Using Virtualbox Ubuntu 20.10 and links listed below
• Run as http server
• Change to https
• For a future test terraform http backend locking feature and/or include locking in server

links

• https://www.terraform.io/docs/language/settings/backends/http.html
• https://linuxhint.com/install-golang-ubuntu/
• https://github.com/MerlinDMC/go-terraform-stateserver
• https://github.com/denji/golang-tls

setup go and test

# go version
go version go1.14.7 linux/amd64

$ pwd
/home/rrosso/tf-go-server
$ cat main.go 
package main
import "fmt"
func main() {
fmt.Println("Hello World");
}
$ go run main.go 
Hello World

$ go build main.go 
$ ./main 
Hello World

run server and test response

$ go run tf-stateserver.go -data_path=/home/rrosso/tf-go-server -listen_address=192.168.1.235:8080

$ curl -sL http://192.168.1.235:8080 | xxd
00000000: 3430 3420 7061 6765 206e 6f74 2066 6f75  404 page not fou
00000010: 6e64 0and.

terraform init with http backend

➜  terraform-poc tail -8 main.tf 
#}
## POC to test a go server
terraform {
  backend "http" {
    address = "http://192.168.1.235:8080/states/terraform.tfstate"
  }
}

➜  terraform-poc terraform init

Initializing the backend...
Do you want to copy existing state to the new backend?
  Pre-existing state was found while migrating the previous "local" backend to the
  newly configured "http" backend. No existing state was found in the newly
  configured "http" backend. Do you want to copy this state to the new "http"
  backend? Enter "yes" to copy and "no" to start with an empty state.

  Enter a value: yes

Successfully configured the backend "http"! Terraform will automatically
use this backend unless the backend configuration changes.

Initializing provider plugins...

The following providers do not have any version constraints in configuration,
so the latest version was installed.

To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.

* provider.oci: version = "~> 4.17"

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

observe server log messages

• file not found if no file exist during init is expected
• if file same and already there GET fine during init
• plan does a GET
• apply does a POST
• did not observe a DELETE
$ go run tf-stateserver.go -data_path=/home/rrosso/tf-go-server -listen_address=192.168.1.235:8080
2021/03/18 09:57:41 received GET for state file: /home/rrosso/tf-go-server/states/terraform.tfstate
...
2021/03/18 09:58:33 received POST for state file: /home/rrosso/tf-go-server/states/terraform.tfstate
...
2021/03/18 10:00:50 received GET for state file: /home/rrosso/tf-go-server/states/terraform.tfstate
2021/03/18 10:00:50 during GET cannot open file: open /home/rrosso/tf-go-server/states/terraform.tfstate: no such file or directory
2021/03/18 10:00:50 received GET for state file: /home/rrosso/tf-go-server/states/terraform.tfstate

change to self signed cert for https test

NOTE:
• use sudo since linux instance is not running low ports like 443 for normal users
• need terraform skip_cert_verification for this self signed certificate

$ openssl genrsa -out server.key 2048
Generating RSA private key, 2048 bit long modulus (2 primes)
..........................+++++
.......................................................+++++
e is 65537 (0x010001)

$ openssl ecparam -genkey -name secp384r1 -out server.key

$ openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650
...

$ sudo go run tf-stateserver.go -certfile="server.crt" -keyfile="server.key" -data_path=./ -listen_address=192.168.1.235:443

$ curl -sL http://192.168.1.235:443 | xxd
00000000: 436c 6965 6e74 2073 656e 7420 616e 2048  Client sent an H
00000010: 5454 5020 7265 7175 6573 7420 746f 2061  TTP request to a
00000020: 6e20 4854 5450 5320 7365 7276 6572 2e0a  n HTTPS server..

➜  terraform-poc tail -8 main.tf 
#}
## POC to test a go server
terraform {
  backend "http" {
    address = "https://192.168.1.235/states/terraform.tfstate"
    skip_cert_verification = true
  }
}

NOTE: terraform init, plan, apply works

additional links

• https://appdividend.com/2019/11/29/golang-json-example-how-to-use-json-with-go/
• https://www.golangprograms.com/web-application-to-read-and-write-json-data-in-json-file.html
• https://tutorialedge.net/golang/parsing-json-with-golang/
• https://dzone.com/articles/how-to-write-a-http-rest-api-server-in-go-in-minut

source

NOTE: subsequently checked source into github here: https://github.com/rrossouw01/terraform-stateserver-go/blob/main/server.go

Can use the file directly with simple wget to the raw link for example:

$ wget https://raw.githubusercontent.com/rrossouw01/terraform-stateserver-go/main/server.go
$ cat tf-stateserver.go 
package main
//https://github.com/MerlinDMC/go-terraform-stateserver/blob/master/main.go

import (
    "flag"
    "io"
    "log"
    "net/http"
    "os"
    "path/filepath"
)

var (
    flagDataPath string
    flagCertFile string
    flagKeyFile  string

    flagListenAddress string
)

func init() {
    flag.StringVar(&flagCertFile, "certfile", "", "path to the certs file for https")
    flag.StringVar(&flagKeyFile, "keyfile", "", "path to the keyfile for https")
    flag.StringVar(&flagDataPath, "data_path", os.TempDir(), "path to the data storage directory")

    flag.StringVar(&flagListenAddress, "listen_address", "0.0.0.0:8080", "address:port to bind listener on")
    //flag.StringVar(&flagListenAddress, "listen_address", "192.168.1.235:8080", "address:port to bind listener on")
}

func main() {
    if !flag.Parsed() {
        flag.Parse()
    }

    router := http.NewServeMux()
    router.HandleFunc("/", requestHandler)

    if flagCertFile != "" && flagKeyFile != "" {
          log.Fatal(http.ListenAndServeTLS(flagListenAddress, flagCertFile, flagKeyFile, router))
    } else {
          log.Fatal(http.ListenAndServe(flagListenAddress, router))
    }
}

func requestHandler(res http.ResponseWriter, req *http.Request) {
    if req.URL.Path == "/" {
        http.NotFound(res, req)
        return
    }

    stateStorageFile := filepath.Join(flagDataPath, req.URL.Path)
    stateStorageDir := filepath.Dir(stateStorageFile)

    switch req.Method {
    case "GET":
        log.Printf("received GET for state file: %s\n", stateStorageFile)
        fh, err := os.Open(stateStorageFile)
        if err != nil {
            log.Printf("during GET cannot open file: %s\n", err)
            goto not_found
        }
        defer fh.Close()

        res.WriteHeader(200)

        io.Copy(res, fh)

        return
    case "POST":
        log.Printf("received POST for state file: %s\n", stateStorageFile)
        if err := os.MkdirAll(stateStorageDir, 0750); err != nil && !os.IsExist(err) {
            log.Printf("during POST cannot create parent directories: %s\n", err)
            goto not_found
        }

        fh, err := os.OpenFile(stateStorageFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
        if err != nil {
            log.Printf("cannot open file: %s\n", err)
            goto not_found
        }
        defer fh.Close()

        if _, err := io.Copy(fh, req.Body); err != nil {
            log.Printf("failed to stream data into statefile: %s\n", err)
            goto not_found
        }

        res.WriteHeader(200)

        return
    case "DELETE":
        log.Printf("received DELETE for state file: %s\n", stateStorageFile)
        //if os.RemoveAll(stateStorageFile) != nil {
        if err := os.RemoveAll(stateStorageFile); err != nil {
            log.Printf("cannot remove file: %s\n", err)
            goto not_found
        }

        res.WriteHeader(200)

        return
    }

not_found:
    http.NotFound(res, req)
}

Comments Off on Terraform Stateserver Using Go
comments

Sep 15

Terraform with Azure

Azure provides a cloud shell with Terraform already installed and even an editor aware of Terraform source. I tested creating a network and VM using the cloud shell in the browser.

REF: https://docs.microsoft.com/en-us/azure/virtual-machines/linux/terraform-install-configure

Note: I am not showing dry run (terraform plan) but you should always be in the habit of plan before apply.

###################################################################
Using cloud shell prompts for a storage account tied to your shell.
###################################################################

Your cloud drive has been created in:

Subscription Id: b[..]b
Resource group:  cloud-shell-storage-southcentralus
Storage account: c[..]4
File share:      cs-[..]2

Initializing your account for Cloud Shell...\
Requesting a Cloud Shell.Succeeded.
Connecting terminal...

Welcome to Azure Cloud Shell

Type "az" to use Azure CLI 2.0
Type "help" to learn about Cloud Shell

#######
Test az
#######

riaan@Azure:~$ az vm list
[
  {
    "availabilitySet": null,
    "diagnosticsProfile": {
      "bootDiagnostics": {
        "enabled": true,
        "storageUri": "https://sitesdiag.blob.core.windows.net/"
      }
    },
    "hardwareProfile": {
      "vmSize": "Standard_B1s"
    },
 [..]
  }
]

###################################################################################################################
Check folder and space available
###################################################################################################################

riaan@Azure:~$ ls
clouddrive
riaan@Azure:~$ df -h .
Filesystem      Size  Used Avail Use% Mounted on
/dev/loop0      5.0G   11M  4.7G   1% /home/riaan

riaan@Azure:~$ az account show --query "{subscriptionId:id, tenantId:tenantId}"
{
  "subscriptionId": "b[..]",
  "tenantId": "f[..]"
}

###################################################################################################################
Simple terraform test add a resource group
###################################################################################################################

riaan@Azure:~$ mkdir sites
riaan@Azure:~$ cd sites
riaan@Azure:~/sites$ vi test.tf
riaan@Azure:~/sites$ terraform init

Initializing provider plugins...
- Checking for available provider plugins on https://releases.hashicorp.com...
- Downloading plugin for provider "azurerm" (1.15.0)...
[..]
* provider.azurerm: version = "~> 1.15"
Terraform has been successfully initialized!

riaan@Azure:~/sites$ terraform apply
[..]
  + create

Terraform will perform the following actions:

  + azurerm_resource_group.rg
      id:       <computed>
      location: "centralus"
      name:     "testResourceGroup"
      tags.%:   <computed>

Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

azurerm_resource_group.rg: Creating...
  location: "" => "centralus"
  name:     "" => "testResourceGroup"
  tags.%:   "" => "<computed>"
azurerm_resource_group.rg: Creation complete after 0s (ID: /subscriptions/b[..]/resourceGroups/testResourceGroup)

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

#######################################################################
Simple terraform test. Destroy a resource group. I just comment it out.
#######################################################################

riaan@Azure:~/sites$ vi test.tf
riaan@Azure:~/sites$ terraform apply
azurerm_resource_group.rg: Refreshing state... (ID: /subscriptions/b[..]/resourceGroups/testResourceGroup)

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

  - azurerm_resource_group.rg

Plan: 0 to add, 0 to change, 1 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

azurerm_resource_group.rg: Destroying... (ID: /subscriptions/b[..]/resourceGroups/testResourceGroup)
azurerm_resource_group.rg: Still destroying... (ID: /subscriptions/b[..]/resourceGroups/testResourceGroup, 10s elapsed)
[..]
azurerm_resource_group.rg: Destruction complete after 45s

Apply complete! Resources: 0 added, 0 changed, 1 destroyed.

###################################################################################################################
Create VM plus network
https://docs.microsoft.com/en-us/azure/virtual-machines/linux/terraform-create-complete-vm
###################################################################################################################

riaan@Azure:~/sites$ rm test.tf
riaan@Azure:~/sites$ vi main.tf
riaan@Azure:~/sites$ terraform apply

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  + azurerm_network_interface.myterraformnic
      id:                                                                    <computed>
      applied_dns_servers.#:                                                 <computed>
      dns_servers.#:                                                         <computed>
      enable_accelerated_networking:                                         "false"
      enable_ip_forwarding:                                                  "false"
      internal_dns_name_label:                                               <computed>
      internal_fqdn:                                                         <computed>
      ip_configuration.#:                                                    "1"
      ip_configuration.0.application_gateway_backend_address_pools_ids.#:    <computed>
      ip_configuration.0.application_security_group_ids.#:                   <computed>
      ip_configuration.0.load_balancer_backend_address_pools_ids.#:          <computed>
      ip_configuration.0.load_balancer_inbound_nat_rules_ids.#:              <computed>
      ip_configuration.0.name:                                               "myNicConfiguration"
      ip_configuration.0.primary:                                            <computed>
      ip_configuration.0.private_ip_address_allocation:                      "dynamic"
      ip_configuration.0.public_ip_address_id:                               "${azurerm_public_ip.myterraformpublicip.id}"
      ip_configuration.0.subnet_id:                                          "${azurerm_subnet.myterraformsubnet.id}"
      location:                                                              "eastus"
      mac_address:                                                           <computed>
      name:                                                                  "myNIC"
      network_security_group_id:                                             "${azurerm_network_security_group.myterraformnsg.id}"
      private_ip_address:                                                    <computed>
      private_ip_addresses.#:                                                <computed>
      resource_group_name:                                                   "myResourceGroup"
      tags.%:                                                                "1"
      tags.environment:                                                      "Terraform Demo"
      virtual_machine_id:                                                    <computed>

  + azurerm_network_security_group.myterraformnsg
      id:                                                                    <computed>
      location:                                                              "eastus"
      name:                                                                  "myNetworkSecurityGroup"
      resource_group_name:                                                   "myResourceGroup"
      security_rule.#:                                                       "1"
      security_rule.2090309246.access:                                       "Allow"
      security_rule.2090309246.description:                                  ""
      security_rule.2090309246.destination_address_prefix:                   "*"
      security_rule.2090309246.destination_address_prefixes.#:               "0"
      security_rule.2090309246.destination_application_security_group_ids.#: "0"
      security_rule.2090309246.destination_port_range:                       "22"
      security_rule.2090309246.destination_port_ranges.#:                    "0"
      security_rule.2090309246.direction:                                    "Inbound"
      security_rule.2090309246.name:                                         "SSH"
      security_rule.2090309246.priority:                                     "1001"
      security_rule.2090309246.protocol:                                     "Tcp"
      security_rule.2090309246.source_address_prefix:                        "*"
      security_rule.2090309246.source_address_prefixes.#:                    "0"
      security_rule.2090309246.source_application_security_group_ids.#:      "0"
      security_rule.2090309246.source_port_range:                            "*"
      security_rule.2090309246.source_port_ranges.#:                         "0"
      tags.%:                                                                "1"
      tags.environment:                                                      "Terraform Demo"

  + azurerm_public_ip.myterraformpublicip
      id:                                                                    <computed>
      fqdn:                                                                  <computed>
      ip_address:                                                            <computed>
      location:                                                              "eastus"
      name:                                                                  "myPublicIP"
      public_ip_address_allocation:                                          "dynamic"
      resource_group_name:                                                   "myResourceGroup"
      sku:                                                                   "Basic"
      tags.%:                                                                "1"
      tags.environment:                                                      "Terraform Demo"

  + azurerm_resource_group.myterraformgroup
      id:                                                                    <computed>
      location:                                                              "eastus"
      name:                                                                  "myResourceGroup"
      tags.%:                                                                "1"
      tags.environment:                                                      "Terraform Demo"

  + azurerm_storage_account.mystorageaccount
      id:                                                                    <computed>
      access_tier:                                                           <computed>
      account_encryption_source:                                             "Microsoft.Storage"
      account_kind:                                                          "Storage"
      account_replication_type:                                              "LRS"
      account_tier:                                                          "Standard"
      enable_blob_encryption:                                                "true"
      enable_file_encryption:                                                "true"
      identity.#:                                                            <computed>
      location:                                                              "eastus"
      name:                                                                  "diag${random_id.randomId.hex}"
      primary_access_key:                                                    <computed>
      primary_blob_connection_string:                                        <computed>
      primary_blob_endpoint:                                                 <computed>
      primary_connection_string:                                             <computed>
      primary_file_endpoint:                                                 <computed>
      primary_location:                                                      <computed>
      primary_queue_endpoint:                                                <computed>
      primary_table_endpoint:                                                <computed>
      resource_group_name:                                                   "myResourceGroup"
      secondary_access_key:                                                  <computed>
      secondary_blob_connection_string:                                      <computed>
      secondary_blob_endpoint:                                               <computed>
      secondary_connection_string:                                           <computed>
      secondary_location:                                                    <computed>
      secondary_queue_endpoint:                                              <computed>
      secondary_table_endpoint:                                              <computed>
      tags.%:                                                                "1"
      tags.environment:                                                      "Terraform Demo"

  + azurerm_subnet.myterraformsubnet
      id:                                                                    <computed>
      address_prefix:                                                        "10.0.1.0/24"
      ip_configurations.#:                                                   <computed>
      name:                                                                  "mySubnet"
      resource_group_name:                                                   "myResourceGroup"
      virtual_network_name:                                                  "myVnet"

  + azurerm_virtual_machine.myterraformvm
      id:                                                                    <computed>
      availability_set_id:                                                   <computed>
      boot_diagnostics.#:                                                    "1"
      boot_diagnostics.0.enabled:                                            "true"
      boot_diagnostics.0.storage_uri:                                        "${azurerm_storage_account.mystorageaccount.primary_blob_endpoint}"
      delete_data_disks_on_termination:                                      "false"
      delete_os_disk_on_termination:                                         "false"
      identity.#:                                                            <computed>
      location:                                                              "eastus"
      name:                                                                  "myVM"
      network_interface_ids.#:                                               <computed>
      os_profile.#:                                                          "1"
      os_profile.1770182618.admin_password:                                  <sensitive>
      os_profile.1770182618.admin_username:                                  "azureuser"
      os_profile.1770182618.computer_name:                                   "myvm"
      os_profile.1770182618.custom_data:                                     <computed>
      os_profile_linux_config.#:                                             "1"
      os_profile_linux_config.69840937.disable_password_authentication:      "true"
      os_profile_linux_config.69840937.ssh_keys.#:                           "1"
      os_profile_linux_config.69840937.ssh_keys.0.key_data:                  "ssh-rsa AAAAB3Nz{snip}hwhqT9h"
      os_profile_linux_config.69840937.ssh_keys.0.path:                      "/home/azureuser/.ssh/authorized_keys"
      resource_group_name:                                                   "myResourceGroup"
      storage_data_disk.#:                                                   <computed>
      storage_image_reference.#:                                             "1"
      storage_image_reference.363552096.id:                                  ""
      storage_image_reference.363552096.offer:                               "UbuntuServer"
      storage_image_reference.363552096.publisher:                           "Canonical"
      storage_image_reference.363552096.sku:                                 "16.04.0-LTS"
      storage_image_reference.363552096.version:                             "latest"
      storage_os_disk.#:                                                     "1"
      storage_os_disk.0.caching:                                             "ReadWrite"
      storage_os_disk.0.create_option:                                       "FromImage"
      storage_os_disk.0.disk_size_gb:                                        <computed>
      storage_os_disk.0.managed_disk_id:                                     <computed>
      storage_os_disk.0.managed_disk_type:                                   "Premium_LRS"
      storage_os_disk.0.name:                                                "myOsDisk"
      storage_os_disk.0.write_accelerator_enabled:                           "false"
      tags.%:                                                                "1"
      tags.environment:                                                      "Terraform Demo"
      vm_size:                                                               "Standard_DS1_v2"

  + azurerm_virtual_network.myterraformnetwork
      id:                                                                    <computed>
      address_space.#:                                                       "1"
      address_space.0:                                                       "10.0.0.0/16"
      location:                                                              "eastus"
      name:                                                                  "myVnet"
      resource_group_name:                                                   "myResourceGroup"
      subnet.#:                                                              <computed>
      tags.%:                                                                "1"
      tags.environment:                                                      "Terraform Demo"

  + random_id.randomId
      id:                                                                    <computed>
      b64:                                                                   <computed>
      b64_std:                                                               <computed>
      b64_url:                                                               <computed>
      byte_length:                                                           "8"
      dec:                                                                   <computed>
      hex:                                                                   <computed>
      keepers.%:                                                             "1"
      keepers.resource_group:                                                "myResourceGroup"


Plan: 9 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

azurerm_resource_group.myterraformgroup: Creating...
  location:         "" => "eastus"
  name:             "" => "myResourceGroup"
  tags.%:           "" => "1"
  tags.environment: "" => "Terraform Demo"
azurerm_resource_group.myterraformgroup: Creation complete after 0s (ID: /subscriptions/b[..]/resourceGroups/myResourceGroup)
azurerm_virtual_network.myterraformnetwork: Creating...
  address_space.#:     "" => "1"
  address_space.0:     "" => "10.0.0.0/16"
  location:            "" => "eastus"
  name:                "" => "myVnet"
  resource_group_name: "" => "myResourceGroup"
  subnet.#:            "" => "<computed>"
  tags.%:              "" => "1"
  tags.environment:    "" => "Terraform Demo"
azurerm_public_ip.myterraformpublicip: Creating...
  fqdn:                         "" => "<computed>"
  ip_address:                   "" => "<computed>"
  location:                     "" => "eastus"
  name:                         "" => "myPublicIP"
  public_ip_address_allocation: "" => "dynamic"
  resource_group_name:          "" => "myResourceGroup"
  sku:                          "" => "Basic"
  tags.%:                       "" => "1"
  tags.environment:             "" => "Terraform Demo"
random_id.randomId: Creating...
  b64:                    "" => "<computed>"
  b64_std:                "" => "<computed>"
  b64_url:                "" => "<computed>"
  byte_length:            "" => "8"
  dec:                    "" => "<computed>"
  hex:                    "" => "<computed>"
  keepers.%:              "" => "1"
  keepers.resource_group: "" => "myResourceGroup"
random_id.randomId: Creation complete after 0s (ID: ZZic4bhOapM)
azurerm_network_security_group.myterraformnsg: Creating...
  location:                                                              "" => "eastus"
  name:                                                                  "" => "myNetworkSecurityGroup"
  resource_group_name:                                                   "" => "myResourceGroup"
  security_rule.#:                                                       "" => "1"
  security_rule.2090309246.access:                                       "" => "Allow"
  security_rule.2090309246.description:                                  "" => ""
  security_rule.2090309246.destination_address_prefix:                   "" => "*"
  security_rule.2090309246.destination_address_prefixes.#:               "" => "0"
  security_rule.2090309246.destination_application_security_group_ids.#: "" => "0"
  security_rule.2090309246.destination_port_range:                       "" => "22"
  security_rule.2090309246.destination_port_ranges.#:                    "" => "0"
  security_rule.2090309246.direction:                                    "" => "Inbound"
  security_rule.2090309246.name:                                         "" => "SSH"
  security_rule.2090309246.priority:                                     "" => "1001"
  security_rule.2090309246.protocol:                                     "" => "Tcp"
  security_rule.2090309246.source_address_prefix:                        "" => "*"
  security_rule.2090309246.source_address_prefixes.#:                    "" => "0"
  security_rule.2090309246.source_application_security_group_ids.#:      "" => "0"
  security_rule.2090309246.source_port_range:                            "" => "*"
  security_rule.2090309246.source_port_ranges.#:                         "" => "0"
  tags.%:                                                                "" => "1"
  tags.environment:                                                      "" => "Terraform Demo"
azurerm_storage_account.mystorageaccount: Creating...
  access_tier:                      "" => "<computed>"
  account_encryption_source:        "" => "Microsoft.Storage"
  account_kind:                     "" => "Storage"
  account_replication_type:         "" => "LRS"
  account_tier:                     "" => "Standard"
  enable_blob_encryption:           "" => "true"
  enable_file_encryption:           "" => "true"
  identity.#:                       "" => "<computed>"
  location:                         "" => "eastus"
  name:                             "" => "diag6[..]"
  primary_access_key:               "<sensitive>" => "<sensitive>"
  primary_blob_connection_string:   "<sensitive>" => "<sensitive>"
  primary_blob_endpoint:            "" => "<computed>"
  primary_connection_string:        "<sensitive>" => "<sensitive>"
  primary_file_endpoint:            "" => "<computed>"
  primary_location:                 "" => "<computed>"
  primary_queue_endpoint:           "" => "<computed>"
  primary_table_endpoint:           "" => "<computed>"
  resource_group_name:              "" => "myResourceGroup"
  secondary_access_key:             "<sensitive>" => "<sensitive>"
  secondary_blob_connection_string: "<sensitive>" => "<sensitive>"
  secondary_blob_endpoint:          "" => "<computed>"
  secondary_connection_string:      "<sensitive>" => "<sensitive>"
  secondary_location:               "" => "<computed>"
  secondary_queue_endpoint:         "" => "<computed>"
  secondary_table_endpoint:         "" => "<computed>"
  tags.%:                           "" => "1"
  tags.environment:                 "" => "Terraform Demo"
azurerm_public_ip.myterraformpublicip: Creation complete after 2s (ID: /subscriptions/b[..]-...t.Network/publicIPAddresses/myPublicIP)
azurerm_network_security_group.myterraformnsg: Creation complete after 2s (ID: /subscriptions/b[..]-...kSecurityGroups/myNetworkSecurityGroup)
azurerm_virtual_network.myterraformnetwork: Still creating... (10s elapsed)
azurerm_storage_account.mystorageaccount: Still creating... (10s elapsed)
azurerm_storage_account.mystorageaccount: Creation complete after 19s (ID: /subscriptions/b[..]-...e/storageAccounts/diag6[..])
azurerm_virtual_network.myterraformnetwork: Still creating... (20s elapsed)
azurerm_virtual_network.myterraformnetwork: Creation complete after 22s (ID: /subscriptions/b[..]-...crosoft.Network/virtualNetworks/myVnet)
azurerm_subnet.myterraformsubnet: Creating...
  address_prefix:       "" => "10.0.1.0/24"
  ip_configurations.#:  "" => "<computed>"
  name:                 "" => "mySubnet"
  resource_group_name:  "" => "myResourceGroup"
  virtual_network_name: "" => "myVnet"
azurerm_subnet.myterraformsubnet: Still creating... (10s elapsed)
azurerm_subnet.myterraformsubnet: Creation complete after 11s (ID: /subscriptions/b[..]-...irtualNetworks/myVnet/subnets/mySubnet)
azurerm_network_interface.myterraformnic: Creating...
  applied_dns_servers.#:                                              "" => "<computed>"
  dns_servers.#:                                                      "" => "<computed>"
  enable_accelerated_networking:                                      "" => "false"
  enable_ip_forwarding:                                               "" => "false"
  internal_dns_name_label:                                            "" => "<computed>"
  internal_fqdn:                                                      "" => "<computed>"
  ip_configuration.#:                                                 "" => "1"
  ip_configuration.0.application_gateway_backend_address_pools_ids.#: "" => "<computed>"
  ip_configuration.0.application_security_group_ids.#:                "" => "<computed>"
  ip_configuration.0.load_balancer_backend_address_pools_ids.#:       "" => "<computed>"
  ip_configuration.0.load_balancer_inbound_nat_rules_ids.#:           "" => "<computed>"
  ip_configuration.0.name:                                            "" => "myNicConfiguration"
  ip_configuration.0.primary:                                         "" => "<computed>"
  ip_configuration.0.private_ip_address_allocation:                   "" => "dynamic"
  ip_configuration.0.public_ip_address_id:                            "" => "/subscriptions/b[..]/resourceGroups/myResourceGroup/providers/Microsoft.Network/publicIPAddresses/myPublicIP"
  ip_configuration.0.subnet_id:                                       "" => "/subscriptions/b[..]/resourceGroups/myResourceGroup/providers/Microsoft.Network/virtualNetworks/myVnet/subnets/mySubnet"
  location:                                                           "" => "eastus"
  mac_address:                                                        "" => "<computed>"
  name:                                                               "" => "myNIC"
  network_security_group_id:                                          "" => "/subscriptions/b[..]/resourceGroups/myResourceGroup/providers/Microsoft.Network/networkSecurityGroups/myNetworkSecurityGroup"
  private_ip_address:                                                 "" => "<computed>"
  private_ip_addresses.#:                                             "" => "<computed>"
  resource_group_name:                                                "" => "myResourceGroup"
  tags.%:                                                             "" => "1"
  tags.environment:                                                   "" => "Terraform Demo"
  virtual_machine_id:                                                 "" => "<computed>"
azurerm_network_interface.myterraformnic: Creation complete after 1s (ID: /subscriptions/b[..]-...rosoft.Network/networkInterfaces/myNIC)
azurerm_virtual_machine.myterraformvm: Creating...
  availability_set_id:                                              "" => "<computed>"
  boot_diagnostics.#:                                               "" => "1"
  boot_diagnostics.0.enabled:                                       "" => "true"
  boot_diagnostics.0.storage_uri:                                   "" => "https://diag[..].blob.core.windows.net/"
  delete_data_disks_on_termination:                                 "" => "false"
  delete_os_disk_on_termination:                                    "" => "false"
  identity.#:                                                       "" => "<computed>"
  location:                                                         "" => "eastus"
  name:                                                             "" => "myVM"
  network_interface_ids.#:                                          "" => "1"
  network_interface_ids.0:                                          "" => "/subscriptions/b[..]/resourceGroups/myResourceGroup/providers/Microsoft.Network/networkInterfaces/myNIC"
  os_profile.#:                                                     "" => "1"
  os_profile.1770182618.admin_password:                             "<sensitive>" => "<sensitive>"
  os_profile.1770182618.admin_username:                             "" => "azureuser"
  os_profile.1770182618.computer_name:                              "" => "myvm"
  os_profile.1770182618.custom_data:                                "" => "<computed>"
  os_profile_linux_config.#:                                        "" => "1"
  os_profile_linux_config.69840937.disable_password_authentication: "" => "true"
  os_profile_linux_config.69840937.ssh_keys.#:                      "" => "1"
  os_profile_linux_config.69840937.ssh_keys.0.key_data:             "" => "ssh-rsa AAAAB3Nz{snip}hwhqT9h"
  os_profile_linux_config.69840937.ssh_keys.0.path:                 "" => "/home/azureuser/.ssh/authorized_keys"
  resource_group_name:                                              "" => "myResourceGroup"
  storage_data_disk.#:                                              "" => "<computed>"
  storage_image_reference.#:                                        "" => "1"
  storage_image_reference.363552096.id:                             "" => ""
  storage_image_reference.363552096.offer:                          "" => "UbuntuServer"
  storage_image_reference.363552096.publisher:                      "" => "Canonical"
  storage_image_reference.363552096.sku:                            "" => "16.04.0-LTS"
  storage_image_reference.363552096.version:                        "" => "latest"
  storage_os_disk.#:                                                "" => "1"
  storage_os_disk.0.caching:                                        "" => "ReadWrite"
  storage_os_disk.0.create_option:                                  "" => "FromImage"
  storage_os_disk.0.disk_size_gb:                                   "" => "<computed>"
  storage_os_disk.0.managed_disk_id:                                "" => "<computed>"
  storage_os_disk.0.managed_disk_type:                              "" => "Premium_LRS"
  storage_os_disk.0.name:                                           "" => "myOsDisk"
  storage_os_disk.0.write_accelerator_enabled:                      "" => "false"
  tags.%:                                                           "" => "1"
  tags.environment:                                                 "" => "Terraform Demo"
  vm_size:                                                          "" => "Standard_DS1_v2"

Error: Error applying plan:

1 error(s) occurred:

* azurerm_virtual_machine.myterraformvm: 1 error(s) occurred:

* azurerm_virtual_machine.myterraformvm: compute.VirtualMachinesClient#CreateOrUpdate: Failure sending request: StatusCode=0 -- Original Error: autorest/azure: Service returned an error. Status=400 Code="InvalidParameter" Message="The value of parameter linuxConfiguration.ssh.publicKeys.keyData is invalid." Target="linuxConfiguration.ssh.publicKeys.keyData"

Terraform does not automatically rollback in the face of errors.
Instead, your Terraform state file has been partially updated with
any resources that successfully completed. Please address the error
above and apply again to incrementally change your infrastructure.

######################################################
Add a valid ssh public key to continue creating the VM
######################################################

riaan@Azure:~/sites$ vi main.tf
riaan@Azure:~/sites$ terraform apply
azurerm_resource_group.myterraformgroup: Refreshing state... (ID: /subscriptions/b[..]/resourceGroups/myResourceGroup)
azurerm_network_security_group.myterraformnsg: Refreshing state... (ID: /subscriptions/b[..]-...kSecurityGroups/myNetworkSecurityGroup)
random_id.randomId: Refreshing state... (ID: ZZic4bhOapM)
azurerm_virtual_network.myterraformnetwork: Refreshing state... (ID: /subscriptions/b[..]-...crosoft.Network/virtualNetworks/myVnet)
azurerm_public_ip.myterraformpublicip: Refreshing state... (ID: /subscriptions/b[..]-...t.Network/publicIPAddresses/myPublicIP)
azurerm_storage_account.mystorageaccount: Refreshing state... (ID: /subscriptions/b[..]-...e/storageAccounts/diag[..])
azurerm_subnet.myterraformsubnet: Refreshing state... (ID: /subscriptions/b[..]-...irtualNetworks/myVnet/subnets/mySubnet)
azurerm_network_interface.myterraformnic: Refreshing state... (ID: /subscriptions/b[..]-...rosoft.Network/networkInterfaces/myNIC)

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  + azurerm_virtual_machine.myterraformvm
      id:                                                               <computed>
      availability_set_id:                                              <computed>
      boot_diagnostics.#:                                               "1"
      boot_diagnostics.0.enabled:                                       "true"
      boot_diagnostics.0.storage_uri:                                   "https://diag[..].blob.core.windows.net/"
      delete_data_disks_on_termination:                                 "false"
      delete_os_disk_on_termination:                                    "false"
      identity.#:                                                       <computed>
      location:                                                         "eastus"
      name:                                                             "myVM"
      network_interface_ids.#:                                          "1"
      network_interface_ids.0:                                          "/subscriptions/b[..]/resourceGroups/myResourceGroup/providers/Microsoft.Network/networkInterfaces/myNIC"
      os_profile.#:                                                     "1"
      os_profile.1770182618.admin_password:                             <sensitive>
      os_profile.1770182618.admin_username:                             "azureuser"
      os_profile.1770182618.computer_name:                              "myvm"
      os_profile.1770182618.custom_data:                                <computed>
      os_profile_linux_config.#:                                        "1"
      os_profile_linux_config.69840937.disable_password_authentication: "true"
      os_profile_linux_config.69840937.ssh_keys.#:                      "1"
      os_profile_linux_config.69840937.ssh_keys.0.key_data:             "ssh-rsa [..]"
      os_profile_linux_config.69840937.ssh_keys.0.path:                 "/home/azureuser/.ssh/authorized_keys"
      resource_group_name:                                              "myResourceGroup"
      storage_data_disk.#:                                              <computed>
      storage_image_reference.#:                                        "1"
      storage_image_reference.363552096.id:                             ""
      storage_image_reference.363552096.offer:                          "UbuntuServer"
      storage_image_reference.363552096.publisher:                      "Canonical"
      storage_image_reference.363552096.sku:                            "16.04.0-LTS"
      storage_image_reference.363552096.version:                        "latest"
      storage_os_disk.#:                                                "1"
      storage_os_disk.0.caching:                                        "ReadWrite"
      storage_os_disk.0.create_option:                                  "FromImage"
      storage_os_disk.0.disk_size_gb:                                   <computed>
      storage_os_disk.0.managed_disk_id:                                <computed>
      storage_os_disk.0.managed_disk_type:                              "Premium_LRS"
      storage_os_disk.0.name:                                           "myOsDisk"
      storage_os_disk.0.write_accelerator_enabled:                      "false"
      tags.%:                                                           "1"
      tags.environment:                                                 "Terraform Demo"
      vm_size:                                                          "Standard_DS1_v2"


Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

azurerm_virtual_machine.myterraformvm: Creating...
  availability_set_id:                                              "" => "<computed>"
  boot_diagnostics.#:                                               "" => "1"
  boot_diagnostics.0.enabled:                                       "" => "true"
  boot_diagnostics.0.storage_uri:                                   "" => "https://diag[..].blob.core.windows.net/"
  delete_data_disks_on_termination:                                 "" => "false"
  delete_os_disk_on_termination:                                    "" => "false"
  identity.#:                                                       "" => "<computed>"
  location:                                                         "" => "eastus"
  name:                                                             "" => "myVM"
  network_interface_ids.#:                                          "" => "1"
  network_interface_ids.0:                                          "" => "/subscriptions/b[..]/resourceGroups/myResourceGroup/providers/Microsoft.Network/networkInterfaces/myNIC"
  os_profile.#:                                                     "" => "1"
  os_profile.1770182618.admin_password:                             "<sensitive>" => "<sensitive>"
  os_profile.1770182618.admin_username:                             "" => "azureuser"
  os_profile.1770182618.computer_name:                              "" => "myvm"
  os_profile.1770182618.custom_data:                                "" => "<computed>"
  os_profile_linux_config.#:                                        "" => "1"
  os_profile_linux_config.69840937.disable_password_authentication: "" => "true"
  os_profile_linux_config.69840937.ssh_keys.#:                      "" => "1"
  os_profile_linux_config.69840937.ssh_keys.0.key_data:             "" => "ssh-rsa A[..]"
  os_profile_linux_config.69840937.ssh_keys.0.path:                 "" => "/home/azureuser/.ssh/authorized_keys"
  resource_group_name:                                              "" => "myResourceGroup"
  storage_data_disk.#:                                              "" => "<computed>"
  storage_image_reference.#:                                        "" => "1"
  storage_image_reference.363552096.id:                             "" => ""
  storage_image_reference.363552096.offer:                          "" => "UbuntuServer"
  storage_image_reference.363552096.publisher:                      "" => "Canonical"
  storage_image_reference.363552096.sku:                            "" => "16.04.0-LTS"
  storage_image_reference.363552096.version:                        "" => "latest"
  storage_os_disk.#:                                                "" => "1"
  storage_os_disk.0.caching:                                        "" => "ReadWrite"
  storage_os_disk.0.create_option:                                  "" => "FromImage"
  storage_os_disk.0.disk_size_gb:                                   "" => "<computed>"
  storage_os_disk.0.managed_disk_id:                                "" => "<computed>"
  storage_os_disk.0.managed_disk_type:                              "" => "Premium_LRS"
  storage_os_disk.0.name:                                           "" => "myOsDisk"
  storage_os_disk.0.write_accelerator_enabled:                      "" => "false"
  tags.%:                                                           "" => "1"
  tags.environment:                                                 "" => "Terraform Demo"
  vm_size:                                                          "" => "Standard_DS1_v2"
azurerm_virtual_machine.myterraformvm: Still creating... (10s elapsed)
azurerm_virtual_machine.myterraformvm: Still creating... (20s elapsed)
azurerm_virtual_machine.myterraformvm: Still creating... (30s elapsed)
azurerm_virtual_machine.myterraformvm: Still creating... (40s elapsed)
azurerm_virtual_machine.myterraformvm: Still creating... (50s elapsed)
azurerm_virtual_machine.myterraformvm: Still creating... (1m0s elapsed)
azurerm_virtual_machine.myterraformvm: Still creating... (1m10s elapsed)
azurerm_virtual_machine.myterraformvm: Still creating... (1m20s elapsed)
azurerm_virtual_machine.myterraformvm: Still creating... (1m30s elapsed)
azurerm_virtual_machine.myterraformvm: Creation complete after 1m35s (ID: /subscriptions/b[..]-...Microsoft.Compute/virtualMachines/myVM)

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

##################################################################
use built-in editor(vscode?) Comment out the VM so we can destroy
##################################################################

riaan@Azure:~/sites$ terraform apply
azurerm_resource_group.myterraformgroup: Refreshing state...
[..]

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

  - azurerm_virtual_machine.myterraformvm


Plan: 0 to add, 0 to change, 1 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

azurerm_virtual_machine.myterraformvm: Destroying... (ID: /subscriptions/[..]-...Microsoft.Compute/virtualMachines/myVM)
[..]
azurerm_virtual_machine.myterraformvm: Destruction complete after 34s

Apply complete! Resources: 0 added, 0 changed, 1 destroyed.

Comments Off on Terraform with Azure
comments

Sep 25

Compute Instance in OCI using terraform

Begin Update 9/26/17.

It is possible to reference the subnet as follow:

subnet_id = "${oci_core_subnet.PrivSubnetAD1.id}"

My problem and workaround originally is because I am using modules. I would prefer modules so I can organize and subdivide better but it caused above to not work. Plus subdividing the work may cause more issues around losing access to variables/references.
End Update 9/26/17

Most likely there is a better way to do this but since I spent some time on it I am jotting down my notes. When creating compute instances with terraform in Oracle Cloud Infrastructure(Oracle Bare Metal Services) you have to specify the subnet_id. The id or ocid as called in OCI is a long unique string.

So if you are looking at automating the terraform build you may struggle with not knowing subnet_id when creating a compute instance. As I said there may be better ways to do this and maybe the AWS plugin for terraform handles this already. I did this with the OCI plugin and came up with the below script and using some custom API calls to create terraform variables of the subnets. Just showing the bash script for some ideas on flow and how it glues together. The terraform source and api calls not shown here.

#!/bin/bash

build_root="/home/rrosso/.terraform.d/MY-PROTO"
api_snippets_location="/home/rrosso/oraclebmc"
terraform_bin="/home/rrosso/.terraform.d/terraform"

function pause(){
   read -p "$*"
}

## Check if environment variables are set. Make very sure correct tenancy, compartment etc
## Or if hard coding these look in main.tf files for correct tenancy, compartment etc
env | grep TF_

pause "Press [Enter] key if Variables look ok and if ready to proceed with networking build"

cd "$build_root/networking"
$terraform_bin apply

pause "Press [Enter] key if networking build went ok and ready to generate a list of subnet ocid's"

cd "$api_snippets_location"
python get_subnet_ocids.py -t ocid1.tenancy.oc1..<cut_long_number_here> -c MYPROTO -v DEV >> $build_root/compute/webservers/variables.tf 
python get_subnet_ocids.py -t ocid1.tenancy.oc1..<cut_long_number_here> -c MYPROTO -v DEV >> $build_root/compute/bastions/variables.tf 

pause "Press [Enter] key if variables.tf looks ok with new subnet ocid's and ready to proceed building compute instances"

cd "$build_root/compute"
$terraform_bin apply

Comments Off on Compute Instance in OCI using terraform
comments