tfplan_validator

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 23, 2022 License: Apache-2.0 Imports: 4 Imported by: 0

README

tfplan-validator

codecov build Go Report Card

A simple way to validate Terraform plans. Designed to assist batch operations on large numbers of similar state files.

Getting started

Run go install github.com/fautom/tfplan-validator@latest to use from the command line. You can now use commands like tfplan-validator create and tfplan-validator check

Read the below example section or see test/multi-env for working code that uses local state files that you can run from your command line.

To use tfplan-validator as a library check the command implementations in internal/app/tfplan-validator

Example

Suppose we have multiple development environments all created from the same Terraform module as our production environment. This means developers do not interfere with each others work and deployments are predictable but over time these environments will diverge due to changes to branches, variables, or state drift from manual changes.

environments/
             dev1/
             dev2/
             dev3/
             prod/

modules/
        environment/

A simple solution to this is to regularly run a terraform apply or a terragrunt run-all apply but this could be quite time consuming if the plans need to be manually reviewed and we risk overriding intentional differences if -auto-approve is used, including destructive changes like deleting databases. Instead let's modify a single development environment by hand and then use the plan for that to validate proposed changes to other environments.

So let's first generate a plan for dev1

➜ cd environments/dev1
➜ terraform plan -out plan.bin

Terraform will perform the following actions:

  # local_file.foo will be created
  + resource "local_file" "foo" {
      + content              = "foo!"
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "./foo.bar"
      + id                   = (known after apply)
    }

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

This is a change we want and we'd like to apply it not only to dev1 but dev2 and dev3 so we create a rules file to block any unwanted changes to other environments.

➜ terraform show -json plan.bin > plan.json
➜ tfplan-validator create plan.json --rules rules.json

Created rules file rules.json that allows Terraform to perform the following actions:

+ module.environment.local_file.bar[0] can be created

Now we can generate plans for every other environment and validate them, starting with dev1 and dev2

➜ tfplan-validator check --rules rules.json environments/dev1/plan.json environments/dev2/plan.json

The plan environments/dev1/plan.json passes checks and will perform the following actions:

+ module.environment.local_file.bar[0] will be created

The plan environments/dev2/plan.json passes checks and will perform the following actions:

+ module.environment.local_file.bar[0] will be created

Since both pass the plans can now be safely applied

➜ terraform apply -auto-approve environments/dev1/plan.bin
➜ terraform apply -auto-approve environments/dev2/plan.bin

However dev3 has a resource that would be destroyed by its plan so tfplan-validator check rejects it with a non-zero status code

➜ tfplan-validator check --rules rules.json environments/dev3/plan.json

The plan environments/dev3/plan.json has been rejected because it has the following actions:

! module.environment.local_file.foo[0] cannot be deleted

It is now up to the developer to decide what to do:

  1. Add the delete action to the rules by using tfplan-validator merge or tfplan-validator create
  2. Fix the code so that the resource is not deleted
  3. Apply the change anyway, destroying the resource

See test/multi-env for a working example using local state files that you can run from your command line.

Documentation

Index

Constants

View Source
const CurrentFormatVersion = "0.1"

CurrentFormatVersion of the rules file

Variables

This section is empty.

Functions

func AreCompatible

func AreCompatible(left Action, right Action) bool

AreCompatible returns true if the actions may be equivalent between different state files

func IsRelevant

func IsRelevant(rc *tfjson.ResourceChange) (bool, error)

IsRelevant returns true if the change is something we care about

func ParsePlan

func ParsePlan(data []byte) (*tfjson.Plan, error)

func ReadPlan

func ReadPlan(path string) (*tfjson.Plan, error)

func ReadPlans

func ReadPlans(paths []string) ([]*tfjson.Plan, error)

Types

type Action

type Action string

Action is an enum that corresponds to a valid tfjson plan array e.g. ["delete", "create"] is ActionDeleteCreate

const (
	ActionInvalid             Action = "invalid"
	ActionNoOp                Action = "no-op"
	ActionRead                Action = "read"
	ActionCreate              Action = "create"
	ActionUpdate              Action = "update"
	ActionDelete              Action = "delete"
	ActionDestroyBeforeCreate Action = "delete-create"
	ActionCreateBeforeDestroy Action = "create-delete"
)

func ConvertAction

func ConvertAction(actions *tfjson.Actions) Action

ConvertAction from the tfjson form to one we can more easily work with

func (Action) Pretty

func (a Action) Pretty() string

Pretty printable string

type Address

type Address string

Address of a resource in a plan

type FilterResults

type FilterResults struct {
	Errors     map[Address]Action
	Changes    map[Address]Action
	PlanFilter *PlanFilter
}

FilterResults describes whether a plan is allowed or not

func CheckPlan

func CheckPlan(filter *PlanFilter, plan *tfjson.Plan) (*FilterResults, error)

CheckPlan

func NewFilterResults

func NewFilterResults(filter *PlanFilter) *FilterResults

func (*FilterResults) HasChanges

func (fr *FilterResults) HasChanges() bool

HasChanges returns true if there's at least 1 change

func (*FilterResults) HasErrors

func (fr *FilterResults) HasErrors() bool

HasErrors returns true if there's at least 1 error

type PlanFilter

type PlanFilter struct {
	// The version of the PlanFilter format
	FormatVersion string `json:"format_version,omitempty"`

	// The actions we can perform on a resource
	AllowedActions map[Address][]Action `json:"allowed_actions"`
}

PlanFilter defines what is allowed in a plan

func MergePlanFilters

func MergePlanFilters(filters []*PlanFilter) (*PlanFilter, error)

MergePlanFilters combines filters from multiple plans into one

func NewFilterFromPlan

func NewFilterFromPlan(plan *tfjson.Plan) (*PlanFilter, error)

FilterForPlan creates a filter that accepts everything in the specified plan

func NewFilterFromPlanPaths

func NewFilterFromPlanPaths(paths []string) (*PlanFilter, error)

NewFilterFromPlanPaths creates filter from a sequence of paths

func NewFilterFromPlans

func NewFilterFromPlans(plans []*tfjson.Plan) (*PlanFilter, error)

NewFilterFromPlans creates a filter that accepts everything in a list of plans

func ParsePlanFilter

func ParsePlanFilter(data []byte) (*PlanFilter, error)

ParsePlanFilter from JSON

func ReadPlanFilter

func ReadPlanFilter(path string) (*PlanFilter, error)

ReadPlanFilter from a path

func ReadPlanFilters

func ReadPlanFilters(paths []string) ([]*PlanFilter, error)

ReadPlanFilters from paths

func (*PlanFilter) HasAction

func (f *PlanFilter) HasAction(address Address, action Action) bool

HasAction returns true if the action is allowed by the filter

func (*PlanFilter) WriteJSON

func (pf *PlanFilter) WriteJSON(path string) error

WritePlanFilter to a path

Directories

Path Synopsis
cmd
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL