step_create_images.go (128 lines of code) (raw):
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package daisy
import (
"context"
"encoding/json"
"sync"
"google.golang.org/api/googleapi"
)
// CreateImages is a Daisy CreateImages workflow step.
type CreateImages struct {
Images []*Image
ImagesAlpha []*ImageAlpha
ImagesBeta []*ImageBeta
}
// UnmarshalJSON unmarshals Image.
func (ci *CreateImages) UnmarshalJSON(b []byte) error {
var imagesAlpha []*ImageAlpha
if err := json.Unmarshal(b, &imagesAlpha); err != nil {
return err
}
ci.ImagesAlpha = imagesAlpha
var imagesBeta []*ImageBeta
if err := json.Unmarshal(b, &imagesBeta); err != nil {
return err
}
ci.ImagesBeta = imagesBeta
var images []*Image
if err := json.Unmarshal(b, &images); err != nil {
return err
}
ci.Images = images
return nil
}
func imageUsesAlphaFeatures(imagesAlpha []*ImageAlpha) bool {
for _, imageAlpha := range imagesAlpha {
if imageAlpha != nil && imageAlpha.RolloutOverride != nil && len(imageAlpha.RolloutOverride.DefaultRolloutTime) > 0 {
return true
}
if imageAlpha != nil && imageAlpha.Deprecated != nil && imageAlpha.Deprecated.StateOverride != nil && len(imageAlpha.Deprecated.StateOverride.DefaultRolloutTime) > 0 {
return true
}
}
return false
}
func imageUsesBetaFeatures(imagesBeta []*ImageBeta) bool {
return false
}
// populate preprocesses fields: Name, Project, Description, SourceDisk, RawDisk, and daisyName.
// - sets defaults
// - extends short partial URLs to include "projects/<project>"
func (ci *CreateImages) populate(ctx context.Context, s *Step) DError {
var errs DError
if ci.Images != nil {
for _, i := range ci.Images {
errs = addErrs(errs, (&i.ImageBase).populate(ctx, i, s))
}
}
if ci.ImagesAlpha != nil {
for _, i := range ci.ImagesAlpha {
errs = addErrs(errs, (&i.ImageBase).populate(ctx, i, s))
}
}
if ci.ImagesBeta != nil {
for _, i := range ci.ImagesBeta {
errs = addErrs(errs, (&i.ImageBase).populate(ctx, i, s))
}
}
return errs
}
func (ci *CreateImages) validate(ctx context.Context, s *Step) DError {
var errs DError
if imageUsesBetaFeatures(ci.ImagesBeta) {
for _, i := range ci.ImagesBeta {
errs = addErrs(errs, (&i.ImageBase).validate(ctx, i, i.Licenses, s))
}
} else {
for _, i := range ci.Images {
errs = addErrs(errs, (&i.ImageBase).validate(ctx, i, i.Licenses, s))
}
}
return errs
}
func (ci *CreateImages) run(ctx context.Context, s *Step) DError {
var wg sync.WaitGroup
w := s.w
e := make(chan DError)
createImage := func(ci ImageInterface, overwrite bool) {
defer wg.Done()
// Get source disk link if SourceDisk is a daisy reference to a disk.
if d, ok := w.disks.get(ci.getSourceDisk()); ok {
ci.setSourceDisk(d.link)
}
// Delete existing if OverWrite is true.
if overwrite {
// Just try to delete it, a 404 here indicates the image doesn't exist.
if err := ci.delete(w.ComputeClient); err != nil {
if apiErr, ok := err.(*googleapi.Error); !ok || apiErr.Code != 404 {
e <- Errf("error deleting existing image: %v", err)
return
}
}
}
w.LogStepInfo(s.name, "CreateImages", "Creating image %q.", ci.getName())
if err := ci.create(w.ComputeClient); err != nil {
e <- newErr("failed to create images", err)
return
}
ci.markCreatedInWorkflow()
}
if imageUsesAlphaFeatures(ci.ImagesAlpha) {
for _, i := range ci.ImagesAlpha {
wg.Add(1)
go createImage(i, i.OverWrite)
}
} else if imageUsesBetaFeatures(ci.ImagesBeta) {
for _, i := range ci.ImagesBeta {
wg.Add(1)
go createImage(i, i.OverWrite)
}
} else {
for _, i := range ci.Images {
wg.Add(1)
go createImage(i, i.OverWrite)
}
}
go func() {
wg.Wait()
e <- nil
}()
select {
case err := <-e:
return err
case <-w.Cancel:
// Wait so Images being created now will complete before we try to clean them up.
wg.Wait()
return nil
}
}