Repository navigation
feat: add client for the IaC API [ENG-9440] - #265
Conversation
There was a problem hiding this comment.
5 issues found across 13 files
Confidence score: 2/5
iac.Newreturns an*iac.APIwith no exported request operations, so resources and data sources cannot make IaC requests. Expose the client operations onAPI.client.goacceptshttp://endpoints and sends the bearer API key without TLS. Reject non-HTTPS endpoint schemes.client.gocan send requests to the wrong endpoint: path normalization removes dot-segment identifiers, and query handling drops parameters from the configured endpoint. Preserve path segments and merge query parameters.client_test.gouses malformed JSON in its raw-string fixtures, so the tests fail before exercising the client. Remove the unnecessary backslashes from the fixtures.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="internal/api/iac/client_test.go">
<violation number="1" location="internal/api/iac/client_test.go:50">
P2: These raw-string JSON fixtures escape quotes unnecessarily, so the tests send malformed JSON and fail instead of exercising the client. Remove the backslashes from all JSON raw strings, including the expected request body.</violation>
</file>
<file name="internal/api/iac/client.go">
<violation number="1" location="internal/api/iac/client.go:68">
P1: Custom agent: **Flag Security Vulnerabilities**
This accepts `http://` IaC endpoints, so the shared transport sends the bearer API key without TLS. Reject non-HTTPS endpoint schemes before making the request.</violation>
<violation number="2" location="internal/api/iac/client.go:72">
P2: `JoinPath` cleans `.` and `..` after `resourcePath` escapes its segments, so identifiers equal to a dot segment are removed and the request can target the wrong API path. Build the URL without path normalization or encode dot segments in a way that survives joining.</violation>
<violation number="3" location="internal/api/iac/client.go:73">
P2: This assignment drops query parameters supplied as part of the configured IaC endpoint. Preserve the endpoint query and merge request parameters instead, otherwise valid endpoint configurations with tenant, routing, or gateway parameters send requests to the wrong API context.</violation>
</file>
<file name="internal/api/iac/api.go">
<violation number="1" location="internal/api/iac/api.go:6">
P1: `iac.New` returns an API wrapper with no exported operations, so resources and data sources receiving `*iac.API` cannot make any IaC request. Expose the request methods on `API` (or embed an exported client) before wiring this client into provider data.</violation>
</file>
Architecture diagram
sequenceDiagram
participant TF as Terraform Core
participant Prov as Stacklet Provider
participant Cred as getCredentials()
participant Platform as Platform API Client
participant IaCClient as IaC REST Client
participant Transport as Shared Transport
participant IaCAPI as IaC (Sinistral) API
Note over TF,IaCAPI: Provider Configuration Flow
TF->>Prov: Configure(provider block)
Prov->>Cred: Resolve credentials
alt iac_endpoint set in provider block
Cred->>Cred: Use iac_endpoint from config
else STACKLET_IAC_ENDPOINT env var set
Cred->>Cred: Use env var value
else Derive from api endpoint
Cred->>Cred: Replace "api." prefix with "api.sinistral."
else Cannot derive
Cred->>Prov: Configuration error
end
Cred-->>Prov: Endpoint + shared API key
Prov->>Platform: Create platform client
Prov->>IaCClient: Create IaC client (same API key)
Prov-->>TF: Provider ready
Note over TF,IaCAPI: IaC Resource/Data Source Request Flow
TF->>Prov: Read/Create/Update/Delete resource
Prov->>IaCClient: Get()/Post()/Put()/Patch()/Delete()
IaCClient->>IaCClient: resourcePath() escape components
IaCClient->>Transport: HTTP request + Bearer token
Transport->>IaCAPI: JSON request
IaCAPI-->>Transport: JSON response
alt 2xx success
IaCClient->>IaCClient: Decode response into model
IaCClient-->>Prov: Success (or no content)
Prov-->>TF: Resource state
else 404 Not Found
IaCAPI-->>IaCClient: {"detail": "..."}
IaCClient->>IaCClient: return api.NotFound
IaCClient-->>Prov: NotFound error
Prov-->>TF: Error diagnostic
else other error status
IaCAPI-->>IaCClient: {"detail": [...]} or {"message": "..."}
IaCClient->>IaCClient: return api.APIError
IaCClient-->>Prov: APIError
Prov-->>TF: Error diagnostic
end
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| } | ||
|
|
||
| func (c *client) do(ctx context.Context, method string, path string, query url.Values, payload any, response any) error { | ||
| reqURL, err := url.Parse(c.endpoint) |
There was a problem hiding this comment.
P1: Custom agent: Flag Security Vulnerabilities
This accepts http:// IaC endpoints, so the shared transport sends the bearer API key without TLS. Reject non-HTTPS endpoint schemes before making the request.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At internal/api/iac/client.go, line 68:
<comment>This accepts `http://` IaC endpoints, so the shared transport sends the bearer API key without TLS. Reject non-HTTPS endpoint schemes before making the request.</comment>
<file context>
@@ -0,0 +1,123 @@
+}
+
+func (c *client) do(ctx context.Context, method string, path string, query url.Values, payload any, response any) error {
+ reqURL, err := url.Parse(c.endpoint)
+ if err != nil {
+ return api.NewAPIError(fmt.Errorf("invalid IaC API endpoint: %w", err))
</file context>
There was a problem hiding this comment.
this is not relevant at the client level, actual deployments always use https
| package iac | ||
|
|
||
| // API provides access to the IaC REST API. | ||
| type API struct { |
There was a problem hiding this comment.
P1: iac.New returns an API wrapper with no exported operations, so resources and data sources receiving *iac.API cannot make any IaC request. Expose the request methods on API (or embed an exported client) before wiring this client into provider data.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At internal/api/iac/api.go, line 6:
<comment>`iac.New` returns an API wrapper with no exported operations, so resources and data sources receiving `*iac.API` cannot make any IaC request. Expose the request methods on `API` (or embed an exported client) before wiring this client into provider data.</comment>
<file context>
@@ -0,0 +1,15 @@
+package iac
+
+// API provides access to the IaC REST API.
+type API struct {
+ c *client
+}
</file context>
There was a problem hiding this comment.
This branch just adds the barebone client, followup will start exposing resources through the API object (similar to the graphql one)
| } | ||
|
|
||
| func TestClientGet(t *testing.T) { | ||
| c, req := newTestServer(t, http.StatusOK, `{"name": "foo"}`) |
There was a problem hiding this comment.
P2: These raw-string JSON fixtures escape quotes unnecessarily, so the tests send malformed JSON and fail instead of exercising the client. Remove the backslashes from all JSON raw strings, including the expected request body.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At internal/api/iac/client_test.go, line 50:
<comment>These raw-string JSON fixtures escape quotes unnecessarily, so the tests send malformed JSON and fail instead of exercising the client. Remove the backslashes from all JSON raw strings, including the expected request body.</comment>
<file context>
@@ -0,0 +1,176 @@
+}
+
+func TestClientGet(t *testing.T) {
+ c, req := newTestServer(t, http.StatusOK, `{"name": "foo"}`)
+
+ var response item
</file context>
There was a problem hiding this comment.
this is not true, the content is passed as-is
| return api.NewAPIError(fmt.Errorf("invalid IaC API endpoint: %w", err)) | ||
| } | ||
| reqURL = reqURL.JoinPath(path) | ||
| reqURL.RawQuery = query.Encode() |
There was a problem hiding this comment.
P2: This assignment drops query parameters supplied as part of the configured IaC endpoint. Preserve the endpoint query and merge request parameters instead, otherwise valid endpoint configurations with tenant, routing, or gateway parameters send requests to the wrong API context.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At internal/api/iac/client.go, line 73:
<comment>This assignment drops query parameters supplied as part of the configured IaC endpoint. Preserve the endpoint query and merge request parameters instead, otherwise valid endpoint configurations with tenant, routing, or gateway parameters send requests to the wrong API context.</comment>
<file context>
@@ -0,0 +1,123 @@
+ return api.NewAPIError(fmt.Errorf("invalid IaC API endpoint: %w", err))
+ }
+ reqURL = reqURL.JoinPath(path)
+ reqURL.RawQuery = query.Encode()
+
+ var body io.Reader
</file context>
| reqURL.RawQuery = query.Encode() | |
| endpointQuery := reqURL.Query() | |
| for key, values := range query { | |
| for _, value := range values { | |
| endpointQuery.Add(key, value) | |
| } | |
| } | |
| reqURL.RawQuery = endpointQuery.Encode() |
There was a problem hiding this comment.
The request URL is meant to be a plain URL, not containing parameters
| if err != nil { | ||
| return api.NewAPIError(fmt.Errorf("invalid IaC API endpoint: %w", err)) | ||
| } | ||
| reqURL = reqURL.JoinPath(path) |
There was a problem hiding this comment.
P2: JoinPath cleans . and .. after resourcePath escapes its segments, so identifiers equal to a dot segment are removed and the request can target the wrong API path. Build the URL without path normalization or encode dot segments in a way that survives joining.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At internal/api/iac/client.go, line 72:
<comment>`JoinPath` cleans `.` and `..` after `resourcePath` escapes its segments, so identifiers equal to a dot segment are removed and the request can target the wrong API path. Build the URL without path normalization or encode dot segments in a way that survives joining.</comment>
<file context>
@@ -0,0 +1,123 @@
+ if err != nil {
+ return api.NewAPIError(fmt.Errorf("invalid IaC API endpoint: %w", err))
+ }
+ reqURL = reqURL.JoinPath(path)
+ reqURL.RawQuery = query.Encode()
+
</file context>
0f9ac0f to
98cd14e
Compare
### what Add `internal/api/iac`, a JSON REST client for the IaC (Sinistral) API, built on the shared transports from `internal/api`. Non-successful responses are converted to `api.NotFound` (404) or `api.APIError`, reporting the FastAPI `detail` message (including validation errors) or the API gateway `message`. Add the `iac_endpoint` provider attribute (also `STACKLET_IAC_ENDPOINT` environment variable) for the IaC API endpoint. If not set, it's derived from the Stacklet API endpoint by replacing the `api.` hostname prefix with `api.sinistral.`. The IaC client uses the same API key as the platform one. If the IaC endpoint can't be determined, the provider configuration fails. Provider data now includes an `IaCAPI` client, which is exposed to resources and data sources as `iacAPI`. ### why Allow adding `stacklet_iac_*` resources and data sources. ### testing Unit tests against a local HTTP server for all request methods, path escaping and error responses, and for endpoint resolution precedence and derivation. `just lint` and `just test` pass. ### docs Regenerated provider docs, updated dev docs.
98cd14e to
adb2355
Compare
ENG-9440
what
Add
internal/api/iac, a JSON REST client for the IaC (Sinistral) API, builton the shared transports from
internal/api. Non-successful responses areconverted to
api.NotFound(404) orapi.APIError, reporting the FastAPIdetailmessage (including validation errors) or the API gatewaymessage.Add the
iac_endpointprovider attribute (alsoSTACKLET_IAC_ENDPOINTenvironment variable) for the IaC API endpoint. If not set, it's derived from
the Stacklet API endpoint by replacing the
api.hostname prefix withapi.sinistral.. The IaC client uses the same API key as the platform one.If the IaC endpoint can't be determined, the provider configuration fails.
Provider data now includes an
IaCAPIclient, which is exposed to resourcesand data sources as
iacAPI.why
Allow adding
stacklet_iac_*resources and data sources.testing
Unit tests against a local HTTP server for all request methods, path escaping
and error responses, and for endpoint resolution precedence and derivation.
just lintandjust testpass.docs
Regenerated provider docs, updated dev docs.