Skip to content

Commit 3f4fe57

Browse files
committed
cmd/openshift-install: Add 'destroy bootstrap' command
Using Terraform to remove all resources created by the bootstrap modules. For this to work, all platforms must define a bootstrap module (and they all currently do). This command moves the previous destroy-cluster into a new 'destroy cluster' subcommand, because grouping different destroy flavors into sub-commands makes the base command easier to understand. We expect both destroy flavors to be long-running, because it's hard to write generic logic for "is the cluster sufficiently live for us to remove the bootstrap". We don't want to hang forever if the cluster dies before coming up, but there's no solid rules for how long to wait before deciding that it's never going to come up. When we start destroying the bootstrap resources automatically in the future, will pick reasonable timeouts, but will want to still provide callers with the ability to manually remove the bootstrap resources if we happen to fall out of that timeout on a cluster that does eventually come up. I've also created a LoadMetadata helper to share the "retrieve the metadata from the asset directory" logic between the destroy-cluster and destroy-bootstrap logic. The new helper lives in the cluster asset plackage close to the code that determines that file's location. I've pushed the Terraform module unpacking and 'terraform init' call down into a helper used by the Apply and Destroy functions to make life easier on the callers. I've also fixed a path.Join -> filepath.Join typo in Apply, which dates back to ff5a57b (pkg/terraform: Modify some helper functions for the new binary layout, 2018-09-19, #289). These aren't network paths ;).
1 parent fdaeb59 commit 3f4fe57

File tree

9 files changed

+183
-52
lines changed

9 files changed

+183
-52
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,5 +53,5 @@ export KUBECONFIG="${DIR}/auth/kubeconfig"
5353
Destroy the cluster and release associated resources with:
5454

5555
```sh
56-
openshift-install destroy-cluster
56+
openshift-install destroy cluster
5757
```

cmd/openshift-install/destroy.go

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,36 @@ import (
66
"github.com/spf13/cobra"
77

88
"github.com/openshift/installer/pkg/destroy"
9+
"github.com/openshift/installer/pkg/destroy/bootstrap"
910
_ "github.com/openshift/installer/pkg/destroy/libvirt"
1011
)
1112

1213
func newDestroyCmd() *cobra.Command {
14+
cmd := &cobra.Command{
15+
Use: "destroy",
16+
Short: "Destroy part of an OpenShift cluster",
17+
Long: "",
18+
RunE: func(cmd *cobra.Command, args []string) error {
19+
return cmd.Help()
20+
},
21+
}
22+
cmd.AddCommand(newDestroyBootstrapCmd())
23+
cmd.AddCommand(newDestroyClusterCmd())
24+
return cmd
25+
}
26+
27+
func newLegacyDestroyClusterCmd() *cobra.Command {
1328
return &cobra.Command{
1429
Use: "destroy-cluster",
30+
Short: "DEPRECATED: Use 'destroy cluster' instead.",
31+
RunE: runDestroyCmd,
32+
}
33+
}
34+
35+
func newDestroyClusterCmd() *cobra.Command {
36+
return &cobra.Command{
37+
Use: "cluster",
1538
Short: "Destroy an OpenShift cluster",
16-
Long: "",
1739
RunE: runDestroyCmd,
1840
}
1941
}
@@ -29,3 +51,13 @@ func runDestroyCmd(cmd *cobra.Command, args []string) error {
2951
}
3052
return nil
3153
}
54+
55+
func newDestroyBootstrapCmd() *cobra.Command {
56+
return &cobra.Command{
57+
Use: "bootstrap",
58+
Short: "Destroy the bootstrap resources",
59+
RunE: func(cmd *cobra.Command, args []string) error {
60+
return bootstrap.Destroy(rootOpts.dir)
61+
},
62+
}
63+
}

cmd/openshift-install/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ func main() {
2222
}
2323
subCmds = append(subCmds,
2424
newDestroyCmd(),
25+
newLegacyDestroyClusterCmd(),
2526
newVersionCmd(),
2627
newGraphCmd(),
2728
)

docs/dev/libvirt-howto.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ EOF
184184
## Build and run the installer
185185

186186
With [libvirt configured](#install-and-enable-libvirt), you can proceed with [the usual quick-start](../../README.md#quick-start).
187-
Set `TAGS` when building if you need `destroy-cluster` support for libvirt; this is not enabled by default because it requires [cgo][]:
187+
Set `TAGS` when building if you need `destroy cluster` support for libvirt; this is not enabled by default because it requires [cgo][]:
188188

189189
```sh
190190
TAGS=libvirt_destroy hack/build.sh
@@ -205,7 +205,7 @@ export OPENSHIFT_INSTALL_LIBVIRT_URI=qemu+tcp://192.168.122.1/system
205205
If you compiled with `libvirt_destroy`, you can use:
206206

207207
```sh
208-
openshift-install destroy-cluster
208+
openshift-install destroy cluster
209209
```
210210

211211
If you did not compile with `libvirt_destroy`, you can use [`virsh-cleanup.sh`](../../scripts/maintenance/virsh-cleanup.sh), but note it will currently destroy *all* libvirt resources.

pkg/asset/cluster/cluster.go

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"github.com/pkg/errors"
1111
"github.com/sirupsen/logrus"
1212

13-
"github.com/openshift/installer/data"
1413
"github.com/openshift/installer/pkg/asset"
1514
"github.com/openshift/installer/pkg/asset/installconfig"
1615
"github.com/openshift/installer/pkg/asset/kubeconfig"
@@ -19,8 +18,8 @@ import (
1918
)
2019

2120
const (
22-
// MetadataFilename is name of the file where clustermetadata is stored.
23-
MetadataFilename = "metadata.json"
21+
// metadataFileName is name of the file where clustermetadata is stored.
22+
metadataFileName = "metadata.json"
2423
)
2524

2625
// Cluster uses the terraform executable to launch a cluster
@@ -65,19 +64,14 @@ func (c *Cluster) Generate(parents asset.Parents) (err error) {
6564
return errors.Wrap(err, "failed to write terraform.tfvars file")
6665
}
6766

68-
platform := installConfig.Config.Platform.Name()
69-
if err := data.Unpack(tmpDir, platform); err != nil {
70-
return err
71-
}
72-
7367
metadata := &types.ClusterMetadata{
7468
ClusterName: installConfig.Config.ObjectMeta.Name,
7569
}
7670

7771
defer func() {
7872
if data, err2 := json.Marshal(metadata); err2 == nil {
7973
c.FileList = append(c.FileList, &asset.File{
80-
Filename: MetadataFilename,
74+
Filename: metadataFileName,
8175
Data: data,
8276
})
8377
} else {
@@ -114,19 +108,8 @@ func (c *Cluster) Generate(parents asset.Parents) (err error) {
114108
return fmt.Errorf("no known platform")
115109
}
116110

117-
if err := data.Unpack(filepath.Join(tmpDir, "config.tf"), "config.tf"); err != nil {
118-
return err
119-
}
120-
121111
logrus.Infof("Using Terraform to create cluster...")
122-
123-
// This runs the terraform in a temp directory, the tfstate file will be returned
124-
// to the asset store to persist it on the disk.
125-
if err := terraform.Init(tmpDir); err != nil {
126-
return errors.Wrap(err, "failed to initialize terraform")
127-
}
128-
129-
stateFile, err := terraform.Apply(tmpDir)
112+
stateFile, err := terraform.Apply(tmpDir, installConfig.Config.Platform.Name())
130113
if err != nil {
131114
err = errors.Wrap(err, "failed to run terraform")
132115
}
@@ -167,3 +150,17 @@ func (c *Cluster) Load(f asset.FileFetcher) (found bool, err error) {
167150

168151
return true, fmt.Errorf("%q already exisits. There may already be a running cluster", terraform.StateFileName)
169152
}
153+
154+
// LoadMetadata loads the cluster metadata from an asset directory.
155+
func LoadMetadata(dir string) (cmetadata *types.ClusterMetadata, err error) {
156+
raw, err := ioutil.ReadFile(filepath.Join(dir, metadataFileName))
157+
if err != nil {
158+
return nil, errors.Wrapf(err, "failed to read %s file", metadataFileName)
159+
}
160+
161+
if err = json.Unmarshal(raw, &cmetadata); err != nil {
162+
return nil, errors.Wrapf(err, "failed to Unmarshal data from %s file to types.ClusterMetadata", metadataFileName)
163+
}
164+
165+
return cmetadata, err
166+
}

pkg/asset/cluster/tfvars.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import (
1212
)
1313

1414
const (
15-
tfvarsFilename = "terraform.tfvars"
15+
// TfVarsFileName is the filename for Terraform variables.
16+
TfVarsFileName = "terraform.tfvars"
1617
tfvarsAssetName = "Terraform Variables"
1718
)
1819

@@ -62,7 +63,7 @@ func (t *TerraformVariables) Generate(parents asset.Parents) error {
6263
return errors.Wrap(err, "failed to get Tfvars")
6364
}
6465
t.File = &asset.File{
65-
Filename: tfvarsFilename,
66+
Filename: TfVarsFileName,
6667
Data: data,
6768
}
6869

@@ -79,7 +80,7 @@ func (t *TerraformVariables) Files() []*asset.File {
7980

8081
// Load reads the terraform.tfvars from disk.
8182
func (t *TerraformVariables) Load(f asset.FileFetcher) (found bool, err error) {
82-
file, err := f.FetchByName(tfvarsFilename)
83+
file, err := f.FetchByName(TfVarsFileName)
8384
if err != nil {
8485
if os.IsNotExist(err) {
8586
return false, nil

pkg/destroy/bootstrap/bootstrap.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Package bootstrap uses Terraform to remove bootstrap resources.
2+
package bootstrap
3+
4+
import (
5+
"io/ioutil"
6+
"os"
7+
"path/filepath"
8+
9+
"github.com/openshift/installer/pkg/asset/cluster"
10+
"github.com/openshift/installer/pkg/terraform"
11+
"github.com/pkg/errors"
12+
"github.com/sirupsen/logrus"
13+
)
14+
15+
// Destroy uses Terraform to remove bootstrap resources.
16+
func Destroy(dir string) (err error) {
17+
metadata, err := cluster.LoadMetadata(dir)
18+
if err != nil {
19+
return err
20+
}
21+
22+
platform := metadata.Platform()
23+
if platform == "" {
24+
return errors.New("no platform configured in metadata")
25+
}
26+
27+
tempDir, err := ioutil.TempDir("", "openshift-install-")
28+
if err != nil {
29+
return errors.Wrap(err, "failed to create temporary directory for Terraform execution")
30+
}
31+
defer os.RemoveAll(tempDir)
32+
33+
for _, filename := range []string{terraform.StateFileName, cluster.TfVarsFileName} {
34+
err = copy(filepath.Join(dir, filename), filepath.Join(tempDir, filename))
35+
if err != nil {
36+
return errors.Wrapf(err, "failed to copy %s to the temporary directory", filename)
37+
}
38+
}
39+
40+
logrus.Infof("Using Terraform to destroy bootstrap resources...")
41+
err = terraform.Destroy(tempDir, platform, "-target=module.bootstrap")
42+
if err != nil {
43+
err = errors.Wrap(err, "failed to run terraform")
44+
}
45+
46+
return os.Rename(filepath.Join(dir, terraform.StateFileName), filepath.Join(tempDir, terraform.StateFileName))
47+
}
48+
49+
func copy(from string, to string) error {
50+
data, err := ioutil.ReadFile(from)
51+
if err != nil {
52+
return err
53+
}
54+
55+
return ioutil.WriteFile(to, data, 0666)
56+
}

pkg/destroy/destroyer.go

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
package destroy
22

33
import (
4-
"encoding/json"
5-
"io/ioutil"
6-
"path/filepath"
7-
84
"github.com/pkg/errors"
95
"github.com/sirupsen/logrus"
106

@@ -26,25 +22,19 @@ var Registry = make(map[string]NewFunc)
2622

2723
// New returns a Destroyer based on `metadata.json` in `rootDir`.
2824
func New(logger logrus.FieldLogger, rootDir string) (Destroyer, error) {
29-
path := filepath.Join(rootDir, cluster.MetadataFilename)
30-
raw, err := ioutil.ReadFile(filepath.Join(rootDir, cluster.MetadataFilename))
25+
metadata, err := cluster.LoadMetadata(rootDir)
3126
if err != nil {
32-
return nil, errors.Wrapf(err, "failed to read %s file", cluster.MetadataFilename)
33-
}
34-
35-
var cmetadata *types.ClusterMetadata
36-
if err := json.Unmarshal(raw, &cmetadata); err != nil {
37-
return nil, errors.Wrapf(err, "failed to Unmarshal data from %s file to types.ClusterMetadata", cluster.MetadataFilename)
27+
return nil, err
3828
}
3929

40-
platform := cmetadata.Platform()
30+
platform := metadata.Platform()
4131
if platform == "" {
42-
return nil, errors.Errorf("no platform configured in %q", path)
32+
return nil, errors.New("no platform configured in metadata")
4333
}
4434

4535
creator, ok := Registry[platform]
4636
if !ok {
4737
return nil, errors.Errorf("no destroyers registered for %q", platform)
4838
}
49-
return creator(logger, cmetadata)
39+
return creator(logger, metadata)
5040
}

pkg/terraform/terraform.go

Lines changed: 63 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ package terraform
22

33
import (
44
"fmt"
5-
"path"
5+
"path/filepath"
66

7+
"github.com/openshift/installer/data"
78
"github.com/pkg/errors"
89
)
910

@@ -26,10 +27,16 @@ func terraformExec(clusterDir string, args ...string) error {
2627
return nil
2728
}
2829

29-
// Apply runs "terraform apply" in the given directory. It returns the absolute
30-
// path of the tfstate file, rooted in the specified directory, along with any
31-
// errors from Terraform.
32-
func Apply(dir string, extraArgs ...string) (string, error) {
30+
// Apply unpacks the platform-specific Terraform modules into the
31+
// given directory and then runs 'terraform init' and 'terraform
32+
// apply'. It returns the absolute path of the tfstate file, rooted
33+
// in the specified directory, along with any errors from Terraform.
34+
func Apply(dir string, platform string, extraArgs ...string) (path string, err error) {
35+
err = unpackAndInit(dir, platform)
36+
if err != nil {
37+
return "", err
38+
}
39+
3340
defaultArgs := []string{
3441
"apply",
3542
"-auto-approve",
@@ -39,10 +46,57 @@ func Apply(dir string, extraArgs ...string) (string, error) {
3946
}
4047
args := append(defaultArgs, extraArgs...)
4148

42-
return path.Join(dir, StateFileName), terraformExec(dir, args...)
49+
return filepath.Join(dir, StateFileName), terraformExec(dir, args...)
50+
}
51+
52+
// Destroy unpacks the platform-specific Terraform modules into the
53+
// given directory and then runs 'terraform init' and 'terraform
54+
// destroy'.
55+
func Destroy(dir string, platform string, extraArgs ...string) (err error) {
56+
err = unpackAndInit(dir, platform)
57+
if err != nil {
58+
return err
59+
}
60+
61+
defaultArgs := []string{
62+
"destroy",
63+
"-auto-approve",
64+
"-no-color",
65+
fmt.Sprintf("-state=%s", StateFileName),
66+
}
67+
args := append(defaultArgs, extraArgs...)
68+
69+
return terraformExec(dir, args...)
4370
}
4471

45-
// Init runs "terraform init" in the given directory.
46-
func Init(dir string) error {
47-
return terraformExec(dir, "init", "-input=false", "-no-color")
72+
// unpack unpacks the platform-specific Terraform modules into the
73+
// given directory.
74+
func unpack(dir string, platform string) (err error) {
75+
err = data.Unpack(dir, platform)
76+
if err != nil {
77+
return err
78+
}
79+
80+
err = data.Unpack(filepath.Join(dir, "config.tf"), "config.tf")
81+
if err != nil {
82+
return err
83+
}
84+
85+
return nil
86+
}
87+
88+
// unpackAndInit unpacks the platform-specific Terraform modules into
89+
// the given directory and then runs 'terraform init'.
90+
func unpackAndInit(dir string, platform string) (err error) {
91+
err = unpack(dir, platform)
92+
if err != nil {
93+
return errors.Wrap(err, "failed to unpack Terraform modules")
94+
}
95+
96+
err = terraformExec(dir, "init", "-input=false", "-no-color")
97+
if err != nil {
98+
return errors.Wrap(err, "failed to initialize Terraform")
99+
}
100+
101+
return nil
48102
}

0 commit comments

Comments
 (0)