-
Notifications
You must be signed in to change notification settings - Fork 0
/
cluster.go
222 lines (188 loc) · 7.33 KB
/
cluster.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
package cluster
import (
"context"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"github.com/hashicorp/terraform-exec/tfexec"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/bailey84j/terraform_installer/pkg/asset"
"github.com/bailey84j/terraform_installer/pkg/asset/cluster/aws"
"github.com/bailey84j/terraform_installer/pkg/asset/installconfig"
"github.com/bailey84j/terraform_installer/pkg/asset/password"
"github.com/bailey84j/terraform_installer/pkg/metrics/timer"
"github.com/bailey84j/terraform_installer/pkg/terraform"
platformstages "github.com/bailey84j/terraform_installer/pkg/terraform/stages/platform"
typesaws "github.com/bailey84j/terraform_installer/pkg/types/aws"
)
var (
// InstallDir is the directory containing install assets.
InstallDir string
)
// Cluster uses the terraform executable to launch a cluster
// with the given terraform tfvar and generated templates.
type Cluster struct {
FileList []*asset.File
}
var _ asset.WritableAsset = (*Cluster)(nil)
// Name returns the human-friendly name of the asset.
func (c *Cluster) Name() string {
return "Cluster"
}
// Dependencies returns the direct dependency for launching
// the cluster.
func (c *Cluster) Dependencies() []asset.Asset {
return []asset.Asset{
&installconfig.ClusterID{},
&installconfig.InstallConfig{},
// PlatformCredsCheck, PlatformPermsCheck and PlatformProvisionCheck
// perform validations & check perms required to provision infrastructure.
// We do not actually use them in this asset directly, hence
// they are put in the dependencies but not fetched in Generate.
//&installconfig.PlatformCredsCheck{},
//&installconfig.PlatformPermsCheck{},
//&installconfig.PlatformProvisionCheck{},
//"a.PlatformQuotaCheck{},
&TerraformVariables{},
&password.TFEPassword{},
}
}
// Generate launches the cluster and generates the terraform state file on disk.
func (c *Cluster) Generate(parents asset.Parents) (err error) {
if InstallDir == "" {
logrus.Fatalf("InstallDir has not been set for the %q asset", c.Name())
}
clusterID := &installconfig.ClusterID{}
installConfig := &installconfig.InstallConfig{}
terraformVariables := &TerraformVariables{}
parents.Get(clusterID, installConfig, terraformVariables)
logrus.Debugf("Trace Me:\nclusterID - %+v\ninstallconfig - %+v\ntfvars - %+v", clusterID, installConfig, terraformVariables)
/*
if fs := installConfig.Config.FeatureSet; strings.HasSuffix(string(fs), "NoUpgrade") {
logrus.Warnf("FeatureSet %q is enabled. This FeatureSet does not allow upgrades and may affect the supportability of the cluster.", fs)
}
if installConfig.Config.Platform.None != nil {
return errors.New("cluster cannot be created with platform set to 'none'")
}
if installConfig.Config.BootstrapInPlace != nil {
return errors.New("cluster cannot be created with bootstrapInPlace set")
}
*/
platform := installConfig.Config.Platform.Name()
/*
if azure := installConfig.Config.Platform.Azure; azure != nil && azure.CloudName == typesazure.StackCloud {
platform = typesazure.StackTerraformName
}
if vsphere := installConfig.Config.Platform.VSphere; vsphere != nil {
if len(vsphere.FailureDomains) != 0 {
platform = typesvsphere.ZoningTerraformName
}
}
*/
stages := platformstages.StagesForPlatform(platform)
terraformDir := filepath.Join(InstallDir, "terraform")
if err := os.Mkdir(terraformDir, 0777); err != nil {
return errors.Wrap(err, "could not create the terraform directory")
}
terraformDirPath, err := filepath.Abs(terraformDir)
if err != nil {
return errors.Wrap(err, "cannot get absolute path of terraform directory")
}
defer os.RemoveAll(terraformDir)
err = terraform.UnpackTerraform(terraformDirPath, stages)
if err != nil {
return errors.Wrap(err, "cannot unpack terraform")
}
logrus.Infof("Creating infrastructure resources...")
switch platform {
case typesaws.Name:
if err := aws.PreTerraform(context.TODO(), clusterID.InfraID, installConfig); err != nil {
return err
}
/*
case typesazure.Name, typesazure.StackTerraformName:
if err := azure.PreTerraform(context.TODO(), clusterID.InfraID, installConfig); err != nil {
return err
}
case typesopenstack.Name:
if err := openstack.PreTerraform(context.TODO(), clusterID.InfraID, installConfig); err != nil {
return err
}
*/
}
tfvarsFiles := make([]*asset.File, 0, len(terraformVariables.Files())+len(stages))
for _, file := range terraformVariables.Files() {
tfvarsFiles = append(tfvarsFiles, file)
}
for _, stage := range stages {
logrus.Debugf("Trace Me: applyStage: %s; %+v; %s; %+v", platform, stage, terraformDirPath, tfvarsFiles)
outputs, err := c.applyStage(platform, stage, terraformDirPath, tfvarsFiles)
if err != nil {
return errors.Wrapf(err, "failure applying terraform for %q stage", stage.Name())
}
tfvarsFiles = append(tfvarsFiles, outputs)
c.FileList = append(c.FileList, outputs)
}
return nil
}
// Files returns the FileList generated by the asset.
func (c *Cluster) Files() []*asset.File {
return c.FileList
}
// Load returns error if the tfstate file is already on-disk, because we want to
// prevent user from accidentally re-launching the cluster.
func (c *Cluster) Load(f asset.FileFetcher) (found bool, err error) {
matches, err := filepath.Glob("terraform(.*)?.tfstate")
if err != nil {
return true, err
}
if len(matches) != 0 {
return true, errors.Errorf("terraform state files alread exist. There may already be a running cluster")
}
return false, nil
}
func (c *Cluster) applyStage(platform string, stage terraform.Stage, terraformDir string, tfvarsFiles []*asset.File) (*asset.File, error) {
// Copy the terraform.tfvars to a temp directory which will contain the terraform plan.
tmpDir, err := ioutil.TempDir("", fmt.Sprintf("openshift-install-%s-", stage.Name()))
if err != nil {
return nil, errors.Wrap(err, "failed to create temp dir for terraform execution")
}
defer os.RemoveAll(tmpDir)
var extraOpts []tfexec.ApplyOption
for _, file := range tfvarsFiles {
if err := ioutil.WriteFile(filepath.Join(tmpDir, file.Filename), file.Data, 0600); err != nil {
return nil, err
}
extraOpts = append(extraOpts, tfexec.VarFile(filepath.Join(tmpDir, file.Filename)))
}
return c.applyTerraform(tmpDir, platform, stage, terraformDir, extraOpts...)
}
func (c *Cluster) applyTerraform(tmpDir string, platform string, stage terraform.Stage, terraformDir string, opts ...tfexec.ApplyOption) (*asset.File, error) {
timer.StartTimer(stage.Name())
defer timer.StopTimer(stage.Name())
applyErr := terraform.Apply(tmpDir, platform, stage, terraformDir, opts...)
// Write the state file to the install directory even if the apply failed.
if data, err := ioutil.ReadFile(filepath.Join(tmpDir, terraform.StateFilename)); err == nil {
c.FileList = append(c.FileList, &asset.File{
Filename: stage.StateFilename(),
Data: data,
})
} else if !os.IsNotExist(err) {
logrus.Errorf("Failed to read tfstate: %v", err)
return nil, errors.Wrap(err, "failed to read tfstate")
}
if applyErr != nil {
return nil, errors.Wrap(applyErr, asset.ClusterCreationError)
}
outputs, err := terraform.Outputs(tmpDir, terraformDir)
if err != nil {
return nil, errors.Wrapf(err, "could not get outputs from stage %q", stage.Name())
}
outputsFile := &asset.File{
Filename: stage.OutputsFilename(),
Data: outputs,
}
return outputsFile, nil
}