diff --git a/ec/armada/resource_armada.go b/ec/armada/resource_armada.go index 0e0e26b..eba75ec 100644 --- a/ec/armada/resource_armada.go +++ b/ec/armada/resource_armada.go @@ -28,7 +28,8 @@ func ResourceArmada() *schema.Resource { } func resourceArmadaRead(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { - clientSet, err := ec.ResolveClientSet(m) + inst, _ := d.Get("instance").(string) + clientSet, err := ec.ResolveClientSet(m, inst) if err != nil { return diag.FromErr(err) } @@ -58,7 +59,8 @@ func resourceArmadaRead(ctx context.Context, d *schema.ResourceData, m any) diag } func resourceArmadaCreate(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { - clientSet, err := ec.ResolveClientSet(m) + inst, _ := d.Get("instance").(string) + clientSet, err := ec.ResolveClientSet(m, inst) if err != nil { return diag.FromErr(err) } @@ -83,7 +85,8 @@ func resourceArmadaCreate(ctx context.Context, d *schema.ResourceData, m any) di } func resourceArmadaUpdate(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { - clientSet, err := ec.ResolveClientSet(m) + inst, _ := d.Get("instance").(string) + clientSet, err := ec.ResolveClientSet(m, inst) if err != nil { return diag.FromErr(err) } @@ -108,7 +111,8 @@ func resourceArmadaUpdate(ctx context.Context, d *schema.ResourceData, m any) di } func resourceArmadaDelete(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { - clientSet, err := ec.ResolveClientSet(m) + inst, _ := d.Get("instance").(string) + clientSet, err := ec.ResolveClientSet(m, inst) if err != nil { return diag.FromErr(err) } diff --git a/ec/armada/resource_armadaset.go b/ec/armada/resource_armadaset.go index 3cf9de6..378a976 100644 --- a/ec/armada/resource_armadaset.go +++ b/ec/armada/resource_armadaset.go @@ -28,7 +28,8 @@ func ResourceArmadaSet() *schema.Resource { } func resourceArmadaSetRead(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { - clientSet, err := ec.ResolveClientSet(m) + inst, _ := d.Get("instance").(string) + clientSet, err := ec.ResolveClientSet(m, inst) if err != nil { return diag.FromErr(err) } @@ -58,7 +59,8 @@ func resourceArmadaSetRead(ctx context.Context, d *schema.ResourceData, m any) d } func resourceArmadaSetCreate(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { - clientSet, err := ec.ResolveClientSet(m) + inst, _ := d.Get("instance").(string) + clientSet, err := ec.ResolveClientSet(m, inst) if err != nil { return diag.FromErr(err) } @@ -83,7 +85,8 @@ func resourceArmadaSetCreate(ctx context.Context, d *schema.ResourceData, m any) } func resourceArmadaSetUpdate(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { - clientSet, err := ec.ResolveClientSet(m) + inst, _ := d.Get("instance").(string) + clientSet, err := ec.ResolveClientSet(m, inst) if err != nil { return diag.FromErr(err) } @@ -108,7 +111,8 @@ func resourceArmadaSetUpdate(ctx context.Context, d *schema.ResourceData, m any) } func resourceArmadaSetDelete(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { - clientSet, err := ec.ResolveClientSet(m) + inst, _ := d.Get("instance").(string) + clientSet, err := ec.ResolveClientSet(m, inst) if err != nil { return diag.FromErr(err) } diff --git a/ec/container/resource_branch.go b/ec/container/resource_branch.go index ee74574..1a16ee7 100644 --- a/ec/container/resource_branch.go +++ b/ec/container/resource_branch.go @@ -28,7 +28,8 @@ func ResourceBranch() *schema.Resource { } func resourceBranchRead(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { - clientSet, err := ec.ResolveClientSet(m) + inst, _ := d.Get("instance").(string) + clientSet, err := ec.ResolveClientSet(m, inst) if err != nil { return diag.FromErr(err) } @@ -58,7 +59,8 @@ func resourceBranchRead(ctx context.Context, d *schema.ResourceData, m any) diag } func resourceBranchCreate(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { - clientSet, err := ec.ResolveClientSet(m) + inst, _ := d.Get("instance").(string) + clientSet, err := ec.ResolveClientSet(m, inst) if err != nil { return diag.FromErr(err) } @@ -83,7 +85,8 @@ func resourceBranchCreate(ctx context.Context, d *schema.ResourceData, m any) di } func resourceBranchUpdate(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { - clientSet, err := ec.ResolveClientSet(m) + inst, _ := d.Get("instance").(string) + clientSet, err := ec.ResolveClientSet(m, inst) if err != nil { return diag.FromErr(err) } @@ -108,7 +111,8 @@ func resourceBranchUpdate(ctx context.Context, d *schema.ResourceData, m any) di } func resourceBranchDelete(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { - clientSet, err := ec.ResolveClientSet(m) + inst, _ := d.Get("instance").(string) + clientSet, err := ec.ResolveClientSet(m, inst) if err != nil { return diag.FromErr(err) } diff --git a/ec/core/resource_core_environment.go b/ec/core/resource_core_environment.go index dfd3d27..3609c05 100644 --- a/ec/core/resource_core_environment.go +++ b/ec/core/resource_core_environment.go @@ -28,7 +28,8 @@ func ResourceEnvironment() *schema.Resource { } func resourceEnvironmentRead(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { - clientSet, err := ec.ResolveClientSet(m) + inst, _ := d.Get("instance").(string) + clientSet, err := ec.ResolveClientSet(m, inst) if err != nil { return diag.FromErr(err) } @@ -58,7 +59,8 @@ func resourceEnvironmentRead(ctx context.Context, d *schema.ResourceData, m any) } func resourceEnvironmentCreate(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { - clientSet, err := ec.ResolveClientSet(m) + inst, _ := d.Get("instance").(string) + clientSet, err := ec.ResolveClientSet(m, inst) if err != nil { return diag.FromErr(err) } @@ -81,7 +83,8 @@ func resourceEnvironmentCreate(ctx context.Context, d *schema.ResourceData, m an } func resourceEnvironmentUpdate(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { - clientSet, err := ec.ResolveClientSet(m) + inst, _ := d.Get("instance").(string) + clientSet, err := ec.ResolveClientSet(m, inst) if err != nil { return diag.FromErr(err) } @@ -104,7 +107,8 @@ func resourceEnvironmentUpdate(ctx context.Context, d *schema.ResourceData, m an } func resourceEnvironmentDelete(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { - clientSet, err := ec.ResolveClientSet(m) + inst, _ := d.Get("instance").(string) + clientSet, err := ec.ResolveClientSet(m, inst) if err != nil { return diag.FromErr(err) } diff --git a/ec/core/resource_core_region.go b/ec/core/resource_core_region.go index 617d223..fbdc7e2 100644 --- a/ec/core/resource_core_region.go +++ b/ec/core/resource_core_region.go @@ -28,7 +28,8 @@ func ResourceRegion() *schema.Resource { } func resourceRegionRead(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { - clientSet, err := ec.ResolveClientSet(m) + inst, _ := d.Get("instance").(string) + clientSet, err := ec.ResolveClientSet(m, inst) if err != nil { return diag.FromErr(err) } @@ -58,7 +59,8 @@ func resourceRegionRead(ctx context.Context, d *schema.ResourceData, m any) diag } func resourceRegionCreate(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { - clientSet, err := ec.ResolveClientSet(m) + inst, _ := d.Get("instance").(string) + clientSet, err := ec.ResolveClientSet(m, inst) if err != nil { return diag.FromErr(err) } @@ -83,7 +85,8 @@ func resourceRegionCreate(ctx context.Context, d *schema.ResourceData, m any) di } func resourceRegionUpdate(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { - clientSet, err := ec.ResolveClientSet(m) + inst, _ := d.Get("instance").(string) + clientSet, err := ec.ResolveClientSet(m, inst) if err != nil { return diag.FromErr(err) } @@ -108,7 +111,8 @@ func resourceRegionUpdate(ctx context.Context, d *schema.ResourceData, m any) di } func resourceRegionDelete(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { - clientSet, err := ec.ResolveClientSet(m) + inst, _ := d.Get("instance").(string) + clientSet, err := ec.ResolveClientSet(m, inst) if err != nil { return diag.FromErr(err) } diff --git a/ec/core/resource_core_site.go b/ec/core/resource_core_site.go index c50d99c..2a7926e 100644 --- a/ec/core/resource_core_site.go +++ b/ec/core/resource_core_site.go @@ -28,7 +28,8 @@ func ResourceSite() *schema.Resource { } func resourceSiteRead(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { - clientSet, err := ec.ResolveClientSet(m) + inst, _ := d.Get("instance").(string) + clientSet, err := ec.ResolveClientSet(m, inst) if err != nil { return diag.FromErr(err) } @@ -58,7 +59,8 @@ func resourceSiteRead(ctx context.Context, d *schema.ResourceData, m any) diag.D } func resourceSiteCreate(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { - clientSet, err := ec.ResolveClientSet(m) + inst, _ := d.Get("instance").(string) + clientSet, err := ec.ResolveClientSet(m, inst) if err != nil { return diag.FromErr(err) } @@ -83,7 +85,8 @@ func resourceSiteCreate(ctx context.Context, d *schema.ResourceData, m any) diag } func resourceSiteUpdate(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { - clientSet, err := ec.ResolveClientSet(m) + inst, _ := d.Get("instance").(string) + clientSet, err := ec.ResolveClientSet(m, inst) if err != nil { return diag.FromErr(err) } @@ -108,7 +111,8 @@ func resourceSiteUpdate(ctx context.Context, d *schema.ResourceData, m any) diag } func resourceSiteDelete(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { - clientSet, err := ec.ResolveClientSet(m) + inst, _ := d.Get("instance").(string) + clientSet, err := ec.ResolveClientSet(m, inst) if err != nil { return diag.FromErr(err) } diff --git a/ec/core/resource_core_site_test.go b/ec/core/resource_core_site_test.go index 4494c7c..7d10af5 100644 --- a/ec/core/resource_core_site_test.go +++ b/ec/core/resource_core_site_test.go @@ -24,6 +24,7 @@ func TestResourceSites(t *testing.T) { { Config: testResourceSitesConfigBasic(name), Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("ec_core_site.test", "instance", "test"), resource.TestCheckResourceAttr("ec_core_site.test", "metadata.0.name", name), resource.TestCheckResourceAttr("ec_core_site.test", "spec.#", "1"), resource.TestCheckResourceAttr("ec_core_site.test", "spec.0.description", "My Site"), @@ -38,6 +39,7 @@ func TestResourceSites(t *testing.T) { { Config: testResourceSitesConfigBasic2(name), Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("ec_core_site.test", "instance", "test"), resource.TestCheckResourceAttr("ec_core_site.test", "metadata.0.name", name), resource.TestCheckResourceAttr("ec_core_site.test", "metadata.0.labels.foo", "bar"), resource.TestCheckResourceAttr("ec_core_site.test", "spec.#", "1"), @@ -53,6 +55,7 @@ func TestResourceSites(t *testing.T) { { Config: testResourceSitesConfigBasicWithEnv(name), Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("ec_core_site.test", "instance", "test"), resource.TestCheckResourceAttr("ec_core_site.test", "metadata.0.name", name), resource.TestCheckResourceAttr("ec_core_site.test", "spec.#", "1"), resource.TestCheckResourceAttr("ec_core_site.test", "spec.0.description", "My Other Site"), @@ -72,9 +75,10 @@ func TestResourceSites(t *testing.T) { ), }, { - ResourceName: "ec_core_site.test", - ImportState: true, - ImportStateVerify: true, + ResourceName: "ec_core_site.test", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"instance"}, }, }, }) @@ -82,6 +86,7 @@ func TestResourceSites(t *testing.T) { func testResourceSitesConfigBasic(name string) string { return fmt.Sprintf(`resource "ec_core_site" "test" { + instance = "test" metadata { name = "%s" } @@ -103,6 +108,7 @@ func testResourceSitesConfigBasic(name string) string { func testResourceSitesConfigBasic2(name string) string { return fmt.Sprintf(`resource "ec_core_site" "test" { + instance = "test" metadata { name = "%s" labels = { @@ -127,6 +133,7 @@ func testResourceSitesConfigBasic2(name string) string { func testResourceSitesConfigBasicWithEnv(name string) string { return fmt.Sprintf(`resource "ec_core_site" "test" { + instance = "test" metadata { name = "%s" } @@ -168,7 +175,7 @@ func testResourceSitesConfigBasicWithEnv(name string) string { func testCheckSiteDestroy(cs clientset.Interface) func(s *terraform.State) error { return func(s *terraform.State) error { for _, rs := range s.RootModule().Resources { - if rs.Type != "ec_armada_site" { + if rs.Type != "ec_core_site" { continue } @@ -176,7 +183,7 @@ func testCheckSiteDestroy(cs clientset.Interface) func(s *terraform.State) error resp, err := cs.CoreV1().Sites().Get(context.Background(), name, metav1.GetOptions{}) if err == nil { if resp.Name == rs.Primary.ID { - return fmt.Errorf("Site still exists: %s", rs.Primary.ID) + return fmt.Errorf("site still exists: %s", rs.Primary.ID) } } } diff --git a/ec/core/schema_environment.go b/ec/core/schema_environment.go index 8360b85..7760665 100644 --- a/ec/core/schema_environment.go +++ b/ec/core/schema_environment.go @@ -9,6 +9,11 @@ import ( func environmentSchema() map[string]*schema.Schema { return map[string]*schema.Schema{ + "instance": { + Type: schema.TypeString, + Description: "Name is an instance name configured in the provider.", + Optional: true, + }, "metadata": { Type: schema.TypeList, Description: "Standard object's metadata.", diff --git a/ec/ec.go b/ec/ec.go index 5133566..0efa19c 100644 --- a/ec/ec.go +++ b/ec/ec.go @@ -2,6 +2,7 @@ package ec import ( "errors" + "fmt" "strings" "github.com/nitrado/tfconv" @@ -9,13 +10,42 @@ import ( "k8s.io/apimachinery/pkg/api/resource" ) +// ProviderContext contains connection context information. +type ProviderContext struct { + defaultCS clientset.Interface + instances map[string]clientset.Interface +} + +// NewProviderContext returns a provider context with the given default clientset and instances. +func NewProviderContext(defCS clientset.Interface, instances map[string]clientset.Interface) ProviderContext { + if instances == nil { + instances = map[string]clientset.Interface{} + } + return ProviderContext{ + defaultCS: defCS, + instances: instances, + } +} + // ResolveClientSet resolves the ClientSet from the given context. -func ResolveClientSet(m any) (clientset.Interface, error) { - clientSet, ok := m.(clientset.Interface) +func ResolveClientSet(m any, name string) (clientset.Interface, error) { + connCtx, ok := m.(ProviderContext) if !ok { - return nil, errors.New("invalid clientset") + return nil, errors.New("invalid connection context") + } + + if name == "" { + if connCtx.defaultCS == nil { + return nil, errors.New("no default clientset found") + } + return connCtx.defaultCS, nil + } + + cs, ok := connCtx.instances[name] + if !ok || cs == nil { + return nil, fmt.Errorf("instance %q clientset not found", name) } - return clientSet, nil + return cs, nil } // ScopedName returns the encoded name of an object. diff --git a/ec/provider/provider.go b/ec/provider/provider.go index 343dc43..9f95f2b 100644 --- a/ec/provider/provider.go +++ b/ec/provider/provider.go @@ -2,11 +2,13 @@ package provider import ( "context" + "errors" "fmt" "net/url" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/nitrado/terraform-provider-ec/ec" "github.com/nitrado/terraform-provider-ec/ec/armada" "github.com/nitrado/terraform-provider-ec/ec/container" "github.com/nitrado/terraform-provider-ec/ec/core" @@ -22,40 +24,84 @@ func Provider() *schema.Provider { Schema: map[string]*schema.Schema{ "host": { Type: schema.TypeString, - Required: true, - DefaultFunc: schema.EnvDefaultFunc("ARMADA_HOST", ""), - Description: "The hostname (in form of URI) of Armada API.", + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("EC_HOST", ""), + Description: "The hostname (in form of URI) of the Enterprise Console API.", }, "token_endpoint": { Type: schema.TypeString, Optional: true, - DefaultFunc: schema.EnvDefaultFunc("ARMADA_TOKEN_ENDPOINT", ""), + DefaultFunc: schema.EnvDefaultFunc("EC_TOKEN_ENDPOINT", ""), Description: "The URI to the token authentication endpoint.", }, "client_id": { Type: schema.TypeString, - Required: true, - DefaultFunc: schema.EnvDefaultFunc("ARMADA_CLIENT_ID", ""), + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("EC_CLIENT_ID", ""), Description: "The oAuth2 client id to authenticate against.", }, "client_secret": { Type: schema.TypeString, Optional: true, - DefaultFunc: schema.EnvDefaultFunc("ARMADA_CLIENT_SECRET", ""), + DefaultFunc: schema.EnvDefaultFunc("EC_CLIENT_SECRET", ""), Description: "The oAuth2 client secret to authenticate against.", }, "username": { Type: schema.TypeString, Optional: true, - DefaultFunc: schema.EnvDefaultFunc("ARMADA_USERNAME", ""), + DefaultFunc: schema.EnvDefaultFunc("EC_USERNAME", ""), Description: "The user to authenticate with.", }, "password": { Type: schema.TypeString, Optional: true, - DefaultFunc: schema.EnvDefaultFunc("ARMADA_PASSWORD", ""), + DefaultFunc: schema.EnvDefaultFunc("EC_PASSWORD", ""), Description: "The password to authenticate with.", }, + "instances": { + Type: schema.TypeList, + Optional: true, + Description: "Named Enterprise Console instances.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + Description: "The instance name.", + }, + "host": { + Type: schema.TypeString, + Required: true, + Description: "The hostname (in form of URI) of the Enterprise Console API.", + }, + "token_endpoint": { + Type: schema.TypeString, + Optional: true, + Description: "The URI to the token authentication endpoint.", + }, + "client_id": { + Type: schema.TypeString, + Required: true, + Description: "The oAuth2 client id to authenticate against.", + }, + "client_secret": { + Type: schema.TypeString, + Optional: true, + Description: "The oAuth2 client secret to authenticate against.", + }, + "username": { + Type: schema.TypeString, + Optional: true, + Description: "The user to authenticate with.", + }, + "password": { + Type: schema.TypeString, + Optional: true, + Description: "The password to authenticate with.", + }, + }, + }, + }, }, DataSourcesMap: map[string]*schema.Resource{ "ec_armada_armada": armada.DataSourceArmada(), @@ -95,77 +141,130 @@ func Provider() *schema.Provider { } func providerConfigure(_ context.Context, d *schema.ResourceData, _ string) (any, diag.Diagnostics) { - //nolint:contextcheck - tok, err := resolveToken(d) - if err != nil { - return nil, diag.FromErr(err) + var defaultClientSet clientset.Interface + if v, ok := d.Get("host").(string); ok && v != "" { + var err error + defaultClientSet, err = createClientSet("", collectConnData(d)) + if err != nil { + return nil, diag.FromErr(err) + } } - if _, err = tok.Token(); err != nil { - return nil, diag.FromErr(err) + + var instances map[string]clientset.Interface + if insts, ok := d.Get("instances").([]any); ok { + instances = make(map[string]clientset.Interface, len(insts)) + for _, v := range insts { + inst := v.(map[string]any) + + name := inst["name"].(string) + cs, err := createClientSet(name, inst) + if err != nil { + return nil, diag.FromErr(err) + } + instances[name] = cs + } } - cfg := createRESTConfig(d) - cfg.BearerTokenSource = tok + if defaultClientSet == nil && len(instances) == 0 { + return nil, diag.FromErr(errors.New("at least one instance or default connection details must be provided")) + } + return ec.NewProviderContext(defaultClientSet, instances), nil +} + +func collectConnData(d *schema.ResourceData) map[string]any { + return map[string]any{ + "host": d.Get("host"), + "token_endpoint": d.Get("token_endpoint"), + "client_id": d.Get("client_id"), + "client_secret": d.Get("client_secret"), + "username": d.Get("username"), + "password": d.Get("password"), + } +} + +func createClientSet(name string, m map[string]any) (clientset.Interface, error) { + var forInstance string + if name != "" { + forInstance = `for instance "` + name + `"` + } + + //nolint:contextcheck + tok, err := resolveToken(m) + if err != nil { + return nil, fmt.Errorf("retrieveing token %s: %w", forInstance, err) + } + cfg := createRESTConfig(m, tok) clientSet, err := clientset.New(cfg) if err != nil { - return nil, diag.FromErr(fmt.Errorf("could not to configure client: %w", err)) + return nil, fmt.Errorf("could not to configure client %s: %w", forInstance, err) } return clientSet, nil } -func createRESTConfig(d *schema.ResourceData) rest.Config { +func createRESTConfig(m map[string]any, tok oauth2.TokenSource) rest.Config { var cfg rest.Config - cfg.BaseURL = d.Get("host").(string) + cfg.BaseURL = m["host"].(string) + cfg.BearerTokenSource = tok return cfg } -func resolveToken(d *schema.ResourceData) (oauth2.TokenSource, error) { - tokURL, err := resolveTokenURL(d) +func resolveToken(m map[string]any) (oauth2.TokenSource, error) { + tokURL, err := resolveTokenURL(m) if err != nil { return nil, err } - clientID := d.Get("client_id").(string) - clientSecret, hasClientSecret := d.Get("client_secret").(string) - user, hasUser := d.Get("username").(string) - pass, hasPass := d.Get("password").(string) + clientID, ok := m["client_id"].(string) + if !ok { + return nil, fmt.Errorf("client id is required") + } + + clientSecret, hasClientSecret := m["client_secret"].(string) + user, hasUser := m["username"].(string) + pass, hasPass := m["password"].(string) switch { case hasUser && hasPass: - cfg := oauth2.Config{ - ClientID: clientID, - Endpoint: oauth2.Endpoint{ - TokenURL: tokURL, - AuthStyle: oauth2.AuthStyleInHeader, - }, - } - tok, err := cfg.PasswordCredentialsToken(context.Background(), user, pass) - if err != nil { - return nil, err - } - return cfg.TokenSource(context.Background(), tok), nil + return newLazyTokenSource(func() (oauth2.TokenSource, error) { + cfg := oauth2.Config{ + ClientID: clientID, + Scopes: []string{"openid", "email", "profile", "offline_access"}, + Endpoint: oauth2.Endpoint{ + AuthStyle: oauth2.AuthStyleInHeader, + TokenURL: tokURL, + }, + } + tok, err := cfg.PasswordCredentialsToken(context.Background(), user, pass) + if err != nil { + return nil, err + } + return cfg.TokenSource(context.Background(), tok), nil + }), nil case hasClientSecret: - cfg := clientcredentials.Config{ - ClientID: clientID, - ClientSecret: clientSecret, - TokenURL: tokURL, - AuthStyle: oauth2.AuthStyleInHeader, - } - return cfg.TokenSource(context.Background()), nil + return newLazyTokenSource(func() (oauth2.TokenSource, error) { + cfg := clientcredentials.Config{ + ClientID: clientID, + ClientSecret: clientSecret, + TokenURL: tokURL, + Scopes: []string{"openid", "email", "profile", "offline_access"}, + AuthStyle: oauth2.AuthStyleInHeader, + } + return cfg.TokenSource(context.Background()), nil + }), nil default: return nil, fmt.Errorf("either client_secret or username and password must be set") } } -func resolveTokenURL(d *schema.ResourceData) (string, error) { - if tokenEndpoint, ok := d.Get("token_endpoint").(string); ok && tokenEndpoint != "" { +func resolveTokenURL(m map[string]any) (string, error) { + if tokenEndpoint, ok := m["token_endpoint"].(string); ok && tokenEndpoint != "" { return tokenEndpoint, nil } - host := d.Get("host").(string) + host := m["host"].(string) u, err := url.Parse(host) if err != nil { return "", fmt.Errorf("invalid host: %w", err) @@ -174,3 +273,23 @@ func resolveTokenURL(d *schema.ResourceData) (string, error) { u.Path = "/auth/realms/enterprise-console/protocol/openid-connect/token" return u.String(), nil } + +type lazyTokenSource struct { + ts oauth2.TokenSource + new func() (oauth2.TokenSource, error) +} + +func newLazyTokenSource(new func() (oauth2.TokenSource, error)) *lazyTokenSource { + return &lazyTokenSource{new: new} +} + +func (s *lazyTokenSource) Token() (*oauth2.Token, error) { + if s.ts == nil { + var err error + s.ts, err = s.new() + if err != nil { + return nil, err + } + } + return s.ts.Token() +} diff --git a/ec/provider/providertest/provider.go b/ec/provider/providertest/provider.go index 99abcac..b4d23ed 100644 --- a/ec/provider/providertest/provider.go +++ b/ec/provider/providertest/provider.go @@ -6,6 +6,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/nitrado/terraform-provider-ec/ec" "github.com/nitrado/terraform-provider-ec/ec/provider" "github.com/stretchr/testify/require" "gitlab.com/nitrado/b2b/ec/apicore/runtime" @@ -25,7 +26,9 @@ func SetupProviderFactories(t *testing.T, objs ...runtime.Object) (map[string]fu "ec": func() (*schema.Provider, error) { p := provider.Provider() p.ConfigureContextFunc = func(context.Context, *schema.ResourceData) (any, diag.Diagnostics) { - return cs, nil + return ec.NewProviderContext(cs, map[string]clientset.Interface{ + "test": cs, + }), nil } return p, nil }, diff --git a/internal/cmd/schema-gen/gen.go b/internal/cmd/schema-gen/gen.go index 5faef79..cb9e727 100644 --- a/internal/cmd/schema-gen/gen.go +++ b/internal/cmd/schema-gen/gen.go @@ -39,24 +39,35 @@ func (g *Generator) Generate(obj any, pkgName, fnName string) ([]byte, error) { return nil, err } - var needsMeta bool + var imports []string for _, s := range fields { - if strings.Contains(s, "meta.") { - needsMeta = true + if !strings.Contains(s, "meta.") { + continue } + imports = append(imports, "github.com/nitrado/terraform-provider-ec/ec/meta") + } + + var isAPIObj bool + for name := range fields { + if name != "metadata" { + continue + } + isAPIObj = true } buf := &bytes.Buffer{} err = fileTmpl.Execute(buf, struct { - PkgName string - FuncName string - Fields map[string]string - NeedsMeta bool + PkgName string + Imports []string + FuncName string + Fields map[string]string + IsAPIObj bool }{ - PkgName: pkgName, - FuncName: fnName, - Fields: fields, - NeedsMeta: needsMeta, + PkgName: pkgName, + Imports: imports, + FuncName: fnName, + Fields: fields, + IsAPIObj: isAPIObj, }) if err != nil { return nil, err @@ -136,10 +147,12 @@ var fileTmpl = template.Must(template.New("code").Parse(`package {{.PkgName}} // Code generated by schema-gen. DO NOT EDIT. -{{ if .NeedsMeta }} +{{ if .Imports }} import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/nitrado/terraform-provider-ec/ec/meta" +{{- range $import := .Imports }} + "{{ $import }}" +{{- end }} ) {{- else }} import "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -147,6 +160,13 @@ import "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" func {{.FuncName}}() map[string]*schema.Schema { return map[string]*schema.Schema{ +{{- if .IsAPIObj }} + "instance": { + Type: schema.TypeString, + Description: "Name is an instance name configured in the provider.", + Optional: true, + }, +{{- end }} {{- range $name, $schema := .Fields }} "{{ $name }}": {{ $schema }}, {{- end }}