Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gather: update the state handling for terraform 0.12 #1763

Merged
merged 3 commits into from
May 28, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 21 additions & 91 deletions cmd/openshift-install/gather.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
package main

import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"

"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"

"github.com/openshift/installer/pkg/asset/installconfig"
assetstore "github.com/openshift/installer/pkg/asset/store"
"github.com/openshift/installer/pkg/terraform"
gatheraws "github.com/openshift/installer/pkg/terraform/gather/aws"
gatherlibvirt "github.com/openshift/installer/pkg/terraform/gather/libvirt"
gatheropenstack "github.com/openshift/installer/pkg/terraform/gather/openstack"
"github.com/openshift/installer/pkg/types"
awstypes "github.com/openshift/installer/pkg/types/aws"
libvirttypes "github.com/openshift/installer/pkg/types/libvirt"
Expand Down Expand Up @@ -85,16 +85,10 @@ func runGatherBootstrapCmd(directory string) error {
return errors.Wrapf(err, "failed to fetch %s", config.Name())
}

sfRaw, err := ioutil.ReadFile(tfStateFilePath)
tfstate, err := terraform.ReadState(tfStateFilePath)
if err != nil {
return errors.Wrapf(err, "failed to read %q", tfStateFilePath)
return errors.Wrapf(err, "failed to read state from %q", tfStateFilePath)
}

var tfstate terraformState
if err := json.Unmarshal(sfRaw, &tfstate); err != nil {
return errors.Wrapf(err, "failed to unmarshal %q", tfStateFilePath)
}

bootstrap, masters, err := extractHostAddresses(config.Config, tfstate)
if err != nil {
if err2, ok := err.(errUnSupportedGatherPlatform); ok {
Expand All @@ -117,105 +111,41 @@ func logGatherBootstrap(bootstrap string, masters []string) {
logrus.Infof("scp core@%s:~/log-bundle.tar.gz .", bootstrap)
}

func extractHostAddresses(config *types.InstallConfig, tfstate terraformState) (bootstrap string, masters []string, err error) {
mcount := *config.ControlPlane.Replicas
func extractHostAddresses(config *types.InstallConfig, tfstate *terraform.State) (bootstrap string, masters []string, err error) {
switch config.Platform.Name() {
case awstypes.Name:
bm := tfstate.Modules["root/bootstrap"]
bootstrap, _, err = unstructured.NestedString(bm.Resources["aws_instance.bootstrap"], "primary", "attributes", "public_ip")
bootstrap, err = gatheraws.BootstrapIP(tfstate)
if err != nil {
return bootstrap, masters, errors.Wrapf(err, "failed to get bootstrap host addresses")
return bootstrap, masters, err
}

mm := tfstate.Modules["root/masters"]
for idx := int64(0); idx < mcount; idx++ {
r := fmt.Sprintf("aws_instance.master.%d", idx)
if mcount == 1 {
r = "aws_instance.master"
}
var master string
master, _, err = unstructured.NestedString(mm.Resources[r], "primary", "attributes", "private_ip")
if err != nil {
return bootstrap, masters, errors.Wrapf(err, "failed to get master host addresses")
}
masters = append(masters, master)
masters, err = gatheraws.ControlPlaneIPs(tfstate)
if err != nil {
logrus.Error(err)
}
case libvirttypes.Name:
bm := tfstate.Modules["root/bootstrap"]
bootstrap, _, err = unstructured.NestedString(bm.Resources["libvirt_domain.bootstrap"], "primary", "attributes", "network_interface.0.hostname")
bootstrap, err = gatherlibvirt.BootstrapIP(tfstate)
if err != nil {
return bootstrap, masters, errors.Wrapf(err, "failed to get bootstrap host addresses")
return bootstrap, masters, err
}

rm := tfstate.Modules["root"]
for idx := int64(0); idx < mcount; idx++ {
r := fmt.Sprintf("libvirt_domain.master.%d", idx)
if mcount == 1 {
r = "libvirt_domain.master"
}
var master string
master, _, err = unstructured.NestedString(rm.Resources[r], "primary", "attributes", "network_interface.0.hostname")
if err != nil {
return bootstrap, masters, errors.Wrapf(err, "failed to get master host addresses")
}
masters = append(masters, master)
masters, err = gatherlibvirt.ControlPlaneIPs(tfstate)
if err != nil {
logrus.Error(err)
}
case openstacktypes.Name:
bm := tfstate.Modules["root/bootstrap"]
bootstrap, _, err = unstructured.NestedString(bm.Resources["openstack_compute_instance_v2.bootstrap"], "primary", "attributes", "access_ip_v4")
bootstrap, err = gatheropenstack.BootstrapIP(tfstate)
if err != nil {
return bootstrap, masters, errors.Wrapf(err, "failed to get bootstrap host addresses")
return bootstrap, masters, err
}

mm := tfstate.Modules["root/masters"]
for idx := int64(0); idx < mcount; idx++ {
r := fmt.Sprintf("openstack_compute_instance_v2.master_conf.%d", idx)
if mcount == 1 {
r = "openstack_compute_instance_v2.master_conf"
}
var master string
master, _, err = unstructured.NestedString(mm.Resources[r], "primary", "attributes", "access_ip_v4")
if err != nil {
return bootstrap, masters, errors.Wrapf(err, "failed to get master host addresses")
}
masters = append(masters, master)
masters, err = gatheropenstack.ControlPlaneIPs(tfstate)
if err != nil {
logrus.Error(err)
}
default:
return "", nil, errUnSupportedGatherPlatform{Message: fmt.Sprintf("Cannot fetch the bootstrap and control plane host addresses from state file for %s platform", config.Platform.Name())}
}
return bootstrap, masters, nil
}

type terraformState struct {
Modules map[string]terraformStateModule
}

type terraformStateModule struct {
Resources map[string]map[string]interface{} `json:"resources"`
}

func (tfs *terraformState) UnmarshalJSON(raw []byte) error {
var transform struct {
Modules []struct {
Path []string `json:"path"`
terraformStateModule
} `json:"modules"`
}
if err := json.Unmarshal(raw, &transform); err != nil {
return err
}
if tfs == nil {
tfs = &terraformState{}
}
if tfs.Modules == nil {
tfs.Modules = make(map[string]terraformStateModule)
}
for _, m := range transform.Modules {
tfs.Modules[strings.Join(m.Path, "/")] = terraformStateModule{Resources: m.Resources}
}
return nil
}

type errUnSupportedGatherPlatform struct {
Message string
}
Expand Down
32 changes: 32 additions & 0 deletions pkg/terraform/exec/state.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package exec

import (
"bytes"
"os"

"github.com/hashicorp/terraform/states/statefile"
"github.com/pkg/errors"
)

// ReadState reads the terraform state from file and returns the contents in bytes
// It returns an error if reading the state was unsuccessful
// ReadState utilizes the terraform's internal wiring to upconvert versions of terraform state to return
// the state it currently recognizes.
func ReadState(file string) ([]byte, error) {
f, err := os.Open(file)
if err != nil {
return nil, errors.Wrapf(err, "failed to open %q", file)
}
defer f.Close()

sf, err := statefile.Read(f)
if err != nil {
return nil, errors.Wrapf(err, "failed to read statefile from %q", file)
}

out := bytes.Buffer{}
if err := statefile.Write(sf, &out); err != nil {
return nil, errors.Wrapf(err, "failed to write statefile")
}
return out.Bytes(), nil
}
45 changes: 45 additions & 0 deletions pkg/terraform/gather/aws/ip.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Package aws contains utilities that help gather AWS specific
// information from terraform state.
package aws

import (
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
utilerrors "k8s.io/apimachinery/pkg/util/errors"

"github.com/openshift/installer/pkg/terraform"
)

// BootstrapIP returns the ip address for bootstrap host.
func BootstrapIP(tfs *terraform.State) (string, error) {
br, err := terraform.LookupResource(tfs, "module.bootstrap", "aws_instance", "bootstrap")
if err != nil {
return "", errors.Wrap(err, "failed to lookup bootstrap")
}
if len(br.Instances) == 0 {
return "", errors.New("no bootstrap instance found")
}
bootstrap, _, err := unstructured.NestedString(br.Instances[0].Attributes, "public_ip")
if err != nil {
return "", errors.New("no public_ip found for bootstrap")
}
return bootstrap, nil
}

// ControlPlaneIPs returns the ip addresses for control plane hosts.
func ControlPlaneIPs(tfs *terraform.State) ([]string, error) {
mrs, err := terraform.LookupResource(tfs, "module.masters", "aws_instance", "master")
if err != nil {
return nil, errors.Wrap(err, "failed to lookup masters")
}
var errs []error
var masters []string
for idx, inst := range mrs.Instances {
master, _, err := unstructured.NestedString(inst.Attributes, "private_ip")
if err != nil {
errs = append(errs, errors.Wrapf(err, "no private_ip for master.%d", idx))
}
masters = append(masters, master)
}
return masters, utilerrors.NewAggregate(errs)
}
60 changes: 60 additions & 0 deletions pkg/terraform/gather/libvirt/ip.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Package libvirt contains utilities that help gather Libvirt specific
// information from terraform state.
package libvirt

import (
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
utilerrors "k8s.io/apimachinery/pkg/util/errors"

"github.com/openshift/installer/pkg/terraform"
)

// BootstrapIP returns the ip address for bootstrap host.
func BootstrapIP(tfs *terraform.State) (string, error) {
br, err := terraform.LookupResource(tfs, "module.bootstrap", "libvirt_domain", "bootstrap")
if err != nil {
return "", errors.Wrap(err, "failed to lookup bootstrap")
}
if len(br.Instances) == 0 {
return "", errors.New("no bootstrap instance found")
}
bootstrap, err := hostnameForDomain(br.Instances[0].Attributes)
if err != nil {
return "", errors.Wrap(err, "failed to lookup hostname")
}
return bootstrap, nil
}

// ControlPlaneIPs returns the ip addresses for control plane hosts.
func ControlPlaneIPs(tfs *terraform.State) ([]string, error) {
mrs, err := terraform.LookupResource(tfs, "", "libvirt_domain", "master")
if err != nil {
return nil, errors.Wrap(err, "failed to lookup masters")
}
var errs []error
var masters []string
for idx, inst := range mrs.Instances {
master, err := hostnameForDomain(inst.Attributes)
if err != nil {
errs = append(errs, errors.Wrapf(err, "failed to lookup hostname for master.%d", idx))
}
masters = append(masters, master)
}
return masters, utilerrors.NewAggregate(errs)
}

func hostnameForDomain(attr map[string]interface{}) (string, error) {
nics, _, err := unstructured.NestedSlice(attr, "network_interface")
if err != nil {
return "", errors.Wrap(err, "failed to lookup network_interface")
}
if len(nics) == 0 {
return "", errors.New("no network_interface found")
}
hostname, _, err := unstructured.NestedString(nics[0].(map[string]interface{}), "hostname")
if err != nil {
return "", errors.New("no hostname found")
}
return hostname, nil
}
45 changes: 45 additions & 0 deletions pkg/terraform/gather/openstack/ip.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Package openstack contains utilities that help gather Openstack specific
// information from terraform state.
package openstack

import (
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
utilerrors "k8s.io/apimachinery/pkg/util/errors"

"github.com/openshift/installer/pkg/terraform"
)

// BootstrapIP returns the ip address for bootstrap host.
func BootstrapIP(tfs *terraform.State) (string, error) {
br, err := terraform.LookupResource(tfs, "module.bootstrap", "openstack_compute_instance_v2", "bootstrap")
if err != nil {
return "", errors.Wrap(err, "failed to lookup bootstrap")
}
if len(br.Instances) == 0 {
return "", errors.New("no bootstrap instance found")
}
bootstrap, _, err := unstructured.NestedString(br.Instances[0].Attributes, "access_ip_v4")
if err != nil {
return "", errors.New("no public_ip found for bootstrap")
}
return bootstrap, nil
}

// ControlPlaneIPs returns the ip addresses for control plane hosts.
func ControlPlaneIPs(tfs *terraform.State) ([]string, error) {
mrs, err := terraform.LookupResource(tfs, "module.masters", "openstack_compute_instance_v2", "master_conf")
if err != nil {
return nil, errors.Wrap(err, "failed to lookup masters")
}
var errs []error
var masters []string
for idx, inst := range mrs.Instances {
master, _, err := unstructured.NestedString(inst.Attributes, "access_ip_v4")
if err != nil {
errs = append(errs, errors.Wrapf(err, "no access_ip_v4 for master_conf.%d", idx))
}
masters = append(masters, master)
}
return masters, utilerrors.NewAggregate(errs)
}
Loading