Mise à jour des templates de VM et nettoyage des images inutilisées
This commit is contained in:
parent
8ac2048fd6
commit
9cd443bef6
|
@ -1,10 +1,14 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"forge.cadoles.com/wpetit/packer-opennebula/stepper"
|
||||||
"github.com/Cadoles/goca"
|
"github.com/Cadoles/goca"
|
||||||
"github.com/hashicorp/packer/common"
|
"github.com/hashicorp/packer/common"
|
||||||
"github.com/hashicorp/packer/helper/config"
|
"github.com/hashicorp/packer/helper/config"
|
||||||
|
@ -28,6 +32,8 @@ type Config struct {
|
||||||
User string
|
User string
|
||||||
Group string
|
Group string
|
||||||
} `mapstructure:"owner"`
|
} `mapstructure:"owner"`
|
||||||
|
AutoUpdateVMTemplates bool `mapstructure:"auto_update_vm_templates"`
|
||||||
|
CleanupUnusedImages bool `mapstructure:"cleanup_unused_images"`
|
||||||
ctx interpolate.Context
|
ctx interpolate.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,139 +59,368 @@ func (pp *PostProcessor) Configure(raws ...interface{}) error {
|
||||||
// PostProcess creates/updates your configured image to OpenNebula using the XML-RPC API
|
// PostProcess creates/updates your configured image to OpenNebula using the XML-RPC API
|
||||||
func (pp *PostProcessor) PostProcess(ui packer.Ui, a packer.Artifact) (packer.Artifact, bool, error) {
|
func (pp *PostProcessor) PostProcess(ui packer.Ui, a packer.Artifact) (packer.Artifact, bool, error) {
|
||||||
|
|
||||||
// Initialize OpenNebula XML-RPC API client
|
ctx := context.Background()
|
||||||
config := goca.NewConfig(pp.Conf.User, pp.Conf.Password, pp.Conf.Endpoint)
|
state := &postProcessorState{
|
||||||
if err := goca.SetClient(config); err != nil {
|
Config: pp.Conf,
|
||||||
|
UI: ui,
|
||||||
|
}
|
||||||
|
status, err := stepper.Run(
|
||||||
|
ctx, state,
|
||||||
|
stepInitializeGocaClient,
|
||||||
|
stepGenerateFullImageName,
|
||||||
|
stepRetrieveImage,
|
||||||
|
stepUpsertImage,
|
||||||
|
stepUpdateImageOwnership,
|
||||||
|
stepUpdateImageACL,
|
||||||
|
stepUpdateVMTemplates,
|
||||||
|
stepCleanupUnusedImages,
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
return a, true, err
|
return a, true, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.Say(fmt.Sprintf("Connecting to OpenNebula RPC endpoint '%s' with provided credentials...", pp.Conf.Endpoint))
|
switch status {
|
||||||
|
case stepper.Completed:
|
||||||
|
ui.Say("Operation completed.")
|
||||||
|
case stepper.Canceled:
|
||||||
|
ui.Say("Operation canceled.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return a, true, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type postProcessorState struct {
|
||||||
|
Config *Config
|
||||||
|
UI packer.Ui
|
||||||
|
Image *goca.Image
|
||||||
|
FullImageName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func stepInitializeGocaClient(ctx context.Context, cancelFunc context.CancelFunc, st interface{}) error {
|
||||||
|
|
||||||
|
state := toPostProcessorState(st)
|
||||||
|
|
||||||
|
// Initialize OpenNebula XML-RPC API client
|
||||||
|
config := goca.NewConfig(state.Config.User, state.Config.Password, state.Config.Endpoint)
|
||||||
|
if err := goca.SetClient(config); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func stepGenerateFullImageName(ctx context.Context, cancelFunc context.CancelFunc, st interface{}) error {
|
||||||
|
|
||||||
|
state := toPostProcessorState(st)
|
||||||
|
|
||||||
|
if !state.Config.AutoUpdateVMTemplates {
|
||||||
|
state.FullImageName = state.Config.ImageName
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
state.UI.Say("VM templates auto update activated. Adding timestamp to image's name.")
|
||||||
|
|
||||||
|
now := time.Now().Format("200601021504")
|
||||||
|
state.FullImageName = fmt.Sprintf("%s-%s", state.Config.ImageName, now)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func stepRetrieveImage(ctx context.Context, cancelFunc context.CancelFunc, st interface{}) error {
|
||||||
|
|
||||||
|
state := toPostProcessorState(st)
|
||||||
|
|
||||||
|
state.UI.Say(fmt.Sprintf("Connecting to OpenNebula RPC endpoint '%s' with provided credentials...", state.Config.Endpoint))
|
||||||
|
|
||||||
// Search image template by its name
|
// Search image template by its name
|
||||||
img, err := goca.NewImageFromName(pp.Conf.ImageName)
|
img, err := goca.NewImageFromName(state.FullImageName)
|
||||||
if err != nil && err.Error() != "resource not found" {
|
if err != nil && err.Error() != "resource not found" {
|
||||||
return a, true, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if img != nil {
|
if img != nil {
|
||||||
|
|
||||||
// Retreive info about the template
|
// Retreive info about the template
|
||||||
if err := img.Info(); err != nil {
|
if err := img.Info(); err != nil {
|
||||||
return a, true, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
state, err := img.State()
|
imgState, err := img.State()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return a, true, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
inUse := state == goca.ImageUsed || state == goca.ImageLockUsed
|
inUse := isImageUsed(imgState)
|
||||||
|
|
||||||
if inUse {
|
if inUse {
|
||||||
ui.Say(fmt.Sprintf("Template '%s' is in use. Cannot delete it.", pp.Conf.ImageName))
|
state.UI.Say(fmt.Sprintf("Image '%s' is in use. Cannot delete it.", state.FullImageName))
|
||||||
}
|
}
|
||||||
|
|
||||||
if pp.Conf.DeleteIfExists && !inUse {
|
if state.Config.DeleteIfExists && !inUse {
|
||||||
|
state.UI.Say(fmt.Sprintf("Deleting image '%s'...", state.FullImageName))
|
||||||
ui.Say(fmt.Sprintf("Deleting template '%s'...", pp.Conf.ImageName))
|
|
||||||
|
|
||||||
if err := img.Delete(); err != nil {
|
if err := img.Delete(); err != nil {
|
||||||
return a, true, err
|
return err
|
||||||
}
|
}
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
img = nil
|
img = nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state.Image = img
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func stepUpsertImage(ctx context.Context, cancelFunc context.CancelFunc, st interface{}) error {
|
||||||
|
|
||||||
|
state := toPostProcessorState(st)
|
||||||
|
|
||||||
// Generate image template
|
// Generate image template
|
||||||
tmplStr := serializeImageTemplate(pp.Conf.ImageTemplate)
|
tmplStr := serializeImageTemplate(state.FullImageName, state.Config.ImageTemplate)
|
||||||
|
|
||||||
// If the image template can not be found, we create it
|
// If the image template can not be found, we create it
|
||||||
if img == nil {
|
if state.Image == nil {
|
||||||
|
|
||||||
ui.Say(fmt.Sprintf("Creating template '%s'...", pp.Conf.ImageName))
|
state.UI.Say(fmt.Sprintf("Creating image '%s'...", state.FullImageName))
|
||||||
|
|
||||||
// Search image datastore's ID
|
// Search image datastore's ID
|
||||||
datastore, err := goca.NewDatastoreFromName(pp.Conf.DatastoreName)
|
datastore, err := goca.NewDatastoreFromName(state.Config.DatastoreName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return a, true, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create image template
|
// Create image template
|
||||||
imageID, err := goca.CreateImage(tmplStr, datastore.ID)
|
imageID, err := goca.CreateImage(tmplStr, datastore.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return a, true, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
img = goca.NewImage(imageID)
|
state.Image = goca.NewImage(imageID)
|
||||||
|
|
||||||
// Retreive info about the template
|
// Retreive info about the template
|
||||||
if err := img.Info(); err != nil {
|
if err := state.Image.Info(); err != nil {
|
||||||
return a, true, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
ui.Say(fmt.Sprintf("Updating template '%s'...", pp.Conf.ImageName))
|
state.UI.Say(fmt.Sprintf("Updating image '%s'...", state.FullImageName))
|
||||||
|
|
||||||
// Update image template
|
// Update image template
|
||||||
if err := img.Update(tmplStr, pp.Conf.MergeTemplate); err != nil {
|
if err := state.Image.Update(tmplStr, state.Config.MergeTemplate); err != nil {
|
||||||
return a, true, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if pp.Conf.Owner != nil {
|
return nil
|
||||||
|
|
||||||
currentUserName, userFound := img.XPath("/IMAGE/UNAME")
|
}
|
||||||
currentGroupName, groupFound := img.XPath("/IMAGE/GNAME")
|
|
||||||
|
|
||||||
isSameUser := userFound && currentUserName == pp.Conf.Owner.User
|
func stepUpdateImageOwnership(ctx context.Context, cancelFunc context.CancelFunc, st interface{}) error {
|
||||||
isSameGroup := groupFound && currentGroupName == pp.Conf.Owner.Group
|
|
||||||
|
state := toPostProcessorState(st)
|
||||||
|
|
||||||
|
if state.Config.Owner == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
owner := state.Config.Owner
|
||||||
|
|
||||||
|
currentUserName, userFound := state.Image.XPath("/IMAGE/UNAME")
|
||||||
|
currentGroupName, groupFound := state.Image.XPath("/IMAGE/GNAME")
|
||||||
|
|
||||||
|
isSameUser := userFound && currentUserName == owner.User
|
||||||
|
isSameGroup := groupFound && currentGroupName == owner.Group
|
||||||
|
|
||||||
userID := -1
|
userID := -1
|
||||||
groupID := -1
|
groupID := -1
|
||||||
|
|
||||||
if pp.Conf.Owner.User != "" && !isSameUser {
|
if owner.User != "" && !isSameUser {
|
||||||
user, err := goca.NewUserFromName(pp.Conf.Owner.User)
|
user, err := goca.NewUserFromName(owner.User)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return a, true, err
|
return err
|
||||||
}
|
}
|
||||||
userID = int(user.ID)
|
userID = int(user.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
if pp.Conf.Owner.Group != "" && !isSameGroup {
|
if owner.Group != "" && !isSameGroup {
|
||||||
group, err := goca.NewGroupFromName(pp.Conf.Owner.Group)
|
group, err := goca.NewGroupFromName(owner.Group)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return a, true, err
|
return err
|
||||||
}
|
}
|
||||||
groupID = int(group.ID)
|
groupID = int(group.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.Say(fmt.Sprintf("Updating template '%s' owner...", pp.Conf.ImageName))
|
state.UI.Say(fmt.Sprintf("Updating image '%s' owner...", state.FullImageName))
|
||||||
if err := img.Chown(userID, groupID); err != nil {
|
if err := state.Image.Chown(userID, groupID); err != nil {
|
||||||
return a, true, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
return nil
|
||||||
|
|
||||||
if pp.Conf.ACL != nil {
|
|
||||||
ui.Say(fmt.Sprintf("Updating template '%s' ACL...", pp.Conf.ImageName))
|
|
||||||
chmodOptions := aclToChmodOptions(pp.Conf.ACL)
|
|
||||||
if err := img.Chmod(chmodOptions); err != nil {
|
|
||||||
return a, true, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Say("Operation completed.")
|
|
||||||
|
|
||||||
return a, true, nil
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func serializeImageTemplate(tmpl []string) string {
|
func stepUpdateImageACL(ctx context.Context, cancelFunc context.CancelFunc, st interface{}) error {
|
||||||
|
|
||||||
|
state := toPostProcessorState(st)
|
||||||
|
|
||||||
|
if state.Config.ACL == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
state.UI.Say(fmt.Sprintf("Updating template '%s' ACL...", state.FullImageName))
|
||||||
|
chmodOptions := aclToChmodOptions(state.Config.ACL)
|
||||||
|
if err := state.Image.Chmod(chmodOptions); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func stepUpdateVMTemplates(ctx context.Context, cancel context.CancelFunc, st interface{}) error {
|
||||||
|
|
||||||
|
state := toPostProcessorState(st)
|
||||||
|
|
||||||
|
if !state.Config.AutoUpdateVMTemplates {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
state.UI.Say("Searching VM templates using old images...")
|
||||||
|
|
||||||
|
templatePool, err := goca.NewTemplatePool(goca.PoolWhoAll, -1, -1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
imageNameRegExp, err := getFullImageNamePattern(state.Config.ImageName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
xPathIter := templatePool.XPathIter("/VMTEMPLATE_POOL/VMTEMPLATE")
|
||||||
|
for xPathIter.Next() {
|
||||||
|
|
||||||
|
node := xPathIter.Node()
|
||||||
|
imageName, _ := node.XPath("TEMPLATE/DISK/IMAGE")
|
||||||
|
|
||||||
|
if !imageNameRegExp.MatchString(imageName) || imageName == state.FullImageName {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
templateName, _ := node.XPath("NAME")
|
||||||
|
state.UI.Say(fmt.Sprintf("Found VM template '%s' using image '%s' ", templateName, imageName))
|
||||||
|
|
||||||
|
rawTemplateID, _ := node.XPath("ID")
|
||||||
|
templateID, err := strconv.ParseUint(rawTemplateID, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
template := goca.NewTemplate(uint(templateID))
|
||||||
|
imageUser, _ := state.Image.XPath("/IMAGE/UNAME")
|
||||||
|
|
||||||
|
templateUpdate := fmt.Sprintf(`
|
||||||
|
DISK = [
|
||||||
|
IMAGE = "%s",
|
||||||
|
IMAGE_UNAME = "%s" ]
|
||||||
|
`, state.FullImageName, imageUser)
|
||||||
|
|
||||||
|
state.UI.Say(fmt.Sprintf("Updating VM template '%s'", templateName))
|
||||||
|
if err := template.Update(templateUpdate, 1); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func stepCleanupUnusedImages(ctx context.Context, cancel context.CancelFunc, st interface{}) error {
|
||||||
|
|
||||||
|
state := toPostProcessorState(st)
|
||||||
|
|
||||||
|
if !state.Config.CleanupUnusedImages {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
state.UI.Say("Cleaning old unused images...")
|
||||||
|
|
||||||
|
imageNameRegExp, err := getFullImageNamePattern(state.Config.ImageName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
imagePool, err := goca.NewImagePool(goca.PoolWhoAll, -1, -1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
xPathIter := imagePool.XPathIter("/IMAGE_POOL/IMAGE")
|
||||||
|
for xPathIter.Next() {
|
||||||
|
|
||||||
|
node := xPathIter.Node()
|
||||||
|
imageName, _ := node.XPath("NAME")
|
||||||
|
|
||||||
|
if !imageNameRegExp.MatchString(imageName) || imageName == state.FullImageName {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
rawImgState, _ := node.XPath("STATE")
|
||||||
|
imgState, err := strconv.Atoi(rawImgState)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if isImageUsed(goca.ImageState(imgState)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
state.UI.Say(fmt.Sprintf("Found unused image '%s'. Deleting it.", imageName))
|
||||||
|
|
||||||
|
rawImageID, _ := node.XPath("ID")
|
||||||
|
imageID, err := strconv.ParseUint(rawImageID, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
image := goca.NewImage(uint(imageID))
|
||||||
|
if err := image.Delete(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func toPostProcessorState(st interface{}) *postProcessorState {
|
||||||
|
state, ok := st.(*postProcessorState)
|
||||||
|
if !ok {
|
||||||
|
panic("couldnt cast state to the expected type")
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
func isImageUsed(imgState goca.ImageState) bool {
|
||||||
|
return imgState == goca.ImageUsed || imgState == goca.ImageLockUsed
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFullImageNamePattern(imageName string) (*regexp.Regexp, error) {
|
||||||
|
return regexp.Compile(fmt.Sprintf(`^%s-\d+$`, imageName))
|
||||||
|
}
|
||||||
|
|
||||||
|
func serializeImageTemplate(imageName string, tmpl []string) string {
|
||||||
|
tmpl = append(tmpl, fmt.Sprintf("NAME = \"%s\"", imageName))
|
||||||
return strings.Join(tmpl, "\n")
|
return strings.Join(tmpl, "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
package stepper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
Error Status = iota
|
||||||
|
Completed Status = iota
|
||||||
|
Canceled Status = iota
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrUnsupportedState = "unsupported state"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Status int
|
||||||
|
|
||||||
|
type Step func(ctx context.Context, cancelFunc context.CancelFunc, state interface{}) error
|
||||||
|
|
||||||
|
func Run(ctx context.Context, state interface{}, steps ...Step) (Status, error) {
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
for _, s := range steps {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return Canceled, nil
|
||||||
|
default:
|
||||||
|
err := s(ctx, cancel, state)
|
||||||
|
if err != nil {
|
||||||
|
return Error, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Completed, nil
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,146 @@
|
||||||
|
package stepper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBasicSteps(t *testing.T) {
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
state := make(map[string]bool)
|
||||||
|
|
||||||
|
step1 := func(ctx context.Context, cancel context.CancelFunc, state interface{}) error {
|
||||||
|
st, ok := state.(map[string]bool)
|
||||||
|
if !ok {
|
||||||
|
t.Error(ErrUnsupportedState)
|
||||||
|
}
|
||||||
|
st["step1"] = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
step2 := func(ctx context.Context, cancel context.CancelFunc, state interface{}) error {
|
||||||
|
st, ok := state.(map[string]bool)
|
||||||
|
if !ok {
|
||||||
|
t.Error(ErrUnsupportedState)
|
||||||
|
}
|
||||||
|
st["step2"] = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
status, err := Run(ctx, state, step1, step2)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if g, e := status, Completed; g != e {
|
||||||
|
t.Errorf("status: expected '%v', got '%v'", e, g)
|
||||||
|
}
|
||||||
|
|
||||||
|
if g, e := state["step1"], true; g != e {
|
||||||
|
t.Errorf("state[\"step1\"]: expected '%v', got '%v'", e, g)
|
||||||
|
}
|
||||||
|
|
||||||
|
if g, e := state["step2"], true; g != e {
|
||||||
|
t.Errorf("state[\"step2\"]: expected '%v', got '%v'", e, g)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepsCancel(t *testing.T) {
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
state := make(map[string]bool)
|
||||||
|
|
||||||
|
step1 := func(ctx context.Context, cancel context.CancelFunc, state interface{}) error {
|
||||||
|
st, ok := state.(map[string]bool)
|
||||||
|
if !ok {
|
||||||
|
t.Error(ErrUnsupportedState)
|
||||||
|
}
|
||||||
|
st["step1"] = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
step2 := func(ctx context.Context, cancel context.CancelFunc, state interface{}) error {
|
||||||
|
cancel()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
step3 := func(ctx context.Context, cancel context.CancelFunc, state interface{}) error {
|
||||||
|
st, ok := state.(map[string]bool)
|
||||||
|
if !ok {
|
||||||
|
t.Error(ErrUnsupportedState)
|
||||||
|
}
|
||||||
|
st["step3"] = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
status, err := Run(ctx, state, step1, step2, step3)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if g, e := status, Canceled; g != e {
|
||||||
|
t.Errorf("status: expected '%v', got '%v'", e, g)
|
||||||
|
}
|
||||||
|
|
||||||
|
if g, e := state["step1"], true; g != e {
|
||||||
|
t.Errorf("state[\"step1\"]: expected '%v', got '%v'", e, g)
|
||||||
|
}
|
||||||
|
|
||||||
|
if g, e := state["step3"], false; g != e {
|
||||||
|
t.Errorf("state[\"step3\"]: expected '%v', got '%v'", e, g)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepsError(t *testing.T) {
|
||||||
|
|
||||||
|
stepErr := errors.New("test error")
|
||||||
|
ctx := context.Background()
|
||||||
|
state := make(map[string]bool)
|
||||||
|
|
||||||
|
step1 := func(ctx context.Context, cancel context.CancelFunc, state interface{}) error {
|
||||||
|
st, ok := state.(map[string]bool)
|
||||||
|
if !ok {
|
||||||
|
t.Error(ErrUnsupportedState)
|
||||||
|
}
|
||||||
|
st["step1"] = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
step2 := func(ctx context.Context, cancel context.CancelFunc, state interface{}) error {
|
||||||
|
return stepErr
|
||||||
|
}
|
||||||
|
|
||||||
|
step3 := func(ctx context.Context, cancel context.CancelFunc, state interface{}) error {
|
||||||
|
st, ok := state.(map[string]bool)
|
||||||
|
if !ok {
|
||||||
|
t.Error(ErrUnsupportedState)
|
||||||
|
}
|
||||||
|
st["step3"] = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
status, err := Run(ctx, state, step1, step2, step3)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("err should not be nil")
|
||||||
|
}
|
||||||
|
if g, e := err, stepErr; g != e {
|
||||||
|
t.Errorf("err: expected '%v', got '%v'", e, g)
|
||||||
|
}
|
||||||
|
|
||||||
|
if g, e := status, Error; g != e {
|
||||||
|
t.Errorf("status: expected '%v', got '%v'", e, g)
|
||||||
|
}
|
||||||
|
|
||||||
|
if g, e := state["step1"], true; g != e {
|
||||||
|
t.Errorf("state[\"step1\"]: expected '%v', got '%v'", e, g)
|
||||||
|
}
|
||||||
|
|
||||||
|
if g, e := state["step3"], false; g != e {
|
||||||
|
t.Errorf("state[\"step3\"]: expected '%v', got '%v'", e, g)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue