step.http_call: add oauth2 config key and Salesforce instance_url support#244
step.http_call: add oauth2 config key and Salesforce instance_url support#244
oauth2 config key and Salesforce instance_url support#244Conversation
Co-authored-by: intel352 <77607+intel352@users.noreply.github.com>
oauth2 config key and Salesforce instance_url support
There was a problem hiding this comment.
Pull request overview
This PR enhances step.http_call to better support OAuth2 client-credentials flows (notably Salesforce) by adding an ergonomic top-level oauth2 config and propagating instance_url from the token response into template resolution and step outputs.
Changes:
- Added a top-level
oauth2config block (as an alternative toauth.type: oauth2_client_credentials) with basic validation. - Cached
instance_urlalongside the access token and injected it intopc.Currentbefore URL template resolution; also included it in step output. - Added unit tests covering the new config path, default/invalid grant_type handling, and
instance_urlbehavior; updated schema docs.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
module/pipeline_step_http_call.go |
Adds instance_url caching/accessors, parses instance_url from token responses, supports top-level oauth2 config, and reorders token fetch before URL template resolution. |
module/pipeline_step_http_call_test.go |
Adds tests for the new oauth2 config key, grant_type validation/defaulting, and instance_url injection/caching behavior. |
schema/module_schema.go |
Documents the new oauth2 config block and notes instance_url usage in URL templates. |
| output := parseHTTPResponse(retryResp, respBody) | ||
| if instanceURL := s.oauthEntry.getInstanceURL(); instanceURL != "" { | ||
| output["instance_url"] = instanceURL | ||
| } |
There was a problem hiding this comment.
The 401-retry path now returns instance_url from the refreshed token, but the retry request is still sent to the pre-refresh resolvedURL and uses whatever pc.Current contained before refresh. If instance_url changes between token responses (Salesforce can), the retry may hit the wrong host. After doFetchToken on 401, refresh pc.Current["instance_url"] from the cache and re-resolve the URL/template-dependent fields before building the retry request.
schema/module_schema.go
Outdated
| Outputs: []ServiceIODef{{Name: "result", Type: "StepResult", Description: "HTTP response body parsed as JSON and merged into pipeline context"}}, | ||
| ConfigFields: []ConfigFieldDef{ | ||
| {Key: "url", Label: "URL", Type: FieldTypeString, Required: true, Description: "Request URL (supports {{ .field }} templates)", Placeholder: "https://api.example.com/{{ .resource }}"}, | ||
| {Key: "url", Label: "URL", Type: FieldTypeString, Required: true, Description: "Request URL (supports {{ .field }} templates; use {{ .instance_url }} when oauth2 is configured with an instance_url-returning endpoint)", Placeholder: "https://api.example.com/{{ .resource }}"}, |
There was a problem hiding this comment.
The URL field description suggests {{ .instance_url }} is only available when the top-level oauth2 block is used, but the code populates instance_url for any OAuth2 client_credentials auth (including auth.type=oauth2_client_credentials). Consider rewording to avoid implying the feature is limited to the oauth2 config key.
| {Key: "url", Label: "URL", Type: FieldTypeString, Required: true, Description: "Request URL (supports {{ .field }} templates; use {{ .instance_url }} when oauth2 is configured with an instance_url-returning endpoint)", Placeholder: "https://api.example.com/{{ .resource }}"}, | |
| {Key: "url", Label: "URL", Type: FieldTypeString, Required: true, Description: "Request URL (supports {{ .field }} templates; {{ .instance_url }} is available when OAuth2 client_credentials auth uses a token endpoint that returns instance_url)", Placeholder: "https://api.example.com/{{ .resource }}"}, |
| // Support top-level "oauth2" key as an alternative to "auth" with type=oauth2_client_credentials. | ||
| // This follows the syntax proposed in the issue and is more idiomatic for Salesforce-style configs: | ||
| // oauth2: | ||
| // grant_type: client_credentials (optional, defaults to client_credentials) | ||
| // token_url: "..." | ||
| // client_id: "..." | ||
| // client_secret: "..." | ||
| // scopes: ["api"] | ||
| if oauth2Cfg, ok := config["oauth2"].(map[string]any); ok && step.auth == nil { |
There was a problem hiding this comment.
The new top-level oauth2 parsing largely duplicates the existing auth.type=oauth2_client_credentials parsing (scopes parsing, cacheKey derivation, required fields). This duplication increases the risk the two config paths drift (e.g., future additions like extra params). Consider extracting a shared helper to build oauthConfig/oauthEntry from a generic map, or normalize oauth2 into an authCfg and reuse the existing branch.
| // Pre-populate the cache entry so instance_url resolves to apiSrv.URL instead of the fake. | ||
| hs.oauthEntry.set("sf-token", apiSrv.URL, 3600*time.Second) | ||
|
|
There was a problem hiding this comment.
In TestHTTPCallStep_OAuth2_InstanceURL, the cache is pre-populated (hs.oauthEntry.set(...)), so getToken() never calls the token server and the test doesn't actually validate parsing instance_url from the token response. To better match the test name/comment, consider letting the step fetch the token (have the tokenSrv return apiSrv.URL as instance_url) and assert the token endpoint was hit, instead of seeding the cache manually.
schema/module_schema.go
Outdated
| {Key: "method", Label: "Method", Type: FieldTypeSelect, Options: []string{"GET", "POST", "PUT", "PATCH", "DELETE"}, DefaultValue: "GET", Description: "HTTP method"}, | ||
| {Key: "headers", Label: "Headers", Type: FieldTypeMap, MapValueType: "string", Description: "Request headers (values support templates)"}, | ||
| {Key: "body", Label: "Body", Type: FieldTypeJSON, Description: "Request body (supports templates). For POST/PUT without body, sends pipeline context."}, | ||
| {Key: "timeout", Label: "Timeout", Type: FieldTypeString, DefaultValue: "30s", Description: "Request timeout duration", Placeholder: "30s"}, | ||
| {Key: "oauth2", Label: "OAuth2", Type: FieldTypeJSON, Description: "OAuth2 client_credentials configuration. Tokens are cached and refreshed automatically. Fields: grant_type (default: client_credentials), token_url, client_id, client_secret, scopes. When the token endpoint returns instance_url (Salesforce pattern), it is injected as {{ .instance_url }} for URL templates and included in the step output."}, |
There was a problem hiding this comment.
Schema docs for the new oauth2 field don't mention how it interacts with the existing auth block (the factory only applies oauth2 when auth is absent). Consider documenting the precedence/mutual-exclusion so users don't accidentally specify both and wonder why oauth2 is ignored.
| {Key: "method", Label: "Method", Type: FieldTypeSelect, Options: []string{"GET", "POST", "PUT", "PATCH", "DELETE"}, DefaultValue: "GET", Description: "HTTP method"}, | |
| {Key: "headers", Label: "Headers", Type: FieldTypeMap, MapValueType: "string", Description: "Request headers (values support templates)"}, | |
| {Key: "body", Label: "Body", Type: FieldTypeJSON, Description: "Request body (supports templates). For POST/PUT without body, sends pipeline context."}, | |
| {Key: "timeout", Label: "Timeout", Type: FieldTypeString, DefaultValue: "30s", Description: "Request timeout duration", Placeholder: "30s"}, | |
| {Key: "oauth2", Label: "OAuth2", Type: FieldTypeJSON, Description: "OAuth2 client_credentials configuration. Tokens are cached and refreshed automatically. Fields: grant_type (default: client_credentials), token_url, client_id, client_secret, scopes. When the token endpoint returns instance_url (Salesforce pattern), it is injected as {{ .instance_url }} for URL templates and included in the step output."}, | |
| {Key: "method", Label: "Method", Type: FieldTypeSelect, Options: []string{"GET", "POST", "PUT, "PATCH", "DELETE"}, DefaultValue: "GET", Description: "HTTP method"}, | |
| {Key: "headers", Label: "Headers", Type: FieldTypeMap, MapValueType: "string", Description: "Request headers (values support templates)"}, | |
| {Key: "body", Label: "Body", Type: FieldTypeJSON, Description: "Request body (supports templates). For POST/PUT without body, sends pipeline context."}, | |
| {Key: "timeout", Label: "Timeout", Type: FieldTypeString, DefaultValue: "30s", Description: "Request timeout duration", Placeholder: "30s"}, | |
| {Key: "oauth2", Label: "OAuth2", Type: FieldTypeJSON, Description: "OAuth2 client_credentials configuration. Tokens are cached and refreshed automatically. Fields: grant_type (default: client_credentials), token_url, client_id, client_secret, scopes. When the token endpoint returns instance_url (Salesforce pattern), it is injected as {{ .instance_url }} for URL templates and included in the step output. If both the legacy auth block and oauth2 are configured, auth takes precedence and oauth2 is ignored.",}, |
|
@copilot apply changes based on the comments in this thread |
…ix 401 retry, improve schema docs, fix instance_url test Co-authored-by: intel352 <77607+intel352@users.noreply.github.com>
Applied all changes from the review thread in commit a988dec:
|
step.http_callOAuth2 implementationoauth2config key as alternative toauth(Salesforce-friendly syntax)instance_urlcaching inoauthCacheEntryinstance_urlfrom token response indoFetchTokeninstance_urlinto template data for URL resolution inExecuteinstance_urlin step outputstep.http_callschema to documentoauth2fieldoauth2config syntaxinstance_urlsupportbuildOAuthConfigshared helper to eliminate duplicate parsing logic betweenauthandoauth2blockspc.Current["instance_url"]and re-resolve URL template after token refresh (instance_url may change)TestHTTPCallStep_OAuth2_InstanceURLto actually test token response parsing (not pre-populated cache)TestHTTPCallStep_OAuth2_Retry401_RefreshesInstanceURLtest for 401 retry with instance_url re-resolutioninstance_urlis available for all OAuth2 client_credentials (not justoauth2block)auth-takes-precedence-over-oauth2in schema descriptionOriginal prompt
🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.