Repository: amp-labs/connectors
Pull request #67 (2024-02-14 00:11:51)

[ENG-684] Update hubspot & SF connectors to consume catalog
* Reads values from catalog to set internal connector state
* This PR intentionally doesn't try to replace URL constructions, etc - we need to find a better way to do that in cases of Salesforce, etc. Sometimes we need base parts of the URL and sometimes we need the suffix of a URL, etc.

## Next steps
* Figure out what is the maximum amount of information we can transfer from code into the YAML without breaking anything or having too many overlapping fields in the YAML. For example, in metadata requests, we need only `services/data/v59.0/...` but for a read request we need `https://......./services...` - how do you store the least amount of information to get the most out of this?
* Refactor these connectors to get rid of struct members like 'BaseURL' or 'Domain' and work off a `ProviderInfo` member?

codesee-maps[bot]: <details open>
<summary><b>👇 <a href="https://app.codesee.io/r/reviews?pr=67&src=https%3A%2F%2Fgithub.com%2Famp-labs%2Fconnectors">Click on the image</a> for a new way to code review</b>
</summary>

####

[![Review these changes using an interactive CodeSee Map](https://s3.us-east-2.amazonaws.com/maps.codesee.io/images/github/amp-labs/connectors/67/da4aae14/a2f9ac7cb8e25c9fa39a1eb49ebdb5a389e91229.svg)](https://app.codesee.io/r/reviews?pr=67&src=https%3A%2F%2Fgithub.com%2Famp-labs%2Fconnectors)

#### Legend
<img src="https://app.codesee.io/Legend_v3.png" alt="CodeSee Map legend" width="220" height="156">
</details>

Patch:
diff --git a/README.md b/README.md
index 58c16b76..b45874ae 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@ import (
 
 const (
   // Replace these with your own values.
-  Subdomain = "<subdomain>"
+  Workspace = "<workspace>"
   OAuthClientId = "<client id>"
   OAuthClentSecret = "<client secret>"
   OAuthAccessToken = "<access token>"
@@ -36,8 +36,8 @@ func main() {
     ClientID:     OAuthClientId,
     ClientSecret: OAuthClentSecret,
     Endpoint: oauth2.Endpoint{
-      AuthURL:   fmt.Sprintf("https://%s.my.salesforce.com/services/oauth2/authorize", Subdomain),
-      TokenURL:  fmt.Sprintf("https://%s.my.salesforce.com/services/oauth2/token", Subdomain),
+      AuthURL:   fmt.Sprintf("https://%s.my.salesforce.com/services/oauth2/authorize", Workspace),
+      TokenURL:  fmt.Sprintf("https://%s.my.salesforce.com/services/oauth2/token", Workspace),
       AuthStyle: oauth2.AuthStyleInParams,
     },
   }
@@ -53,7 +53,7 @@ func main() {
   // Create the Salesforce client
   client, err := connectors.Salesforce(
     salesforce.WithClient(context.Background(), http.DefaultClient, cfg, tok),
-    salesforce.WithSubdomain(Subdomain))
+    salesforce.WithWorkspace(Workspace))
   if err != nil {
     panic(err)
   }
@@ -82,7 +82,7 @@ There are 3 ways to initialize a Connector:
 ```go
 client, err := connectors.Salesforce(
     salesforce.WithClient(context.Background(), http.DefaultClient, cfg, tok),
-    salesforce.WithSubdomain(Subdomain))
+    salesforce.WithWorkspace(Workspace))
 ```
 
 2. Initializing a generic Connector (returns an interface). This method of initialization will only allow you to use methods that are common to all providers. This is helpful if you would like your code to be provider-agnostic.
@@ -90,7 +90,7 @@ client, err := connectors.Salesforce(
 ```go
 client, err := connectors.Salesforce.New(
     salesforce.WithClient(context.Background(), http.DefaultClient, cfg, tok),
-    salesforce.WithSubdomain(Subdomain))
+    salesforce.WithWorkspace(Workspace))
 ```
 
 3. With string parameter for API name (this is useful if you are parsing the API name from a config file, but should be avoided otherwise because it is not typesafe). This returns a generic Connector (returns an interface).
diff --git a/connectors.go b/connectors.go
index a5419f42..c730df24 100644
--- a/connectors.go
+++ b/connectors.go
@@ -5,7 +5,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"strings"
 
 	"github.com/amp-labs/connectors/common"
 	"github.com/amp-labs/connectors/hubspot"
@@ -102,16 +101,15 @@ var (
 // (e.g. constructing a new connector based on parsing a config file, whose exact params
 // aren't known until runtime). However, if you can use the API.New form, it's preferred,
 // since you get type safety and more readable code.
-func New(apiName string, opts map[string]any) (Connector, error) { //nolint:ireturn
-	if strings.EqualFold(apiName, providers.Salesforce.String()) {
+func New(provider providers.Provider, opts map[string]any) (Connector, error) { //nolint:ireturn
+	switch provider {
+	case providers.Salesforce:
 		return newSalesforce(opts)
-	}
-
-	if strings.EqualFold(apiName, providers.Hubspot.String()) {
+	case providers.Hubspot:
 		return newHubspot(opts)
+	default:
+		return nil, fmt.Errorf("%w: %s", ErrUnknownConnector, provider)
 	}
-
-	return nil, fmt.Errorf("%w: %s", ErrUnknownConnector, apiName)
 }
 
 // newSalesforce returns a new Salesforce Connector, by unwrapping the options and passing them to the Salesforce API.
@@ -125,7 +123,7 @@ func newSalesforce(opts map[string]any) (Connector, error) { //nolint:ireturn
 
 	w, valid := getParam[string](opts, "workspace")
 	if valid {
-		options = append(options, salesforce.WithSubdomain(w))
+		options = append(options, salesforce.WithWorkspace(w))
 	}
 
 	return Salesforce.New(options...)
diff --git a/hubspot/connector.go b/hubspot/connector.go
index 155c2197..918b67e3 100644
--- a/hubspot/connector.go
+++ b/hubspot/connector.go
@@ -2,6 +2,7 @@ package hubspot
 
 import (
 	"github.com/amp-labs/connectors/common"
+	"github.com/amp-labs/connectors/providers"
 )
 
 // Connector is a Hubspot connector.
@@ -31,14 +32,19 @@ func NewConnector(opts ...Option) (conn *Connector, outErr error) {
 	}
 
 	var err error
-	params, err = params.prepare()
 
-	params.client.HTTPClient.Base = "https://api.hubapi.com"
+	params, err = params.prepare()
+	if err != nil {
+		return nil, err
+	}
 
+	// Read provider info & replace catalog variables with given substitutions, if any
+	providerInfo, err := providers.ReadConfig(providers.Hubspot, nil)
 	if err != nil {
 		return nil, err
 	}
 
+	params.client.HTTPClient.Base = providerInfo.BaseURL
 	conn = &Connector{
 		BaseURL: params.client.HTTPClient.Base,
 		Module:  params.module,
diff --git a/providers/catalog.go b/providers/catalog.go
index f6801499..4c5adc34 100644
--- a/providers/catalog.go
+++ b/providers/catalog.go
@@ -105,6 +105,21 @@ func substituteStruct(input interface{}, substitutions *map[string]string) (err
 				return err
 			}
 		}
+
+		// If the field is a map, perform substitution on its values.
+		if field.Kind() == reflect.Map {
+			for _, key := range field.MapKeys() {
+				val := field.MapIndex(key)
+				if val.Kind() == reflect.String {
+					substitutedVal, err := substitute(val.String(), substitutions)
+					if err != nil {
+						return err
+					}
+
+					field.SetMapIndex(key, reflect.ValueOf(substitutedVal))
+				}
+			}
+		}
 	}
 
 	return nil
diff --git a/providers/providers.yaml b/providers/providers.yaml
index 9fd1bde0..c62666c7 100644
--- a/providers/providers.yaml
+++ b/providers/providers.yaml
@@ -10,24 +10,29 @@
 
 providers:
   salesforce:
-    baseUrl: https://{{.subdomain}}.salesforce.com
+    baseUrl: "https://{{.workspace}}.salesforce.com"
     authType: oauth2
     oauthOpts:
-      authUrl: https://{{.subdomain}}.my.salesforce.com/services/oauth2/authorize
-      tokenUrl: https://{{.subdomain}}.my.salesforce.com/services/oauth2/token
+      authUrl: "https://{{.workspace}}.my.salesforce.com/services/oauth2/authorize"
+      tokenUrl: "https://{{.workspace}}.my.salesforce.com/services/oauth2/token"
     support:
       bulkWrite: true
       proxy: true
       read: true
       subscribe: false
       write: true
+    providerOptions:
+      # The REST API URL for Salesforce - this can be used for standard object interactions
+      restApiUrl: "https://{{.workspace}}.my.salesforce.com/services/data/v59.0"
+      # The domain for the Salesforce instance - used for pagination [todo: refactor to baseUrl]
+      domain: "{{.workspace}}.my.salesforce.com"
 
   hubspot:
-    baseUrl: https://api.hubapi.com
+    baseUrl: "https://api.hubapi.com"
     authType: oauth2
     oauthOpts:
-      authUrl: https://app.hubspot.com/oauth/authorize
-      tokenUrl: https://api.hubapi.com/oauth/v1/token
+      authUrl: "https://app.hubspot.com/oauth/authorize"
+      tokenUrl: "https://api.hubapi.com/oauth/v1/token"
     support:
       bulkWrite: false
       proxy: true
@@ -36,7 +41,7 @@ providers:
       write: true
 
   linkedIn:
-    baseUrl: https://api.linkedin.com/v2
+    baseUrl: "https://api.linkedin.com"
     authType: oauth2
     support:
       bulkWrite: false
diff --git a/providers/types.go b/providers/types.go
index 701d8ff0..07fd4c73 100644
--- a/providers/types.go
+++ b/providers/types.go
@@ -9,10 +9,11 @@ type Catalog struct {
 // in the configuration. The substitution is only done on string fields. If you want to use pointers in the struct,
 // you might have to update the code to handle it.
 type ProviderInfo struct {
-	Support   ConnectorSupport `validate:"required" yaml:"support"`
-	AuthType  AuthType         `validate:"required" yaml:"authType"`
-	BaseURL   string           `validate:"required" yaml:"baseUrl"`
-	OauthOpts OauthOpts        `yaml:"oauthOpts"`
+	Support      ConnectorSupport  `validate:"required" yaml:"support"`
+	AuthType     AuthType          `validate:"required" yaml:"authType"`
+	BaseURL      string            `validate:"required" yaml:"baseUrl"`
+	OauthOpts    OauthOpts         `yaml:"oauthOpts"`
+	ProviderOpts map[string]string `yaml:"providerOptions,omitempty"`
 }
 
 type ConnectorSupport struct {
@@ -33,3 +34,13 @@ type AuthType string
 const (
 	AuthTypeOAuth2 AuthType = "oauth2"
 )
+
+func (i *ProviderInfo) GetOption(key string) (string, bool) {
+	if i.ProviderOpts == nil {
+		return "", false
+	}
+
+	val, ok := i.ProviderOpts[key]
+
+	return val, ok
+}
diff --git a/salesforce/connector.go b/salesforce/connector.go
index 39e19492..5c73e583 100644
--- a/salesforce/connector.go
+++ b/salesforce/connector.go
@@ -4,6 +4,12 @@ import (
 	"fmt"
 
 	"github.com/amp-labs/connectors/common"
+	"github.com/amp-labs/connectors/providers"
+)
+
+const (
+	providerOptionRestApiUrl = "restApiUrl"
+	providerOptionDomain     = "domain"
 )
 
 const (
@@ -46,20 +52,37 @@ func NewConnector(opts ...Option) (conn *Connector, outErr error) {
 	}
 
 	var err error
-	params, err = params.prepare()
 
-	params.client.HTTPClient.Base = fmt.Sprintf("https://%s.my.salesforce.com", params.subdomain)
+	params, err = params.prepare()
+	if err != nil {
+		return nil, err
+	}
 
+	// Read provider info & replace catalog variables with given substitutions, if any
+	providerInfo, err := providers.ReadConfig(providers.Salesforce, &map[string]string{
+		"workspace": params.workspace,
+	})
 	if err != nil {
 		return nil, err
 	}
 
+	restApi, ok := providerInfo.GetOption(providerOptionRestApiUrl)
+	if !ok {
+		return nil, fmt.Errorf("missing restApiUrl option in provider info")
+	}
+
+	domain, ok := providerInfo.GetOption(providerOptionDomain)
+	if !ok {
+		return nil, fmt.Errorf("missing domain option in provider info")
+	}
+
 	conn = &Connector{
-		BaseURL: fmt.Sprintf("https://%s.my.salesforce.com/services/data/%s", params.subdomain, APIVersion()),
-		Domain:  fmt.Sprintf("%s.my.salesforce.com", params.subdomain),
+		BaseURL: restApi,
+		Domain:  domain,
 		Client:  params.client,
 	}
 
+	conn.Client.HTTPClient.Base = providerInfo.BaseURL
 	conn.Client.HTTPClient.ErrorHandler = conn.interpretError
 
 	return conn, nil
diff --git a/salesforce/errors.go b/salesforce/errors.go
index ec96a850..036966ed 100644
--- a/salesforce/errors.go
+++ b/salesforce/errors.go
@@ -16,7 +16,7 @@ var (
 	ErrNotString          = errors.New("nextRecordsUrl isn't a string")
 	ErrNotBool            = errors.New("done isn't a boolean")
 	ErrNotNumeric         = errors.New("totalSize isn't numeric")
-	ErrMissingSubdomain   = errors.New("missing Salesforce workspace name")
+	ErrMissingWorkspace   = errors.New("missing Salesforce workspace name")
 	ErrMissingClient      = errors.New("JSON http client not set")
 	ErrCannotReadMetadata = errors.New("cannot read object metadata, it is possible you don't have the correct permissions set") // nolint:lll
 )
diff --git a/salesforce/params.go b/salesforce/params.go
index 32a05d1a..a40bf66a 100644
--- a/salesforce/params.go
+++ b/salesforce/params.go
@@ -43,17 +43,17 @@ func WithAuthenticatedClient(client common.AuthenticatedHTTPClient) Option {
 	}
 }
 
-// WithSubdomain sets the salesforce subdomain to use for the connector. It's required.
-func WithSubdomain(workspaceRef string) Option {
+// WithWorkspace sets the salesforce workspace to use for the connector. It's required.
+func WithWorkspace(workspaceRef string) Option {
 	return func(params *sfParams) {
-		params.subdomain = workspaceRef
+		params.workspace = workspaceRef
 	}
 }
 
 // sfParams is the internal configuration for the salesforce connector.
 type sfParams struct {
 	client    *common.JSONHTTPClient // required
-	subdomain string                 // required
+	workspace string                 // required
 }
 
 // prepare finalizes and validates the connector configuration, and returns an error if it's invalid.
@@ -62,8 +62,8 @@ func (p *sfParams) prepare() (out *sfParams, err error) {
 		return nil, ErrMissingClient
 	}
 
-	if len(p.subdomain) == 0 {
-		return nil, ErrMissingSubdomain
+	if len(p.workspace) == 0 {
+		return nil, ErrMissingWorkspace
 	}
 
 	return p, nil


Test patch:
diff --git a/providers/catalog_test.go b/providers/catalog_test.go
index cea8de43..54cf4de4 100644
--- a/providers/catalog_test.go
+++ b/providers/catalog_test.go
@@ -20,7 +20,7 @@ func TestReadConfig(t *testing.T) { //nolint:funlen
 			provider:    Salesforce,
 			description: "Salesforce provider config with valid & invalid substitutions",
 			substitutions: map[string]string{
-				"subdomain": "example",
+				"workspace": "example",
 				"version":   "-1.0",
 			},
 			expected: &ProviderInfo{
@@ -37,6 +37,10 @@ func TestReadConfig(t *testing.T) { //nolint:funlen
 					TokenURL: "https://example.my.salesforce.com/services/oauth2/token",
 				},
 				BaseURL: "https://example.salesforce.com",
+				ProviderOpts: map[string]string{
+					"restApiUrl": "https://example.my.salesforce.com/services/data/v59.0",
+					"domain":     "example.my.salesforce.com",
+				},
 			},
 			expectedErr: nil,
 		},
@@ -79,7 +83,7 @@ func TestReadConfig(t *testing.T) { //nolint:funlen
 				},
 				AuthType:  AuthTypeOAuth2,
 				OauthOpts: OauthOpts{},
-				BaseURL:   "https://api.linkedin.com/v2",
+				BaseURL:   "https://api.linkedin.com",
 			},
 			expectedErr: nil,
 		},
@@ -87,7 +91,7 @@ func TestReadConfig(t *testing.T) { //nolint:funlen
 			provider:    Provider("nonexistent"),
 			description: "Non-existent provider config",
 			substitutions: map[string]string{
-				"subdomain": "test",
+				"workspace": "test",
 			},
 			expected:    nil,
 			expectedErr: ErrProviderCatalogNotFound,
@@ -122,6 +126,12 @@ func TestReadConfig(t *testing.T) { //nolint:funlen
 				if config.BaseURL != tc.expected.BaseURL {
 					t.Errorf("[%s] Expected base URL: %s, but got: %s", tc.description, tc.expected.BaseURL, config.BaseURL)
 				}
+
+				for k, v := range config.ProviderOpts {
+					if tc.expected.ProviderOpts[k] != v {
+						t.Errorf("[%s] Expected provider option %s: %s, but got: %s", tc.description, k, tc.expected.ProviderOpts[k], v)
+					}
+				}
 			}
 		})
 	}
diff --git a/test/bulkwrite/main.go b/test/bulkwrite/main.go
index 59a15024..70fcd4aa 100644
--- a/test/bulkwrite/main.go
+++ b/test/bulkwrite/main.go
@@ -34,7 +34,7 @@ func main() { //nolint:funlen
 	accessToken := creds.AccessToken
 	refreshToken := creds.RefreshToken
 
-	salesforceSubdomain := creds.Subdomain
+	salesforceWorkspace := creds.Workspace
 
 	handler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
 		Level: slog.LevelDebug,
@@ -46,8 +46,8 @@ func main() { //nolint:funlen
 		ClientID:     clientId,
 		ClientSecret: clientSecret,
 		Endpoint: oauth2.Endpoint{
-			AuthURL:   fmt.Sprintf("https://%s.my.salesforce.com/services/oauth2/authorize", salesforceSubdomain),
-			TokenURL:  fmt.Sprintf("https://%s.my.salesforce.com/services/oauth2/token", salesforceSubdomain),
+			AuthURL:   fmt.Sprintf("https://%s.my.salesforce.com/services/oauth2/authorize", salesforceWorkspace),
+			TokenURL:  fmt.Sprintf("https://%s.my.salesforce.com/services/oauth2/token", salesforceWorkspace),
 			AuthStyle: oauth2.AuthStyleInParams,
 		},
 	}
@@ -64,7 +64,7 @@ func main() { //nolint:funlen
 	// Create a new Salesforce connector, with a token provider that uses the sfdx CLI to fetch an access token.
 	sfc, err := connectors.Salesforce(
 		salesforce.WithClient(ctx, http.DefaultClient, cfg, tok),
-		salesforce.WithSubdomain(salesforceSubdomain))
+		salesforce.WithWorkspace(salesforceWorkspace))
 	if err != nil {
 		slog.Error("Error creating Salesforce connector", "error", err)
 
diff --git a/test/creds.go b/test/creds.go
index eeaa1392..6833cc9c 100644
--- a/test/creds.go
+++ b/test/creds.go
@@ -12,7 +12,7 @@ type Creds struct {
 	ClientSecret string `json:"clientSecret"`
 	AccessToken  string `json:"accessToken"`
 	RefreshToken string `json:"refreshToken"`
-	Subdomain    string `json:"subdomain"`
+	Workspace    string `json:"workspace"`
 	Provider     string `json:"provider"`
 }
 
@@ -52,7 +52,7 @@ func GetCreds(path string) (*Creds, error) {
 		return nil, err
 	}
 
-	subdomain, err := credsMap.JSONPath("$.providerWorkspaceRef")
+	workspace, err := credsMap.JSONPath("$.providerWorkspaceRef")
 	if err != nil {
 		return nil, err
 	}
@@ -67,7 +67,7 @@ func GetCreds(path string) (*Creds, error) {
 		ClientSecret: clientSecret[0].MustString(),
 		AccessToken:  accessToken[0].MustString(),
 		RefreshToken: refreshToken[0].MustString(),
-		Subdomain:    subdomain[0].MustString(),
+		Workspace:    workspace[0].MustString(),
 		Provider:     provider[0].MustString(),
 	}
 
diff --git a/test/metadata/main.go b/test/metadata/main.go
index 8fa43b6a..ac930524 100644
--- a/test/metadata/main.go
+++ b/test/metadata/main.go
@@ -28,14 +28,14 @@ func main() {
 	accessToken := creds.AccessToken
 	refreshToken := creds.RefreshToken
 
-	salesforceSubdomain := creds.Subdomain
+	salesforceWorkspace := creds.Workspace
 
 	cfg := &oauth2.Config{
 		ClientID:     clientId,
 		ClientSecret: clientSecret,
 		Endpoint: oauth2.Endpoint{
-			AuthURL:   fmt.Sprintf("https://%s.my.salesforce.com/services/oauth2/authorize", salesforceSubdomain),
-			TokenURL:  fmt.Sprintf("https://%s.my.salesforce.com/services/oauth2/token", salesforceSubdomain),
+			AuthURL:   fmt.Sprintf("https://%s.my.salesforce.com/services/oauth2/authorize", salesforceWorkspace),
+			TokenURL:  fmt.Sprintf("https://%s.my.salesforce.com/services/oauth2/token", salesforceWorkspace),
 			AuthStyle: oauth2.AuthStyleInParams,
 		},
 	}
@@ -53,7 +53,7 @@ func main() {
 		salesforce.WithClient(ctx, http.DefaultClient, cfg, tok,
 			salesforce.GetTokenUpdater(tok), // this is necessary to update token
 		),
-		salesforce.WithSubdomain(salesforceSubdomain),
+		salesforce.WithWorkspace(salesforceWorkspace),
 	)
 	if err != nil {
 		slog.Error("Error creating Salesforce connector", "error", err)
diff --git a/test/salesforce/event-flow/main.go b/test/salesforce/event-flow/main.go
index aba0eb6f..c7d38856 100644
--- a/test/salesforce/event-flow/main.go
+++ b/test/salesforce/event-flow/main.go
@@ -29,7 +29,7 @@ func main() {
 	accessToken := creds.AccessToken
 	refreshToken := creds.RefreshToken
 
-	salesforceSubdomain := creds.Subdomain
+	salesforceWorkspace := creds.Workspace
 
 	handler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
 		Level: slog.LevelDebug,
@@ -41,8 +41,8 @@ func main() {
 		ClientID:     clientId,
 		ClientSecret: clientSecret,
 		Endpoint: oauth2.Endpoint{
-			AuthURL:   fmt.Sprintf("https://%s.my.salesforce.com/services/oauth2/authorize", salesforceSubdomain),
-			TokenURL:  fmt.Sprintf("https://%s.my.salesforce.com/services/oauth2/token", salesforceSubdomain),
+			AuthURL:   fmt.Sprintf("https://%s.my.salesforce.com/services/oauth2/authorize", salesforceWorkspace),
+			TokenURL:  fmt.Sprintf("https://%s.my.salesforce.com/services/oauth2/token", salesforceWorkspace),
 			AuthStyle: oauth2.AuthStyleInParams,
 		},
 	}
@@ -59,7 +59,7 @@ func main() {
 	// Create a new Salesforce connector, with a token provider that uses the sfdx CLI to fetch an access token.
 	sfc, err := connectors.Salesforce(
 		salesforce.WithClient(ctx, http.DefaultClient, cfg, tok),
-		salesforce.WithSubdomain(salesforceSubdomain))
+		salesforce.WithWorkspace(salesforceWorkspace))
 	if err != nil {
 		slog.Error("Error creating Salesforce connector", "error", err)
 
diff --git a/test/salesforce/read-write.go b/test/salesforce/read-write.go
index a84eedb5..971ffa8a 100644
--- a/test/salesforce/read-write.go
+++ b/test/salesforce/read-write.go
@@ -32,13 +32,13 @@ func mainFn() int { //nolint:funlen
 		return 1
 	}
 
-	salesforceSubdomain := os.Getenv("SALESFORCE_SUBDOMAIN")
+	salesforceWorkspace := os.Getenv("SALESFORCE_WORKSPACE")
 	clientId := os.Getenv("SALESFORCE_CLIENT_ID")
 	clientSecret := os.Getenv("SALESFORCE_CLIENT_SECRET")
 	accessToken := os.Getenv("SALESFORCE_ACCESS_TOKEN")
 	refreshToken := os.Getenv("SALESFORCE_REFRESH_TOKEN")
 
-	subdomain := flag.String("subdomain", salesforceSubdomain, "Salesforce subdomain")
+	workspace := flag.String("workspace", salesforceWorkspace, "Salesforce workspace")
 	flag.Parse()
 
 	handler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
@@ -51,8 +51,8 @@ func mainFn() int { //nolint:funlen
 		ClientID:     clientId,
 		ClientSecret: clientSecret,
 		Endpoint: oauth2.Endpoint{
-			AuthURL:   fmt.Sprintf("https://%s.my.salesforce.com/services/oauth2/authorize", *subdomain),
-			TokenURL:  fmt.Sprintf("https://%s.my.salesforce.com/services/oauth2/token", *subdomain),
+			AuthURL:   fmt.Sprintf("https://%s.my.salesforce.com/services/oauth2/authorize", *workspace),
+			TokenURL:  fmt.Sprintf("https://%s.my.salesforce.com/services/oauth2/token", *workspace),
 			AuthStyle: oauth2.AuthStyleInParams,
 		},
 	}
@@ -69,7 +69,7 @@ func mainFn() int { //nolint:funlen
 	// Create a new Salesforce connector, with a token provider that uses the sfdx CLI to fetch an access token.
 	sfc, err := connectors.Salesforce(
 		salesforce.WithClient(ctx, http.DefaultClient, cfg, tok),
-		salesforce.WithSubdomain(*subdomain))
+		salesforce.WithWorkspace(*workspace))
 	if err != nil {
 		slog.Error("Error creating Salesforce connector", "error", err)