github.com/angdraug/packer@v1.3.2/builder/oracle/oci/config.go (about)

     1  package oci
     2  
     3  import (
     4  	"encoding/base64"
     5  	"errors"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"log"
     9  	"os"
    10  	"path/filepath"
    11  	"strings"
    12  
    13  	"github.com/hashicorp/packer/common"
    14  	"github.com/hashicorp/packer/helper/communicator"
    15  	"github.com/hashicorp/packer/helper/config"
    16  	"github.com/hashicorp/packer/packer"
    17  	"github.com/hashicorp/packer/template/interpolate"
    18  	ocicommon "github.com/oracle/oci-go-sdk/common"
    19  
    20  	"github.com/mitchellh/go-homedir"
    21  )
    22  
    23  type Config struct {
    24  	common.PackerConfig `mapstructure:",squash"`
    25  	Comm                communicator.Config `mapstructure:",squash"`
    26  
    27  	ConfigProvider ocicommon.ConfigurationProvider
    28  
    29  	AccessCfgFile        string `mapstructure:"access_cfg_file"`
    30  	AccessCfgFileAccount string `mapstructure:"access_cfg_file_account"`
    31  
    32  	// Access config overrides
    33  	UserID       string `mapstructure:"user_ocid"`
    34  	TenancyID    string `mapstructure:"tenancy_ocid"`
    35  	Region       string `mapstructure:"region"`
    36  	Fingerprint  string `mapstructure:"fingerprint"`
    37  	KeyFile      string `mapstructure:"key_file"`
    38  	PassPhrase   string `mapstructure:"pass_phrase"`
    39  	UsePrivateIP bool   `mapstructure:"use_private_ip"`
    40  
    41  	AvailabilityDomain string `mapstructure:"availability_domain"`
    42  	CompartmentID      string `mapstructure:"compartment_ocid"`
    43  
    44  	// Image
    45  	BaseImageID string `mapstructure:"base_image_ocid"`
    46  	Shape       string `mapstructure:"shape"`
    47  	ImageName   string `mapstructure:"image_name"`
    48  
    49  	// Instance
    50  	InstanceName string `mapstructure:"instance_name"`
    51  
    52  	// Metadata optionally contains custom metadata key/value pairs provided in the
    53  	// configuration. While this can be used to set metadata["user_data"] the explicit
    54  	// "user_data" and "user_data_file" values will have precedence.
    55  	// An instance's metadata can be obtained from at http://169.254.169.254 on the
    56  	// launched instance.
    57  	Metadata map[string]string `mapstructure:"metadata"`
    58  
    59  	// UserData and UserDataFile file are both optional and mutually exclusive.
    60  	UserData     string `mapstructure:"user_data"`
    61  	UserDataFile string `mapstructure:"user_data_file"`
    62  
    63  	// Networking
    64  	SubnetID string `mapstructure:"subnet_ocid"`
    65  
    66  	// Tagging
    67  	Tags map[string]string `mapstructure:"tags"`
    68  
    69  	ctx interpolate.Context
    70  }
    71  
    72  func NewConfig(raws ...interface{}) (*Config, error) {
    73  	c := &Config{}
    74  
    75  	// Decode from template
    76  	err := config.Decode(c, &config.DecodeOpts{
    77  		Interpolate:        true,
    78  		InterpolateContext: &c.ctx,
    79  	}, raws...)
    80  	if err != nil {
    81  		return nil, fmt.Errorf("Failed to mapstructure Config: %+v", err)
    82  	}
    83  
    84  	// Determine where the SDK config is located
    85  	if c.AccessCfgFile == "" {
    86  		c.AccessCfgFile, err = getDefaultOCISettingsPath()
    87  		if err != nil {
    88  			log.Println("Default OCI settings file not found")
    89  		}
    90  	}
    91  
    92  	if c.AccessCfgFileAccount == "" {
    93  		c.AccessCfgFileAccount = "DEFAULT"
    94  	}
    95  
    96  	var keyContent []byte
    97  	if c.KeyFile != "" {
    98  		path, err := homedir.Expand(c.KeyFile)
    99  		if err != nil {
   100  			return nil, err
   101  		}
   102  
   103  		// Read API signing key
   104  		keyContent, err = ioutil.ReadFile(path)
   105  		if err != nil {
   106  			return nil, err
   107  		}
   108  	}
   109  
   110  	fileProvider, _ := ocicommon.ConfigurationProviderFromFileWithProfile(c.AccessCfgFile, c.AccessCfgFileAccount, c.PassPhrase)
   111  	if c.Region == "" {
   112  		var region string
   113  		if fileProvider != nil {
   114  			region, _ = fileProvider.Region()
   115  		}
   116  		if region == "" {
   117  			c.Region = "us-phoenix-1"
   118  		}
   119  	}
   120  
   121  	providers := []ocicommon.ConfigurationProvider{
   122  		NewRawConfigurationProvider(c.TenancyID, c.UserID, c.Region, c.Fingerprint, string(keyContent), &c.PassPhrase),
   123  	}
   124  
   125  	if fileProvider != nil {
   126  		providers = append(providers, fileProvider)
   127  	}
   128  
   129  	// Load API access configuration from SDK
   130  	configProvider, err := ocicommon.ComposingConfigurationProvider(providers)
   131  	if err != nil {
   132  		return nil, err
   133  	}
   134  
   135  	var errs *packer.MultiError
   136  	if es := c.Comm.Prepare(&c.ctx); len(es) > 0 {
   137  		errs = packer.MultiErrorAppend(errs, es...)
   138  	}
   139  
   140  	if userOCID, _ := configProvider.UserOCID(); userOCID == "" {
   141  		errs = packer.MultiErrorAppend(
   142  			errs, errors.New("'user_ocid' must be specified"))
   143  	}
   144  
   145  	tenancyOCID, _ := configProvider.TenancyOCID()
   146  	if tenancyOCID == "" {
   147  		errs = packer.MultiErrorAppend(
   148  			errs, errors.New("'tenancy_ocid' must be specified"))
   149  	}
   150  
   151  	if fingerprint, _ := configProvider.KeyFingerprint(); fingerprint == "" {
   152  		errs = packer.MultiErrorAppend(
   153  			errs, errors.New("'fingerprint' must be specified"))
   154  	}
   155  
   156  	if _, err := configProvider.PrivateRSAKey(); err != nil {
   157  		errs = packer.MultiErrorAppend(
   158  			errs, errors.New("'key_file' must be specified"))
   159  	}
   160  
   161  	c.ConfigProvider = configProvider
   162  
   163  	if c.AvailabilityDomain == "" {
   164  		errs = packer.MultiErrorAppend(
   165  			errs, errors.New("'availability_domain' must be specified"))
   166  	}
   167  
   168  	if c.CompartmentID == "" && tenancyOCID != "" {
   169  		c.CompartmentID = tenancyOCID
   170  	}
   171  
   172  	if c.Shape == "" {
   173  		errs = packer.MultiErrorAppend(
   174  			errs, errors.New("'shape' must be specified"))
   175  	}
   176  
   177  	if c.SubnetID == "" {
   178  		errs = packer.MultiErrorAppend(
   179  			errs, errors.New("'subnet_ocid' must be specified"))
   180  	}
   181  
   182  	if c.BaseImageID == "" {
   183  		errs = packer.MultiErrorAppend(
   184  			errs, errors.New("'base_image_ocid' must be specified"))
   185  	}
   186  
   187  	// Validate tag lengths. TODO (hlowndes) maximum number of tags allowed.
   188  	if c.Tags != nil {
   189  		for k, v := range c.Tags {
   190  			k = strings.TrimSpace(k)
   191  			v = strings.TrimSpace(v)
   192  			if len(k) > 100 {
   193  				errs = packer.MultiErrorAppend(
   194  					errs, fmt.Errorf("Tag key length too long. Maximum 100 but found %d. Key: %s", len(k), k))
   195  			}
   196  			if len(k) == 0 {
   197  				errs = packer.MultiErrorAppend(
   198  					errs, errors.New("Tag key empty in config"))
   199  			}
   200  			if len(v) > 100 {
   201  				errs = packer.MultiErrorAppend(
   202  					errs, fmt.Errorf("Tag value length too long. Maximum 100 but found %d. Key: %s", len(v), k))
   203  			}
   204  			if len(v) == 0 {
   205  				errs = packer.MultiErrorAppend(
   206  					errs, errors.New("Tag value empty in config"))
   207  			}
   208  		}
   209  	}
   210  
   211  	if c.ImageName == "" {
   212  		name, err := interpolate.Render("packer-{{timestamp}}", nil)
   213  		if err != nil {
   214  			errs = packer.MultiErrorAppend(errs,
   215  				fmt.Errorf("unable to parse image name: %s", err))
   216  		} else {
   217  			c.ImageName = name
   218  		}
   219  	}
   220  
   221  	// Optional UserData config
   222  	if c.UserData != "" && c.UserDataFile != "" {
   223  		errs = packer.MultiErrorAppend(errs, fmt.Errorf("Only one of user_data or user_data_file can be specified."))
   224  	} else if c.UserDataFile != "" {
   225  		if _, err := os.Stat(c.UserDataFile); err != nil {
   226  			errs = packer.MultiErrorAppend(errs, fmt.Errorf("user_data_file not found: %s", c.UserDataFile))
   227  		}
   228  	}
   229  	// read UserDataFile into string.
   230  	if c.UserDataFile != "" {
   231  		fiData, err := ioutil.ReadFile(c.UserDataFile)
   232  		if err != nil {
   233  			errs = packer.MultiErrorAppend(errs, fmt.Errorf("Problem reading user_data_file: %s", err))
   234  		}
   235  		c.UserData = string(fiData)
   236  	}
   237  	// Test if UserData is encoded already, and if not, encode it
   238  	if c.UserData != "" {
   239  		if _, err := base64.StdEncoding.DecodeString(c.UserData); err != nil {
   240  			log.Printf("[DEBUG] base64 encoding user data...")
   241  			c.UserData = base64.StdEncoding.EncodeToString([]byte(c.UserData))
   242  		}
   243  	}
   244  
   245  	if errs != nil && len(errs.Errors) > 0 {
   246  		return nil, errs
   247  	}
   248  
   249  	return c, nil
   250  }
   251  
   252  // getDefaultOCISettingsPath uses mitchellh/go-homedir to compute the default
   253  // config file location ($HOME/.oci/config).
   254  func getDefaultOCISettingsPath() (string, error) {
   255  	home, err := homedir.Dir()
   256  	if err != nil {
   257  		return "", err
   258  	}
   259  
   260  	path := filepath.Join(home, ".oci", "config")
   261  	if _, err := os.Stat(path); err != nil {
   262  		return "", err
   263  	}
   264  
   265  	return path, nil
   266  }