package main import ( "fmt" "strings" "time" "github.com/Cadoles/goca" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/template/interpolate" ) // Config handles configuration options of the OpenNebula post-processor type Config struct { common.PackerConfig `mapstructure:",squash"` User string Password string Endpoint string ImageName string `mapstructure:"image_name"` ImageTemplate []string `mapstructure:"image_template"` DatastoreName string `mapstructure:"datastore_name"` MergeTemplate int `mapstructure:"merge_template"` DeleteIfExists bool `mapstructure:"delete_if_exists"` ACL map[string]int `mapstructure:"acl"` Owner *struct { User string Group string } `mapstructure:"owner"` ctx interpolate.Context } // PostProcessor is an OpenNebula post processor for packer type PostProcessor struct { Conf *Config } // Configure configures the packer OpenNebula post-processor func (pp *PostProcessor) Configure(raws ...interface{}) error { conf := &Config{} err := config.Decode(conf, &config.DecodeOpts{ Interpolate: true, InterpolateContext: &conf.ctx, }, raws...) if err != nil { return err } pp.Conf = conf return nil } // 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) { // Initialize OpenNebula XML-RPC API client config := goca.NewConfig(pp.Conf.User, pp.Conf.Password, pp.Conf.Endpoint) if err := goca.SetClient(config); err != nil { return a, true, err } ui.Say(fmt.Sprintf("Connecting to OpenNebula RPC endpoint '%s' with provided credentials...", pp.Conf.Endpoint)) // Search image template by its name img, err := goca.NewImageFromName(pp.Conf.ImageName) if err != nil && err.Error() != "resource not found" { return a, true, err } if img != nil { // Retreive info about the template if err := img.Info(); err != nil { return a, true, err } state, err := img.State() if err != nil { return a, true, err } inUse := state == goca.ImageUsed || state == goca.ImageLockUsed if inUse { ui.Say(fmt.Sprintf("Template '%s' is in use. Cannot delete it.", pp.Conf.ImageName)) } if pp.Conf.DeleteIfExists && !inUse { ui.Say(fmt.Sprintf("Deleting template '%s'...", pp.Conf.ImageName)) if err := img.Delete(); err != nil { return a, true, err } time.Sleep(1 * time.Second) img = nil } } // Generate image template tmplStr := serializeImageTemplate(pp.Conf.ImageTemplate) // If the image template can not be found, we create it if img == nil { ui.Say(fmt.Sprintf("Creating template '%s'...", pp.Conf.ImageName)) // Search image datastore's ID datastore, err := goca.NewDatastoreFromName(pp.Conf.DatastoreName) if err != nil { return a, true, err } // Create image template imageID, err := goca.CreateImage(tmplStr, datastore.ID) if err != nil { return a, true, err } img = goca.NewImage(imageID) // Retreive info about the template if err := img.Info(); err != nil { return a, true, err } } else { ui.Say(fmt.Sprintf("Updating template '%s'...", pp.Conf.ImageName)) // Update image template if err := img.Update(tmplStr, pp.Conf.MergeTemplate); err != nil { return a, true, err } } if pp.Conf.Owner != nil { currentUserName, userFound := img.XPath("/IMAGE/UNAME") currentGroupName, groupFound := img.XPath("/IMAGE/GNAME") isSameUser := userFound && currentUserName == pp.Conf.Owner.User isSameGroup := groupFound && currentGroupName == pp.Conf.Owner.Group userID := -1 groupID := -1 if pp.Conf.Owner.User != "" && !isSameUser { user, err := goca.NewUserFromName(pp.Conf.Owner.User) if err != nil { return a, true, err } userID = int(user.ID) } if pp.Conf.Owner.Group != "" && !isSameGroup { group, err := goca.NewGroupFromName(pp.Conf.Owner.Group) if err != nil { return a, true, err } groupID = int(group.ID) } ui.Say(fmt.Sprintf("Updating template '%s' owner...", pp.Conf.ImageName)) if err := img.Chown(userID, groupID); err != nil { return a, true, err } } 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 { return strings.Join(tmpl, "\n") } func aclToChmodOptions(acl map[string]int) goca.ImageChmodOptions { chmodOptions := goca.NewImageChmodOptions() for key, val := range acl { switch key { case "user_use": chmodOptions.UserUse = val case "user_manage": chmodOptions.UserManage = val case "user_admin": chmodOptions.UserAdmin = val case "group_use": chmodOptions.GroupUse = val case "group_manage": chmodOptions.GroupManage = val case "group_admin": chmodOptions.GroupAdmin = val case "other_use": chmodOptions.OtherUse = val case "other_manage": chmodOptions.OtherManage = val case "other_admin": chmodOptions.OtherAdmin = val } } return chmodOptions }