diff --git a/builder/oracle/oci/artifact.go b/builder/oracle/oci/artifact.go index e237d48e4..a454ff7c7 100644 --- a/builder/oracle/oci/artifact.go +++ b/builder/oracle/oci/artifact.go @@ -3,12 +3,12 @@ package oci import ( "fmt" - client "github.com/hashicorp/packer/builder/oracle/oci/client" + "github.com/oracle/oci-go-sdk/core" ) // Artifact is an artifact implementation that contains a built Custom Image. type Artifact struct { - Image client.Image + Image core.Image Region string driver Driver } @@ -26,13 +26,18 @@ func (a *Artifact) Files() []string { // Id returns the OCID of the associated Image. func (a *Artifact) Id() string { - return a.Image.ID + return *a.Image.Id } func (a *Artifact) String() string { + var displayName string + if a.Image.DisplayName != nil { + displayName = *a.Image.DisplayName + } + return fmt.Sprintf( "An image was created: '%v' (OCID: %v) in region '%v'", - a.Image.DisplayName, a.Image.ID, a.Region, + displayName, *a.Image.Id, a.Region, ) } @@ -42,5 +47,5 @@ func (a *Artifact) State(name string) interface{} { // Destroy deletes the custom image associated with the artifact. func (a *Artifact) Destroy() error { - return a.driver.DeleteImage(a.Image.ID) + return a.driver.DeleteImage(*a.Image.Id) } diff --git a/builder/oracle/oci/builder.go b/builder/oracle/oci/builder.go index 612fdf4f6..6ceaaf2d2 100644 --- a/builder/oracle/oci/builder.go +++ b/builder/oracle/oci/builder.go @@ -7,11 +7,11 @@ import ( "log" ocommon "github.com/hashicorp/packer/builder/oracle/common" - 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/multistep" "github.com/hashicorp/packer/packer" + "github.com/oracle/oci-go-sdk/core" ) // BuilderId uniquely identifies the builder @@ -78,10 +78,15 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe return nil, rawErr.(error) } + region, err := b.config.ConfigProvider.Region() + if err != nil { + return nil, err + } + // Build the artifact and return it artifact := &Artifact{ - Image: state.Get("image").(client.Image), - Region: b.config.AccessCfg.Region, + Image: state.Get("image").(core.Image), + Region: region, driver: driver, } diff --git a/builder/oracle/oci/client/base_client.go b/builder/oracle/oci/client/base_client.go deleted file mode 100644 index 01d66215a..000000000 --- a/builder/oracle/oci/client/base_client.go +++ /dev/null @@ -1,216 +0,0 @@ -package oci - -import ( - "bytes" - "encoding/json" - "net/http" - "net/url" - - "github.com/google/go-querystring/query" -) - -const ( - contentType = "Content-Type" - jsonContentType = "application/json" -) - -// baseClient provides a basic (AND INTENTIONALLY INCOMPLETE) JSON REST client -// that abstracts away some of the repetitive code required in the OCI Client. -type baseClient struct { - httpClient *http.Client - method string - url string - queryStruct interface{} - header http.Header - body interface{} -} - -// newBaseClient constructs a default baseClient. -func newBaseClient() *baseClient { - return &baseClient{ - httpClient: http.DefaultClient, - method: "GET", - header: make(http.Header), - } -} - -// New creates a copy of an existing baseClient. -func (c *baseClient) New() *baseClient { - // Copy headers - header := make(http.Header) - for k, v := range c.header { - header[k] = v - } - - return &baseClient{ - httpClient: c.httpClient, - method: c.method, - url: c.url, - header: header, - } -} - -// Client sets the http Client used to perform requests. -func (c *baseClient) Client(httpClient *http.Client) *baseClient { - if httpClient == nil { - c.httpClient = http.DefaultClient - } else { - c.httpClient = httpClient - } - return c -} - -// Base sets the base client url. -func (c *baseClient) Base(path string) *baseClient { - c.url = path - return c -} - -// Path extends the client url. -func (c *baseClient) Path(path string) *baseClient { - baseURL, baseErr := url.Parse(c.url) - pathURL, pathErr := url.Parse(path) - // Bail on parsing error leaving the client's url unmodified - if baseErr != nil || pathErr != nil { - return c - } - - c.url = baseURL.ResolveReference(pathURL).String() - return c -} - -// QueryStruct sets the struct from which the request querystring is built. -func (c *baseClient) QueryStruct(params interface{}) *baseClient { - c.queryStruct = params - return c -} - -// SetBody wraps a given struct for serialisation and sets the client body. -func (c *baseClient) SetBody(params interface{}) *baseClient { - c.body = params - return c -} - -// Header - -// AddHeader adds a HTTP header to the client. Existing keys will be extended. -func (c *baseClient) AddHeader(key, value string) *baseClient { - c.header.Add(key, value) - return c -} - -// SetHeader sets a HTTP header on the client. Existing keys will be -// overwritten. -func (c *baseClient) SetHeader(key, value string) *baseClient { - c.header.Add(key, value) - return c -} - -// HTTP methods (subset) - -// Get sets the client's HTTP method to GET. -func (c *baseClient) Get(path string) *baseClient { - c.method = "GET" - return c.Path(path) -} - -// Post sets the client's HTTP method to POST. -func (c *baseClient) Post(path string) *baseClient { - c.method = "POST" - return c.Path(path) -} - -// Delete sets the client's HTTP method to DELETE. -func (c *baseClient) Delete(path string) *baseClient { - c.method = "DELETE" - return c.Path(path) -} - -// Do executes a HTTP request and returns the response encoded as either error -// or success values. -func (c *baseClient) Do(req *http.Request, successV, failureV interface{}) (*http.Response, error) { - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - if resp.StatusCode >= 200 && resp.StatusCode < 300 { - if successV != nil { - err = json.NewDecoder(resp.Body).Decode(successV) - } - } else { - if failureV != nil { - err = json.NewDecoder(resp.Body).Decode(failureV) - } - } - - return resp, err -} - -// Request builds a http.Request from the baseClient instance. -func (c *baseClient) Request() (*http.Request, error) { - reqURL, err := url.Parse(c.url) - if err != nil { - return nil, err - } - - if c.queryStruct != nil { - err = addQueryStruct(reqURL, c.queryStruct) - if err != nil { - return nil, err - } - } - - body := &bytes.Buffer{} - if c.body != nil { - if err := json.NewEncoder(body).Encode(c.body); err != nil { - return nil, err - } - } - - req, err := http.NewRequest(c.method, reqURL.String(), body) - if err != nil { - return nil, err - } - - // Add headers to request - for k, vs := range c.header { - for _, v := range vs { - req.Header.Add(k, v) - } - } - - return req, nil -} - -// Receive creates a http request from the client and executes it returning the -// response. -func (c *baseClient) Receive(successV, failureV interface{}) (*http.Response, error) { - req, err := c.Request() - if err != nil { - return nil, err - } - return c.Do(req, successV, failureV) -} - -// addQueryStruct converts a struct to a querystring and merges any values -// provided in the URL itself. -func addQueryStruct(reqURL *url.URL, queryStruct interface{}) error { - urlValues, err := url.ParseQuery(reqURL.RawQuery) - if err != nil { - return err - } - queryValues, err := query.Values(queryStruct) - if err != nil { - return err - } - - for k, vs := range queryValues { - for _, v := range vs { - urlValues.Add(k, v) - } - } - reqURL.RawQuery = urlValues.Encode() - return nil -} diff --git a/builder/oracle/oci/client/client.go b/builder/oracle/oci/client/client.go deleted file mode 100644 index 8e6c1762d..000000000 --- a/builder/oracle/oci/client/client.go +++ /dev/null @@ -1,31 +0,0 @@ -package oci - -import ( - "net/http" -) - -const ( - apiVersion = "20160918" - userAgent = "go-oci/" + apiVersion - baseURLPattern = "https://%s.%s.oraclecloud.com/%s/" -) - -// Client is the main interface through which consumers interact with the OCI -// API. -type Client struct { - UserAgent string - Compute *ComputeClient - Config *Config -} - -// NewClient creates a new Client for communicating with the OCI API. -func NewClient(config *Config) (*Client, error) { - transport := NewTransport(http.DefaultTransport, config) - base := newBaseClient().Client(&http.Client{Transport: transport}) - - return &Client{ - UserAgent: userAgent, - Compute: NewComputeClient(base.New().Base(config.getBaseURL("iaas"))), - Config: config, - }, nil -} diff --git a/builder/oracle/oci/client/client_test.go b/builder/oracle/oci/client/client_test.go deleted file mode 100644 index f2ebd8f87..000000000 --- a/builder/oracle/oci/client/client_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package oci - -import ( - "net/http" - "net/http/httptest" - "net/url" - "os" - - "github.com/go-ini/ini" -) - -var ( - mux *http.ServeMux - client *Client - server *httptest.Server - keyFile *os.File -) - -// setup sets up a test HTTP server along with a oci.Client that is -// configured to talk to that test server. Tests should register handlers on -// mux which provide mock responses for the API method being tested. -func setup() { - mux = http.NewServeMux() - server = httptest.NewServer(mux) - parsedURL, _ := url.Parse(server.URL) - - config := &Config{} - config.baseURL = parsedURL.String() - - var cfg *ini.File - var err error - cfg, keyFile, err = BaseTestConfig() - - config, err = loadConfigSection(cfg, "DEFAULT", config) - if err != nil { - panic(err) - } - - client, err = NewClient(config) - if err != nil { - panic("Failed to instantiate test client") - } -} - -// teardown closes the test HTTP server -func teardown() { - server.Close() - os.Remove(keyFile.Name()) -} diff --git a/builder/oracle/oci/client/compute.go b/builder/oracle/oci/client/compute.go deleted file mode 100644 index 183a3794f..000000000 --- a/builder/oracle/oci/client/compute.go +++ /dev/null @@ -1,21 +0,0 @@ -package oci - -// ComputeClient is a client for the OCI Compute API. -type ComputeClient struct { - BaseURL string - Instances *InstanceService - Images *ImageService - VNICAttachments *VNICAttachmentService - VNICs *VNICService -} - -// NewComputeClient creates a new client for communicating with the OCI -// Compute API. -func NewComputeClient(s *baseClient) *ComputeClient { - return &ComputeClient{ - Instances: NewInstanceService(s), - Images: NewImageService(s), - VNICAttachments: NewVNICAttachmentService(s), - VNICs: NewVNICService(s), - } -} diff --git a/builder/oracle/oci/client/config.go b/builder/oracle/oci/client/config.go deleted file mode 100644 index 4df6b6a01..000000000 --- a/builder/oracle/oci/client/config.go +++ /dev/null @@ -1,240 +0,0 @@ -package oci - -import ( - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "encoding/pem" - "errors" - "fmt" - "io/ioutil" - "os" - - "github.com/go-ini/ini" - "github.com/mitchellh/go-homedir" -) - -// Config API authentication and target configuration -type Config struct { - // User OCID e.g. ocid1.user.oc1..aaaaaaaadcshyehbkvxl7arse3lv7z5oknexjgfhnhwidtugsxhlm4247 - User string `ini:"user"` - - // User's Tenancy OCID e.g. ocid1.tenancy.oc1..aaaaaaaagtgvshv6opxzjyzkupkt64ymd32n6kbomadanpcg43d - Tenancy string `ini:"tenancy"` - - // Bare metal region identifier (e.g. us-phoenix-1) - Region string `ini:"region"` - - // Hex key fingerprint (e.g. b5:a0:62:57:28:0d:fd:c9:59:16:eb:d4:51:9f:70:e4) - Fingerprint string `ini:"fingerprint"` - - // Path to OCI config file (e.g. ~/.oci/config) - KeyFile string `ini:"key_file"` - - // Passphrase used for the key, if it is encrypted. - PassPhrase string `ini:"pass_phrase"` - - // Private key (loaded via LoadPrivateKey or ParsePrivateKey) - Key *rsa.PrivateKey - - // Used to override base API URL. - baseURL string -} - -// getBaseURL returns either the specified base URL or builds the appropriate -// URL based on service, region, and API version. -func (c *Config) getBaseURL(service string) string { - if c.baseURL != "" { - return c.baseURL - } - return fmt.Sprintf(baseURLPattern, service, c.Region, apiVersion) -} - -// LoadConfigsFromFile loads all oracle oci configurations from a file -// (generally ~/.oci/config). -func LoadConfigsFromFile(path string) (map[string]*Config, error) { - if _, err := os.Stat(path); err != nil { - return nil, fmt.Errorf("Oracle OCI config file is missing: %s", path) - } - - cfgFile, err := ini.Load(path) - if err != nil { - err := fmt.Errorf("Failed to parse config file %s: %s", path, err.Error()) - return nil, err - } - - configs := make(map[string]*Config) - - // Load DEFAULT section to populate defaults for all other configs - config, err := loadConfigSection(cfgFile, "DEFAULT", nil) - if err != nil { - return nil, err - } - configs["DEFAULT"] = config - - // Load other sections. - for _, sectionName := range cfgFile.SectionStrings() { - if sectionName == "DEFAULT" { - continue - } - - // Map to Config struct with defaults from DEFAULT section. - config, err := loadConfigSection(cfgFile, sectionName, configs["DEFAULT"]) - if err != nil { - return nil, err - } - configs[sectionName] = config - } - - return configs, nil -} - -// Loads an individual Config object from a ini.Section in the Oracle OCI config -// file. -func loadConfigSection(f *ini.File, sectionName string, config *Config) (*Config, error) { - if config == nil { - config = &Config{} - } - - section, err := f.GetSection(sectionName) - if err != nil { - return nil, fmt.Errorf("Config file does not contain a %s section", sectionName) - } - - if err := section.MapTo(config); err != nil { - return nil, err - } - - config.Key, err = LoadPrivateKey(config) - if err != nil { - return nil, err - } - - return config, err -} - -// LoadPrivateKey loads private key from disk and parses it. -func LoadPrivateKey(config *Config) (*rsa.PrivateKey, error) { - // Expand '~' to $HOME - path, err := homedir.Expand(config.KeyFile) - if err != nil { - return nil, err - } - - // Read and parse API signing key - keyContent, err := ioutil.ReadFile(path) - if err != nil { - return nil, err - } - key, err := ParsePrivateKey(keyContent, []byte(config.PassPhrase)) - - return key, err -} - -// ParsePrivateKey parses a PEM encoded array of bytes into an rsa.PrivateKey. -// Attempts to decrypt the PEM encoded array of bytes with the given password -// if the PEM encoded byte array is encrypted. -func ParsePrivateKey(content, password []byte) (*rsa.PrivateKey, error) { - keyBlock, _ := pem.Decode(content) - - if keyBlock == nil { - return nil, errors.New("could not decode PEM private key") - } - - var der []byte - var err error - if x509.IsEncryptedPEMBlock(keyBlock) { - if len(password) < 1 { - return nil, errors.New("encrypted private key but no pass phrase provided") - } - der, err = x509.DecryptPEMBlock(keyBlock, password) - if err != nil { - return nil, err - } - } else { - der = keyBlock.Bytes - } - - if key, err := x509.ParsePKCS1PrivateKey(der); err == nil { - return key, nil - } - - key, err := x509.ParsePKCS8PrivateKey(der) - if err == nil { - switch key := key.(type) { - case *rsa.PrivateKey: - return key, nil - default: - return nil, errors.New("Private key is not an RSA private key") - } - } - return nil, fmt.Errorf("Failed to parse private key :%s", err) -} - -// BaseTestConfig creates the base (DEFAULT) config including a temporary key -// file. -// NOTE: Caller is responsible for removing temporary key file. -func BaseTestConfig() (*ini.File, *os.File, error) { - keyFile, err := generateRSAKeyFile() - if err != nil { - return nil, keyFile, err - } - // Build ini - cfg := ini.Empty() - section, _ := cfg.NewSection("DEFAULT") - section.NewKey("region", "us-ashburn-1") - section.NewKey("tenancy", "ocid1.tenancy.oc1..aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") - section.NewKey("user", "ocid1.user.oc1..aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") - section.NewKey("fingerprint", "3c:b6:44:d7:49:1a:ac:bf:de:7d:76:22:a7:f5:df:55") - section.NewKey("key_file", keyFile.Name()) - - return cfg, keyFile, nil -} - -// WriteTestConfig writes a ini.File to a temporary file for use in unit tests. -// NOTE: Caller is responsible for removing temporary file. -func WriteTestConfig(cfg *ini.File) (*os.File, error) { - confFile, err := ioutil.TempFile("", "config_file") - if err != nil { - return nil, err - } - - _, err = cfg.WriteTo(confFile) - if err != nil { - os.Remove(confFile.Name()) - return nil, err - } - - return confFile, nil -} - -// generateRSAKeyFile generates an RSA key file for use in unit tests. -// NOTE: The caller is responsible for deleting the temporary file. -func generateRSAKeyFile() (*os.File, error) { - // Create temporary file for the key - f, err := ioutil.TempFile("", "key") - if err != nil { - return nil, err - } - - // Generate key - priv, err := rsa.GenerateKey(rand.Reader, 2014) - if err != nil { - return nil, err - } - - // ASN.1 DER encoded form - privDer := x509.MarshalPKCS1PrivateKey(priv) - privBlk := pem.Block{ - Type: "RSA PRIVATE KEY", - Headers: nil, - Bytes: privDer, - } - - // Write the key out - if _, err := f.Write(pem.EncodeToMemory(&privBlk)); err != nil { - return nil, err - } - - return f, nil -} diff --git a/builder/oracle/oci/client/config_test.go b/builder/oracle/oci/client/config_test.go deleted file mode 100644 index 1be34c5f0..000000000 --- a/builder/oracle/oci/client/config_test.go +++ /dev/null @@ -1,283 +0,0 @@ -package oci - -import ( - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "encoding/asn1" - "encoding/pem" - "os" - "reflect" - "strings" - "testing" -) - -func TestNewConfigMissingFile(t *testing.T) { - // WHEN - _, err := LoadConfigsFromFile("some/invalid/path") - - // THEN - - if err == nil { - t.Error("Expected missing file error") - } -} - -func TestNewConfigDefaultOnly(t *testing.T) { - // GIVEN - - // Get DEFAULT config - cfg, keyFile, err := BaseTestConfig() - defer os.Remove(keyFile.Name()) - - // Write test config to file - f, err := WriteTestConfig(cfg) - if err != nil { - t.Fatal(err) - } - defer os.Remove(f.Name()) // clean up - - // WHEN - - // Load configs - cfgs, err := LoadConfigsFromFile(f.Name()) - if err != nil { - t.Fatal(err) - } - - // THEN - - if _, ok := cfgs["DEFAULT"]; !ok { - t.Fatal("Expected DEFAULT config to exist in map") - } -} - -func TestNewConfigDefaultsPopulated(t *testing.T) { - // GIVEN - - // Get DEFAULT config - cfg, keyFile, err := BaseTestConfig() - defer os.Remove(keyFile.Name()) - - admin := cfg.Section("ADMIN") - admin.NewKey("user", "ocid1.user.oc1..bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb") - admin.NewKey("fingerprint", "11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11") - - // Write test config to file - f, err := WriteTestConfig(cfg) - if err != nil { - t.Fatal(err) - } - defer os.Remove(f.Name()) // clean up - - // WHEN - - cfgs, err := LoadConfigsFromFile(f.Name()) - adminConfig, ok := cfgs["ADMIN"] - - // THEN - - if !ok { - t.Fatal("Expected ADMIN config to exist in map") - } - - if adminConfig.Region != "us-ashburn-1" { - t.Errorf("Expected 'us-ashburn-1', got '%s'", adminConfig.Region) - } -} - -func TestNewConfigDefaultsOverridden(t *testing.T) { - // GIVEN - - // Get DEFAULT config - cfg, keyFile, err := BaseTestConfig() - defer os.Remove(keyFile.Name()) - - admin := cfg.Section("ADMIN") - admin.NewKey("user", "ocid1.user.oc1..bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb") - admin.NewKey("fingerprint", "11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11") - - // Write test config to file - f, err := WriteTestConfig(cfg) - if err != nil { - t.Fatal(err) - } - defer os.Remove(f.Name()) // clean up - - // WHEN - - cfgs, err := LoadConfigsFromFile(f.Name()) - adminConfig, ok := cfgs["ADMIN"] - - // THEN - - if !ok { - t.Fatal("Expected ADMIN config to exist in map") - } - - if adminConfig.Fingerprint != "11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11" { - t.Errorf("Expected fingerprint '11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11', got '%s'", - adminConfig.Fingerprint) - } -} - -func TestParseEncryptedPrivateKeyValidPassword(t *testing.T) { - // Generate private key - priv, err := rsa.GenerateKey(rand.Reader, 2014) - if err != nil { - t.Fatalf("Unexpected generating RSA key: %+v", err) - } - publicKey := priv.PublicKey - - // ASN.1 DER encoded form - privDer := x509.MarshalPKCS1PrivateKey(priv) - - blockType := "RSA PRIVATE KEY" - password := []byte("password") - cipherType := x509.PEMCipherAES256 - - // Encrypt priv with password - encryptedPEMBlock, err := x509.EncryptPEMBlock( - rand.Reader, - blockType, - privDer, - password, - cipherType) - if err != nil { - t.Fatalf("Unexpected error encrypting PEM block: %+v", err) - } - - // Parse private key - key, err := ParsePrivateKey(pem.EncodeToMemory(encryptedPEMBlock), password) - if err != nil { - t.Fatalf("unexpected error: %+v", err) - } - - // Check we get the same key back - if !reflect.DeepEqual(publicKey, key.PublicKey) { - t.Errorf("expected public key of encrypted and decrypted key to match") - } -} - -func TestParseEncryptedPrivateKeyPKCS8(t *testing.T) { - // Generate private key - priv, err := rsa.GenerateKey(rand.Reader, 2014) - if err != nil { - t.Fatalf("Unexpected generating RSA key: %+v", err) - } - publicKey := priv.PublicKey - - // Implements x509.MarshalPKCS8PrivateKey which is not included in the - // standard library. - pkey := struct { - Version int - PrivateKeyAlgorithm []asn1.ObjectIdentifier - PrivateKey []byte - }{ - Version: 0, - PrivateKeyAlgorithm: []asn1.ObjectIdentifier{{1, 2, 840, 113549, 1, 1, 1}}, - PrivateKey: x509.MarshalPKCS1PrivateKey(priv), - } - privDer, err := asn1.Marshal(pkey) - if err != nil { - t.Fatalf("Unexpected marshaling RSA key: %+v", err) - } - - blockType := "RSA PRIVATE KEY" - password := []byte("password") - cipherType := x509.PEMCipherAES256 - - // Encrypt priv with password - encryptedPEMBlock, err := x509.EncryptPEMBlock( - rand.Reader, - blockType, - privDer, - password, - cipherType) - if err != nil { - t.Fatalf("Unexpected error encrypting PEM block: %+v", err) - } - - // Parse private key - key, err := ParsePrivateKey(pem.EncodeToMemory(encryptedPEMBlock), password) - if err != nil { - t.Fatalf("unexpected error: %+v", err) - } - - // Check we get the same key back - if !reflect.DeepEqual(publicKey, key.PublicKey) { - t.Errorf("expected public key of encrypted and decrypted key to match") - } -} - -func TestParseEncryptedPrivateKeyInvalidPassword(t *testing.T) { - // Generate private key - priv, err := rsa.GenerateKey(rand.Reader, 2014) - if err != nil { - t.Fatalf("Unexpected generating RSA key: %+v", err) - } - - // ASN.1 DER encoded form - privDer := x509.MarshalPKCS1PrivateKey(priv) - - blockType := "RSA PRIVATE KEY" - password := []byte("password") - cipherType := x509.PEMCipherAES256 - - // Encrypt priv with password - encryptedPEMBlock, err := x509.EncryptPEMBlock( - rand.Reader, - blockType, - privDer, - password, - cipherType) - if err != nil { - t.Fatalf("Unexpected error encrypting PEM block: %+v", err) - } - - // Parse private key (with wrong password) - _, err = ParsePrivateKey(pem.EncodeToMemory(encryptedPEMBlock), []byte("foo")) - if err == nil { - t.Fatalf("Expected error, got nil") - } - - if !strings.Contains(err.Error(), "decryption password incorrect") { - t.Errorf("Expected error to contain 'decryption password incorrect', got %+v", err) - } -} - -func TestParseEncryptedPrivateKeyInvalidNoPassword(t *testing.T) { - // Generate private key - priv, err := rsa.GenerateKey(rand.Reader, 2014) - if err != nil { - t.Fatalf("Unexpected generating RSA key: %+v", err) - } - - // ASN.1 DER encoded form - privDer := x509.MarshalPKCS1PrivateKey(priv) - - blockType := "RSA PRIVATE KEY" - password := []byte("password") - cipherType := x509.PEMCipherAES256 - - // Encrypt priv with password - encryptedPEMBlock, err := x509.EncryptPEMBlock( - rand.Reader, - blockType, - privDer, - password, - cipherType) - if err != nil { - t.Fatalf("Unexpected error encrypting PEM block: %+v", err) - } - - // Parse private key (with wrong password) - _, err = ParsePrivateKey(pem.EncodeToMemory(encryptedPEMBlock), []byte{}) - if err == nil { - t.Fatalf("Expected error, got nil") - } - - if !strings.Contains(err.Error(), "no pass phrase provided") { - t.Errorf("Expected error to contain 'no pass phrase provided', got %+v", err) - } -} diff --git a/builder/oracle/oci/client/error.go b/builder/oracle/oci/client/error.go deleted file mode 100644 index a97112f81..000000000 --- a/builder/oracle/oci/client/error.go +++ /dev/null @@ -1,27 +0,0 @@ -package oci - -import "fmt" - -// APIError encapsulates an error returned from the API -type APIError struct { - Code string `json:"code"` - Message string `json:"message"` -} - -func (e APIError) Error() string { - return fmt.Sprintf("OCI: [%s] '%s'", e.Code, e.Message) -} - -// firstError is a helper function to work out which error to return from calls -// to the API. -func firstError(err error, apiError *APIError) error { - if err != nil { - return err - } - - if apiError != nil && len(apiError.Code) > 0 { - return apiError - } - - return nil -} diff --git a/builder/oracle/oci/client/image.go b/builder/oracle/oci/client/image.go deleted file mode 100644 index 67ada02d5..000000000 --- a/builder/oracle/oci/client/image.go +++ /dev/null @@ -1,122 +0,0 @@ -package oci - -import ( - "time" -) - -// ImageService enables communicating with the OCI compute API's instance -// related endpoints. -type ImageService struct { - client *baseClient -} - -// NewImageService creates a new ImageService for communicating with the -// OCI compute API's instance related endpoints. -func NewImageService(s *baseClient) *ImageService { - return &ImageService{ - client: s.New().Path("images/"), - } -} - -// Image details a OCI boot disk image. -type Image struct { - // The OCID of the image originally used to launch the instance. - BaseImageID string `json:"baseImageId,omitempty"` - - // The OCID of the compartment containing the instance you want to use - // as the basis for the image. - CompartmentID string `json:"compartmentId"` - - // Whether instances launched with this image can be used to create new - // images. - CreateImageAllowed bool `json:"createImageAllowed"` - - // A user-friendly name for the image. It does not have to be unique, - // and it's changeable. You cannot use an Oracle-provided image name - // as a custom image name. - DisplayName string `json:"displayName,omitempty"` - - // The OCID of the image. - ID string `json:"id"` - - // Current state of the image. Allowed values are: - // - PROVISIONING - // - AVAILABLE - // - DISABLED - // - DELETED - LifecycleState string `json:"lifecycleState"` - - // The image's operating system (e.g. Oracle Linux). - OperatingSystem string `json:"operatingSystem"` - - // The image's operating system version (e.g. 7.2). - OperatingSystemVersion string `json:"operatingSystemVersion"` - - // The date and time the image was created. - TimeCreated time.Time `json:"timeCreated"` -} - -// GetImageParams are the parameters available when communicating with the -// GetImage API endpoint. -type GetImageParams struct { - ID string `url:"imageId"` -} - -// Get returns a single Image -func (s *ImageService) Get(params *GetImageParams) (Image, error) { - image := Image{} - e := &APIError{} - - _, err := s.client.New().Get(params.ID).Receive(&image, e) - err = firstError(err, e) - - return image, err -} - -// CreateImageParams are the parameters available when communicating with -// the CreateImage API endpoint. -type CreateImageParams struct { - CompartmentID string `json:"compartmentId"` - DisplayName string `json:"displayName,omitempty"` - InstanceID string `json:"instanceId"` -} - -// Create creates a new custom image based on a running compute instance. It -// does *not* wait for the imaging process to finish. -func (s *ImageService) Create(params *CreateImageParams) (Image, error) { - image := Image{} - e := &APIError{} - - _, err := s.client.New().Post("").SetBody(params).Receive(&image, &e) - err = firstError(err, e) - - return image, err -} - -// GetResourceState GETs the LifecycleState of the given image id. -func (s *ImageService) GetResourceState(id string) (string, error) { - image, err := s.Get(&GetImageParams{ID: id}) - if err != nil { - return "", err - } - return image.LifecycleState, nil - -} - -// DeleteImageParams are the parameters available when communicating with -// the DeleteImage API endpoint. -type DeleteImageParams struct { - ID string `url:"imageId"` -} - -// Delete deletes an existing custom image. -// NOTE: Deleting an image results in the API endpoint returning 404 on -// subsequent calls. As such deletion can't be waited on with a Waiter. -func (s *ImageService) Delete(params *DeleteImageParams) error { - e := &APIError{} - - _, err := s.client.New().Delete(params.ID).SetBody(params).Receive(nil, e) - err = firstError(err, e) - - return err -} diff --git a/builder/oracle/oci/client/image_test.go b/builder/oracle/oci/client/image_test.go deleted file mode 100644 index ef55cc552..000000000 --- a/builder/oracle/oci/client/image_test.go +++ /dev/null @@ -1,115 +0,0 @@ -package oci - -import ( - "fmt" - "net/http" - "reflect" - "testing" -) - -func TestGetImage(t *testing.T) { - setup() - defer teardown() - - id := "ocid1.image.oc1.phx.a" - path := fmt.Sprintf("/images/%s", id) - mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, `{"id":"%s"}`, id) - }) - - image, err := client.Compute.Images.Get(&GetImageParams{ID: id}) - if err != nil { - t.Errorf("Client.Compute.Images.Get() returned error: %v", err) - } - - want := Image{ID: id} - - if !reflect.DeepEqual(image, want) { - t.Errorf("Client.Compute.Images.Get() returned %+v, want %+v", image, want) - } -} - -func TestCreateImage(t *testing.T) { - setup() - defer teardown() - - mux.HandleFunc("/images/", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, `{"displayName": "go-oci test"}`) - }) - - params := &CreateImageParams{ - CompartmentID: "ocid1.compartment.oc1..a", - DisplayName: "go-oci test image", - InstanceID: "ocid1.image.oc1.phx.a", - } - - image, err := client.Compute.Images.Create(params) - if err != nil { - t.Errorf("Client.Compute.Images.Create() returned error: %v", err) - } - - want := Image{DisplayName: "go-oci test"} - - if !reflect.DeepEqual(image, want) { - t.Errorf("Client.Compute.Images.Create() returned %+v, want %+v", image, want) - } -} - -func TestImageGetResourceState(t *testing.T) { - setup() - defer teardown() - - id := "ocid1.image.oc1.phx.a" - path := fmt.Sprintf("/images/%s", id) - mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, `{"LifecycleState": "AVAILABLE"}`) - }) - - state, err := client.Compute.Images.GetResourceState(id) - if err != nil { - t.Errorf("Client.Compute.Images.GetResourceState() returned error: %v", err) - } - - want := "AVAILABLE" - if state != want { - t.Errorf("Client.Compute.Images.GetResourceState() returned %+v, want %+v", state, want) - } -} - -func TestImageGetResourceStateInvalidID(t *testing.T) { - setup() - defer teardown() - - id := "ocid1.image.oc1.phx.a" - path := fmt.Sprintf("/images/%s", id) - mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotFound) - fmt.Fprint(w, `{"code": "NotAuthorizedOrNotFound"}`) - }) - - state, err := client.Compute.Images.GetResourceState(id) - if err == nil { - t.Errorf("Client.Compute.Images.GetResourceState() expected error, got %v", state) - } - - want := &APIError{Code: "NotAuthorizedOrNotFound"} - if !reflect.DeepEqual(err, want) { - t.Errorf("Client.Compute.Images.GetResourceState() errored with %+v, want %+v", err, want) - } -} - -func TestDeleteInstance(t *testing.T) { - setup() - defer teardown() - - id := "ocid1.image.oc1.phx.a" - path := fmt.Sprintf("/images/%s", id) - mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNoContent) - }) - - err := client.Compute.Images.Delete(&DeleteImageParams{ID: id}) - if err != nil { - t.Errorf("Client.Compute.Images.Delete() returned error: %v", err) - } -} diff --git a/builder/oracle/oci/client/instance.go b/builder/oracle/oci/client/instance.go deleted file mode 100644 index e81ad3a44..000000000 --- a/builder/oracle/oci/client/instance.go +++ /dev/null @@ -1,129 +0,0 @@ -package oci - -import ( - "time" -) - -// InstanceService enables communicating with the OCI compute API's instance -// related endpoints. -type InstanceService struct { - client *baseClient -} - -// NewInstanceService creates a new InstanceService for communicating with the -// OCI compute API's instance related endpoints. -func NewInstanceService(s *baseClient) *InstanceService { - return &InstanceService{ - client: s.New().Path("instances/"), - } -} - -// Instance details a OCI compute instance. -type Instance struct { - // The Availability Domain the instance is running in. - AvailabilityDomain string `json:"availabilityDomain"` - - // The OCID of the compartment that contains the instance. - CompartmentID string `json:"compartmentId"` - - // A user-friendly name. Does not have to be unique, and it's changeable. - DisplayName string `json:"displayName,omitempty"` - - // The OCID of the instance. - ID string `json:"id"` - - // The image used to boot the instance. - ImageID string `json:"imageId,omitempty"` - - // The current state of the instance. Allowed values: - // - PROVISIONING - // - RUNNING - // - STARTING - // - STOPPING - // - STOPPED - // - CREATING_IMAGE - // - TERMINATING - // - TERMINATED - LifecycleState string `json:"lifecycleState"` - - // Custom metadata that you provide. - Metadata map[string]string `json:"metadata,omitempty"` - - // The region that contains the Availability Domain the instance is running in. - Region string `json:"region"` - - // The shape of the instance. The shape determines the number of CPUs - // and the amount of memory allocated to the instance. - Shape string `json:"shape"` - - // The date and time the instance was created. - TimeCreated time.Time `json:"timeCreated"` -} - -// GetInstanceParams are the parameters available when communicating with the -// GetInstance API endpoint. -type GetInstanceParams struct { - ID string `url:"instanceId,omitempty"` -} - -// Get returns a single Instance -func (s *InstanceService) Get(params *GetInstanceParams) (Instance, error) { - instance := Instance{} - e := &APIError{} - - _, err := s.client.New().Get(params.ID).Receive(&instance, e) - err = firstError(err, e) - - return instance, err -} - -// LaunchInstanceParams are the parameters available when communicating with -// the LunchInstance API endpoint. -type LaunchInstanceParams struct { - AvailabilityDomain string `json:"availabilityDomain,omitempty"` - CompartmentID string `json:"compartmentId,omitempty"` - DisplayName string `json:"displayName,omitempty"` - ImageID string `json:"imageId,omitempty"` - Metadata map[string]string `json:"metadata,omitempty"` - OPCiPXEScript string `json:"opcIpxeScript,omitempty"` - Shape string `json:"shape,omitempty"` - SubnetID string `json:"subnetId,omitempty"` -} - -// Launch creates a new OCI compute instance. It does *not* wait for the -// instance to boot. -func (s *InstanceService) Launch(params *LaunchInstanceParams) (Instance, error) { - instance := &Instance{} - e := &APIError{} - - _, err := s.client.New().Post("").SetBody(params).Receive(instance, e) - err = firstError(err, e) - - return *instance, err -} - -// TerminateInstanceParams are the parameters available when communicating with -// the TerminateInstance API endpoint. -type TerminateInstanceParams struct { - ID string `url:"instanceId,omitempty"` -} - -// Terminate terminates a running OCI compute instance. -// instance to boot. -func (s *InstanceService) Terminate(params *TerminateInstanceParams) error { - e := &APIError{} - - _, err := s.client.New().Delete(params.ID).SetBody(params).Receive(nil, e) - err = firstError(err, e) - - return err -} - -// GetResourceState GETs the LifecycleState of the given instance id. -func (s *InstanceService) GetResourceState(id string) (string, error) { - instance, err := s.Get(&GetInstanceParams{ID: id}) - if err != nil { - return "", err - } - return instance.LifecycleState, nil -} diff --git a/builder/oracle/oci/client/instance_test.go b/builder/oracle/oci/client/instance_test.go deleted file mode 100644 index e45b707af..000000000 --- a/builder/oracle/oci/client/instance_test.go +++ /dev/null @@ -1,96 +0,0 @@ -package oci - -import ( - "fmt" - "net/http" - "reflect" - "testing" -) - -func TestGetInstance(t *testing.T) { - setup() - defer teardown() - - id := "ocid1.instance.oc1.phx.a" - path := fmt.Sprintf("/instances/%s", id) - mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, `{"id":"%s"}`, id) - }) - - instance, err := client.Compute.Instances.Get(&GetInstanceParams{ID: id}) - if err != nil { - t.Errorf("Client.Compute.Instances.Get() returned error: %v", err) - } - - want := Instance{ID: id} - - if !reflect.DeepEqual(instance, want) { - t.Errorf("Client.Compute.Instances.Get() returned %+v, want %+v", instance, want) - } -} - -func TestLaunchInstance(t *testing.T) { - setup() - defer teardown() - - mux.HandleFunc("/instances/", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, `{"displayName": "go-oci test"}`) - }) - - params := &LaunchInstanceParams{ - AvailabilityDomain: "aaaa:PHX-AD-1", - CompartmentID: "ocid1.compartment.oc1..a", - DisplayName: "go-oci test", - ImageID: "ocid1.image.oc1.phx.a", - Shape: "VM.Standard1.1", - SubnetID: "ocid1.subnet.oc1.phx.a", - } - - instance, err := client.Compute.Instances.Launch(params) - if err != nil { - t.Errorf("Client.Compute.Instances.Launch() returned error: %v", err) - } - - want := Instance{DisplayName: "go-oci test"} - - if !reflect.DeepEqual(instance, want) { - t.Errorf("Client.Compute.Instances.Launch() returned %+v, want %+v", instance, want) - } -} - -func TestTerminateInstance(t *testing.T) { - setup() - defer teardown() - - id := "ocid1.instance.oc1.phx.a" - path := fmt.Sprintf("/instances/%s", id) - mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNoContent) - }) - - err := client.Compute.Instances.Terminate(&TerminateInstanceParams{ID: id}) - if err != nil { - t.Errorf("Client.Compute.Instances.Terminate() returned error: %v", err) - } -} - -func TestInstanceGetResourceState(t *testing.T) { - setup() - defer teardown() - - id := "ocid1.instance.oc1.phx.a" - path := fmt.Sprintf("/instances/%s", id) - mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, `{"LifecycleState": "RUNNING"}`) - }) - - state, err := client.Compute.Instances.GetResourceState(id) - if err != nil { - t.Errorf("Client.Compute.Instances.GetResourceState() returned error: %v", err) - } - - want := "RUNNING" - if state != want { - t.Errorf("Client.Compute.Instances.GetResourceState() returned %+v, want %+v", state, want) - } -} diff --git a/builder/oracle/oci/client/transport.go b/builder/oracle/oci/client/transport.go deleted file mode 100644 index 79da20509..000000000 --- a/builder/oracle/oci/client/transport.go +++ /dev/null @@ -1,116 +0,0 @@ -package oci - -import ( - "bytes" - "crypto" - "crypto/rand" - "crypto/rsa" - "crypto/sha256" - "encoding/base64" - "fmt" - "io" - "net/http" - "strconv" - "strings" - "time" -) - -type nopCloser struct { - io.Reader -} - -func (nopCloser) Close() error { - return nil -} - -// Transport adds OCI signature authentication to each outgoing request. -type Transport struct { - transport http.RoundTripper - config *Config -} - -// NewTransport creates a new Transport to add OCI signature authentication -// to each outgoing request. -func NewTransport(transport http.RoundTripper, config *Config) *Transport { - return &Transport{ - transport: transport, - config: config, - } -} - -func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) { - var buf *bytes.Buffer - - if req.Body != nil { - buf = new(bytes.Buffer) - buf.ReadFrom(req.Body) - req.Body = nopCloser{buf} - } - if req.Header.Get("date") == "" { - req.Header.Set("date", time.Now().UTC().Format(http.TimeFormat)) - } - if req.Header.Get("content-type") == "" { - req.Header.Set("content-type", "application/json") - } - if req.Header.Get("accept") == "" { - req.Header.Set("accept", "application/json") - } - if req.Header.Get("host") == "" { - req.Header.Set("host", req.URL.Host) - } - - var signheaders []string - if (req.Method == "PUT" || req.Method == "POST") && buf != nil { - signheaders = []string{"(request-target)", "host", "date", - "content-length", "content-type", "x-content-sha256"} - - if req.Header.Get("content-length") == "" { - req.Header.Set("content-length", strconv.Itoa(buf.Len())) - } - - hasher := sha256.New() - hasher.Write(buf.Bytes()) - hash := hasher.Sum(nil) - req.Header.Set("x-content-sha256", base64.StdEncoding.EncodeToString(hash)) - } else { - signheaders = []string{"date", "host", "(request-target)"} - } - - var signbuffer bytes.Buffer - for idx, header := range signheaders { - signbuffer.WriteString(header) - signbuffer.WriteString(": ") - - if header == "(request-target)" { - signbuffer.WriteString(strings.ToLower(req.Method)) - signbuffer.WriteString(" ") - signbuffer.WriteString(req.URL.RequestURI()) - } else { - signbuffer.WriteString(req.Header.Get(header)) - } - - if idx < len(signheaders)-1 { - signbuffer.WriteString("\n") - } - } - - h := sha256.New() - h.Write(signbuffer.Bytes()) - digest := h.Sum(nil) - signature, err := rsa.SignPKCS1v15(rand.Reader, t.config.Key, crypto.SHA256, digest) - if err != nil { - return nil, err - } - - authHeader := fmt.Sprintf("Signature headers=\"%s\","+ - "keyId=\"%s/%s/%s\","+ - "algorithm=\"rsa-sha256\","+ - "signature=\"%s\","+ - "version=\"1\"", - strings.Join(signheaders, " "), - t.config.Tenancy, t.config.User, t.config.Fingerprint, - base64.StdEncoding.EncodeToString(signature)) - req.Header.Add("Authorization", authHeader) - - return t.transport.RoundTrip(req) -} diff --git a/builder/oracle/oci/client/transport_test.go b/builder/oracle/oci/client/transport_test.go deleted file mode 100644 index ebf02040b..000000000 --- a/builder/oracle/oci/client/transport_test.go +++ /dev/null @@ -1,156 +0,0 @@ -package oci - -import ( - "net/http" - "strings" - "testing" -) - -const testKey = `-----BEGIN RSA PRIVATE KEY----- -MIIEpQIBAAKCAQEAyLnyzmYj0dZuDo2nclIdEyLZrFZLtw5xFldWpCUl5W3SxKDL -iIgwxpSO75Yf++Rzqc5j6S5bpIrdca6AwVXCNmjjxMPO7vLLm4l4IUOZMv5FqKaC -I2plFz9uBkzGscnYnMbsDA430082E07lYpNv1xy8JwpbrIsqIMh4XCKci/Od5sLR -kEicmOpQK42FGRTQjjmQoWtv+9XED+vYTRL0AxQEC/6i/E7yssFXZ+fpHSKWeKTQ -K/1Fc4pZ1zNzJcDXGuweISx/QMLz78TAPH5OBq/EQzHKSpKvfnWFRyBHne8pwuN8 -8wzbbD+/7OFjz28jNSERVJvfYe3X1k69IWMNGwIDAQABAoIBAQCZhcdU38AzxSrG -DMfuYymDslsEObiNWQlbig9lWlhCwx26cDVbxrZvm747NvpdgVyJmqbF+UP0dJVs -Voh51qrFTLIwk4bZMXBTFPCBmJ865knG9RuCFOUew8/WF7C82GHJf0eY7OL7xpDY -cbZ2D8gxofOydHSrYoElM88CwSI00xPCbBKEMrBO94oXC8yfp2bmV6bNhVXwFDEM -qda7M6jVAsBrTOzxUF5VdUUU/MLsu2cCk/ap1zer2Bml9Afk1bMeGJ3XDQgol0pS -CLxaGczpSNVMF9+pjA5sFHR5rmcl0b/kC9HsgOJGhLOimtS94O64dSdWifgsjf6+ -fhT2SMiRAoGBAOUDwkdzNqQfvS+qrP2ULpB4vt7MZ70rDGmyMDLZ1VWgcm2cDIad -b7MkFG6GCa48mKkFXK9mOPyq8ELoTjZo2p+relEqf49BpaNxr+cp11pX7g0AkzCa -a8LwdOOUW/poqYl2xLuw9Rz6ky6ybzatMvCwpQCwnbAdABIVxz4oQKHpAoGBAOBg -3uYC/ynGdF9gJTfdS5XPYoLcKKRRspBZrvvDHaWyBnanm5KYwDAZPzLQMqcpyPeo -5xgwMmtNlc6lKKyGkhSLNCV+eO3yAx1h/cq7ityvMS7u6o5sq+/bvtEnbUPYbEtk -AhVD7/w5Yyzzi4beiQxDKe0q1mvUAH56aGqJivBjAoGBALmUMTPbDhUzTwg4Y1Rd -ZtpVrj43H31wS+++oEYktTZc/T0LLi9Llr9w5kmlvmR94CtfF/ted6FwF5/wRajb -kQXAXC83pAR/au0mbCeDhWpFRLculxfUmqxuVBozF9G0TGYDY2rA+++OsgQuPebt -tRDL4/nKJQ4Ygf0lvr4EulM5AoGBALoIdyabu3WmfhwJujH8P8wA+ztmUCgVOIio -YwWIe49C8Er2om1ESqxWcmit6CFi6qY0Gw6Z/2OqGxgPJY8NsBZqaBziJF+cdWqq -MWMiZXqdopi4LC9T+KZROn9tQhGrYfaL/5IkFti3t/uwHbH/1f8dvKhQCSGzz4kN -8n7KdTDjAoGAKh8XdIOQlThFK108VT2yp4BGZXEOvWrR19DLbuUzHVrEX+Bd+uFo -Ruk9iKEH7PSnlRnIvWc1y9qN7fUi5OR3LvQNGlXHyUl6CtmH3/b064bBKudC+XTn -VBelcIoGpH7Dn9I6pKUFauJz1TSbQCIjYGNqrjyzLtG+lH/gy5q4xs8= ------END RSA PRIVATE KEY-----` - -type testTarget struct { - CapturedReq *http.Request - Response *http.Response -} - -func (target *testTarget) RoundTrip(req *http.Request) (*http.Response, error) { - target.CapturedReq = req - return target.Response, nil -} - -func testReq(method string, url string, body string, headers map[string]string) *http.Request { - req, err := http.NewRequest(method, url, strings.NewReader(body)) - if err != nil { - panic(err.Error()) - } - - for k, v := range headers { - req.Header.Add(k, v) - } - return req -} - -var KnownGoodCases = []struct { - name string - inputRequest func() *http.Request - expectedHeaders map[string]string -}{ - { - name: "Simple GET", - inputRequest: func() *http.Request { - return testReq("GET", "https://example.com/", "", map[string]string{ - "date": "Mon, 26 Sep 2016 11:04:22 GMT"}) - }, - expectedHeaders: map[string]string{ - "date": "Mon, 26 Sep 2016 11:04:22 GMT", - "content-type": "application/json", - "host": "example.com", - "accept": "application/json", - "Authorization": "Signature headers=\"date host (request-target)\",keyId=\"tenant/testuser/3c:b6:44:d7:49:1a:ac:bf:de:7d:76:22:a7:f5:df:55\",algorithm=\"rsa-sha256\",signature=\"UMw/FImQYZ5JBpfYcR9YN72lhupGl5yS+522NS9glLodU9f4oKRqaocpGdSUSRhhSDKxIx01rV547/HemJ6QqEPaJJuDQPXsGthokWMU2DBGyaMAqhLClgCJiRQMwpg4rdL2tETzkM3wy6UN+I52RYoNSdsnat2ZArCkfl8dIl9ydcwD8/+BqB8d2wyaAIS4iagdPKLAC/Mu9OzyUPOXQhYGYsoEdOowOUkHOlob65PFrlHmKJDdjEF3MDcygEpApItf4iUEloP5bjixAbZEVpj3HLQ5uaPx9m+RsLzYMeO0adE0wOv2YNmwZrExGhXh1BpTU33m5amHeUBxSaG+2A==\",version=\"1\"", - }, - }, - { - name: "Simple PUT request", - inputRequest: func() *http.Request { - return testReq("PUT", "https://example.com/", "Some Content", map[string]string{ - "date": "Mon, 26 Sep 2016 11:04:22 GMT"}) - }, - expectedHeaders: map[string]string{ - "date": "Mon, 26 Sep 2016 11:04:22 GMT", - "content-type": "application/json", - "content-length": "12", - "x-content-sha256": "lQ8fsURxamLtHxnwTYqd3MNYadJ4ZB/U9yQBKzu/fXA=", - "accept": "application/json", - "Authorization": "Signature headers=\"(request-target) host date content-length content-type x-content-sha256\",keyId=\"tenant/testuser/3c:b6:44:d7:49:1a:ac:bf:de:7d:76:22:a7:f5:df:55\",algorithm=\"rsa-sha256\",signature=\"FHyPt4PE2HGH+iftzcViB76pCJ2R9+DdTCo1Ss4mH4KHQJdyQtPsCpe6Dc19zie6cRr6dsenk21yYnncic8OwZhII8DULj2//qLFGmgFi84s7LJqMQ/COiP7O9KtCN+U8MMt4PV7ZDsiGFn3/8EUJ1wxYscxSIB19S1NpuEL062JgGfkqxTkTPd7V3Xh1NlmUUtQrAMR3l56k1iV0zXY9Uw0CjWYjueMP0JUmkO7zycYAVBrx7Q8wkmejlyD7yFrAnObyEsMm9cIL9IcruWFHeCHFxRLslw7AoLxibAm2Dc9EROuvCK2UkUp8AFkE+QyYDMrrSm1NLBMWdnYqdickA==\",version=\"1\"", - }, - }, - { - name: "Simple POST request", - inputRequest: func() *http.Request { - return testReq("POST", "https://example.com/", "Some Content", map[string]string{ - "date": "Mon, 26 Sep 2016 11:04:22 GMT"}) - }, - expectedHeaders: map[string]string{ - "date": "Mon, 26 Sep 2016 11:04:22 GMT", - "content-type": "application/json", - "content-length": "12", - "x-content-sha256": "lQ8fsURxamLtHxnwTYqd3MNYadJ4ZB/U9yQBKzu/fXA=", - "accept": "application/json", - "Authorization": "Signature headers=\"(request-target) host date content-length content-type x-content-sha256\",keyId=\"tenant/testuser/3c:b6:44:d7:49:1a:ac:bf:de:7d:76:22:a7:f5:df:55\",algorithm=\"rsa-sha256\",signature=\"WzGIoySkjqydwabMTxjVs05UBu0hThAEBzVs7HbYO45o2XpaoqGiNX67mNzs1PeYrGHpJp8+Ysoz66PChWV/1trxuTU92dQ/FgwvcwBRy5dQvdLkjWCZihNunSk4gt9652w6zZg/ybLon0CFbLRnlanDJDX9BgR3ttuTxf30t5qr2A4fnjFF4VjaU/CzE13cNfaWftjSd+xNcla2sbArF3R0+CEEb0xZEPzTyjjjkyvXdaPZwEprVn8IDmdJvLmRP4EniAPxE1EZIhd712M5ondQkR4/WckM44/hlKDeXGFb4y+QnU02i4IWgOWs3dh2tuzS1hp1zfq7qgPbZ4hp0A==\",version=\"1\"", - }, - }, - - { - name: "Simple DELETE", - inputRequest: func() *http.Request { - return testReq("DELETE", "https://example.com/", "Some Content", map[string]string{ - "date": "Mon, 26 Sep 2016 11:04:22 GMT"}) - }, - expectedHeaders: map[string]string{ - "date": "Mon, 26 Sep 2016 11:04:22 GMT", - "content-type": "application/json", - "accept": "application/json", - "Authorization": "Signature headers=\"date host (request-target)\",keyId=\"tenant/testuser/3c:b6:44:d7:49:1a:ac:bf:de:7d:76:22:a7:f5:df:55\",algorithm=\"rsa-sha256\",signature=\"Kj4YSpONZG1cibLbNgxIp4VoS5+80fsB2Fh2Ue28+QyXq4wwrJpMP+8jEupz1yTk1SNPYuxsk7lNOgtI6G1Hq0YJJVum74j46sUwRWe+f08tMJ3c9J+rrzLfpIrakQ8PaudLhHU0eK5kuTZme1dCwRWXvZq3r5IqkGot/OGMabKpBygRv9t0i5ry+bTslSjMqafTWLosY9hgIiGrXD+meB5tpyn+gPVYc//Hc/C7uNNgLJIMk5DKVa4U0YnoY3ojafZTXZQQNGRn2NDMcZUX3f3nJlUIfiZRiOCTkbPwx/fWb4MZtYaEsY5OPficbJRvfOBxSG1wjX+8rgO7ijhMAA==\",version=\"1\"", - }, - }, -} - -func TestKnownGoodRequests(t *testing.T) { - pKey, err := ParsePrivateKey([]byte(testKey), []byte{}) - if err != nil { - t.Fatalf("Failed to parse test key: %s", err.Error()) - } - - config := &Config{ - Key: pKey, - User: "testuser", - Tenancy: "tenant", - Fingerprint: "3c:b6:44:d7:49:1a:ac:bf:de:7d:76:22:a7:f5:df:55", - } - - expectedResponse := &http.Response{} - for _, tt := range KnownGoodCases { - targetBackend := &testTarget{Response: expectedResponse} - target := NewTransport(targetBackend, config) - - _, err = target.RoundTrip(tt.inputRequest()) - if err != nil { - t.Fatalf("%s: Failed to handle request %s", tt.name, err.Error()) - } - - sentReq := targetBackend.CapturedReq - - for header, val := range tt.expectedHeaders { - if sentReq.Header.Get(header) != val { - t.Fatalf("%s: Header mismatch in response,\n\t expecting \"%s\"\n\t got \"%s\"", tt.name, val, sentReq.Header.Get(header)) - } - } - } - -} diff --git a/builder/oracle/oci/client/vnic.go b/builder/oracle/oci/client/vnic.go deleted file mode 100644 index 9922d34aa..000000000 --- a/builder/oracle/oci/client/vnic.go +++ /dev/null @@ -1,47 +0,0 @@ -package oci - -import ( - "time" -) - -// VNICService enables communicating with the OCI compute API's VNICs -// endpoint. -type VNICService struct { - client *baseClient -} - -// NewVNICService creates a new VNICService for communicating with the -// OCI compute API's instance related endpoints. -func NewVNICService(s *baseClient) *VNICService { - return &VNICService{client: s.New().Path("vnics/")} -} - -// VNIC - a virtual network interface card. -type VNIC struct { - AvailabilityDomain string `json:"availabilityDomain"` - CompartmentID string `json:"compartmentId"` - DisplayName string `json:"displayName,omitempty"` - ID string `json:"id"` - LifecycleState string `json:"lifecycleState"` - PrivateIP string `json:"privateIp"` - PublicIP string `json:"publicIp"` - SubnetID string `json:"subnetId"` - TimeCreated time.Time `json:"timeCreated"` -} - -// GetVNICParams are the parameters available when communicating with the -// ListVNICs API endpoint. -type GetVNICParams struct { - ID string `url:"vnicId"` -} - -// Get returns an individual VNIC. -func (s *VNICService) Get(params *GetVNICParams) (VNIC, error) { - VNIC := &VNIC{} - e := &APIError{} - - _, err := s.client.New().Get(params.ID).Receive(VNIC, e) - err = firstError(err, e) - - return *VNIC, err -} diff --git a/builder/oracle/oci/client/vnic_attachment.go b/builder/oracle/oci/client/vnic_attachment.go deleted file mode 100644 index 1e42a0573..000000000 --- a/builder/oracle/oci/client/vnic_attachment.go +++ /dev/null @@ -1,52 +0,0 @@ -package oci - -import ( - "time" -) - -// VNICAttachmentService enables communicating with the OCI compute API's VNIC -// attachment endpoint. -type VNICAttachmentService struct { - client *baseClient -} - -// NewVNICAttachmentService creates a new VNICAttachmentService for communicating with the -// OCI compute API's instance related endpoints. -func NewVNICAttachmentService(s *baseClient) *VNICAttachmentService { - return &VNICAttachmentService{ - client: s.New().Path("vnicAttachments/"), - } -} - -// VNICAttachment details the attachment of a VNIC to a OCI instance. -type VNICAttachment struct { - AvailabilityDomain string `json:"availabilityDomain"` - CompartmentID string `json:"compartmentId"` - DisplayName string `json:"displayName,omitempty"` - ID string `json:"id"` - InstanceID string `json:"instanceId"` - LifecycleState string `json:"lifecycleState"` - SubnetID string `json:"subnetId"` - TimeCreated time.Time `json:"timeCreated"` - VNICID string `json:"vnicId"` -} - -// ListVnicAttachmentsParams are the parameters available when communicating -// with the ListVnicAttachments API endpoint. -type ListVnicAttachmentsParams struct { - AvailabilityDomain string `url:"availabilityDomain,omitempty"` - CompartmentID string `url:"compartmentId"` - InstanceID string `url:"instanceId,omitempty"` - VNICID string `url:"vnicId,omitempty"` -} - -// List returns an array of VNICAttachments. -func (s *VNICAttachmentService) List(params *ListVnicAttachmentsParams) ([]VNICAttachment, error) { - vnicAttachments := new([]VNICAttachment) - e := new(APIError) - - _, err := s.client.New().Get("").QueryStruct(params).Receive(vnicAttachments, e) - err = firstError(err, e) - - return *vnicAttachments, err -} diff --git a/builder/oracle/oci/client/vnic_attachment_test.go b/builder/oracle/oci/client/vnic_attachment_test.go deleted file mode 100644 index 704423fb0..000000000 --- a/builder/oracle/oci/client/vnic_attachment_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package oci - -import ( - "fmt" - "net/http" - "reflect" - "testing" -) - -func TestListVNICAttachments(t *testing.T) { - setup() - defer teardown() - - id := "ocid1.image.oc1.phx.a" - mux.HandleFunc("/vnicAttachments/", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, `[{"id":"%s"}]`, id) - }) - - params := &ListVnicAttachmentsParams{InstanceID: id} - - vnicAttachment, err := client.Compute.VNICAttachments.List(params) - if err != nil { - t.Errorf("Client.Compute.VNICAttachments.List() returned error: %v", err) - } - - want := []VNICAttachment{{ID: id}} - - if !reflect.DeepEqual(vnicAttachment, want) { - t.Errorf("Client.Compute.VNICAttachments.List() returned %+v, want %+v", vnicAttachment, want) - } -} diff --git a/builder/oracle/oci/client/vnic_test.go b/builder/oracle/oci/client/vnic_test.go deleted file mode 100644 index c208db5c4..000000000 --- a/builder/oracle/oci/client/vnic_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package oci - -import ( - "fmt" - "net/http" - "reflect" - "testing" -) - -func TestGetVNIC(t *testing.T) { - setup() - defer teardown() - - id := "ocid1.vnic.oc1.phx.a" - path := fmt.Sprintf("/vnics/%s", id) - mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, `{"id": "%s"}`, id) - }) - - vnic, err := client.Compute.VNICs.Get(&GetVNICParams{ID: id}) - if err != nil { - t.Errorf("Client.Compute.VNICs.Get() returned error: %v", err) - } - - want := &VNIC{ID: id} - if reflect.DeepEqual(vnic, want) { - t.Errorf("Client.Compute.VNICs.Get() returned %+v, want %+v", vnic, want) - } -} diff --git a/builder/oracle/oci/client/waiters.go b/builder/oracle/oci/client/waiters.go deleted file mode 100644 index bb9ca1c19..000000000 --- a/builder/oracle/oci/client/waiters.go +++ /dev/null @@ -1,59 +0,0 @@ -package oci - -import ( - "fmt" - "time" -) - -const ( - defaultWaitDurationMS = 5000 - defaultMaxRetries = 0 -) - -type Waiter struct { - WaitDurationMS int - MaxRetries int -} - -type WaitableService interface { - GetResourceState(id string) (string, error) -} - -func stringSliceContains(slice []string, value string) bool { - for _, elem := range slice { - if elem == value { - return true - } - } - return false -} - -// NewWaiter creates a waiter with default wait duration and unlimited retry -// operations. -func NewWaiter() *Waiter { - return &Waiter{WaitDurationMS: defaultWaitDurationMS, MaxRetries: defaultMaxRetries} -} - -// WaitForResourceToReachState polls a resource that implements WaitableService -// repeatedly until it reaches a known state or fails if it reaches an -// unexpected state. The duration of the interval and number of polls is -// determined by the Waiter configuration. -func (w *Waiter) WaitForResourceToReachState(svc WaitableService, id string, waitStates []string, terminalState string) error { - for i := 0; w.MaxRetries == 0 || i < w.MaxRetries; i++ { - state, err := svc.GetResourceState(id) - if err != nil { - return err - } - - if stringSliceContains(waitStates, state) { - time.Sleep(time.Duration(w.WaitDurationMS) * time.Millisecond) - continue - } else if state == terminalState { - return nil - } - - return fmt.Errorf("Unexpected resource state %s, expecting a waiting state %s or terminal state %s ", state, waitStates, terminalState) - } - - return fmt.Errorf("Maximum number of retries (%d) exceeded; resource did not reach state %s", w.MaxRetries, terminalState) -} diff --git a/builder/oracle/oci/client/waiters_test.go b/builder/oracle/oci/client/waiters_test.go deleted file mode 100644 index ce52b7922..000000000 --- a/builder/oracle/oci/client/waiters_test.go +++ /dev/null @@ -1,80 +0,0 @@ -package oci - -import ( - "errors" - "fmt" - "testing" -) - -const ( - ValidID = "ID" -) - -type testWaitSvc struct { - states []string - idx int - err error -} - -func (tw *testWaitSvc) GetResourceState(id string) (string, error) { - if id != ValidID { - return "", fmt.Errorf("Invalid id %s", id) - } - if tw.err != nil { - return "", tw.err - } - - if tw.idx >= len(tw.states) { - panic("Invalid test state") - } - state := tw.states[tw.idx] - tw.idx++ - return state, nil -} - -func TestReturnsWhenWaitStateIsReachedImmediately(t *testing.T) { - ws := &testWaitSvc{states: []string{"OK"}} - w := NewWaiter() - err := w.WaitForResourceToReachState(ws, ValidID, []string{}, "OK") - if err != nil { - t.Errorf("Failed to reach expected state, got %s", err) - } -} - -func TestReturnsWhenResourceWaitsInValidWaitingState(t *testing.T) { - w := &Waiter{WaitDurationMS: 1, MaxRetries: defaultMaxRetries} - ws := &testWaitSvc{states: []string{"WAITING", "OK"}} - err := w.WaitForResourceToReachState(ws, ValidID, []string{"WAITING"}, "OK") - if err != nil { - t.Errorf("Failed to reach expected state, got %s", err) - } -} - -func TestPropagatesErrorFromGetter(t *testing.T) { - w := NewWaiter() - ws := &testWaitSvc{states: []string{}, err: errors.New("ERROR")} - err := w.WaitForResourceToReachState(ws, ValidID, []string{"WAITING"}, "OK") - if err != ws.err { - t.Errorf("Expected error from getter got %s", err) - } -} - -func TestReportsInvalidTransitionStateAsError(t *testing.T) { - w := NewWaiter() - tw := &testWaitSvc{states: []string{"UNKNOWN_STATE"}, err: errors.New("ERROR")} - err := w.WaitForResourceToReachState(tw, ValidID, []string{"WAITING"}, "OK") - if err == nil { - t.Fatal("Expected error from getter") - } -} - -func TestErrorsWhenMaxWaitTriesExceeded(t *testing.T) { - w := Waiter{WaitDurationMS: 1, MaxRetries: 1} - - ws := &testWaitSvc{states: []string{"WAITING", "OK"}} - - err := w.WaitForResourceToReachState(ws, ValidID, []string{"WAITING"}, "OK") - if err == nil { - t.Fatal("Expecting error but wait terminated") - } -} diff --git a/builder/oracle/oci/config.go b/builder/oracle/oci/config.go index 3af6dcbdf..75fea183b 100644 --- a/builder/oracle/oci/config.go +++ b/builder/oracle/oci/config.go @@ -9,12 +9,12 @@ import ( "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" + ocicommon "github.com/oracle/oci-go-sdk/common" "github.com/mitchellh/go-homedir" ) @@ -23,7 +23,7 @@ type Config struct { common.PackerConfig `mapstructure:",squash"` Comm communicator.Config `mapstructure:",squash"` - AccessCfg *client.Config + ConfigProvider ocicommon.ConfigurationProvider AccessCfgFile string `mapstructure:"access_cfg_file"` AccessCfgFileAccount string `mapstructure:"access_cfg_file_account"` @@ -67,68 +67,54 @@ func NewConfig(raws ...interface{}) (*Config, error) { } // Determine where the SDK config is located - var accessCfgFile string - if c.AccessCfgFile != "" { - accessCfgFile = c.AccessCfgFile - } else { - accessCfgFile, err = getDefaultOCISettingsPath() + if c.AccessCfgFile == "" { + c.AccessCfgFile, err = getDefaultOCISettingsPath() if err != nil { - accessCfgFile = "" // Access cfg might be in template + log.Println("Default OCI settings file not found") } } - 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.AccessCfgFileAccount == "" { + c.AccessCfgFileAccount = "DEFAULT" } + var keyContent []byte if c.KeyFile != "" { - accessCfg.KeyFile = c.KeyFile - accessCfg.Key, err = client.LoadPrivateKey(accessCfg) + path, err := homedir.Expand(c.KeyFile) if err != nil { - return nil, fmt.Errorf("Failed to load private key %s : %s", accessCfg.KeyFile, err) + return nil, err } + + // Read API signing key + keyContent, err = ioutil.ReadFile(path) + if err != nil { + return nil, err + } + } + + 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" + } + } + + providers := []ocicommon.ConfigurationProvider{ + NewRawConfigurationProvider(c.TenancyID, c.UserID, c.Region, c.Fingerprint, string(keyContent), &c.PassPhrase), + } + + if fileProvider != nil { + providers = append(providers, fileProvider) + } + + // Load API access configuration from SDK + configProvider, err := ocicommon.ComposingConfigurationProvider(providers) + if err != nil { + return nil, err } var errs *packer.MultiError @@ -136,44 +122,36 @@ func NewConfig(raws ...interface{}) (*Config, error) { errs = packer.MultiErrorAppend(errs, es...) } - // Required AccessCfg configuration options - - if accessCfg.User == "" { + if userOCID, _ := configProvider.UserOCID(); userOCID == "" { errs = packer.MultiErrorAppend( errs, errors.New("'user_ocid' must be specified")) } - if accessCfg.Tenancy == "" { + tenancyOCID, _ := configProvider.TenancyOCID() + if tenancyOCID == "" { 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 == "" { + if fingerprint, _ := configProvider.KeyFingerprint(); fingerprint == "" { errs = packer.MultiErrorAppend( errs, errors.New("'fingerprint' must be specified")) } - if accessCfg.Key == nil { + if _, err := configProvider.PrivateRSAKey(); err != nil { errs = packer.MultiErrorAppend( errs, errors.New("'key_file' must be specified")) } - c.AccessCfg = accessCfg - - // Required non AccessCfg configuration options + c.ConfigProvider = configProvider if c.AvailabilityDomain == "" { errs = packer.MultiErrorAppend( errs, errors.New("'availability_domain' must be specified")) } - if c.CompartmentID == "" { - c.CompartmentID = accessCfg.Tenancy + if c.CompartmentID == "" && tenancyOCID != "" { + c.CompartmentID = tenancyOCID } if c.Shape == "" { diff --git a/builder/oracle/oci/config_provider.go b/builder/oracle/oci/config_provider.go new file mode 100644 index 000000000..25d467a4c --- /dev/null +++ b/builder/oracle/oci/config_provider.go @@ -0,0 +1,76 @@ +package oci + +import ( + "crypto/rsa" + "errors" + "fmt" + + "github.com/oracle/oci-go-sdk/common" +) + +// rawConfigurationProvider allows a user to simply construct a configuration +// provider from raw values. It errors on access when those values are empty. +type rawConfigurationProvider struct { + tenancy string + user string + region string + fingerprint string + privateKey string + privateKeyPassphrase *string +} + +// NewRawConfigurationProvider will create a rawConfigurationProvider. +func NewRawConfigurationProvider(tenancy, user, region, fingerprint, privateKey string, privateKeyPassphrase *string) common.ConfigurationProvider { + return rawConfigurationProvider{tenancy, user, region, fingerprint, privateKey, privateKeyPassphrase} +} + +func (p rawConfigurationProvider) PrivateRSAKey() (key *rsa.PrivateKey, err error) { + return common.PrivateKeyFromBytes([]byte(p.privateKey), p.privateKeyPassphrase) +} + +func (p rawConfigurationProvider) KeyID() (keyID string, err error) { + tenancy, err := p.TenancyOCID() + if err != nil { + return + } + + user, err := p.UserOCID() + if err != nil { + return + } + + fingerprint, err := p.KeyFingerprint() + if err != nil { + return + } + + return fmt.Sprintf("%s/%s/%s", tenancy, user, fingerprint), nil +} + +func (p rawConfigurationProvider) TenancyOCID() (string, error) { + if p.tenancy == "" { + return "", errors.New("no tenancy provided") + } + return p.tenancy, nil +} + +func (p rawConfigurationProvider) UserOCID() (string, error) { + if p.user == "" { + return "", errors.New("no user provided") + } + return p.user, nil +} + +func (p rawConfigurationProvider) KeyFingerprint() (string, error) { + if p.fingerprint == "" { + return "", errors.New("no fingerprint provided") + } + return p.fingerprint, nil +} + +func (p rawConfigurationProvider) Region() (string, error) { + if p.region == "" { + return "", errors.New("no region provided") + } + return p.region, nil +} diff --git a/builder/oracle/oci/config_test.go b/builder/oracle/oci/config_test.go index 84002e937..72491d8d0 100644 --- a/builder/oracle/oci/config_test.go +++ b/builder/oracle/oci/config_test.go @@ -1,13 +1,16 @@ package oci import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/pem" "io/ioutil" "os" - "reflect" "strings" "testing" - client "github.com/hashicorp/packer/builder/oracle/oci/client" + "github.com/go-ini/ini" ) func testConfig(accessConfFile *os.File) map[string]interface{} { @@ -29,22 +32,16 @@ func testConfig(accessConfFile *os.File) map[string]interface{} { } } -func getField(c *client.Config, field string) string { - r := reflect.ValueOf(c) - f := reflect.Indirect(r).FieldByName(field) - return string(f.String()) -} - func TestConfig(t *testing.T) { // Shared set-up and defered deletion - cfg, keyFile, err := client.BaseTestConfig() + cfg, keyFile, err := baseTestConfigWithTmpKeyFile() if err != nil { t.Fatal(err) } defer os.Remove(keyFile.Name()) - cfgFile, err := client.WriteTestConfig(cfg) + cfgFile, err := writeTestConfig(cfg) if err != nil { t.Fatal(err) } @@ -55,7 +52,7 @@ func TestConfig(t *testing.T) { tmpHome, err := ioutil.TempDir("", "packer_config_test") if err != nil { - t.Fatalf("err: %+v", err) + t.Fatalf("Unexpected error when creating temporary directory: %+v", err) } defer os.Remove(tmpHome) @@ -64,15 +61,13 @@ func TestConfig(t *testing.T) { defer os.Setenv("HOME", home) // Config tests - t.Run("BaseConfig", func(t *testing.T) { raw := testConfig(cfgFile) _, errs := NewConfig(raw) if errs != nil { - t.Fatalf("err: %+v", errs) + t.Fatalf("Unexpected error in configuration %+v", errs) } - }) t.Run("NoAccessConfig", func(t *testing.T) { @@ -81,14 +76,14 @@ func TestConfig(t *testing.T) { _, errs := NewConfig(raw) - s := errs.Error() expectedErrors := []string{ - "'user_ocid'", "'tenancy_ocid'", "'fingerprint'", - "'key_file'", + "'user_ocid'", "'tenancy_ocid'", "'fingerprint'", "'key_file'", } + + s := errs.Error() for _, expected := range expectedErrors { if !strings.Contains(s, expected) { - t.Errorf("Expected %s to contain '%s'", s, expected) + t.Errorf("Expected %q to contain '%s'", s, expected) } } }) @@ -113,12 +108,17 @@ func TestConfig(t *testing.T) { raw := testConfig(cfgFile) c, errs := NewConfig(raw) if errs != nil { - t.Fatalf("err: %+v", errs) + t.Fatalf("Unexpected error in configuration %+v", errs) } - expected := "ocid1.tenancy.oc1..aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" - if c.AccessCfg.Tenancy != expected { - t.Errorf("Expected tenancy: %s, got %s.", expected, c.AccessCfg.Tenancy) + tenancy, err := c.ConfigProvider.TenancyOCID() + if err != nil { + t.Fatalf("Unexpected error getting tenancy ocid: %v", err) + } + + expected := "ocid1.tenancy.oc1..aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + if tenancy != expected { + t.Errorf("Expected tenancy: %s, got %s.", expected, tenancy) } }) @@ -127,12 +127,17 @@ func TestConfig(t *testing.T) { raw := testConfig(cfgFile) c, errs := NewConfig(raw) if errs != nil { - t.Fatalf("err: %+v", errs) + t.Fatalf("Unexpected error in configuration %+v", errs) + } + + region, err := c.ConfigProvider.Region() + if err != nil { + t.Fatalf("Unexpected error getting region: %v", err) } expected := "us-ashburn-1" - if c.AccessCfg.Region != expected { - t.Errorf("Expected region: %s, got %s.", expected, c.AccessCfg.Region) + if region != expected { + t.Errorf("Expected region: %s, got %s.", expected, region) } }) @@ -159,7 +164,7 @@ func TestConfig(t *testing.T) { c, errs := NewConfig(raw) if errs != nil { - t.Errorf("Unexpected error(s): %s", errs) + t.Fatalf("Unexpected error in configuration %+v", errs) } if !strings.Contains(c.ImageName, "packer-") { @@ -167,30 +172,138 @@ func TestConfig(t *testing.T) { } }) - // Test that AccessCfgFile properties are overridden by their - // corresponding template keys. - accessOverrides := map[string]string{ - "user_ocid": "User", - "tenancy_ocid": "Tenancy", - "region": "Region", - "fingerprint": "Fingerprint", - } - for k, v := range accessOverrides { - t.Run("AccessCfg."+v+"Overridden", func(t *testing.T) { - expected := "override" + t.Run("user_ocid_overridden", func(t *testing.T) { + expected := "override" + raw := testConfig(cfgFile) + raw["user_ocid"] = expected - raw := testConfig(cfgFile) - raw[k] = expected + c, errs := NewConfig(raw) + if errs != nil { + t.Fatalf("Unexpected error in configuration %+v", errs) + } - c, errs := NewConfig(raw) - if errs != nil { - t.Fatalf("err: %+v", errs) - } + user, _ := c.ConfigProvider.UserOCID() + if user != expected { + t.Errorf("Expected ConfigProvider.UserOCID: %s, got %s", expected, user) + } + }) - accessVal := getField(c.AccessCfg, v) - if accessVal != expected { - t.Errorf("Expected AccessCfg.%s: %s, got %s", v, expected, accessVal) - } - }) - } + t.Run("tenancy_ocid_overidden", func(t *testing.T) { + expected := "override" + raw := testConfig(cfgFile) + raw["tenancy_ocid"] = expected + + c, errs := NewConfig(raw) + if errs != nil { + t.Fatalf("Unexpected error in configuration %+v", errs) + } + + tenancy, _ := c.ConfigProvider.TenancyOCID() + if tenancy != expected { + t.Errorf("Expected ConfigProvider.TenancyOCID: %s, got %s", expected, tenancy) + } + }) + + t.Run("region_overidden", func(t *testing.T) { + expected := "override" + raw := testConfig(cfgFile) + raw["region"] = expected + + c, errs := NewConfig(raw) + if errs != nil { + t.Fatalf("Unexpected error in configuration %+v", errs) + } + + region, _ := c.ConfigProvider.Region() + if region != expected { + t.Errorf("Expected ConfigProvider.Region: %s, got %s", expected, region) + } + }) + + t.Run("fingerprint_overidden", func(t *testing.T) { + expected := "override" + raw := testConfig(cfgFile) + raw["fingerprint"] = expected + + c, errs := NewConfig(raw) + if errs != nil { + t.Fatalf("Unexpected error in configuration: %+v", errs) + } + + fingerprint, _ := c.ConfigProvider.KeyFingerprint() + if fingerprint != expected { + t.Errorf("Expected ConfigProvider.KeyFingerprint: %s, got %s", expected, fingerprint) + } + }) +} + +// BaseTestConfig creates the base (DEFAULT) config including a temporary key +// file. +// NOTE: Caller is responsible for removing temporary key file. +func baseTestConfigWithTmpKeyFile() (*ini.File, *os.File, error) { + keyFile, err := generateRSAKeyFile() + if err != nil { + return nil, keyFile, err + } + // Build ini + cfg := ini.Empty() + section, _ := cfg.NewSection("DEFAULT") + section.NewKey("region", "us-ashburn-1") + section.NewKey("tenancy", "ocid1.tenancy.oc1..aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + section.NewKey("user", "ocid1.user.oc1..aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + section.NewKey("fingerprint", "70:04:5z:b3:19:ab:90:75:a4:1f:50:d4:c7:c3:33:20") + section.NewKey("key_file", keyFile.Name()) + + return cfg, keyFile, nil +} + +// WriteTestConfig writes a ini.File to a temporary file for use in unit tests. +// NOTE: Caller is responsible for removing temporary file. +func writeTestConfig(cfg *ini.File) (*os.File, error) { + confFile, err := ioutil.TempFile("", "config_file") + if err != nil { + return nil, err + } + + if _, err := confFile.Write([]byte("[DEFAULT]\n")); err != nil { + os.Remove(confFile.Name()) + return nil, err + } + + if _, err := cfg.WriteTo(confFile); err != nil { + os.Remove(confFile.Name()) + return nil, err + } + return confFile, nil +} + +// generateRSAKeyFile generates an RSA key file for use in unit tests. +// NOTE: The caller is responsible for deleting the temporary file. +func generateRSAKeyFile() (*os.File, error) { + // Create temporary file for the key + f, err := ioutil.TempFile("", "key") + if err != nil { + return nil, err + } + + // Generate key + priv, err := rsa.GenerateKey(rand.Reader, 2014) + if err != nil { + return nil, err + } + + // ASN.1 DER encoded form + privDer := x509.MarshalPKCS1PrivateKey(priv) + privBlk := pem.Block{ + Type: "RSA PRIVATE KEY", + Headers: nil, + Bytes: privDer, + } + + // Write the key out + if _, err := f.Write(pem.EncodeToMemory(&privBlk)); err != nil { + return nil, err + } + + return f, nil } diff --git a/builder/oracle/oci/driver.go b/builder/oracle/oci/driver.go index 51f6c364a..704b2f2a9 100644 --- a/builder/oracle/oci/driver.go +++ b/builder/oracle/oci/driver.go @@ -1,13 +1,11 @@ package oci -import ( - client "github.com/hashicorp/packer/builder/oracle/oci/client" -) +import "github.com/oracle/oci-go-sdk/core" // Driver interfaces between the builder steps and the OCI SDK. type Driver interface { CreateInstance(publicKey string) (string, error) - CreateImage(id string) (client.Image, error) + CreateImage(id string) (core.Image, error) DeleteImage(id string) error GetInstanceIP(id string) (string, error) TerminateInstance(id string) error diff --git a/builder/oracle/oci/driver_mock.go b/builder/oracle/oci/driver_mock.go index f8bd9f920..1f2e7ec2b 100644 --- a/builder/oracle/oci/driver_mock.go +++ b/builder/oracle/oci/driver_mock.go @@ -1,8 +1,6 @@ package oci -import ( - client "github.com/hashicorp/packer/builder/oracle/oci/client" -) +import "github.com/oracle/oci-go-sdk/core" // driverMock implements the Driver interface and communicates with Oracle // OCI. @@ -40,12 +38,12 @@ func (d *driverMock) CreateInstance(publicKey string) (string, error) { } // CreateImage creates a new custom image. -func (d *driverMock) CreateImage(id string) (client.Image, error) { +func (d *driverMock) CreateImage(id string) (core.Image, error) { if d.CreateImageErr != nil { - return client.Image{}, d.CreateImageErr + return core.Image{}, d.CreateImageErr } d.CreateImageID = id - return client.Image{ID: id}, nil + return core.Image{Id: &id}, nil } // DeleteImage mocks deleting a custom image. diff --git a/builder/oracle/oci/driver_oci.go b/builder/oracle/oci/driver_oci.go index 1b6b0a3f9..90d05ea0a 100644 --- a/builder/oracle/oci/driver_oci.go +++ b/builder/oracle/oci/driver_oci.go @@ -1,123 +1,191 @@ package oci import ( + "context" "errors" "fmt" + "time" - client "github.com/hashicorp/packer/builder/oracle/oci/client" + core "github.com/oracle/oci-go-sdk/core" ) // driverOCI implements the Driver interface and communicates with Oracle // OCI. type driverOCI struct { - client *client.Client - cfg *Config + computeClient core.ComputeClient + vcnClient core.VirtualNetworkClient + cfg *Config } -// NewDriverOCI Creates a new driverOCI with a connected client. +// NewDriverOCI Creates a new driverOCI with a connected compute client and a connected vcn client. func NewDriverOCI(cfg *Config) (Driver, error) { - client, err := client.NewClient(cfg.AccessCfg) + coreClient, err := core.NewComputeClientWithConfigurationProvider(cfg.ConfigProvider) if err != nil { return nil, err } - return &driverOCI{client: client, cfg: cfg}, nil + + vcnClient, err := core.NewVirtualNetworkClientWithConfigurationProvider(cfg.ConfigProvider) + if err != nil { + return nil, err + } + + return &driverOCI{ + computeClient: coreClient, + vcnClient: vcnClient, + cfg: cfg, + }, nil } // CreateInstance creates a new compute instance. func (d *driverOCI) CreateInstance(publicKey string) (string, error) { - params := &client.LaunchInstanceParams{ - AvailabilityDomain: d.cfg.AvailabilityDomain, - CompartmentID: d.cfg.CompartmentID, - ImageID: d.cfg.BaseImageID, - Shape: d.cfg.Shape, - SubnetID: d.cfg.SubnetID, - Metadata: map[string]string{ - "ssh_authorized_keys": publicKey, - }, + metadata := map[string]string{ + "ssh_authorized_keys": publicKey, } if d.cfg.UserData != "" { - params.Metadata["user_data"] = d.cfg.UserData + metadata["user_data"] = d.cfg.UserData } - instance, err := d.client.Compute.Instances.Launch(params) + + instance, err := d.computeClient.LaunchInstance(context.TODO(), core.LaunchInstanceRequest{LaunchInstanceDetails: core.LaunchInstanceDetails{ + AvailabilityDomain: &d.cfg.AvailabilityDomain, + CompartmentId: &d.cfg.CompartmentID, + ImageId: &d.cfg.BaseImageID, + Shape: &d.cfg.Shape, + SubnetId: &d.cfg.SubnetID, + Metadata: metadata, + }}) + if err != nil { return "", err } - return instance.ID, nil + return *instance.Id, nil } // CreateImage creates a new custom image. -func (d *driverOCI) CreateImage(id string) (client.Image, error) { - params := &client.CreateImageParams{ - CompartmentID: d.cfg.CompartmentID, - InstanceID: id, - DisplayName: d.cfg.ImageName, - } - image, err := d.client.Compute.Images.Create(params) +func (d *driverOCI) CreateImage(id string) (core.Image, error) { + res, err := d.computeClient.CreateImage(context.TODO(), core.CreateImageRequest{CreateImageDetails: core.CreateImageDetails{ + CompartmentId: &d.cfg.CompartmentID, + InstanceId: &id, + DisplayName: &d.cfg.ImageName, + }}) + if err != nil { - return client.Image{}, err + return core.Image{}, err } - return image, nil + return res.Image, nil } // DeleteImage deletes a custom image. func (d *driverOCI) DeleteImage(id string) error { - return d.client.Compute.Images.Delete(&client.DeleteImageParams{ID: id}) + _, err := d.computeClient.DeleteImage(context.TODO(), core.DeleteImageRequest{ImageId: &id}) + return err } // GetInstanceIP returns the public or private IP corresponding to the given instance id. func (d *driverOCI) GetInstanceIP(id string) (string, error) { - // get nvic and cross ref to find pub ip address - vnics, err := d.client.Compute.VNICAttachments.List( - &client.ListVnicAttachmentsParams{ - InstanceID: id, - CompartmentID: d.cfg.CompartmentID, - }, - ) + vnics, err := d.computeClient.ListVnicAttachments(context.TODO(), core.ListVnicAttachmentsRequest{ + InstanceId: &id, + CompartmentId: &d.cfg.CompartmentID, + }) if err != nil { return "", err } - if len(vnics) < 1 { + if len(vnics.Items) == 0 { return "", errors.New("instance has zero VNICs") } - vnic, err := d.client.Compute.VNICs.Get(&client.GetVNICParams{ID: vnics[0].VNICID}) + vnic, err := d.vcnClient.GetVnic(context.TODO(), core.GetVnicRequest{VnicId: vnics.Items[0].VnicId}) if err != nil { return "", fmt.Errorf("Error getting VNIC details: %s", err) } if d.cfg.UsePrivateIP { - return vnic.PrivateIP, nil + return *vnic.PrivateIp, nil } - return vnic.PublicIP, nil + + if vnic.PublicIp == nil { + return "", fmt.Errorf("Error getting VNIC Public Ip for: %s", id) + } + + return *vnic.PublicIp, nil } // TerminateInstance terminates a compute instance. func (d *driverOCI) TerminateInstance(id string) error { - params := &client.TerminateInstanceParams{ID: id} - return d.client.Compute.Instances.Terminate(params) + _, err := d.computeClient.TerminateInstance(context.TODO(), core.TerminateInstanceRequest{ + InstanceId: &id, + }) + return err } // WaitForImageCreation waits for a provisioning custom image to reach the // "AVAILABLE" state. func (d *driverOCI) WaitForImageCreation(id string) error { - return client.NewWaiter().WaitForResourceToReachState( - d.client.Compute.Images, + return waitForResourceToReachState( + func(string) (string, error) { + image, err := d.computeClient.GetImage(context.TODO(), core.GetImageRequest{ImageId: &id}) + if err != nil { + return "", err + } + return string(image.LifecycleState), nil + }, id, []string{"PROVISIONING"}, "AVAILABLE", + 0, //Unlimited Retries + 5*time.Second, //5 second wait between retries ) } // WaitForInstanceState waits for an instance to reach the a given terminal // state. func (d *driverOCI) WaitForInstanceState(id string, waitStates []string, terminalState string) error { - return client.NewWaiter().WaitForResourceToReachState( - d.client.Compute.Instances, + return waitForResourceToReachState( + func(string) (string, error) { + instance, err := d.computeClient.GetInstance(context.TODO(), core.GetInstanceRequest{InstanceId: &id}) + if err != nil { + return "", err + } + return string(instance.LifecycleState), nil + }, id, waitStates, terminalState, + 0, //Unlimited Retries + 5*time.Second, //5 second wait between retries ) } + +// WaitForResourceToReachState checks the response of a request through a +// polled get and waits until the desired state or until the max retried has +// been reached. +func waitForResourceToReachState(getResourceState func(string) (string, error), id string, waitStates []string, terminalState string, maxRetries int, waitDuration time.Duration) error { + for i := 0; maxRetries == 0 || i < maxRetries; i++ { + state, err := getResourceState(id) + if err != nil { + return err + } + + if stringSliceContains(waitStates, state) { + time.Sleep(waitDuration) + continue + } else if state == terminalState { + return nil + } + return fmt.Errorf("Unexpected resource state %q, expecting a waiting state %s or terminal state %q ", state, waitStates, terminalState) + } + return fmt.Errorf("Maximum number of retries (%d) exceeded; resource did not reach state %q", maxRetries, terminalState) +} + +// stringSliceContains loops through a slice of strings returning a boolean +// based on whether a given value is contained in the slice. +func stringSliceContains(slice []string, value string) bool { + for _, elem := range slice { + if elem == value { + return true + } + } + return false +} diff --git a/builder/oracle/oci/step_image.go b/builder/oracle/oci/step_image.go index ff767f709..9589594c8 100644 --- a/builder/oracle/oci/step_image.go +++ b/builder/oracle/oci/step_image.go @@ -27,7 +27,7 @@ func (s *stepImage) Run(_ context.Context, state multistep.StateBag) multistep.S return multistep.ActionHalt } - err = driver.WaitForImageCreation(image.ID) + err = driver.WaitForImageCreation(*image.Id) if err != nil { err = fmt.Errorf("Error waiting for image creation to finish: %s", err) ui.Error(err.Error()) diff --git a/builder/oracle/oci/step_test.go b/builder/oracle/oci/step_test.go index cca58666c..55afba523 100644 --- a/builder/oracle/oci/step_test.go +++ b/builder/oracle/oci/step_test.go @@ -6,33 +6,32 @@ import ( "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" - - client "github.com/hashicorp/packer/builder/oracle/oci/client" ) // TODO(apryde): It would be good not to have to write a key file to disk to // load the config. func baseTestConfig() *Config { - _, keyFile, err := client.BaseTestConfig() + _, keyFile, err := baseTestConfigWithTmpKeyFile() if err != nil { panic(err) } cfg, err := NewConfig(map[string]interface{}{ - "availability_domain": "aaaa:PHX-AD-3", + "availability_domain": "aaaa:US-ASHBURN-AD-1", // Image - "base_image_ocid": "ocd1...", + "base_image_ocid": "ocid1.image.oc1.iad.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "shape": "VM.Standard1.1", "image_name": "HelloWorld", + "region": "us-ashburn-1", // Networking - "subnet_ocid": "ocd1...", + "subnet_ocid": "ocid1.subnet.oc1.iad.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", // AccessConfig - "user_ocid": "ocid1...", - "tenancy_ocid": "ocid1...", - "fingerprint": "00:00...", + "user_ocid": "ocid1.user.oc1..aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "tenancy_ocid": "ocid1.tenancy.oc1..aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "fingerprint": "70:04:5z:b3:19:ab:90:75:a4:1f:50:d4:c7:c3:33:20", "key_file": keyFile.Name(), // Comm