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 }