~egtann/terrafirma

5ea0c0991b761c75ce2fa3be24c98780be672695 — Evan Tann 4 months ago e97186f
add ignore deleted flag for inventory

The ignore deleted flag (-i) can be used after deploying new infra to
ensure that you only provision and deploy the servers that matter, not
the servers that are about to come offline.
2 files changed, 31 insertions(+), 15 deletions(-)

M README.md
M cmd/terrafirma/main.go
M README.md => README.md +5 -1
@@ 133,7 133,9 @@ terrafirma -b "$(binp)" plan > tf_plan.json
if terrafirma create; then
	echo "waiting 60s for servers to boot..." && sleep 60
fi
terrafirma inventory > inventory.json

# Get our new inventory only containing our future state
terrafirma -i inventory > inventory.json

# Provision our servers using our usual deployment tools (cup, ansible, etc.).
up -t openbsd -c provision_openbsd


@@ 141,9 143,11 @@ up -t debian  -c provision_debian

# Now we know our reverse proxy is sending traffic to these new boxes, so we
# can bring down our old ones and clean up any temporary files when we're done.
# First retrieve our full inventory again.
#
# Note that Terrafirma will NOT delete static IPs. You must manually delete
# them.
terrafirma inventory > inventory.json
terrafirma destroy
rm tf_plan.json
```

M cmd/terrafirma/main.go => cmd/terrafirma/main.go +26 -14
@@ 33,11 33,12 @@ func main() {
func run() error {
	const defaultPlanFile = "tf_plan.json"
	var (
		configFile = flag.String("f", "services.json", "services file")
		planFile   = flag.String("p", defaultPlanFile, "plan file")
		bins       = flag.String("b", "", "bins")
		timeout    = flag.Duration("t", 5*time.Minute, "timeout")
		external   = flag.Bool("x", false, "show external ips (inventory only)")
		configFile    = flag.String("f", "services.json", "services file")
		planFile      = flag.String("p", defaultPlanFile, "plan file")
		bins          = flag.String("b", "", "bins")
		timeout       = flag.Duration("t", 5*time.Minute, "timeout")
		external      = flag.Bool("x", false, "show external ips (inventory only)")
		ignoreDestroy = flag.Bool("i", false, "ignore servers to be destroyed")
	)
	flag.Parse()



@@ 58,10 59,8 @@ func run() error {
			return errors.New("bins -b must be specified")
		}
		if *planFile != defaultPlanFile {
			return errors.New("-p cannot be specified with -plan")
			return errors.New("-p cannot be specified with plan")
		}
	} else if *bins != "" {
		return errors.New("bins -b may only be defined with -plan")
	}

	conf, err := parseConfig(*configFile)


@@ 96,16 95,34 @@ func run() error {
		return nil
	}

	// We're creating or destroying servers or taking inventory. In any
	// case, parse our planfile
	tfPlan, err := parsePlan(*planFile)
	switch {
	case os.IsNotExist(err) && cmd == "inventory":
		tfPlan = &tf.Plan{}
	case err != nil:
		return fmt.Errorf("load plan: %w", err)
	}

	if cmd == "inventory" {
		vms, err := terra.Inventory()
		if err != nil {
			return fmt.Errorf("get all: %w", err)
		}

		destroy := make(map[string]struct{}, len(tfPlan.Destroy))
		for _, vm := range tfPlan.Destroy {
			destroy[vm.Name] = struct{}{}
		}

		// TODO(egtann) allow each service to configure whether
		// inventory should include external or internal IPs?
		inv := map[string][]string{}
		for _, vm := range vms {
			if _, ok := destroy[vm.Name]; ok && *ignoreDestroy {
				continue
			}
			for _, ip := range vm.IPs {
				if *external {
					if ip.Type == tf.IPExternal {


@@ 124,12 141,6 @@ func run() error {
		return nil
	}

	// We're creating or destroying servers. In either case, parse our
	// planfile
	tfPlan, err := parsePlan(*planFile)
	if err != nil {
		return fmt.Errorf("load plan: %w", err)
	}
	switch cmd {
	case "create":
		if len(tfPlan.Create) == 0 {


@@ 173,6 184,7 @@ func parsePlan(filename string) (*tf.Plan, error) {
	} else {
		fi, err := os.Open(filename)
		if err != nil {
			// Must return the error without wrapping
			return nil, err
		}
		defer func() { _ = fi.Close() }()