250 lines
6.4 KiB
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
|
|
}
|