packer-opennebula/cmd/post-processor/image-template/image_template.go

218 lines
5.3 KiB
Go

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
}