packer-cn/builder/oracle/oci/config.go

250 lines
6.4 KiB
Go

package oci
import (
"encoding/base64"
"errors"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
client "github.com/hashicorp/packer/builder/oracle/oci/client"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"github.com/mitchellh/go-homedir"
)
type Config struct {
common.PackerConfig `mapstructure:",squash"`
Comm communicator.Config `mapstructure:",squash"`
AccessCfg *client.Config
AccessCfgFile string `mapstructure:"access_cfg_file"`
AccessCfgFileAccount string `mapstructure:"access_cfg_file_account"`
// Access config overrides
UserID string `mapstructure:"user_ocid"`
TenancyID string `mapstructure:"tenancy_ocid"`
Region string `mapstructure:"region"`
Fingerprint string `mapstructure:"fingerprint"`
KeyFile string `mapstructure:"key_file"`
PassPhrase string `mapstructure:"pass_phrase"`
UsePrivateIP bool `mapstructure:"use_private_ip"`
AvailabilityDomain string `mapstructure:"availability_domain"`
CompartmentID string `mapstructure:"compartment_ocid"`
// Image
BaseImageID string `mapstructure:"base_image_ocid"`
Shape string `mapstructure:"shape"`
ImageName string `mapstructure:"image_name"`
// UserData and UserDataFile file are both optional and mutually exclusive.
UserData string `mapstructure:"user_data"`
UserDataFile string `mapstructure:"user_data_file"`
// Networking
SubnetID string `mapstructure:"subnet_ocid"`
ctx interpolate.Context
}
func NewConfig(raws ...interface{}) (*Config, error) {
c := &Config{}
// Decode from template
err := config.Decode(c, &config.DecodeOpts{
Interpolate: true,
InterpolateContext: &c.ctx,
}, raws...)
if err != nil {
return nil, fmt.Errorf("Failed to mapstructure Config: %+v", err)
}
// Determine where the SDK config is located
var accessCfgFile string
if c.AccessCfgFile != "" {
accessCfgFile = c.AccessCfgFile
} else {
accessCfgFile, err = getDefaultOCISettingsPath()
if err != nil {
accessCfgFile = "" // Access cfg might be in template
}
}
accessCfg := &client.Config{}
if accessCfgFile != "" {
loadedAccessCfgs, err := client.LoadConfigsFromFile(accessCfgFile)
if err != nil {
return nil, fmt.Errorf("Invalid config file %s: %s", accessCfgFile, err)
}
cfgAccount := "DEFAULT"
if c.AccessCfgFileAccount != "" {
cfgAccount = c.AccessCfgFileAccount
}
var ok bool
accessCfg, ok = loadedAccessCfgs[cfgAccount]
if !ok {
return nil, fmt.Errorf("No account section '%s' found in config file %s", cfgAccount, accessCfgFile)
}
}
// Override SDK client config with any non-empty template properties
if c.UserID != "" {
accessCfg.User = c.UserID
}
if c.TenancyID != "" {
accessCfg.Tenancy = c.TenancyID
}
if c.Region != "" {
accessCfg.Region = c.Region
}
// Default if the template nor the API config contains a region.
if accessCfg.Region == "" {
accessCfg.Region = "us-phoenix-1"
}
if c.Fingerprint != "" {
accessCfg.Fingerprint = c.Fingerprint
}
if c.PassPhrase != "" {
accessCfg.PassPhrase = c.PassPhrase
}
if c.KeyFile != "" {
accessCfg.KeyFile = c.KeyFile
accessCfg.Key, err = client.LoadPrivateKey(accessCfg)
if err != nil {
return nil, fmt.Errorf("Failed to load private key %s : %s", accessCfg.KeyFile, err)
}
}
var errs *packer.MultiError
if es := c.Comm.Prepare(&c.ctx); len(es) > 0 {
errs = packer.MultiErrorAppend(errs, es...)
}
// Required AccessCfg configuration options
if accessCfg.User == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("'user_ocid' must be specified"))
}
if accessCfg.Tenancy == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("'tenancy_ocid' must be specified"))
}
if accessCfg.Region == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("'region' must be specified"))
}
if accessCfg.Fingerprint == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("'fingerprint' must be specified"))
}
if accessCfg.Key == nil {
errs = packer.MultiErrorAppend(
errs, errors.New("'key_file' must be specified"))
}
c.AccessCfg = accessCfg
// Required non AccessCfg configuration options
if c.AvailabilityDomain == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("'availability_domain' must be specified"))
}
if c.CompartmentID == "" {
c.CompartmentID = accessCfg.Tenancy
}
if c.Shape == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("'shape' must be specified"))
}
if c.SubnetID == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("'subnet_ocid' must be specified"))
}
if c.BaseImageID == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("'base_image_ocid' must be specified"))
}
if c.ImageName == "" {
name, err := interpolate.Render("packer-{{timestamp}}", nil)
if err != nil {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("unable to parse image name: %s", err))
} else {
c.ImageName = name
}
}
// Optional UserData config
if c.UserData != "" && c.UserDataFile != "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Only one of user_data or user_data_file can be specified."))
} else if c.UserDataFile != "" {
if _, err := os.Stat(c.UserDataFile); err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("user_data_file not found: %s", c.UserDataFile))
}
}
// read UserDataFile into string.
if c.UserDataFile != "" {
fiData, err := ioutil.ReadFile(c.UserDataFile)
if err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Problem reading user_data_file: %s", err))
}
c.UserData = string(fiData)
}
// Test if UserData is encoded already, and if not, encode it
if c.UserData != "" {
if _, err := base64.StdEncoding.DecodeString(c.UserData); err != nil {
log.Printf("[DEBUG] base64 encoding user data...")
c.UserData = base64.StdEncoding.EncodeToString([]byte(c.UserData))
}
}
if errs != nil && len(errs.Errors) > 0 {
return nil, errs
}
return c, nil
}
// getDefaultOCISettingsPath uses mitchellh/go-homedir to compute the default
// config file location ($HOME/.oci/config).
func getDefaultOCISettingsPath() (string, error) {
home, err := homedir.Dir()
if err != nil {
return "", err
}
path := filepath.Join(home, ".oci", "config")
if _, err := os.Stat(path); err != nil {
return "", err
}
return path, nil
}