github.phpd.cn/hashicorp/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 }