scottwinkler / terraform-provider-shell

Terraform provider for executing shell commands and saving output to state file
Mozilla Public License 2.0
279 stars 61 forks source link

WIP - Update support plus some rejigging #2

Closed nhoughto closed 6 years ago

nhoughto commented 6 years ago

Super rough WIP of what we're currently working through as an FYI

Currently using it with kubectl and is working well, as discussed in #1 it is a bit of a departure from your existing usage. Notably i've rejigged how passing back the state works, so rather than using stdout which is hard to control (having to pipe every script cmd somewhere), i've made it opt-in, you pipe important output to an extra fd via ... >3&.

Process terraform goes through:

  1. Refreshes data which in this simple example reads the image tag in the manifest
  2. Checks if resource already exists in state, if it does it will compare the state property against the local value calculated in step 1 (image tag comparison)
  3. If value has changed then it will trigger an update script, kubectl will re-align the image
  4. If no value change then apply complete
  5. If no resource, then do a create.

Example usage:

data "shell_script" "test_state" {
  lifecycle_commands {
    read = <<EOF
kubectl convert --local -o jsonpath="{.spec.template.spec.containers[*].image}" -f ~/pod.yml
EOF
  }
}

resource "shell_script" "test" {
  state = "${data.shell_script.test_state.stdout}"
  lifecycle_commands {
    read = <<EOF
kubectl get deploy -o jsonpath="{.items[*].spec.template.spec.containers[*].image}" >&3
EOF
    create = <<EOF
kubectl apply -f ~/pod.yml
EOF
    update = <<EOF
kubectl apply -f ~/pod.yml
EOF
    delete = <<EOF
kubectl delete --ignore-not-found -f ~/pod.yml
EOF
  }
}

pod.yml

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: testdeploy
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: test
    spec:
      containers:
      - name: pod
        image: alpine@sha256:7df6db5aa61ae9480f52f0b3a06a140ab98d427f86d8d5de0bedab9b8df6b1c0
        command: ["sleep"]
        args: ["1800"]
nhoughto commented 6 years ago

Soo i don't think you will merge this now, but more FYI because i've learnt a bunch while doing it.

Latest changes do resolve most of your points but likely create some new ones, new example TF has two forms and some new properties, looks like:

Supports destroy and create without update workflow via recreate_triggers, a property that is set via some means (in this example its a data shell script, could be hash of file, env value etc) that drives TF to destroy and recreate.

data "shell_script" "test_state" {
  lifecycle_commands {
    read = <<EOF
kubectl convert --local -o jsonpath="{.spec.template.spec.containers[*].image}" -f pod.yml >&3
EOF
  }
}

resource "shell_script" "test" {
  recreate_triggers {
    key = "${data.shell_script.test_state.extraout}"
  }
  lifecycle_commands {
    create = <<EOF
kubectl apply -f pod.yml
EOF
    delete = <<EOF
kubectl delete --ignore-not-found -f pod.yml
EOF
  }
}

Support inplace updates workflow via update_trigger, in this flow you set read() and update() as before, there is also an extra idempotent property that means you can exclude update as create/update would be identical.

data "shell_script" "test_state" {
  lifecycle_commands {
    read = <<EOF
kubectl convert --local -o jsonpath="{.spec.template.spec.containers[*].image}" -f pod.yml >&3
EOF
  }
}

resource "shell_script" "test" {
  update_trigger = "${data.shell_script.test_state.extraout}"
  lifecycle_commands {
    #idempotent = true
    read = <<EOF
kubectl get deploy -o jsonpath="{.items[*].spec.template.spec.containers[*].image}" >&3
EOF
    create = <<EOF
kubectl apply -f pod.yml
EOF
    update = <<EOF
kubectl apply -f pod.yml
EOF
    delete = <<EOF
kubectl delete --ignore-not-found -f pod.yml
EOF
  }
}
nhoughto commented 6 years ago

Also opted to use file rather than a fd pipe as pipe isn't platform agnostic, will work on linux but not windows (probably, untested). I did test a unix pipe and it didn't work immediately so I scrapped it. Agree though it would be cleaner.

scottwinkler commented 6 years ago

I have started work on a new branch based on some inspiration i gained looking at your work. You can check it out here: https://github.com/scottwinkler/terraform-provider-shell/tree/feature/state. Basically I got rid of stdout, stderr and state. In my opinion, this greatly simplifies using this provider. Now all there is is outputs, which coupled with the environment variables is the "state". Also made read and update optional. The data resource is working, but haven't yet gotten the shell script resource working, hopefully tomorrow.

Not going to merge this branch because it has diverged from what I would want, but I'm happy you were able to solve your problem. Interested to see what further developments you make 👍

nhoughto commented 6 years ago

Cool, expected as much :+1: thanks for your help Now if there was only a simple way to use a custom provider with terraform..