Skip to content

Commit 84a261b

Browse files
AlaaShakeralexng-canuck
authored andcommitted
Add OBO & instance principal support
- Add support for using OBO tokens. - Add support for instance principal based authentication.
1 parent ade4b2d commit 84a261b

File tree

2 files changed

+148
-47
lines changed

2 files changed

+148
-47
lines changed

provider/provider.go

Lines changed: 95 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,16 @@ import (
1111
"net/http"
1212
"os"
1313
"runtime"
14+
"strconv"
15+
"strings"
1416
"time"
1517

1618
"github.com/hashicorp/terraform/helper/schema"
19+
"github.com/hashicorp/terraform/helper/validation"
1720
"github.com/hashicorp/terraform/terraform"
1821

1922
oci_common "github.com/oracle/oci-go-sdk/common"
23+
oci_common_auth "github.com/oracle/oci-go-sdk/common/auth"
2024
oci_core "github.com/oracle/oci-go-sdk/core"
2125
oci_database "github.com/oracle/oci-go-sdk/database"
2226
oci_identity "github.com/oracle/oci-go-sdk/identity"
@@ -28,21 +32,31 @@ var descriptions map[string]string
2832
var disableAutoRetries bool
2933

3034
const (
31-
defaultRequestTimeout = 0
32-
defaultConnectionTimeout = 10 * time.Second
33-
defaultTLSHandshakeTimeout = 5 * time.Second
35+
authAPIKeySetting = "ApiKey"
36+
authInstancePrincipalSetting = "InstancePrincipal"
37+
defaultRequestTimeout = 0
38+
defaultConnectionTimeout = 10 * time.Second
39+
defaultTLSHandshakeTimeout = 5 * time.Second
40+
userAgentFormatter = "Oracle-GoSDK/%s (go/%s; %s/%s; terraform/%s) Oracle-TerraformProvider/%s"
3441
)
3542

43+
type oboTokenProviderFromEnv struct{}
44+
45+
func (p oboTokenProviderFromEnv) OboToken() (string, error) {
46+
return getEnvSetting("obo_token", ""), nil
47+
}
48+
3649
func init() {
3750
descriptions = map[string]string{
38-
"tenancy_ocid": "(Required) The tenancy OCID for a user. The tenancy OCID can be found at the bottom of user settings in the Oracle Cloud Infrastructure console.",
39-
"user_ocid": "(Required) The user OCID. This can be found in user settings in the Oracle Cloud Infrastructure console.",
40-
"fingerprint": "(Required) The fingerprint for the user's RSA key. This can be found in user settings in the Oracle Cloud Infrastructure console.",
51+
"auth": fmt.Sprintf("(Optional) The type of auth to use. Options are '%s' and '%s'. By default, '%s' will be used.", authAPIKeySetting, authInstancePrincipalSetting, authAPIKeySetting),
52+
"tenancy_ocid": fmt.Sprintf("(Optional) The tenancy OCID for a user. The tenancy OCID can be found at the bottom of user settings in the Oracle Cloud Infrastructure console. Required if auth is set to '%s', ignored otherwise.", authAPIKeySetting),
53+
"user_ocid": fmt.Sprintf("(Optional) The user OCID. This can be found in user settings in the Oracle Cloud Infrastructure console. Required if auth is set to '%s', ignored otherwise.", authAPIKeySetting),
54+
"fingerprint": fmt.Sprintf("(Optional) The fingerprint for the user's RSA key. This can be found in user settings in the Oracle Cloud Infrastructure console. Required if auth is set to '%s', ignored otherwise.", authAPIKeySetting),
4155
"region": "(Required) The region for API connections (e.g. us-ashburn-1).",
4256
"private_key": "(Optional) A PEM formatted RSA private key for the user.\n" +
43-
"A private_key or a private_key_path must be provided.",
57+
fmt.Sprintf("A private_key or a private_key_path must be provided if auth is set to '%s', ignored otherwise.", authAPIKeySetting),
4458
"private_key_path": "(Optional) The path to the user's PEM formatted private key.\n" +
45-
"A private_key or a private_key_path must be provided.",
59+
fmt.Sprintf("A private_key or a private_key_path must be provided if auth is set to '%s', ignored otherwise.", authAPIKeySetting),
4660
"private_key_password": "(Optional) The password used to secure the private key.",
4761
"disable_auto_retries": "(Optional) Disable Automatic retries for retriable errors.\n" +
4862
"Auto retries were introduced to solve some eventual consistency problems but it also introduced performance issues on destroy operations.",
@@ -61,21 +75,28 @@ func Provider(configfn schema.ConfigureFunc) terraform.ResourceProvider {
6175

6276
func schemaMap() map[string]*schema.Schema {
6377
return map[string]*schema.Schema{
78+
"auth": {
79+
Type: schema.TypeString,
80+
Optional: true,
81+
Description: descriptions["auth"],
82+
DefaultFunc: schema.EnvDefaultFunc("OCI_AUTH", authAPIKeySetting),
83+
ValidateFunc: validation.StringInSlice([]string{authAPIKeySetting, authInstancePrincipalSetting}, true),
84+
},
6485
"tenancy_ocid": {
6586
Type: schema.TypeString,
66-
Required: true,
87+
Optional: true,
6788
Description: descriptions["tenancy_ocid"],
6889
DefaultFunc: schema.EnvDefaultFunc("OCI_TENANCY_OCID", nil),
6990
},
7091
"user_ocid": {
7192
Type: schema.TypeString,
72-
Required: true,
93+
Optional: true,
7394
Description: descriptions["user_ocid"],
7495
DefaultFunc: schema.EnvDefaultFunc("OCI_USER_OCID", nil),
7596
},
7697
"fingerprint": {
7798
Type: schema.TypeString,
78-
Required: true,
99+
Optional: true,
79100
Description: descriptions["fingerprint"],
80101
DefaultFunc: schema.EnvDefaultFunc("OCI_FINGERPRINT", nil),
81102
},
@@ -246,12 +267,22 @@ func getRequiredEnvSetting(s string) string {
246267
return v
247268
}
248269

270+
func validateConfigForAPIKeyAuth(d *schema.ResourceData) error {
271+
_, hasTenancyOCID := d.GetOkExists("tenancy_ocid")
272+
_, hasUserOCID := d.GetOkExists("user_ocid")
273+
_, hasFingerprint := d.GetOkExists("fingerprint")
274+
if !hasTenancyOCID || !hasUserOCID || !hasFingerprint {
275+
return fmt.Errorf("when auth is set to '%s', tenancy_ocid, user_ocid, and fingerprint are required", authAPIKeySetting)
276+
}
277+
return nil
278+
}
279+
249280
func ProviderConfig(d *schema.ResourceData) (clients interface{}, err error) {
250281
clients = &OracleClients{}
251282
disableAutoRetries = d.Get("disable_auto_retries").(bool)
283+
auth := strings.ToLower(d.Get("auth").(string))
252284

253-
userAgent := fmt.Sprintf("Oracle-GoSDK/%s (go/%s; %s/%s; terraform/%s) Oracle-TerraformProvider/%s",
254-
oci_common.Version(), runtime.Version(), runtime.GOOS, runtime.GOARCH, terraform.VersionString(), Version)
285+
userAgent := fmt.Sprintf(userAgentFormatter, oci_common.Version(), runtime.Version(), runtime.GOOS, runtime.GOARCH, terraform.VersionString(), Version)
255286

256287
httpClient := &http.Client{
257288
Timeout: defaultRequestTimeout,
@@ -265,14 +296,31 @@ func ProviderConfig(d *schema.ResourceData) (clients interface{}, err error) {
265296
},
266297
}
267298

268-
tfConfigProvider := ResourceDataConfigProvider{d}
299+
var configProviders []oci_common.ConfigurationProvider
300+
301+
switch auth {
302+
case strings.ToLower(authAPIKeySetting):
303+
if err := validateConfigForAPIKeyAuth(d); err != nil {
304+
return nil, err
305+
}
306+
case strings.ToLower(authInstancePrincipalSetting):
307+
cfg, err := oci_common_auth.InstancePrincipalConfigurationProvider()
308+
if err != nil {
309+
return nil, err
310+
}
311+
configProviders = append(configProviders, cfg)
312+
default:
313+
return nil, fmt.Errorf("auth must be one of '%s' or '%s'", authAPIKeySetting, authInstancePrincipalSetting)
314+
}
315+
316+
configProviders = append(configProviders, ResourceDataConfigProvider{d})
269317

270318
// TODO: DefaultConfigProvider will return us a composingConfigurationProvider that reads from SDK config files,
271319
// and then from the environment variables ("TF_VAR" prefix). References to "TF_VAR" prefix should be removed from
272320
// the SDK, since it's Terraform specific. When that happens, we need to update this to pass in the right prefix.
273-
defaultConfigProvider := oci_common.DefaultConfigProvider()
321+
configProviders = append(configProviders, oci_common.DefaultConfigProvider())
274322

275-
officialSdkConfigProvider, err := oci_common.ComposingConfigurationProvider([]oci_common.ConfigurationProvider{tfConfigProvider, defaultConfigProvider})
323+
officialSdkConfigProvider, err := oci_common.ComposingConfigurationProvider(configProviders)
276324
if err != nil {
277325
return nil, err
278326
}
@@ -322,33 +370,39 @@ func setGoSDKClients(clients *OracleClients, officialSdkConfigProvider oci_commo
322370
return
323371
}
324372

325-
clients.blockStorageClient = &blockStorageClient
326-
clients.blockStorageClient.BaseClient.HTTPClient = httpClient
327-
clients.blockStorageClient.UserAgent = userAgent
373+
useOboToken, err := strconv.ParseBool(getEnvSetting("use_obo_token", "false"))
374+
if err != nil {
375+
return
376+
}
328377

329-
clients.computeClient = &computeClient
330-
clients.computeClient.BaseClient.HTTPClient = httpClient
331-
clients.computeClient.UserAgent = userAgent
378+
var oboTokenProvider oci_common.OboTokenProvider
379+
if useOboToken {
380+
oboTokenProvider = oboTokenProviderFromEnv{}
381+
} else {
382+
oboTokenProvider = oci_common.NewEmptyOboTokenProvider()
383+
}
332384

333-
clients.databaseClient = &databaseClient
334-
clients.databaseClient.BaseClient.HTTPClient = httpClient
335-
clients.databaseClient.UserAgent = userAgent
385+
configureClient := func(client *oci_common.BaseClient) {
386+
client.HTTPClient = httpClient
387+
client.UserAgent = userAgent
388+
client.Obo = oboTokenProvider
389+
}
336390

337-
clients.identityClient = &identityClient
338-
clients.identityClient.BaseClient.HTTPClient = httpClient
339-
clients.identityClient.UserAgent = userAgent
391+
configureClient(&blockStorageClient.BaseClient)
392+
configureClient(&computeClient.BaseClient)
393+
configureClient(&databaseClient.BaseClient)
394+
configureClient(&identityClient.BaseClient)
395+
configureClient(&virtualNetworkClient.BaseClient)
396+
configureClient(&objectStorageClient.BaseClient)
397+
configureClient(&loadBalancerClient.BaseClient)
340398

399+
clients.blockStorageClient = &blockStorageClient
400+
clients.computeClient = &computeClient
401+
clients.databaseClient = &databaseClient
402+
clients.identityClient = &identityClient
341403
clients.virtualNetworkClient = &virtualNetworkClient
342-
clients.virtualNetworkClient.BaseClient.HTTPClient = httpClient
343-
clients.virtualNetworkClient.UserAgent = userAgent
344-
345404
clients.objectStorageClient = &objectStorageClient
346-
clients.objectStorageClient.BaseClient.HTTPClient = httpClient
347-
clients.objectStorageClient.UserAgent = userAgent
348-
349405
clients.loadBalancerClient = &loadBalancerClient
350-
clients.loadBalancerClient.BaseClient.HTTPClient = httpClient
351-
clients.loadBalancerClient.UserAgent = userAgent
352406

353407
return
354408
}
@@ -375,28 +429,28 @@ func (p ResourceDataConfigProvider) TenancyOCID() (string, error) {
375429
if tenancyOCID, ok := p.D.GetOkExists("tenancy_ocid"); ok {
376430
return tenancyOCID.(string), nil
377431
}
378-
return "", fmt.Errorf("Can not get tenancy_ocid from Terraform configuration")
432+
return "", fmt.Errorf("can not get tenancy_ocid from Terraform configuration")
379433
}
380434

381435
func (p ResourceDataConfigProvider) UserOCID() (string, error) {
382436
if userOCID, ok := p.D.GetOkExists("user_ocid"); ok {
383437
return userOCID.(string), nil
384438
}
385-
return "", fmt.Errorf("Can not get user_ocid from Terraform configuration")
439+
return "", fmt.Errorf("can not get user_ocid from Terraform configuration")
386440
}
387441

388442
func (p ResourceDataConfigProvider) KeyFingerprint() (string, error) {
389443
if fingerprint, ok := p.D.GetOkExists("fingerprint"); ok {
390444
return fingerprint.(string), nil
391445
}
392-
return "", fmt.Errorf("Can not get fingerprint from Terraform configuration")
446+
return "", fmt.Errorf("can not get fingerprint from Terraform configuration")
393447
}
394448

395449
func (p ResourceDataConfigProvider) Region() (string, error) {
396450
if region, ok := p.D.GetOkExists("region"); ok {
397451
return region.(string), nil
398452
}
399-
return "", fmt.Errorf("Can not get region from Terraform configuration")
453+
return "", fmt.Errorf("can not get region from Terraform configuration")
400454
}
401455

402456
func (p ResourceDataConfigProvider) KeyID() (string, error) {
@@ -436,5 +490,5 @@ func (p ResourceDataConfigProvider) PrivateRSAKey() (key *rsa.PrivateKey, err er
436490
return oci_common.PrivateKeyFromBytes(pemFileContent, &password)
437491
}
438492

439-
return nil, fmt.Errorf("Can not get private_key or private_key_path from Terraform configuration")
493+
return nil, fmt.Errorf("can not get private_key or private_key_path from Terraform configuration")
440494
}

provider/provider_test.go

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@
33
package provider
44

55
import (
6+
"fmt"
7+
"runtime"
68
"testing"
79

810
"github.com/hashicorp/terraform/helper/schema"
911
"github.com/hashicorp/terraform/terraform"
12+
oci_common "github.com/oracle/oci-go-sdk/common"
1013
"github.com/stretchr/testify/assert"
1114
)
1215

@@ -24,7 +27,6 @@ func init() {
2427
}
2528

2629
func testProviderConfig() string {
27-
2830
return `
2931
provider "oci" {
3032
tenancy_ocid = "ocid.tenancy.aaaa"
@@ -197,6 +199,7 @@ func GetTestProvider() *OracleClients {
197199
d := r.Data(nil)
198200
d.SetId(getRequiredEnvSetting("tenancy_ocid"))
199201

202+
d.Set("auth", getEnvSetting("auth", authAPIKeySetting))
200203
d.Set("tenancy_ocid", getRequiredEnvSetting("tenancy_ocid"))
201204
d.Set("user_ocid", getRequiredEnvSetting("user_ocid"))
202205
d.Set("fingerprint", getRequiredEnvSetting("fingerprint"))
@@ -214,7 +217,6 @@ func GetTestProvider() *OracleClients {
214217

215218
// This test runs the Provider sanity checks.
216219
func TestProvider(t *testing.T) {
217-
218220
// Real client for the sanity check. Makes this more of an acceptance test.
219221
client := &OracleClients{}
220222
if err := Provider(func(d *schema.ResourceData) (interface{}, error) {
@@ -260,23 +262,68 @@ var testKeyFingerPrint = "b4:8a:7d:54:e6:81:04:b2:fa:ce:ba:55:34:dd:00:00"
260262
var testTenancyOCID = "ocid1.tenancy.oc1..faketenancy"
261263
var testUserOCID = "ocid1.user.oc1..fakeuser"
262264

263-
func TestProviderConfig(t *testing.T) {
265+
func providerConfigTest(t *testing.T, disableRetries bool, skipRequiredField bool, auth string) {
264266
r := &schema.Resource{
265267
Schema: schemaMap(),
266268
}
267269
d := r.Data(nil)
268270
d.SetId("tenancy_ocid")
269-
270-
d.Set("tenancy_ocid", testTenancyOCID)
271+
d.Set("auth", auth)
272+
if !skipRequiredField {
273+
d.Set("tenancy_ocid", testTenancyOCID)
274+
}
271275
d.Set("user_ocid", testUserOCID)
272276
d.Set("fingerprint", testKeyFingerPrint)
273277
d.Set("private_key", testPrivateKey)
274278
//d.Set("private_key_path", "")
275279
d.Set("private_key_password", "password")
276280

281+
if disableRetries {
282+
d.Set("disable_auto_retries", disableRetries)
283+
}
284+
277285
client, err := ProviderConfig(d)
286+
287+
switch auth {
288+
case authAPIKeySetting, "":
289+
if skipRequiredField {
290+
assert.Error(t, err, fmt.Sprintf("when auth is set to '%s', tenancy_ocid, user_ocid, and fingerprint are required", authAPIKeySetting))
291+
return
292+
}
293+
case authInstancePrincipalSetting:
294+
assert.Regexp(t, "failed to create a new key provider for instance principal.*", err.Error())
295+
return
296+
default:
297+
assert.Error(t, err, fmt.Sprintf("auth must be one of '%s' or '%s'", authAPIKeySetting, authInstancePrincipalSetting))
298+
return
299+
}
278300
assert.Nil(t, err)
279301
assert.NotNil(t, client)
280-
_, ok := client.(*OracleClients)
302+
303+
oracleClient, ok := client.(*OracleClients)
281304
assert.True(t, ok)
305+
306+
userAgent := fmt.Sprintf(userAgentFormatter, oci_common.Version(), runtime.Version(), runtime.GOOS, runtime.GOARCH, terraform.VersionString(), Version)
307+
testClient := func(c *oci_common.BaseClient) {
308+
assert.NotNil(t, c)
309+
assert.NotNil(t, c.HTTPClient)
310+
assert.Exactly(t, c.UserAgent, userAgent)
311+
assert.NotNil(t, c.Obo)
312+
}
313+
314+
assert.Exactly(t, disableAutoRetries, disableRetries)
315+
testClient(&oracleClient.blockStorageClient.BaseClient)
316+
testClient(&oracleClient.computeClient.BaseClient)
317+
testClient(&oracleClient.databaseClient.BaseClient)
318+
testClient(&oracleClient.identityClient.BaseClient)
319+
testClient(&oracleClient.virtualNetworkClient.BaseClient)
320+
testClient(&oracleClient.objectStorageClient.BaseClient)
321+
testClient(&oracleClient.loadBalancerClient.BaseClient)
322+
}
323+
324+
func TestProviderConfig(t *testing.T) {
325+
providerConfigTest(t, true, true, authAPIKeySetting) // ApiKey with required fields + disable auto-retries
326+
providerConfigTest(t, false, true, authAPIKeySetting) // ApiKey without required fields
327+
providerConfigTest(t, false, false, authInstancePrincipalSetting) // InstancePrincipal
328+
providerConfigTest(t, true, false, "invalid-auth-setting") // Invalid auth + disable auto-retries
282329
}

0 commit comments

Comments
 (0)