2020-10-14 17:09:09 -04:00
|
|
|
//go:generate mapstructure-to-hcl2 -type Config,CreateVNICDetails,ListImagesRequest
|
2019-10-14 10:43:59 -04:00
|
|
|
|
2017-09-12 11:30:39 -04:00
|
|
|
package oci
|
2017-02-13 05:35:14 -05:00
|
|
|
|
|
|
|
import (
|
2018-03-28 16:27:41 -04:00
|
|
|
"encoding/base64"
|
2017-02-13 05:35:14 -05:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
2018-03-28 16:27:41 -04:00
|
|
|
"io/ioutil"
|
|
|
|
"log"
|
2017-02-13 05:35:14 -05:00
|
|
|
"os"
|
2018-12-02 18:30:01 -05:00
|
|
|
"os/user"
|
2017-02-13 05:35:14 -05:00
|
|
|
"path/filepath"
|
2018-06-04 09:18:18 -04:00
|
|
|
"strings"
|
2017-02-13 05:35:14 -05:00
|
|
|
|
2017-04-07 06:20:33 -04:00
|
|
|
"github.com/hashicorp/packer/common"
|
|
|
|
"github.com/hashicorp/packer/helper/communicator"
|
|
|
|
"github.com/hashicorp/packer/helper/config"
|
|
|
|
"github.com/hashicorp/packer/packer"
|
2020-11-11 13:21:37 -05:00
|
|
|
"github.com/hashicorp/packer/packer-plugin-sdk/template/interpolate"
|
2018-04-11 05:20:40 -04:00
|
|
|
ocicommon "github.com/oracle/oci-go-sdk/common"
|
2020-03-15 13:22:06 -04:00
|
|
|
ociauth "github.com/oracle/oci-go-sdk/common/auth"
|
2017-02-13 05:35:14 -05:00
|
|
|
)
|
|
|
|
|
2020-09-08 23:30:37 -04:00
|
|
|
type CreateVNICDetails struct {
|
2020-09-05 14:45:16 -04:00
|
|
|
// fields that can be specified under "create_vnic_details"
|
2020-09-08 14:33:50 -04:00
|
|
|
AssignPublicIp *bool `mapstructure:"assign_public_ip" required:"false"`
|
|
|
|
DefinedTags map[string]map[string]interface{} `mapstructure:"defined_tags" required:"false"`
|
|
|
|
DisplayName *string `mapstructure:"display_name" required:"false"`
|
|
|
|
FreeformTags map[string]string `mapstructure:"tags" required:"false"`
|
|
|
|
HostnameLabel *string `mapstructure:"hostname_label" required:"false"`
|
|
|
|
NsgIds []string `mapstructure:"nsg_ids" required:"false"`
|
|
|
|
PrivateIp *string `mapstructure:"private_ip" required:"false"`
|
|
|
|
SkipSourceDestCheck *bool `mapstructure:"skip_source_dest_check" required:"false"`
|
|
|
|
SubnetId *string `mapstructure:"subnet_id" required:"false"`
|
2020-09-05 14:45:16 -04:00
|
|
|
}
|
|
|
|
|
2020-10-14 17:09:09 -04:00
|
|
|
type ListImagesRequest struct {
|
|
|
|
// fields that can be specified under "base_image_filter"
|
|
|
|
CompartmentId *string `mapstructure:"compartment_id"`
|
|
|
|
DisplayName *string `mapstructure:"display_name"`
|
2020-10-15 15:28:38 -04:00
|
|
|
DisplayNameSearch *string `mapstructure:"display_name_search"`
|
2020-10-14 17:09:09 -04:00
|
|
|
OperatingSystem *string `mapstructure:"operating_system"`
|
|
|
|
OperatingSystemVersion *string `mapstructure:"operating_system_version"`
|
|
|
|
Shape *string `mapstructure:"shape"`
|
|
|
|
}
|
|
|
|
|
2017-02-13 05:35:14 -05:00
|
|
|
type Config struct {
|
|
|
|
common.PackerConfig `mapstructure:",squash"`
|
|
|
|
Comm communicator.Config `mapstructure:",squash"`
|
|
|
|
|
2019-10-14 10:08:16 -04:00
|
|
|
configProvider ocicommon.ConfigurationProvider
|
2017-02-13 05:35:14 -05:00
|
|
|
|
2020-03-15 13:22:06 -04:00
|
|
|
// Instance Principals (OPTIONAL)
|
|
|
|
// If set to true the following can't have non empty values
|
|
|
|
// - AccessCfgFile
|
|
|
|
// - AccessCfgFileAccount
|
|
|
|
// - UserID
|
|
|
|
// - TenancyID
|
|
|
|
// - Region
|
|
|
|
// - Fingerprint
|
|
|
|
// - KeyFile
|
|
|
|
// - PassPhrase
|
|
|
|
InstancePrincipals bool `mapstructure:"use_instance_principals"`
|
|
|
|
|
2017-02-13 05:35:14 -05:00
|
|
|
AccessCfgFile string `mapstructure:"access_cfg_file"`
|
|
|
|
AccessCfgFileAccount string `mapstructure:"access_cfg_file_account"`
|
|
|
|
|
|
|
|
// Access config overrides
|
2018-02-13 08:20:26 -05:00
|
|
|
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"`
|
2017-02-13 05:35:14 -05:00
|
|
|
|
|
|
|
AvailabilityDomain string `mapstructure:"availability_domain"`
|
|
|
|
CompartmentID string `mapstructure:"compartment_ocid"`
|
|
|
|
|
|
|
|
// Image
|
2020-10-14 17:09:09 -04:00
|
|
|
BaseImageID string `mapstructure:"base_image_ocid"`
|
|
|
|
BaseImageFilter ListImagesRequest `mapstructure:"base_image_filter"`
|
|
|
|
ImageName string `mapstructure:"image_name"`
|
|
|
|
ImageCompartmentID string `mapstructure:"image_compartment_ocid"`
|
2018-06-22 07:38:25 -04:00
|
|
|
|
|
|
|
// Instance
|
2020-10-14 17:09:09 -04:00
|
|
|
InstanceName *string `mapstructure:"instance_name"`
|
2020-08-31 08:36:09 -04:00
|
|
|
InstanceTags map[string]string `mapstructure:"instance_tags"`
|
|
|
|
InstanceDefinedTags map[string]map[string]interface{} `mapstructure:"instance_defined_tags"`
|
2020-08-25 12:13:34 -04:00
|
|
|
Shape string `mapstructure:"shape"`
|
2020-10-01 02:01:51 -04:00
|
|
|
BootVolumeSizeInGBs int64 `mapstructure:"disk_size"`
|
2018-06-22 07:38:25 -04:00
|
|
|
|
2018-07-17 11:41:19 -04:00
|
|
|
// Metadata optionally contains custom metadata key/value pairs provided in the
|
|
|
|
// configuration. While this can be used to set metadata["user_data"] the explicit
|
|
|
|
// "user_data" and "user_data_file" values will have precedence.
|
|
|
|
// An instance's metadata can be obtained from at http://169.254.169.254 on the
|
|
|
|
// launched instance.
|
|
|
|
Metadata map[string]string `mapstructure:"metadata"`
|
2018-07-02 04:48:08 -04:00
|
|
|
|
2018-03-28 16:27:41 -04:00
|
|
|
// UserData and UserDataFile file are both optional and mutually exclusive.
|
|
|
|
UserData string `mapstructure:"user_data"`
|
|
|
|
UserDataFile string `mapstructure:"user_data_file"`
|
2017-02-13 05:35:14 -05:00
|
|
|
|
|
|
|
// Networking
|
2020-09-08 23:30:37 -04:00
|
|
|
SubnetID string `mapstructure:"subnet_ocid"`
|
|
|
|
CreateVnicDetails CreateVNICDetails `mapstructure:"create_vnic_details"`
|
2017-02-13 05:35:14 -05:00
|
|
|
|
2018-06-04 08:14:59 -04:00
|
|
|
// Tagging
|
2019-09-27 08:49:37 -04:00
|
|
|
Tags map[string]string `mapstructure:"tags"`
|
|
|
|
DefinedTags map[string]map[string]interface{} `mapstructure:"defined_tags"`
|
2018-06-04 08:14:59 -04:00
|
|
|
|
2017-02-13 05:35:14 -05:00
|
|
|
ctx interpolate.Context
|
|
|
|
}
|
|
|
|
|
2019-10-14 10:08:16 -04:00
|
|
|
func (c *Config) ConfigProvider() ocicommon.ConfigurationProvider {
|
|
|
|
return c.configProvider
|
|
|
|
}
|
|
|
|
|
2019-12-17 05:25:56 -05:00
|
|
|
func (c *Config) Prepare(raws ...interface{}) error {
|
2017-02-13 05:35:14 -05:00
|
|
|
|
|
|
|
// Decode from template
|
|
|
|
err := config.Decode(c, &config.DecodeOpts{
|
|
|
|
Interpolate: true,
|
|
|
|
InterpolateContext: &c.ctx,
|
|
|
|
}, raws...)
|
|
|
|
if err != nil {
|
2019-12-17 05:25:56 -05:00
|
|
|
return fmt.Errorf("Failed to mapstructure Config: %+v", err)
|
2017-02-13 05:35:14 -05:00
|
|
|
}
|
|
|
|
|
2020-03-15 13:22:06 -04:00
|
|
|
var errs *packer.MultiError
|
|
|
|
if es := c.Comm.Prepare(&c.ctx); len(es) > 0 {
|
|
|
|
errs = packer.MultiErrorAppend(errs, es...)
|
2017-02-13 05:35:14 -05:00
|
|
|
}
|
|
|
|
|
2020-03-15 13:22:06 -04:00
|
|
|
var tenancyOCID string
|
2017-02-13 05:35:14 -05:00
|
|
|
|
2020-03-15 13:22:06 -04:00
|
|
|
if c.InstancePrincipals {
|
|
|
|
// We could go through all keys in one go and report that the below set
|
|
|
|
// of keys cannot coexist with use_instance_principals but decided to
|
|
|
|
// split them and report them seperately so that the user sees the specific
|
|
|
|
// key involved.
|
|
|
|
var message string = " cannot be present when use_instance_principals is set to true."
|
|
|
|
if c.AccessCfgFile != "" {
|
|
|
|
errs = packer.MultiErrorAppend(errs, fmt.Errorf("access_cfg_file"+message))
|
|
|
|
}
|
|
|
|
if c.AccessCfgFileAccount != "" {
|
|
|
|
errs = packer.MultiErrorAppend(errs, fmt.Errorf("access_cfg_file_account"+message))
|
|
|
|
}
|
|
|
|
if c.UserID != "" {
|
|
|
|
errs = packer.MultiErrorAppend(errs, fmt.Errorf("user_ocid"+message))
|
|
|
|
}
|
|
|
|
if c.TenancyID != "" {
|
|
|
|
errs = packer.MultiErrorAppend(errs, fmt.Errorf("tenancy_ocid"+message))
|
|
|
|
}
|
|
|
|
if c.Region != "" {
|
|
|
|
errs = packer.MultiErrorAppend(errs, fmt.Errorf("region"+message))
|
|
|
|
}
|
|
|
|
if c.Fingerprint != "" {
|
|
|
|
errs = packer.MultiErrorAppend(errs, fmt.Errorf("fingerprint"+message))
|
|
|
|
}
|
|
|
|
if c.KeyFile != "" {
|
|
|
|
errs = packer.MultiErrorAppend(errs, fmt.Errorf("key_file"+message))
|
|
|
|
}
|
|
|
|
if c.PassPhrase != "" {
|
|
|
|
errs = packer.MultiErrorAppend(errs, fmt.Errorf("pass_phrase"+message))
|
|
|
|
}
|
|
|
|
// This check is used to facilitate testing. During testing a Mock struct
|
|
|
|
// is assigned to c.configProvider otherwise testing fails because Instance
|
|
|
|
// Principals cannot be obtained.
|
|
|
|
if c.configProvider == nil {
|
|
|
|
// Even though the previous configuraion checks might fail we don't want
|
|
|
|
// to skip this step. It seems that the logic behind the checks in this
|
|
|
|
// file is to check everything even getting the configProvider.
|
|
|
|
c.configProvider, err = ociauth.InstancePrincipalConfigurationProvider()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
tenancyOCID, err = c.configProvider.TenancyOCID()
|
2017-02-13 05:35:14 -05:00
|
|
|
if err != nil {
|
2019-12-17 05:25:56 -05:00
|
|
|
return err
|
2017-02-13 05:35:14 -05:00
|
|
|
}
|
2020-03-15 13:22:06 -04:00
|
|
|
} else {
|
|
|
|
// Determine where the SDK config is located
|
|
|
|
if c.AccessCfgFile == "" {
|
|
|
|
c.AccessCfgFile, err = getDefaultOCISettingsPath()
|
|
|
|
if err != nil {
|
|
|
|
log.Println("Default OCI settings file not found")
|
|
|
|
}
|
|
|
|
}
|
2017-02-13 05:35:14 -05:00
|
|
|
|
2020-03-15 13:22:06 -04:00
|
|
|
if c.AccessCfgFileAccount == "" {
|
|
|
|
c.AccessCfgFileAccount = "DEFAULT"
|
2017-02-13 05:35:14 -05:00
|
|
|
}
|
|
|
|
|
2020-03-15 13:22:06 -04:00
|
|
|
var keyContent []byte
|
|
|
|
if c.KeyFile != "" {
|
|
|
|
path, err := packer.ExpandUser(c.KeyFile)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read API signing key
|
|
|
|
keyContent, err = ioutil.ReadFile(path)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-04-11 05:20:40 -04:00
|
|
|
}
|
2020-03-15 13:22:06 -04:00
|
|
|
|
|
|
|
fileProvider, _ := ocicommon.ConfigurationProviderFromFileWithProfile(c.AccessCfgFile, c.AccessCfgFileAccount, c.PassPhrase)
|
|
|
|
if c.Region == "" {
|
|
|
|
var region string
|
|
|
|
if fileProvider != nil {
|
|
|
|
region, _ = fileProvider.Region()
|
|
|
|
}
|
|
|
|
if region == "" {
|
|
|
|
c.Region = "us-phoenix-1"
|
|
|
|
}
|
2018-04-11 05:20:40 -04:00
|
|
|
}
|
2017-02-13 05:35:14 -05:00
|
|
|
|
2020-03-15 13:22:06 -04:00
|
|
|
providers := []ocicommon.ConfigurationProvider{
|
|
|
|
NewRawConfigurationProvider(c.TenancyID, c.UserID, c.Region, c.Fingerprint, string(keyContent), &c.PassPhrase),
|
|
|
|
}
|
2017-02-13 05:35:14 -05:00
|
|
|
|
2020-03-15 13:22:06 -04:00
|
|
|
if fileProvider != nil {
|
|
|
|
providers = append(providers, fileProvider)
|
|
|
|
}
|
2017-02-14 05:58:38 -05:00
|
|
|
|
2020-03-15 13:22:06 -04:00
|
|
|
// Load API access configuration from SDK
|
|
|
|
configProvider, err := ocicommon.ComposingConfigurationProvider(providers)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-02-13 05:35:14 -05:00
|
|
|
|
2020-03-15 13:22:06 -04:00
|
|
|
if userOCID, _ := configProvider.UserOCID(); userOCID == "" {
|
|
|
|
errs = packer.MultiErrorAppend(
|
|
|
|
errs, errors.New("'user_ocid' must be specified"))
|
|
|
|
}
|
2017-02-13 05:35:14 -05:00
|
|
|
|
2020-03-15 13:22:06 -04:00
|
|
|
tenancyOCID, _ = configProvider.TenancyOCID()
|
|
|
|
if tenancyOCID == "" {
|
|
|
|
errs = packer.MultiErrorAppend(
|
|
|
|
errs, errors.New("'tenancy_ocid' must be specified"))
|
|
|
|
}
|
2017-02-13 05:35:14 -05:00
|
|
|
|
2020-03-15 13:22:06 -04:00
|
|
|
if fingerprint, _ := configProvider.KeyFingerprint(); fingerprint == "" {
|
|
|
|
errs = packer.MultiErrorAppend(
|
|
|
|
errs, errors.New("'fingerprint' must be specified"))
|
|
|
|
}
|
2017-02-13 05:35:14 -05:00
|
|
|
|
2020-03-15 13:22:06 -04:00
|
|
|
if _, err := configProvider.PrivateRSAKey(); err != nil {
|
|
|
|
errs = packer.MultiErrorAppend(
|
|
|
|
errs, errors.New("'key_file' must be specified"))
|
|
|
|
}
|
2017-02-13 05:35:14 -05:00
|
|
|
|
2020-03-15 13:22:06 -04:00
|
|
|
c.configProvider = configProvider
|
2017-02-13 05:35:14 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if c.AvailabilityDomain == "" {
|
|
|
|
errs = packer.MultiErrorAppend(
|
|
|
|
errs, errors.New("'availability_domain' must be specified"))
|
|
|
|
}
|
|
|
|
|
2018-04-11 05:20:40 -04:00
|
|
|
if c.CompartmentID == "" && tenancyOCID != "" {
|
|
|
|
c.CompartmentID = tenancyOCID
|
2017-02-13 05:35:14 -05:00
|
|
|
}
|
|
|
|
|
2020-10-02 22:27:34 -04:00
|
|
|
if c.ImageCompartmentID == "" {
|
|
|
|
c.ImageCompartmentID = c.CompartmentID
|
|
|
|
}
|
|
|
|
|
2017-02-13 05:35:14 -05:00
|
|
|
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"))
|
|
|
|
}
|
|
|
|
|
2020-10-14 17:09:09 -04:00
|
|
|
if (c.BaseImageID == "") && (c.BaseImageFilter == ListImagesRequest{}) {
|
2017-02-13 05:35:14 -05:00
|
|
|
errs = packer.MultiErrorAppend(
|
2020-10-14 17:09:09 -04:00
|
|
|
errs, errors.New("'base_image_ocid' or 'base_image_filter' must be specified"))
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.BaseImageFilter.CompartmentId == nil {
|
|
|
|
c.BaseImageFilter.CompartmentId = &c.CompartmentID
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.BaseImageFilter.Shape == nil {
|
|
|
|
c.BaseImageFilter.Shape = &c.Shape
|
2017-02-13 05:35:14 -05:00
|
|
|
}
|
|
|
|
|
2018-06-04 09:18:18 -04:00
|
|
|
// Validate tag lengths. TODO (hlowndes) maximum number of tags allowed.
|
|
|
|
if c.Tags != nil {
|
|
|
|
for k, v := range c.Tags {
|
|
|
|
k = strings.TrimSpace(k)
|
|
|
|
v = strings.TrimSpace(v)
|
|
|
|
if len(k) > 100 {
|
|
|
|
errs = packer.MultiErrorAppend(
|
|
|
|
errs, fmt.Errorf("Tag key length too long. Maximum 100 but found %d. Key: %s", len(k), k))
|
|
|
|
}
|
|
|
|
if len(k) == 0 {
|
|
|
|
errs = packer.MultiErrorAppend(
|
|
|
|
errs, errors.New("Tag key empty in config"))
|
|
|
|
}
|
|
|
|
if len(v) > 100 {
|
|
|
|
errs = packer.MultiErrorAppend(
|
|
|
|
errs, fmt.Errorf("Tag value length too long. Maximum 100 but found %d. Key: %s", len(v), k))
|
|
|
|
}
|
|
|
|
if len(v) == 0 {
|
|
|
|
errs = packer.MultiErrorAppend(
|
|
|
|
errs, errors.New("Tag value empty in config"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-13 05:35:14 -05:00
|
|
|
if c.ImageName == "" {
|
2017-08-02 05:51:08 -04:00
|
|
|
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
|
|
|
|
}
|
2017-02-13 05:35:14 -05:00
|
|
|
}
|
|
|
|
|
2018-03-28 16:27:41 -04:00
|
|
|
// 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))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-30 06:31:37 -04:00
|
|
|
// Set default boot volume size to 50 if not set
|
|
|
|
// Check if size set is allowed by OCI
|
|
|
|
if c.BootVolumeSizeInGBs == 0 {
|
|
|
|
c.BootVolumeSizeInGBs = 50
|
|
|
|
} else if c.BootVolumeSizeInGBs < 50 || c.BootVolumeSizeInGBs > 16384 {
|
|
|
|
errs = packer.MultiErrorAppend(
|
2020-10-01 02:01:51 -04:00
|
|
|
errs, errors.New("'disk_size' must be between 50 and 16384 GBs"))
|
2020-09-30 06:31:37 -04:00
|
|
|
}
|
|
|
|
|
2017-02-13 05:35:14 -05:00
|
|
|
if errs != nil && len(errs.Errors) > 0 {
|
2019-12-17 05:25:56 -05:00
|
|
|
return errs
|
2017-02-13 05:35:14 -05:00
|
|
|
}
|
|
|
|
|
2019-12-17 05:25:56 -05:00
|
|
|
return nil
|
2017-02-13 05:35:14 -05:00
|
|
|
}
|
|
|
|
|
2018-12-02 18:30:01 -05:00
|
|
|
// getDefaultOCISettingsPath uses os/user to compute the default
|
2017-09-12 11:30:39 -04:00
|
|
|
// config file location ($HOME/.oci/config).
|
|
|
|
func getDefaultOCISettingsPath() (string, error) {
|
2018-12-02 18:30:01 -05:00
|
|
|
u, err := user.Current()
|
2017-02-13 05:35:14 -05:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
2018-12-02 18:30:01 -05:00
|
|
|
if u.HomeDir == "" {
|
|
|
|
return "", fmt.Errorf("Unable to determine the home directory for the current user.")
|
|
|
|
}
|
|
|
|
|
|
|
|
path := filepath.Join(u.HomeDir, ".oci", "config")
|
2017-02-13 05:35:14 -05:00
|
|
|
if _, err := os.Stat(path); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return path, nil
|
|
|
|
}
|