github.com/vijayrajah/packer@v1.3.2/post-processor/googlecompute-import/post-processor.go (about)

     1  package googlecomputeimport
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"os"
     7  	"strings"
     8  	"time"
     9  
    10  	"google.golang.org/api/compute/v1"
    11  	"google.golang.org/api/storage/v1"
    12  
    13  	"github.com/hashicorp/packer/builder/googlecompute"
    14  	"github.com/hashicorp/packer/common"
    15  	"github.com/hashicorp/packer/helper/config"
    16  	"github.com/hashicorp/packer/helper/multistep"
    17  	"github.com/hashicorp/packer/packer"
    18  	"github.com/hashicorp/packer/post-processor/compress"
    19  	"github.com/hashicorp/packer/template/interpolate"
    20  
    21  	"golang.org/x/oauth2"
    22  	"golang.org/x/oauth2/jwt"
    23  )
    24  
    25  type Config struct {
    26  	common.PackerConfig `mapstructure:",squash"`
    27  
    28  	Bucket            string            `mapstructure:"bucket"`
    29  	GCSObjectName     string            `mapstructure:"gcs_object_name"`
    30  	ImageDescription  string            `mapstructure:"image_description"`
    31  	ImageFamily       string            `mapstructure:"image_family"`
    32  	ImageLabels       map[string]string `mapstructure:"image_labels"`
    33  	ImageName         string            `mapstructure:"image_name"`
    34  	ProjectId         string            `mapstructure:"project_id"`
    35  	AccountFile       string            `mapstructure:"account_file"`
    36  	KeepOriginalImage bool              `mapstructure:"keep_input_artifact"`
    37  	SkipClean         bool              `mapstructure:"skip_clean"`
    38  
    39  	ctx interpolate.Context
    40  }
    41  
    42  type PostProcessor struct {
    43  	config Config
    44  	runner multistep.Runner
    45  }
    46  
    47  func (p *PostProcessor) Configure(raws ...interface{}) error {
    48  	err := config.Decode(&p.config, &config.DecodeOpts{
    49  		Interpolate:        true,
    50  		InterpolateContext: &p.config.ctx,
    51  		InterpolateFilter: &interpolate.RenderFilter{
    52  			Exclude: []string{
    53  				"gcs_object_name",
    54  			},
    55  		},
    56  	}, raws...)
    57  	if err != nil {
    58  		return err
    59  	}
    60  
    61  	// Set defaults
    62  	if p.config.GCSObjectName == "" {
    63  		p.config.GCSObjectName = "packer-import-{{timestamp}}.tar.gz"
    64  	}
    65  
    66  	errs := new(packer.MultiError)
    67  
    68  	// Check and render gcs_object_name
    69  	if err = interpolate.Validate(p.config.GCSObjectName, &p.config.ctx); err != nil {
    70  		errs = packer.MultiErrorAppend(
    71  			errs, fmt.Errorf("Error parsing gcs_object_name template: %s", err))
    72  	}
    73  
    74  	templates := map[string]*string{
    75  		"bucket":       &p.config.Bucket,
    76  		"image_name":   &p.config.ImageName,
    77  		"project_id":   &p.config.ProjectId,
    78  		"account_file": &p.config.AccountFile,
    79  	}
    80  	for key, ptr := range templates {
    81  		if *ptr == "" {
    82  			errs = packer.MultiErrorAppend(
    83  				errs, fmt.Errorf("%s must be set", key))
    84  		}
    85  	}
    86  
    87  	if len(errs.Errors) > 0 {
    88  		return errs
    89  	}
    90  
    91  	return nil
    92  }
    93  
    94  func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) {
    95  	var err error
    96  
    97  	if artifact.BuilderId() != compress.BuilderId {
    98  		err = fmt.Errorf(
    99  			"incompatible artifact type: %s\nCan only import from Compress post-processor artifacts",
   100  			artifact.BuilderId())
   101  		return nil, false, err
   102  	}
   103  
   104  	p.config.GCSObjectName, err = interpolate.Render(p.config.GCSObjectName, &p.config.ctx)
   105  	if err != nil {
   106  		return nil, false, fmt.Errorf("Error rendering gcs_object_name template: %s", err)
   107  	}
   108  
   109  	rawImageGcsPath, err := UploadToBucket(p.config.AccountFile, ui, artifact, p.config.Bucket, p.config.GCSObjectName)
   110  	if err != nil {
   111  		return nil, p.config.KeepOriginalImage, err
   112  	}
   113  
   114  	gceImageArtifact, err := CreateGceImage(p.config.AccountFile, ui, p.config.ProjectId, rawImageGcsPath, p.config.ImageName, p.config.ImageDescription, p.config.ImageFamily, p.config.ImageLabels)
   115  	if err != nil {
   116  		return nil, p.config.KeepOriginalImage, err
   117  	}
   118  
   119  	if !p.config.SkipClean {
   120  		err = DeleteFromBucket(p.config.AccountFile, ui, p.config.Bucket, p.config.GCSObjectName)
   121  		if err != nil {
   122  			return nil, p.config.KeepOriginalImage, err
   123  		}
   124  	}
   125  
   126  	return gceImageArtifact, p.config.KeepOriginalImage, nil
   127  }
   128  
   129  func UploadToBucket(accountFile string, ui packer.Ui, artifact packer.Artifact, bucket string, gcsObjectName string) (string, error) {
   130  	var client *http.Client
   131  	var account googlecompute.AccountFile
   132  
   133  	err := googlecompute.ProcessAccountFile(&account, accountFile)
   134  	if err != nil {
   135  		return "", err
   136  	}
   137  
   138  	var DriverScopes = []string{"https://www.googleapis.com/auth/devstorage.full_control"}
   139  	conf := jwt.Config{
   140  		Email:      account.ClientEmail,
   141  		PrivateKey: []byte(account.PrivateKey),
   142  		Scopes:     DriverScopes,
   143  		TokenURL:   "https://accounts.google.com/o/oauth2/token",
   144  	}
   145  
   146  	client = conf.Client(oauth2.NoContext)
   147  	service, err := storage.New(client)
   148  	if err != nil {
   149  		return "", err
   150  	}
   151  
   152  	ui.Say("Looking for tar.gz file in list of artifacts...")
   153  	source := ""
   154  	for _, path := range artifact.Files() {
   155  		ui.Say(fmt.Sprintf("Found artifact %v...", path))
   156  		if strings.HasSuffix(path, ".tar.gz") {
   157  			source = path
   158  			break
   159  		}
   160  	}
   161  
   162  	if source == "" {
   163  		return "", fmt.Errorf("No tar.gz file found in list of articats")
   164  	}
   165  
   166  	artifactFile, err := os.Open(source)
   167  	if err != nil {
   168  		err := fmt.Errorf("error opening %v", source)
   169  		return "", err
   170  	}
   171  
   172  	ui.Say(fmt.Sprintf("Uploading file %v to GCS bucket %v/%v...", source, bucket, gcsObjectName))
   173  	storageObject, err := service.Objects.Insert(bucket, &storage.Object{Name: gcsObjectName}).Media(artifactFile).Do()
   174  	if err != nil {
   175  		ui.Say(fmt.Sprintf("Failed to upload: %v", storageObject))
   176  		return "", err
   177  	}
   178  
   179  	return "https://storage.googleapis.com/" + bucket + "/" + gcsObjectName, nil
   180  }
   181  
   182  func CreateGceImage(accountFile string, ui packer.Ui, project string, rawImageURL string, imageName string, imageDescription string, imageFamily string, imageLabels map[string]string) (packer.Artifact, error) {
   183  	var client *http.Client
   184  	var account googlecompute.AccountFile
   185  
   186  	err := googlecompute.ProcessAccountFile(&account, accountFile)
   187  	if err != nil {
   188  		return nil, err
   189  	}
   190  
   191  	var DriverScopes = []string{"https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/devstorage.full_control"}
   192  	conf := jwt.Config{
   193  		Email:      account.ClientEmail,
   194  		PrivateKey: []byte(account.PrivateKey),
   195  		Scopes:     DriverScopes,
   196  		TokenURL:   "https://accounts.google.com/o/oauth2/token",
   197  	}
   198  
   199  	client = conf.Client(oauth2.NoContext)
   200  
   201  	service, err := compute.New(client)
   202  	if err != nil {
   203  		return nil, err
   204  	}
   205  
   206  	gceImage := &compute.Image{
   207  		Name:        imageName,
   208  		Description: imageDescription,
   209  		Family:      imageFamily,
   210  		Labels:      imageLabels,
   211  		RawDisk:     &compute.ImageRawDisk{Source: rawImageURL},
   212  		SourceType:  "RAW",
   213  	}
   214  
   215  	ui.Say(fmt.Sprintf("Creating GCE image %v...", imageName))
   216  	op, err := service.Images.Insert(project, gceImage).Do()
   217  	if err != nil {
   218  		ui.Say("Error creating GCE image")
   219  		return nil, err
   220  	}
   221  
   222  	ui.Say("Waiting for GCE image creation operation to complete...")
   223  	for op.Status != "DONE" {
   224  		op, err = service.GlobalOperations.Get(project, op.Name).Do()
   225  		if err != nil {
   226  			return nil, err
   227  		}
   228  
   229  		time.Sleep(5 * time.Second)
   230  	}
   231  
   232  	// fail if image creation operation has an error
   233  	if op.Error != nil {
   234  		var imageError string
   235  		for _, error := range op.Error.Errors {
   236  			imageError += error.Message
   237  		}
   238  		err = fmt.Errorf("failed to create GCE image %s: %s", imageName, imageError)
   239  		return nil, err
   240  	}
   241  
   242  	return &Artifact{paths: []string{op.TargetLink}}, nil
   243  }
   244  
   245  func DeleteFromBucket(accountFile string, ui packer.Ui, bucket string, gcsObjectName string) error {
   246  	var client *http.Client
   247  	var account googlecompute.AccountFile
   248  
   249  	err := googlecompute.ProcessAccountFile(&account, accountFile)
   250  	if err != nil {
   251  		return err
   252  	}
   253  
   254  	var DriverScopes = []string{"https://www.googleapis.com/auth/devstorage.full_control"}
   255  	conf := jwt.Config{
   256  		Email:      account.ClientEmail,
   257  		PrivateKey: []byte(account.PrivateKey),
   258  		Scopes:     DriverScopes,
   259  		TokenURL:   "https://accounts.google.com/o/oauth2/token",
   260  	}
   261  
   262  	client = conf.Client(oauth2.NoContext)
   263  	service, err := storage.New(client)
   264  	if err != nil {
   265  		return err
   266  	}
   267  
   268  	ui.Say(fmt.Sprintf("Deleting import source from GCS %s/%s...", bucket, gcsObjectName))
   269  	err = service.Objects.Delete(bucket, gcsObjectName).Do()
   270  	if err != nil {
   271  		ui.Say(fmt.Sprintf("Failed to delete: %v/%v", bucket, gcsObjectName))
   272  		return err
   273  	}
   274  
   275  	return nil
   276  }