Files
Packer-Cn/post-processor/googlecompute-import/post-processor.go
T

267 lines
7.7 KiB
Go
Raw Normal View History

2019-10-14 16:43:59 +02:00
//go:generate mapstructure-to-hcl2 -type Config
2018-03-29 22:50:58 -05:00
package googlecomputeimport
import (
2019-03-22 14:56:02 +01:00
"context"
2018-03-29 22:50:58 -05:00
"fmt"
"net/http"
"os"
"strings"
"time"
"golang.org/x/oauth2/jwt"
2018-03-29 22:50:58 -05:00
"google.golang.org/api/compute/v1"
"google.golang.org/api/storage/v1"
2019-12-17 11:25:56 +01:00
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/packer/builder/googlecompute"
2018-03-29 22:50:58 -05:00
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/post-processor/artifice"
2018-03-29 22:50:58 -05:00
"github.com/hashicorp/packer/post-processor/compress"
"github.com/hashicorp/packer/template/interpolate"
)
type Config struct {
common.PackerConfig `mapstructure:",squash"`
2019-01-22 00:09:29 +01:00
AccountFile string `mapstructure:"account_file"`
ProjectId string `mapstructure:"project_id"`
IAP bool `mapstructure:"iap"`
2019-01-22 00:09:29 +01:00
Bucket string `mapstructure:"bucket"`
GCSObjectName string `mapstructure:"gcs_object_name"`
ImageDescription string `mapstructure:"image_description"`
ImageFamily string `mapstructure:"image_family"`
ImageGuestOsFeatures []string `mapstructure:"image_guest_os_features"`
ImageLabels map[string]string `mapstructure:"image_labels"`
ImageName string `mapstructure:"image_name"`
SkipClean bool `mapstructure:"skip_clean"`
VaultGCPOauthEngine string `mapstructure:"vault_gcp_oauth_engine"`
2018-03-29 22:50:58 -05:00
account *jwt.Config
2019-01-22 15:16:58 +01:00
ctx interpolate.Context
2018-03-29 22:50:58 -05:00
}
type PostProcessor struct {
config Config
}
2019-12-17 11:25:56 +01:00
func (p *PostProcessor) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() }
2018-03-29 22:50:58 -05:00
func (p *PostProcessor) Configure(raws ...interface{}) error {
err := config.Decode(&p.config, &config.DecodeOpts{
Interpolate: true,
InterpolateContext: &p.config.ctx,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{
"gcs_object_name",
},
},
}, raws...)
if err != nil {
return err
}
2019-01-22 15:16:58 +01:00
errs := new(packer.MultiError)
2018-03-29 22:50:58 -05:00
// Set defaults
if p.config.GCSObjectName == "" {
p.config.GCSObjectName = "packer-import-{{timestamp}}.tar.gz"
}
// Check and render gcs_object_name
if err = interpolate.Validate(p.config.GCSObjectName, &p.config.ctx); err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Error parsing gcs_object_name template: %s", err))
}
2019-01-22 15:16:58 +01:00
if p.config.AccountFile != "" {
2020-04-23 14:19:39 -07:00
cfg, err := googlecompute.ProcessAccountFile(p.config.AccountFile)
if err != nil {
2019-01-22 15:16:58 +01:00
errs = packer.MultiErrorAppend(errs, err)
}
p.config.account = cfg
2019-01-22 15:16:58 +01:00
}
if p.config.AccountFile != "" && p.config.VaultGCPOauthEngine != "" {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("May set either account_file or "+
"vault_gcp_oauth_engine, but not both."))
}
2018-03-29 22:50:58 -05:00
templates := map[string]*string{
2019-01-22 15:16:58 +01:00
"bucket": &p.config.Bucket,
"image_name": &p.config.ImageName,
"project_id": &p.config.ProjectId,
2018-03-29 22:50:58 -05:00
}
for key, ptr := range templates {
if *ptr == "" {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("%s must be set", key))
}
}
if len(errs.Errors) > 0 {
return errs
}
return nil
}
func (p *PostProcessor) PostProcess(ctx context.Context, ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, bool, error) {
generatedData := artifact.State("generated_data")
if generatedData == nil {
// Make sure it's not a nil map so we can assign to it later.
generatedData = make(map[string]interface{})
}
p.config.ctx.Data = generatedData
2020-04-23 14:19:39 -07:00
client, err := googlecompute.NewClientGCE(p.config.account, p.config.VaultGCPOauthEngine)
2019-01-22 15:16:58 +01:00
if err != nil {
2019-04-02 16:51:58 -07:00
return nil, false, false, err
2019-01-22 15:16:58 +01:00
}
2018-03-29 22:50:58 -05:00
switch artifact.BuilderId() {
case compress.BuilderId, artifice.BuilderId:
break
default:
err := fmt.Errorf(
"Unknown artifact type: %s\nCan only import from Compress post-processor and Artifice post-processor artifacts.",
2018-03-29 22:50:58 -05:00
artifact.BuilderId())
2019-04-02 16:51:58 -07:00
return nil, false, false, err
2018-03-29 22:50:58 -05:00
}
p.config.GCSObjectName, err = interpolate.Render(p.config.GCSObjectName, &p.config.ctx)
if err != nil {
2019-04-02 16:51:58 -07:00
return nil, false, false, fmt.Errorf("Error rendering gcs_object_name template: %s", err)
2018-03-29 22:50:58 -05:00
}
2019-01-22 15:16:58 +01:00
rawImageGcsPath, err := UploadToBucket(client, ui, artifact, p.config.Bucket, p.config.GCSObjectName)
2018-03-29 22:50:58 -05:00
if err != nil {
return nil, false, false, err
2018-03-29 22:50:58 -05:00
}
2019-01-22 15:16:58 +01:00
gceImageArtifact, err := CreateGceImage(client, ui, p.config.ProjectId, rawImageGcsPath, p.config.ImageName, p.config.ImageDescription, p.config.ImageFamily, p.config.ImageLabels, p.config.ImageGuestOsFeatures)
2018-03-29 22:50:58 -05:00
if err != nil {
return nil, false, false, err
2018-03-29 22:50:58 -05:00
}
if !p.config.SkipClean {
2019-01-22 15:16:58 +01:00
err = DeleteFromBucket(client, ui, p.config.Bucket, p.config.GCSObjectName)
if err != nil {
return nil, false, false, err
}
}
return gceImageArtifact, false, false, nil
2018-03-29 22:50:58 -05:00
}
2019-01-22 15:16:58 +01:00
func UploadToBucket(client *http.Client, ui packer.Ui, artifact packer.Artifact, bucket string, gcsObjectName string) (string, error) {
2018-03-29 22:50:58 -05:00
service, err := storage.New(client)
if err != nil {
return "", err
}
ui.Say("Looking for tar.gz file in list of artifacts...")
source := ""
for _, path := range artifact.Files() {
ui.Say(fmt.Sprintf("Found artifact %v...", path))
if strings.HasSuffix(path, ".tar.gz") {
source = path
break
}
}
if source == "" {
2019-01-22 15:16:58 +01:00
return "", fmt.Errorf("No tar.gz file found in list of artifacts")
2018-03-29 22:50:58 -05:00
}
artifactFile, err := os.Open(source)
if err != nil {
err := fmt.Errorf("error opening %v", source)
return "", err
}
ui.Say(fmt.Sprintf("Uploading file %v to GCS bucket %v/%v...", source, bucket, gcsObjectName))
storageObject, err := service.Objects.Insert(bucket, &storage.Object{Name: gcsObjectName}).Media(artifactFile).Do()
if err != nil {
ui.Say(fmt.Sprintf("Failed to upload: %v", storageObject))
return "", err
}
2019-01-22 15:16:58 +01:00
return storageObject.SelfLink, nil
2018-03-29 22:50:58 -05:00
}
2019-01-22 15:16:58 +01:00
func CreateGceImage(client *http.Client, ui packer.Ui, project string, rawImageURL string, imageName string, imageDescription string, imageFamily string, imageLabels map[string]string, imageGuestOsFeatures []string) (packer.Artifact, error) {
2018-03-29 22:50:58 -05:00
service, err := compute.New(client)
if err != nil {
return nil, err
}
2019-01-22 00:09:29 +01:00
// Build up the imageFeatures
imageFeatures := make([]*compute.GuestOsFeature, len(imageGuestOsFeatures))
for _, v := range imageGuestOsFeatures {
imageFeatures = append(imageFeatures, &compute.GuestOsFeature{
Type: v,
})
}
2018-03-29 22:50:58 -05:00
gceImage := &compute.Image{
2019-01-22 00:09:29 +01:00
Description: imageDescription,
Family: imageFamily,
GuestOsFeatures: imageFeatures,
Labels: imageLabels,
Name: imageName,
RawDisk: &compute.ImageRawDisk{Source: rawImageURL},
SourceType: "RAW",
2018-03-29 22:50:58 -05:00
}
ui.Say(fmt.Sprintf("Creating GCE image %v...", imageName))
op, err := service.Images.Insert(project, gceImage).Do()
if err != nil {
ui.Say("Error creating GCE image")
return nil, err
}
ui.Say("Waiting for GCE image creation operation to complete...")
for op.Status != "DONE" {
op, err = service.GlobalOperations.Get(project, op.Name).Do()
if err != nil {
return nil, err
}
time.Sleep(5 * time.Second)
}
// fail if image creation operation has an error
if op.Error != nil {
var imageError string
for _, error := range op.Error.Errors {
imageError += error.Message
}
err = fmt.Errorf("failed to create GCE image %s: %s", imageName, imageError)
return nil, err
}
return &Artifact{paths: []string{op.TargetLink}}, nil
}
2019-01-22 15:16:58 +01:00
func DeleteFromBucket(client *http.Client, ui packer.Ui, bucket string, gcsObjectName string) error {
service, err := storage.New(client)
if err != nil {
return err
}
ui.Say(fmt.Sprintf("Deleting import source from GCS %s/%s...", bucket, gcsObjectName))
err = service.Objects.Delete(bucket, gcsObjectName).Do()
if err != nil {
ui.Say(fmt.Sprintf("Failed to delete: %v/%v", bucket, gcsObjectName))
return err
}
return nil
}