diff --git a/.github/workflows/acceptance_tests_lite.yaml b/.github/workflows/acceptance_tests_lite.yaml index e8c91e50..3dcccbcb 100644 --- a/.github/workflows/acceptance_tests_lite.yaml +++ b/.github/workflows/acceptance_tests_lite.yaml @@ -30,7 +30,8 @@ jobs: - run: make testacc TESTARGS="-run='TestAccResourceVmcSddcZerocloud|TestAccResourceVmcClusterZerocloud|TestAccResourceVmcSiteRecoveryZerocloud|TestAccResourceVmcSrmNodeZerocloud|TestAccDataSourceVmcCustomerSubnetsBasic|TestAccDataSourceVmcConnectedAccountsBasic|TestAccDataSourceVmcOrgBasic|TestAccDataSourceVmcSddcBasic|TestAccResourceSddcGroupZerocloud' -parallel 4" env: TF_ACC: '1' - API_TOKEN: ${{ secrets.API_TOKEN }} + CLIENT_ID: ${{ secrets.CLIENT_ID }} + CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }} AWS_ACCOUNT_NUMBER: ${{ secrets.AWS_ACCOUNT_NUMBER }} CSP_URL: ${{ secrets.CSP_URL }} ORG_DISPLAY_NAME: ${{ secrets.ORG_DISPLAY_NAME }} diff --git a/go.mod b/go.mod index c0b4afd3..0dfdbf54 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/vmware/vsphere-automation-sdk-go/services/vmc v0.10.0 github.com/vmware/vsphere-automation-sdk-go/services/vmc/autoscaler v0.4.0 github.com/vmware/vsphere-automation-sdk-go/services/vmc/draas v0.4.0 + golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d ) require ( diff --git a/go.sum b/go.sum index c63f8cd7..fbc9be5c 100644 --- a/go.sum +++ b/go.sum @@ -285,6 +285,7 @@ golang.org/x/net v0.0.0-20210510120150-4163338589ed h1:p9UgmWI9wKpfYmgaV/IZKGdXc golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/vmc/connector/clientconnector.go b/vmc/connector/clientconnector.go index af54e22d..9fe795bc 100644 --- a/vmc/connector/clientconnector.go +++ b/vmc/connector/clientconnector.go @@ -5,10 +5,12 @@ package connector import ( + "context" "encoding/json" "errors" "fmt" "github.com/vmware/terraform-provider-vmc/vmc/constants" + "golang.org/x/oauth2/clientcredentials" "io" "net/http" "reflect" @@ -26,23 +28,39 @@ type Authenticator interface { type Wrapper struct { client.Connector RefreshToken string + ClientID string + ClientSecret string OrgID string VmcURL string CspURL string } +func CopyWrapper(original Wrapper) *Wrapper { + return &original +} + func (c *Wrapper) Authenticate() error { var err error httpClient := http.Client{} - c.Connector, err = NewClientConnectorByRefreshToken(c.RefreshToken, c.VmcURL, c.CspURL, httpClient) - if err != nil { - return err + if len(c.RefreshToken) > 0 { + c.Connector, err = newClientConnectorByRefreshToken(c.RefreshToken, c.VmcURL, c.CspURL, httpClient) + if err != nil { + return err + } + return nil + } + if len(c.ClientID) > 0 && len(c.ClientSecret) > 0 { + c.Connector, err = newClientConnectorByClientID(c.ClientID, c.ClientSecret, c.VmcURL, c.CspURL, httpClient) + if err != nil { + return err + } + return nil } - return nil + return fmt.Errorf("no refreshToken or ClientID/ClientSecret provided") } -// NewClientConnectorByRefreshToken returns client connector to any VMC service by using OAuth authentication using Refresh Token. -func NewClientConnectorByRefreshToken(refreshToken, serviceURL, cspURL string, +// newClientConnectorByRefreshToken returns client connector to any VMC service by using OAuth authentication using Refresh Token. +func newClientConnectorByRefreshToken(refreshToken, serviceURL, cspURL string, httpClient http.Client) (client.Connector, error) { if len(serviceURL) <= 0 { @@ -57,7 +75,7 @@ func NewClientConnectorByRefreshToken(refreshToken, serviceURL, cspURL string, constants.CspRefreshURLSuffix } - securityCtx, err := SecurityContextByRefreshToken(refreshToken, cspURL) + securityCtx, err := securityContextByRefreshToken(refreshToken, cspURL) if err != nil { return nil, err } @@ -69,7 +87,7 @@ func NewClientConnectorByRefreshToken(refreshToken, serviceURL, cspURL string, } // SecurityContextByRefreshToken returns Security Context with access token that is received from Cloud Service Provider using Refresh Token by OAuth authentication scheme. -func SecurityContextByRefreshToken(refreshToken string, cspURL string) (core.SecurityContext, error) { +func securityContextByRefreshToken(refreshToken string, cspURL string) (core.SecurityContext, error) { payload := strings.NewReader("refresh_token=" + refreshToken) req, _ := http.NewRequest("POST", cspURL, payload) @@ -82,15 +100,68 @@ func SecurityContextByRefreshToken(refreshToken string, cspURL string) (core.Sec return nil, err } - if res.StatusCode != 200 { - b, _ := io.ReadAll(res.Body) - return nil, fmt.Errorf("response from Cloud Service Provider contains status code %d : %s", res.StatusCode, string(b)) + securityCtx, err := parseAuthnResponse(res) + if err != nil { + return nil, err } + return securityCtx, nil +} - defer res.Body.Close() +// newClientConnectorByClientID returns client connector to any VMC service by using OAuth authentication using clientId and secret. +func newClientConnectorByClientID(clientID, clientSecret, serviceURL, cspURL string, + httpClient http.Client) (client.Connector, error) { + + if len(serviceURL) <= 0 { + serviceURL = constants.DefaultVmcURL + } + + if len(cspURL) <= 0 { + cspURL = constants.DefaultCspURL + + constants.CspTokenURLSuffix + } else { + cspURL = cspURL + + constants.CspTokenURLSuffix + } + + securityCtx, err := securityContextByClientID(clientID, clientSecret, cspURL) + if err != nil { + return nil, err + } + + connector := client.NewRestConnector(serviceURL, httpClient) + connector.SetSecurityContext(securityCtx) + + return connector, nil +} + +func securityContextByClientID(clientID string, clientSecret string, cspTokenEndpointURL string) (core.SecurityContext, error) { + oauth2Config := clientcredentials.Config{ + ClientID: clientID, + ClientSecret: clientSecret, + TokenURL: cspTokenEndpointURL, + } + token, err := oauth2Config.Token(context.TODO()) + if err != nil { + return nil, err + } + return security.NewOauthSecurityContext(token.AccessToken), nil +} + +func parseAuthnResponse(response *http.Response) (*security.OauthSecurityContext, error) { + if response.StatusCode != 200 { + b, _ := io.ReadAll(response.Body) + return nil, fmt.Errorf("response from Cloud Service Provider contains status code %d : %s", response.StatusCode, string(b)) + } + + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + fmt.Println(err) + } + }(response.Body) var jsondata map[string]interface{} - err = json.NewDecoder(res.Body).Decode(&jsondata) + err := json.NewDecoder(response.Body).Decode(&jsondata) if err != nil { return nil, fmt.Errorf("error decoding response : %v", err) } @@ -104,7 +175,7 @@ func SecurityContextByRefreshToken(refreshToken string, cspURL string) (core.Sec return nil, errors.New(errMsg) } } else { - return nil, errors.New("Cloud Service Provider authentication response does not contain access token") + return nil, errors.New("cloud Service Provider authentication response does not contain access token") } securityCtx := security.NewOauthSecurityContext(accessToken) diff --git a/vmc/constants/constants.go b/vmc/constants/constants.go index 4b208dab..3099d5f8 100644 --- a/vmc/constants/constants.go +++ b/vmc/constants/constants.go @@ -10,9 +10,12 @@ const ( // DefaultCspURL defines the default URL for CSP. DefaultCspURL string = "https://console.cloud.vmware.com" - // CspRefreshURLSuffix defines the CSP Refresh API endpoint. + // CspRefreshURLSuffix defines the CSP Refresh Token API endpoint. CspRefreshURLSuffix string = "/csp/gateway/am/api/auth/api-tokens/authorize" + // CspTokenURLSuffix defines the CSP Oauth API endpoint. + CspTokenURLSuffix string = "/csp/gateway/am/api/auth/token" + // sksNSXTManager to be stripped from nsxt reverse proxy url for public IP resource SksNSXTManager string = "/sks-nsxt-manager" @@ -67,6 +70,8 @@ const ( VmcURL string = "VMC_URL" CspURL string = "CSP_URL" APIToken string = "API_TOKEN" + ClientID string = "CLIENT_ID" + ClientSecret string = "CLIENT_SECRET" OrgID string = "ORG_ID" OrgDisplayName string = "ORG_DISPLAY_NAME" // TestSddcID ID of an existing SDDC used for sddc data source, site recovery and srm node tests diff --git a/vmc/data_source_vmc_sddc.go b/vmc/data_source_vmc_sddc.go index 91183f42..c673eeea 100644 --- a/vmc/data_source_vmc_sddc.go +++ b/vmc/data_source_vmc_sddc.go @@ -212,9 +212,14 @@ func dataSourceVmcSddcRead(d *schema.ResourceData, m interface{}) error { d.Set("nsxt_ui", *sddc.ResourceConfig.Nsxt) if sddc.ResourceConfig.NsxCloudAdmin != nil { d.Set("nsxt_cloudadmin", *sddc.ResourceConfig.NsxCloudAdmin) - d.Set("nsxt_cloudadmin_password", *sddc.ResourceConfig.NsxCloudAdminPassword) + // Evade nil pointer dereference when user's access_token doesn't have NSX roles + if sddc.ResourceConfig.NsxCloudAdminPassword != nil { + _ = d.Set("nsxt_cloudadmin_password", *sddc.ResourceConfig.NsxCloudAdminPassword) + } + if sddc.ResourceConfig.NsxCloudAuditPassword != nil { + _ = d.Set("nsxt_cloudaudit_password", *sddc.ResourceConfig.NsxCloudAuditPassword) + } d.Set("nsxt_cloudaudit", *sddc.ResourceConfig.NsxCloudAudit) - d.Set("nsxt_cloudaudit_password", *sddc.ResourceConfig.NsxCloudAuditPassword) d.Set("nsxt_private_ip", *sddc.ResourceConfig.NsxMgrManagementIp) d.Set("nsxt_private_url", *sddc.ResourceConfig.NsxMgrLoginUrl) } diff --git a/vmc/provider.go b/vmc/provider.go index cd08a3ba..6eb626dc 100644 --- a/vmc/provider.go +++ b/vmc/provider.go @@ -5,12 +5,9 @@ package vmc import ( "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/vmware/terraform-provider-vmc/vmc/connector" "github.com/vmware/terraform-provider-vmc/vmc/constants" - "net/http" - "os" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) // Provider for VMware VMC Console APIs. Returns terraform.ResourceProvider @@ -18,9 +15,24 @@ func Provider() *schema.Provider { return &schema.Provider{ Schema: map[string]*schema.Schema{ "refresh_token": { - Type: schema.TypeString, - Required: true, - DefaultFunc: schema.EnvDefaultFunc(constants.APIToken, nil), + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc(constants.APIToken, nil), + ConflictsWith: []string{"client_id", "client_secret"}, + }, + "client_id": { + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc(constants.ClientID, nil), + ConflictsWith: []string{"refresh_token"}, + RequiredWith: []string{"client_secret"}, + }, + "client_secret": { + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc(constants.ClientSecret, nil), + ConflictsWith: []string{"refresh_token"}, + RequiredWith: []string{"client_id"}, }, "org_id": { Type: schema.TypeString, @@ -61,26 +73,26 @@ func Provider() *schema.Provider { func providerConfigure(d *schema.ResourceData) (interface{}, error) { refreshToken := d.Get("refresh_token").(string) - if len(refreshToken) <= 0 { - return nil, fmt.Errorf("refresh token cannot be empty") + clientID := d.Get("client_id").(string) + clientSecret := d.Get("client_secret").(string) + if len(refreshToken) == 0 && len(clientID) == 0 && len(clientSecret) == 0 { + return nil, fmt.Errorf("must provide value for refresh_token or client_id and client_secret") } - // set refresh token to env variable so that it can be used by other connectors - _ = os.Setenv(constants.APIToken, refreshToken) vmcURL := d.Get("vmc_url").(string) cspURL := d.Get("csp_url").(string) - _ = os.Setenv(constants.CspURL, cspURL) orgID := d.Get("org_id").(string) - httpClient := http.Client{} - apiConnector, err := connector.NewClientConnectorByRefreshToken(refreshToken, vmcURL, cspURL, httpClient) + connectorWrapper := connector.Wrapper{ + RefreshToken: refreshToken, + ClientID: clientID, + ClientSecret: clientSecret, + OrgID: orgID, + VmcURL: vmcURL, + CspURL: cspURL, + } + err := connectorWrapper.Authenticate() if err != nil { return nil, HandleCreateError("Client connector", err) } - return &connector.Wrapper{ - Connector: apiConnector, - RefreshToken: refreshToken, - OrgID: orgID, - VmcURL: vmcURL, - CspURL: cspURL}, - nil + return &connectorWrapper, err } diff --git a/vmc/provider_test.go b/vmc/provider_test.go index 7be866fc..16ae77f3 100644 --- a/vmc/provider_test.go +++ b/vmc/provider_test.go @@ -67,8 +67,11 @@ func testAccPreCheckZerocloud(t *testing.T) { if v := os.Getenv(constants.CspURL); v == "" { t.Fatal(constants.CspURL + " must be set for Zerocloud acceptance tests") } - if v := os.Getenv(constants.APIToken); v == "" { - t.Fatal(constants.APIToken + " must be set for acceptance tests") + if v := os.Getenv(constants.ClientID); v == "" { + t.Fatal(constants.ClientID + " must be set for acceptance tests") + } + if v := os.Getenv(constants.ClientSecret); v == "" { + t.Fatal(constants.ClientSecret + " must be set for acceptance tests") } if v := os.Getenv(constants.OrgID); v == "" { t.Fatal(constants.OrgID + " must be set for acceptance tests") diff --git a/vmc/resource_vmc_public_ip.go b/vmc/resource_vmc_public_ip.go index 703b1760..26fe81c4 100644 --- a/vmc/resource_vmc_public_ip.go +++ b/vmc/resource_vmc_public_ip.go @@ -5,6 +5,7 @@ package vmc import ( "fmt" + "github.com/vmware/terraform-provider-vmc/vmc/connector" "strings" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -59,7 +60,8 @@ func resourcePublicIP() *schema.Resource { func resourcePublicIPCreate(d *schema.ResourceData, m interface{}) error { nsxtReverseProxyURL := d.Get("nsxt_reverse_proxy_url").(string) - connector, err := getNsxtReverseProxyURLConnector(nsxtReverseProxyURL) + connectorWrapper := m.(*connector.Wrapper) + connector, err := getNsxtReverseProxyURLConnector(nsxtReverseProxyURL, connectorWrapper) if err != nil { return HandleCreateError("NSXT reverse proxy URL connector", err) } @@ -87,7 +89,8 @@ func resourcePublicIPCreate(d *schema.ResourceData, m interface{}) error { func resourcePublicIPRead(d *schema.ResourceData, m interface{}) error { nsxtReverseProxyURL := d.Get("nsxt_reverse_proxy_url").(string) - connector, err := getNsxtReverseProxyURLConnector(nsxtReverseProxyURL) + connectorWrapper := m.(*connector.Wrapper) + connector, err := getNsxtReverseProxyURLConnector(nsxtReverseProxyURL, connectorWrapper) if err != nil { return HandleCreateError("NSXT reverse proxy URL connector", err) } @@ -124,7 +127,8 @@ func resourcePublicIPRead(d *schema.ResourceData, m interface{}) error { func resourcePublicIPUpdate(d *schema.ResourceData, m interface{}) error { nsxtReverseProxyURL := d.Get("nsxt_reverse_proxy_url").(string) - connector, err := getNsxtReverseProxyURLConnector(nsxtReverseProxyURL) + connectorWrapper := m.(*connector.Wrapper) + connector, err := getNsxtReverseProxyURLConnector(nsxtReverseProxyURL, connectorWrapper) if err != nil { return HandleCreateError("NSXT reverse proxy URL connector", err) } @@ -154,7 +158,8 @@ func resourcePublicIPUpdate(d *schema.ResourceData, m interface{}) error { func resourcePublicIPDelete(d *schema.ResourceData, m interface{}) error { nsxtReverseProxyURL := d.Get("nsxt_reverse_proxy_url").(string) - connector, err := getNsxtReverseProxyURLConnector(nsxtReverseProxyURL) + connectorWrapper := m.(*connector.Wrapper) + connector, err := getNsxtReverseProxyURLConnector(nsxtReverseProxyURL, connectorWrapper) if err != nil { return HandleCreateError("NSXT reverse proxy URL connector", err) } diff --git a/vmc/resource_vmc_public_ip_test.go b/vmc/resource_vmc_public_ip_test.go index 58362369..9711c428 100644 --- a/vmc/resource_vmc_public_ip_test.go +++ b/vmc/resource_vmc_public_ip_test.go @@ -5,6 +5,7 @@ package vmc import ( "fmt" + "github.com/vmware/terraform-provider-vmc/vmc/connector" "github.com/vmware/terraform-provider-vmc/vmc/constants" "os" "testing" @@ -49,12 +50,13 @@ func testAccCheckVmcPublicIPExists(name string) resource.TestCheckFunc { } uuid := rs.Primary.Attributes["id"] displayName := rs.Primary.Attributes["display_name"] - connector, err := getNsxtReverseProxyURLConnector(os.Getenv(constants.NsxtReverseProxyURL)) + connectorWrapper := testAccProvider.Meta().(*connector.Wrapper) + nsxConnector, err := getNsxtReverseProxyURLConnector(os.Getenv(constants.NsxtReverseProxyURL), connectorWrapper) if err != nil { - return fmt.Errorf("error creating client connector : %v ", err) + return fmt.Errorf("error creating client nsxConnector : %v ", err) } - nsxVmcAwsClient := api.NewCloudServiceVMCOnAWSPublicIPClient(connector) + nsxVmcAwsClient := api.NewCloudServiceVMCOnAWSPublicIPClient(nsxConnector) publicIP, err := nsxVmcAwsClient.GetPublicIp(uuid) if err != nil { return fmt.Errorf("error getting public IP with ID %s : %v", uuid, err) @@ -69,11 +71,12 @@ func testAccCheckVmcPublicIPExists(name string) resource.TestCheckFunc { func testCheckVmcPublicIPDestroy(s *terraform.State) error { fmt.Printf("Reverse proxy : %s", os.Getenv(constants.NsxtReverseProxyURL)) - connector, err := getNsxtReverseProxyURLConnector(os.Getenv(constants.NsxtReverseProxyURL)) + connectorWrapper := testAccProvider.Meta().(*connector.Wrapper) + nsxConnector, err := getNsxtReverseProxyURLConnector(os.Getenv(constants.NsxtReverseProxyURL), connectorWrapper) if err != nil { - return fmt.Errorf("error creating client connector : %v ", err) + return fmt.Errorf("error creating client nsxConnector : %v ", err) } - nsxVmcAwsClient := api.NewCloudServiceVMCOnAWSPublicIPClient(connector) + nsxVmcAwsClient := api.NewCloudServiceVMCOnAWSPublicIPClient(nsxConnector) for _, rs := range s.RootModule().Resources { if rs.Type != "vmc_public_ip" { diff --git a/vmc/resource_vmc_sddc.go b/vmc/resource_vmc_sddc.go index cbf49e28..8a4a0f76 100644 --- a/vmc/resource_vmc_sddc.go +++ b/vmc/resource_vmc_sddc.go @@ -394,10 +394,10 @@ func resourceSddcCreate(d *schema.ResourceData, m interface{}) error { } func resourceSddcRead(d *schema.ResourceData, m interface{}) error { - connectorWrapper := (m.(*connector.Wrapper)).Connector + connectorWrapper := m.(*connector.Wrapper) sddcID := d.Id() orgID := (m.(*connector.Wrapper)).OrgID - sddc, err := GetSddc(connectorWrapper, orgID, sddcID) + sddc, err := GetSddc(connectorWrapper.Connector, orgID, sddcID) if err != nil { return HandleReadError(d, "SDDC", sddcID, err) } @@ -428,7 +428,7 @@ func resourceSddcRead(d *schema.ResourceData, m interface{}) error { d.Set("account_link_state", sddc.AccountLinkState) d.Set("sddc_access_state", sddc.SddcAccessState) d.Set("sddc_state", sddc.SddcState) - primaryClusterClient := sddcs.NewPrimaryclusterClient(connectorWrapper) + primaryClusterClient := sddcs.NewPrimaryclusterClient(connectorWrapper.Connector) primaryCluster, err := primaryClusterClient.Get(orgID, sddcID) if err != nil { return HandleReadError(d, "Primary Cluster", sddcID, err) @@ -474,14 +474,19 @@ func resourceSddcRead(d *schema.ResourceData, m interface{}) error { d.Set("sddc_size", sddcSizeInfo) if sddc.ResourceConfig.NsxCloudAdmin != nil { d.Set("nsxt_cloudadmin", *sddc.ResourceConfig.NsxCloudAdmin) - d.Set("nsxt_cloudadmin_password", *sddc.ResourceConfig.NsxCloudAdminPassword) + // Evade nil pointer dereference when user's access_token doesn't have NSX roles + if sddc.ResourceConfig.NsxCloudAdminPassword != nil { + _ = d.Set("nsxt_cloudadmin_password", *sddc.ResourceConfig.NsxCloudAdminPassword) + } + if sddc.ResourceConfig.NsxCloudAuditPassword != nil { + _ = d.Set("nsxt_cloudaudit_password", *sddc.ResourceConfig.NsxCloudAuditPassword) + } d.Set("nsxt_cloudaudit", *sddc.ResourceConfig.NsxCloudAudit) - d.Set("nsxt_cloudaudit_password", *sddc.ResourceConfig.NsxCloudAuditPassword) d.Set("nsxt_private_ip", *sddc.ResourceConfig.NsxMgrManagementIp) d.Set("nsxt_private_url", *sddc.ResourceConfig.NsxMgrLoginUrl) } } - edrsPolicyClient := autoscalercluster.NewEdrsPolicyClient(connectorWrapper) + edrsPolicyClient := autoscalercluster.NewEdrsPolicyClient(connectorWrapper.Connector) edrsPolicy, err := edrsPolicyClient.Get(orgID, sddcID, primaryCluster.ClusterId) if err != nil { return HandleReadError(d, "SDDC", sddcID, err) @@ -494,7 +499,7 @@ func resourceSddcRead(d *schema.ResourceData, m interface{}) error { if *sddc.Provider != constants.ZeroCloudProviderType { // store intranet_mtu_uplink only for non zerocloud provider types nsxtReverseProxyURL := d.Get("nsxt_reverse_proxy_url").(string) - nsxtReverseProxyURLConnector, err := getNsxtReverseProxyURLConnector(nsxtReverseProxyURL) + nsxtReverseProxyURLConnector, err := getNsxtReverseProxyURLConnector(nsxtReverseProxyURL, connectorWrapper) if err != nil { return HandleCreateError("NSXT reverse proxy URL connectorWrapper", err) } @@ -652,7 +657,7 @@ func resourceSddcUpdate(d *schema.ResourceData, m interface{}) error { } intranetMTUUplink := d.Get("intranet_mtu_uplink").(int) nsxtReverseProxyURL := d.Get("nsxt_reverse_proxy_url").(string) - nxstReverseProxyURLConnector, err := getNsxtReverseProxyURLConnector(nsxtReverseProxyURL) + nxstReverseProxyURLConnector, err := getNsxtReverseProxyURLConnector(nsxtReverseProxyURL, connectorWrapper) if err != nil { return HandleCreateError("NSXT reverse proxy URL connector", err) } diff --git a/vmc/resource_vmc_sddc_group.go b/vmc/resource_vmc_sddc_group.go index 56bc97da..25a0dbc2 100644 --- a/vmc/resource_vmc_sddc_group.go +++ b/vmc/resource_vmc_sddc_group.go @@ -170,8 +170,7 @@ func sddcGroupSchema() map[string]*schema.Schema { func resourceSddcGroupCreate(ctx context.Context, data *schema.ResourceData, i interface{}) diag.Diagnostics { connectorWrapper := i.(*connector.Wrapper) - sddcGroupsClient := sddcgroup.NewSddcGroupClient(connectorWrapper.VmcURL, connectorWrapper.CspURL, - connectorWrapper.RefreshToken, connectorWrapper.OrgID) + sddcGroupsClient := sddcgroup.NewSddcGroupClient(*connectorWrapper) err := sddcGroupsClient.Authenticate() if err != nil { return diag.FromErr(err) @@ -209,8 +208,7 @@ func resourceSddcGroupCreate(ctx context.Context, data *schema.ResourceData, i i func resourceSddcGroupRead(_ context.Context, data *schema.ResourceData, i interface{}) diag.Diagnostics { connectorWrapper := i.(*connector.Wrapper) - sddcGroupsClient := sddcgroup.NewSddcGroupClient(connectorWrapper.VmcURL, connectorWrapper.CspURL, - connectorWrapper.RefreshToken, connectorWrapper.OrgID) + sddcGroupsClient := sddcgroup.NewSddcGroupClient(*connectorWrapper) err := sddcGroupsClient.Authenticate() if err != nil { return diag.FromErr(err) @@ -296,8 +294,7 @@ func resourceSddcGroupUpdate(ctx context.Context, data *schema.ResourceData, i i func resourceSddcGroupDelete(_ context.Context, data *schema.ResourceData, i interface{}) diag.Diagnostics { connectorWrapper := i.(*connector.Wrapper) - sddcGroupsClient := sddcgroup.NewSddcGroupClient(connectorWrapper.VmcURL, connectorWrapper.CspURL, - connectorWrapper.RefreshToken, connectorWrapper.OrgID) + sddcGroupsClient := sddcgroup.NewSddcGroupClient(*connectorWrapper) err := sddcGroupsClient.Authenticate() if err != nil { return diag.FromErr(err) @@ -332,8 +329,7 @@ func resourceSddcGroupDelete(_ context.Context, data *schema.ResourceData, i int func updateSddcGroupMembers(data *schema.ResourceData, i interface{}, addedIds *[]string, removedIds *[]string) diag.Diagnostics { connectorWrapper := i.(*connector.Wrapper) - sddcGroupsClient := sddcgroup.NewSddcGroupClient(connectorWrapper.VmcURL, connectorWrapper.CspURL, - connectorWrapper.RefreshToken, connectorWrapper.OrgID) + sddcGroupsClient := sddcgroup.NewSddcGroupClient(*connectorWrapper) err := sddcGroupsClient.Authenticate() if err != nil { return diag.FromErr(err) diff --git a/vmc/resource_vmc_sddc_group_test.go b/vmc/resource_vmc_sddc_group_test.go index d3dd0018..0d5aac8f 100644 --- a/vmc/resource_vmc_sddc_group_test.go +++ b/vmc/resource_vmc_sddc_group_test.go @@ -55,8 +55,7 @@ func testSddcGroupExists(s *terraform.State) error { func sddcGroupExists(s *terraform.State) bool { connectorWrapper := testAccProvider.Meta().(*connector.Wrapper) - sddcGroupClient := sddcgroup.NewSddcGroupClient(connectorWrapper.VmcURL, - connectorWrapper.CspURL, connectorWrapper.RefreshToken, connectorWrapper.OrgID) + sddcGroupClient := sddcgroup.NewSddcGroupClient(*connectorWrapper) err := sddcGroupClient.Authenticate() if err != nil { return false diff --git a/vmc/resource_vmc_sddc_test.go b/vmc/resource_vmc_sddc_test.go index 4c965706..0d85c20c 100644 --- a/vmc/resource_vmc_sddc_test.go +++ b/vmc/resource_vmc_sddc_test.go @@ -194,8 +194,8 @@ data "vmc_customer_subnets" "my_subnets" { resource "vmc_sddc" "sddc_zerocloud" { sddc_name = %q - vpc_cidr = "10.2.0.0/16" - num_host = 3 + vpc_cidr = "10.40.0.0/16" + num_host = 2 provider_type = "ZEROCLOUD" host_instance_type = "I3_METAL" region = "US_WEST_2" diff --git a/vmc/sddcgroup/sddc_group_client.go b/vmc/sddcgroup/sddc_group_client.go index 308a28a9..92f840f1 100644 --- a/vmc/sddcgroup/sddc_group_client.go +++ b/vmc/sddcgroup/sddc_group_client.go @@ -8,7 +8,7 @@ import ( "encoding/json" "fmt" "github.com/vmware/terraform-provider-vmc/vmc/connector" - "github.com/vmware/terraform-provider-vmc/vmc/constants" + "github.com/vmware/vsphere-automation-sdk-go/runtime/protocol/client" "github.com/vmware/vsphere-automation-sdk-go/runtime/security" "io" "net/http" @@ -33,44 +33,37 @@ type HTTPClient interface { } type ClientImpl struct { - vmcURL string - cspURL string - refreshToken string - orgID string - accessToken string - httpClient HTTPClient + connector connector.Wrapper + httpClient HTTPClient } -func NewSddcGroupClient(vmcURL string, cspURL string, refreshToken string, orgID string) *ClientImpl { +func NewSddcGroupClient(wrapper connector.Wrapper) *ClientImpl { + copyWrapper := connector.CopyWrapper(wrapper) return &ClientImpl{ - vmcURL: vmcURL, - cspURL: cspURL, - refreshToken: refreshToken, - orgID: orgID, - httpClient: http.DefaultClient, + connector: *copyWrapper, + httpClient: http.DefaultClient, } } // newTestSddcGroupClient intended for injecting dummy accessToken and stubbed httpClient for // testing purposes. func newTestSddcGroupClient(vmcURL string, orgID string, accessToken string, httpClient HTTPClient) *ClientImpl { + testConnector := connector.Wrapper{ + VmcURL: vmcURL, + OrgID: orgID, + } + // Create a dummy connector to house the access token in a security context + testConnector.Connector = client.NewRestConnector("", http.Client{}) + testConnector.Connector.SetSecurityContext(security.NewOauthSecurityContext(accessToken)) return &ClientImpl{ - vmcURL: vmcURL, - orgID: orgID, - accessToken: accessToken, - httpClient: httpClient, + connector: testConnector, + httpClient: httpClient, } } // Authenticate grab an access token and set it into the Client instance for later use func (client *ClientImpl) Authenticate() error { - authURL := client.cspURL + constants.CspRefreshURLSuffix - securityContext, err := connector.SecurityContextByRefreshToken(client.refreshToken, authURL) - if err != nil { - return err - } - client.accessToken = securityContext.Property(security.ACCESS_TOKEN).(string) - return nil + return client.connector.Authenticate() } func (client *ClientImpl) ValidateCreateSddcGroup(sddcIDs *[]string) error { @@ -97,7 +90,7 @@ func (client *ClientImpl) validateCreateUpdateSddcGroupInternal(groupID string, return err } validateCreateURL := client.getBaseURL() + fmt.Sprintf( - "/network/%s/core/network-connectivity-configs/validate-members", client.orgID) + "/network/%s/core/network-connectivity-configs/validate-members", client.connector.OrgID) req := client.createNewRequest(http.MethodPost, validateCreateURL, bytes.NewBuffer(requestPayload)) @@ -114,7 +107,8 @@ func (client *ClientImpl) validateCreateUpdateSddcGroupInternal(groupID string, func (client *ClientImpl) GetSddcGroup(groupID string) (*DeploymentGroup, *NetworkConnectivityConfig, error) { - getSddcGroupURL := client.getBaseURL() + fmt.Sprintf("/inventory/%s/core/deployment-groups/%s", client.orgID, groupID) + getSddcGroupURL := client.getBaseURL() + fmt.Sprintf("/inventory/%s/core/deployment-groups/%s", + client.connector.OrgID, groupID) req := client.createNewRequest(http.MethodGet, getSddcGroupURL, nil) rawResponse, statusCode, err := client.executeRequest(req) var group *DeploymentGroup @@ -141,7 +135,8 @@ func (client *ClientImpl) GetSddcGroup(groupID string) (*DeploymentGroup, } getTraitsURL := client.getBaseURL() + fmt.Sprintf("/network/%s/core/network-connectivity-configs/%s/"+ "?trait=AwsVpcAttachmentsTrait,AwsDirectConnectGatewayAssociationsTrait,"+ - "AwsNetworkConnectivityTrait,AwsCustomerTransitGatewayAssociationsTrait", client.orgID, resourceID) + "AwsNetworkConnectivityTrait,AwsCustomerTransitGatewayAssociationsTrait", + client.connector.OrgID, resourceID) req = client.createNewRequest(http.MethodGet, getTraitsURL, nil) rawResponse, statusCode, err = client.executeRequest(req) if err != nil { @@ -176,7 +171,8 @@ func (client *ClientImpl) CreateSddcGroup( return "", "", err } createSddcGroupURL := client.getBaseURL() + fmt.Sprintf( - "/network/%s/core/network-connectivity-configs/create-group-network-connectivity", client.orgID) + "/network/%s/core/network-connectivity-configs/create-group-network-connectivity", + client.connector.OrgID) req := client.createNewRequest(http.MethodPost, createSddcGroupURL, bytes.NewBuffer(requestPayload)) @@ -219,7 +215,7 @@ func (client *ClientImpl) UpdateSddcGroupMembers( return "", err } config := NewAwsUpdateDeploymentGroupMembersConfig(addMembers, removeMembers) - networkOperation := NewNetworkOperation(client.orgID, resourceID, UpdateMembersNetworkOperationType, *config) + networkOperation := NewNetworkOperation(client.connector.OrgID, resourceID, UpdateMembersNetworkOperationType, *config) networkOperationResponse, err := client.executeNetworkOperation(networkOperation) if err != nil { return "", err @@ -233,7 +229,7 @@ func (client *ClientImpl) DeleteSddcGroup(groupID string) (taskID string, error return "", err } config := NewAwsDeleteDeploymentGroupConfig() - networkOperation := NewNetworkOperation(client.orgID, resourceID, DeleteSddcGroupNetworkOperationType, *config) + networkOperation := NewNetworkOperation(client.connector.OrgID, resourceID, DeleteSddcGroupNetworkOperationType, *config) networkOperationResponse, err := client.executeNetworkOperation(networkOperation) if err != nil { return "", err @@ -243,7 +239,7 @@ func (client *ClientImpl) DeleteSddcGroup(groupID string) (taskID string, error func (client *ClientImpl) getResourceIDFromGroupID(groupID string) (resourceID string, error error) { getResourceIDURL := client.getBaseURL() + fmt.Sprintf( - "/network/%s/core/network-connectivity-configs/?group_id=%s", client.orgID, groupID) + "/network/%s/core/network-connectivity-configs/?group_id=%s", client.connector.OrgID, groupID) req := client.createNewRequest(http.MethodGet, getResourceIDURL, nil) @@ -292,7 +288,7 @@ func (client *ClientImpl) executeNetworkOperation(networkOperation *NetworkOpera func (client *ClientImpl) getNetworkOperationsURL() string { return client.getBaseURL() + fmt.Sprintf( - "/network/%s/aws/operations", client.orgID) + "/network/%s/aws/operations", client.connector.OrgID) } // executeRequest Returns the body of the response as byte array pointer, the status code @@ -325,12 +321,12 @@ func (client *ClientImpl) executeRequest( } func (client *ClientImpl) getBaseURL() string { - return client.vmcURL + "/api" + return client.connector.VmcURL + "/api" } func (client *ClientImpl) createNewRequest(method string, URL string, body io.Reader) *http.Request { req, _ := http.NewRequest(method, URL, body) - req.Header.Add(authnHeader, client.accessToken) + req.Header.Add(authnHeader, client.connector.Connector.SecurityContext().Property(security.ACCESS_TOKEN).(string)) if method == http.MethodPost { req.Header.Add("content-type", "application/json") } diff --git a/vmc/task/task_convert.go b/vmc/task/task_convert.go index a664b510..cb4d437f 100644 --- a/vmc/task/task_convert.go +++ b/vmc/task/task_convert.go @@ -24,8 +24,7 @@ func GetTask(connectorWrapper *connector.Wrapper, taskID string) (model.Task, er // GetV2Task returns an adapted model.Task with specified ID func GetV2Task(connectorWrapper *connector.Wrapper, taskID string) (model.Task, error) { - tasksV2Client := NewV2ClientImpl(connectorWrapper.VmcURL, connectorWrapper.CspURL, - connectorWrapper.RefreshToken, connectorWrapper.OrgID) + tasksV2Client := NewV2ClientImpl(*connectorWrapper) err := tasksV2Client.Authenticate() if err != nil { return model.Task{}, err diff --git a/vmc/task/task_v2_client.go b/vmc/task/task_v2_client.go index 332df215..ab4f910d 100644 --- a/vmc/task/task_v2_client.go +++ b/vmc/task/task_v2_client.go @@ -8,7 +8,7 @@ import ( "encoding/json" "fmt" "github.com/vmware/terraform-provider-vmc/vmc/connector" - "github.com/vmware/terraform-provider-vmc/vmc/constants" + "github.com/vmware/vsphere-automation-sdk-go/runtime/protocol/client" "github.com/vmware/vsphere-automation-sdk-go/runtime/security" "io" "net/http" @@ -39,50 +39,43 @@ type HTTPClient interface { } type V2ClientImpl struct { - vmcURL string - cspURL string - refreshToken string - orgID string - accessToken string - HTTPClient HTTPClient + connector connector.Wrapper + HTTPClient HTTPClient } -func NewV2ClientImpl(vmcURL string, cspURL string, refreshToken string, orgID string) *V2ClientImpl { +func NewV2ClientImpl(wrapper connector.Wrapper) *V2ClientImpl { + copyWrapper := connector.CopyWrapper(wrapper) return &V2ClientImpl{ - vmcURL: vmcURL, - cspURL: cspURL, - refreshToken: refreshToken, - orgID: orgID, - HTTPClient: http.DefaultClient, + connector: *copyWrapper, + HTTPClient: http.DefaultClient, } } // newTestV2ClientImpl intended for injecting dummy accessToken and stubbed HTTPClient for // testing purposes. func newTestV2ClientImpl(vmcURL string, orgID string, accessToken string, httpClient HTTPClient) *V2ClientImpl { + testConnector := connector.Wrapper{ + VmcURL: vmcURL, + OrgID: orgID, + } + // Create a dummy connector to house the access token in a security context + testConnector.Connector = client.NewRestConnector("", http.Client{}) + testConnector.Connector.SetSecurityContext(security.NewOauthSecurityContext(accessToken)) return &V2ClientImpl{ - vmcURL: vmcURL, - orgID: orgID, - accessToken: accessToken, - HTTPClient: httpClient, + connector: testConnector, + HTTPClient: httpClient, } } -// Authenticate grab an access token and set it into the SddcGroupClient instance for later use +// Authenticate grab an access token and set it into the V2Client instance for later use func (client *V2ClientImpl) Authenticate() error { - authURL := client.cspURL + constants.CspRefreshURLSuffix - securityContext, err := connector.SecurityContextByRefreshToken(client.refreshToken, authURL) - if err != nil { - return err - } - client.accessToken = securityContext.Property(security.ACCESS_TOKEN).(string) - return nil + return client.connector.Authenticate() } func (client *V2ClientImpl) GetTask(taskID string) (V2Task, error) { - getTaskV2URL := client.getBaseURL() + fmt.Sprintf("/operation/%s/core/operations/%s", client.orgID, taskID) + getTaskV2URL := client.getBaseURL() + fmt.Sprintf("/operation/%s/core/operations/%s", client.connector.OrgID, taskID) req, _ := http.NewRequest(http.MethodGet, getTaskV2URL, nil) - req.Header.Add(authnHeader, client.accessToken) + req.Header.Add(authnHeader, client.connector.Connector.SecurityContext().Property(security.ACCESS_TOKEN).(string)) var result V2Task rawResponse, statusCode, err := client.executeRequest(req) if err != nil { @@ -99,7 +92,7 @@ func (client *V2ClientImpl) GetTask(taskID string) (V2Task, error) { } func (client *V2ClientImpl) getBaseURL() string { - return client.vmcURL + "/api" + return client.connector.VmcURL + "/api" } // executeRequest Returns the body of the response as byte array pointer, the status code diff --git a/vmc/utils.go b/vmc/utils.go index d736a3b3..07b4ec64 100644 --- a/vmc/utils.go +++ b/vmc/utils.go @@ -7,9 +7,7 @@ import ( "fmt" "github.com/vmware/terraform-provider-vmc/vmc/connector" "github.com/vmware/terraform-provider-vmc/vmc/constants" - "net/http" "net/url" - "os" "strings" uuid "github.com/satori/go.uuid" @@ -77,19 +75,23 @@ func expandMsftLicenseConfig(config []interface{}) *model.MsftLicensingConfig { return &licenseConfig } -func getNsxtReverseProxyURLConnector(nsxtReverseProxyURL string) (client.Connector, error) { - APIToken := os.Getenv(constants.APIToken) +func getNsxtReverseProxyURLConnector(nsxtReverseProxyURL string, wrapper *connector.Wrapper) (client.Connector, error) { if len(nsxtReverseProxyURL) == 0 { return nil, fmt.Errorf("NSX reverse proxy url is required for public IP resource creation") } + if wrapper == nil { + return nil, fmt.Errorf("nil connector.Wrapper provided") + } nsxtReverseProxyURL = strings.Replace(nsxtReverseProxyURL, constants.SksNSXTManager, "", -1) - httpClient := http.Client{} - cspURL := os.Getenv(constants.CspURL) - apiConnector, err := connector.NewClientConnectorByRefreshToken(APIToken, nsxtReverseProxyURL, cspURL, httpClient) + copyWrapper := connector.CopyWrapper(*wrapper) + // The wrapper uses the VmcURL as service URL, so setting it to the NSX URL will + // force authentication against the NSX instance + copyWrapper.VmcURL = nsxtReverseProxyURL + err := copyWrapper.Authenticate() if err != nil { - return nil, HandleCreateError("NSXT reverse proxy URL connector", err) + return nil, err } - return apiConnector, nil + return copyWrapper.Connector, nil } // getHostCountCluster tries to find the amount of hosts on a Cluster in