File: post-processor.go

package info (click to toggle)
packer 1.6.6%2Bds1-2
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 32,016 kB
  • sloc: sh: 1,154; python: 619; makefile: 251; ruby: 205; xml: 97
file content (235 lines) | stat: -rw-r--r-- 8,023 bytes parent folder | download | duplicates (2)
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
223
224
225
226
227
228
229
230
231
232
233
234
235
//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type Config

package googlecomputeexport

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/hashicorp/hcl/v2/hcldec"
	"github.com/hashicorp/packer/builder/googlecompute"
	"github.com/hashicorp/packer/packer-plugin-sdk/common"
	"github.com/hashicorp/packer/packer-plugin-sdk/communicator"
	"github.com/hashicorp/packer/packer-plugin-sdk/multistep"
	"github.com/hashicorp/packer/packer-plugin-sdk/multistep/commonsteps"
	packersdk "github.com/hashicorp/packer/packer-plugin-sdk/packer"
	"github.com/hashicorp/packer/packer-plugin-sdk/template/config"
	"github.com/hashicorp/packer/packer-plugin-sdk/template/interpolate"
	"github.com/hashicorp/packer/post-processor/artifice"
)

type Config struct {
	common.PackerConfig `mapstructure:",squash"`

	//The JSON file containing your account credentials.
	//If specified, the account file will take precedence over any `googlecompute` builder authentication method.
	AccountFile string `mapstructure:"account_file"`
	// This allows service account impersonation as per the [docs](https://cloud.google.com/iam/docs/impersonating-service-accounts).
	ImpersonateServiceAccount string `mapstructure:"impersonate_service_account" required:"false"`
	//The size of the export instances disk.
	//The disk is unused for the export but a larger size will increase `pd-ssd` read speed.
	//This defaults to `200`, which is 200GB.
	DiskSizeGb int64 `mapstructure:"disk_size"`
	//Type of disk used to back the export instance, like
	//`pd-ssd` or `pd-standard`. Defaults to `pd-ssd`.
	DiskType string `mapstructure:"disk_type"`
	//The export instance machine type. Defaults to `"n1-highcpu-4"`.
	MachineType string `mapstructure:"machine_type"`
	//The Google Compute network id or URL to use for the export instance.
	//Defaults to `"default"`. If the value is not a URL, it
	//will be interpolated to `projects/((builder_project_id))/global/networks/((network))`.
	//This value is not required if a `subnet` is specified.
	Network string `mapstructure:"network"`
	//A list of GCS paths where the image will be exported.
	//For example `'gs://mybucket/path/to/file.tar.gz'`
	Paths []string `mapstructure:"paths" required:"true"`
	//The Google Compute subnetwork id or URL to use for
	//the export instance. Only required if the `network` has been created with
	//custom subnetting. Note, the region of the subnetwork must match the
	//`zone` in which the VM is launched. If the value is not a URL,
	//it will be interpolated to
	//`projects/((builder_project_id))/regions/((region))/subnetworks/((subnetwork))`
	Subnetwork string `mapstructure:"subnetwork"`
	//The zone in which to launch the export instance. Defaults
	//to `googlecompute` builder zone. Example: `"us-central1-a"`
	Zone                string `mapstructure:"zone"`
	IAP                 bool   `mapstructure-to-hcl2:",skip"`
	VaultGCPOauthEngine string `mapstructure:"vault_gcp_oauth_engine"`
	ServiceAccountEmail string `mapstructure:"service_account_email"`

	account *googlecompute.ServiceAccount
	ctx     interpolate.Context
}

type PostProcessor struct {
	config Config
	runner multistep.Runner
}

func (p *PostProcessor) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() }

func (p *PostProcessor) Configure(raws ...interface{}) error {
	err := config.Decode(&p.config, &config.DecodeOpts{
		PluginType:         BuilderId,
		Interpolate:        true,
		InterpolateContext: &p.config.ctx,
	}, raws...)
	if err != nil {
		return err
	}

	errs := new(packersdk.MultiError)

	if len(p.config.Paths) == 0 {
		errs = packersdk.MultiErrorAppend(
			errs, fmt.Errorf("paths must be specified"))
	}

	// Set defaults.
	if p.config.DiskSizeGb == 0 {
		p.config.DiskSizeGb = 200
	}

	if p.config.DiskType == "" {
		p.config.DiskType = "pd-ssd"
	}

	if p.config.MachineType == "" {
		p.config.MachineType = "n1-highcpu-4"
	}

	if p.config.Network == "" && p.config.Subnetwork == "" {
		p.config.Network = "default"
	}

	if p.config.AccountFile != "" && p.config.VaultGCPOauthEngine != "" {
		errs = packersdk.MultiErrorAppend(
			errs, fmt.Errorf("May set either account_file or "+
				"vault_gcp_oauth_engine, but not both."))
	}

	if len(errs.Errors) > 0 {
		return errs
	}

	return nil
}

func (p *PostProcessor) PostProcess(ctx context.Context, ui packersdk.Ui, artifact packersdk.Artifact) (packersdk.Artifact, bool, bool, error) {
	switch artifact.BuilderId() {
	case googlecompute.BuilderId, artifice.BuilderId:
		break
	default:
		err := fmt.Errorf(
			"Unknown artifact type: %s\nCan only export from Google Compute Engine builder and Artifice post-processor artifacts.",
			artifact.BuilderId())
		return nil, false, false, err
	}

	builderAccountFile := artifact.State("AccountFilePath").(string)
	builderImageName := artifact.State("ImageName").(string)
	builderProjectId := artifact.State("ProjectId").(string)
	builderZone := artifact.State("BuildZone").(string)

	ui.Say(fmt.Sprintf("Exporting image %v to destination: %v", builderImageName, p.config.Paths))

	if p.config.Zone == "" {
		p.config.Zone = builderZone
	}

	// Set up credentials for GCE driver.
	if builderAccountFile != "" {
		cfg, err := googlecompute.ProcessAccountFile(builderAccountFile)
		if err != nil {
			return nil, false, false, err
		}
		p.config.account = cfg
	}
	if p.config.AccountFile != "" {
		cfg, err := googlecompute.ProcessAccountFile(p.config.AccountFile)
		if err != nil {
			return nil, false, false, err
		}
		p.config.account = cfg
	}

	// Set up exporter instance configuration.
	exporterName := fmt.Sprintf("%s-exporter", artifact.Id())
	exporterMetadata := map[string]string{
		"image_name":     builderImageName,
		"name":           exporterName,
		"paths":          strings.Join(p.config.Paths, " "),
		"startup-script": StartupScript,
		"zone":           p.config.Zone,
	}
	exporterConfig := googlecompute.Config{
		DiskName:             exporterName,
		DiskSizeGb:           p.config.DiskSizeGb,
		DiskType:             p.config.DiskType,
		InstanceName:         exporterName,
		MachineType:          p.config.MachineType,
		Metadata:             exporterMetadata,
		Network:              p.config.Network,
		NetworkProjectId:     builderProjectId,
		StateTimeout:         5 * time.Minute,
		SourceImageFamily:    "debian-9-worker",
		SourceImageProjectId: []string{"compute-image-tools"},
		Subnetwork:           p.config.Subnetwork,
		Zone:                 p.config.Zone,
		Scopes: []string{
			"https://www.googleapis.com/auth/compute",
			"https://www.googleapis.com/auth/devstorage.full_control",
			"https://www.googleapis.com/auth/userinfo.email",
			"https://www.googleapis.com/auth/logging.write",
		},
	}
	if p.config.ServiceAccountEmail != "" {
		exporterConfig.ServiceAccountEmail = p.config.ServiceAccountEmail
	}
	cfg := googlecompute.GCEDriverConfig{
		Ui:                            ui,
		ProjectId:                     builderProjectId,
		Account:                       p.config.account,
		ImpersonateServiceAccountName: p.config.ImpersonateServiceAccount,
		VaultOauthEngineName:          p.config.VaultGCPOauthEngine,
	}

	driver, err := googlecompute.NewDriverGCE(cfg)
	if err != nil {
		return nil, false, false, err
	}

	// Set up the state.
	state := new(multistep.BasicStateBag)
	state.Put("config", &exporterConfig)
	state.Put("driver", driver)
	state.Put("ui", ui)

	// Build the steps.
	steps := []multistep.Step{
		&communicator.StepSSHKeyGen{
			CommConf: &exporterConfig.Comm,
		},
		multistep.If(p.config.PackerDebug,
			&communicator.StepDumpSSHKey{
				Path: fmt.Sprintf("gce_%s.pem", p.config.PackerBuildName),
			},
		),
		&googlecompute.StepCreateInstance{
			Debug: p.config.PackerDebug,
		},
		new(googlecompute.StepWaitStartupScript),
		new(googlecompute.StepTeardownInstance),
	}

	// Run the steps.
	p.runner = commonsteps.NewRunner(steps, p.config.PackerConfig, ui)
	p.runner.Run(ctx, state)

	result := &Artifact{paths: p.config.Paths}

	return result, false, false, nil
}