diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2900114..b1366ee 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -80,5 +80,5 @@ jobs: TF_ACC: "1" CTRLPLANE_TOKEN: ${{ secrets.CTRLPLANE_TOKEN }} CTRLPLANE_WORKSPACE: ${{ vars.CTRLPLANE_WORKSPACE }} - run: go test -v -cover ./internal/provider/ + run: go test -v -cover ./internal/provider/... ./internal/resources/... timeout-minutes: 10 diff --git a/GNUmakefile b/GNUmakefile index 76f07f0..51f2e91 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -39,7 +39,7 @@ testacc: -timeout=$${GO_TEST_TIMEOUT:-120m} \ -parallel=$${GO_TEST_PARALLEL:-4} \ -cover \ - $${TEST:-./internal/provider/...} $${TESTARGS} + $${TEST:-./internal/provider/... ./internal/resources/...} $${TESTARGS} testacc-quiet: @if [ ! -f .env ]; then \ @@ -58,7 +58,7 @@ testacc-quiet: -parallel=$${GO_TEST_PARALLEL:-4} \ -cover \ -v=0 \ - $${TEST:-./internal/provider/...} $${TESTARGS} + $${TEST:-./internal/provider/... ./internal/resources/...} $${TESTARGS} testint: @if [ ! -f .env ]; then \ @@ -89,4 +89,4 @@ clean: rm -f .terraform.lock.hcl rm -f terraform.tfstate* -.PHONY: fmt lint test testacc testint testexamples build install generate clean install-local +.PHONY: fmt lint test testacc testacc-quiet testint testexamples build install generate clean install-local diff --git a/client/client.gen.go b/client/client.gen.go index 1b7c907..8939c99 100644 --- a/client/client.gen.go +++ b/client/client.gen.go @@ -62,6 +62,13 @@ const ( Some PolicySuccessType = "some" ) +// Defines values for GetCloudProviderRegionsParamsProvider. +const ( + Aws GetCloudProviderRegionsParamsProvider = "aws" + Azure GetCloudProviderRegionsParamsProvider = "azure" + Gcp GetCloudProviderRegionsParamsProvider = "gcp" +) + // Defines values for UpsertReleaseJSONBodyStatus. const ( UpsertReleaseJSONBodyStatusBuilding UpsertReleaseJSONBodyStatus = "building" @@ -76,6 +83,18 @@ const ( UpdateReleaseJSONBodyStatusReady UpdateReleaseJSONBodyStatus = "ready" ) +// CloudRegionGeoData defines model for CloudRegionGeoData. +type CloudRegionGeoData struct { + // Latitude Latitude coordinate for the region + Latitude float32 `json:"latitude"` + + // Longitude Longitude coordinate for the region + Longitude float32 `json:"longitude"` + + // Timezone Timezone of the region in UTC format + Timezone string `json:"timezone"` +} + // Deployment defines model for Deployment. type Deployment struct { Description string `json:"description"` @@ -83,14 +102,19 @@ type Deployment struct { JobAgentConfig map[string]interface{} `json:"jobAgentConfig"` JobAgentId *openapi_types.UUID `json:"jobAgentId"` Name string `json:"name"` + RetryCount *int `json:"retryCount,omitempty"` Slug string `json:"slug"` SystemId openapi_types.UUID `json:"systemId"` + Timeout *int `json:"timeout"` } // Environment defines model for Environment. type Environment struct { - CreatedAt time.Time `json:"createdAt"` - Description *string `json:"description,omitempty"` + CreatedAt time.Time `json:"createdAt"` + Description *string `json:"description,omitempty"` + + // Directory The directory path of the environment + Directory string `json:"directory"` Id openapi_types.UUID `json:"id"` Metadata *map[string]string `json:"metadata,omitempty"` Name string `json:"name"` @@ -256,6 +280,20 @@ type System struct { WorkspaceId openapi_types.UUID `json:"workspaceId"` } +// UpdateDeployment defines model for UpdateDeployment. +type UpdateDeployment struct { + Description string `json:"description"` + Id openapi_types.UUID `json:"id"` + JobAgentConfig map[string]interface{} `json:"jobAgentConfig"` + JobAgentId *openapi_types.UUID `json:"jobAgentId"` + Name string `json:"name"` + RetryCount *int `json:"retryCount,omitempty"` + Slug string `json:"slug"` + SystemId openapi_types.UUID `json:"systemId"` + Timeout *int `json:"timeout"` + AdditionalProperties map[string]interface{} `json:"-"` +} + // Variable defines model for Variable. type Variable struct { Key string `json:"key"` @@ -295,6 +333,9 @@ type Workspace struct { Slug string `json:"slug"` } +// GetCloudProviderRegionsParamsProvider defines parameters for GetCloudProviderRegions. +type GetCloudProviderRegionsParamsProvider string + // CreateDeploymentJSONBody defines parameters for CreateDeployment. type CreateDeploymentJSONBody struct { // Description The description of the deployment @@ -327,13 +368,16 @@ type CreateDeploymentJSONBody struct { // CreateEnvironmentJSONBody defines parameters for CreateEnvironment. type CreateEnvironmentJSONBody struct { - Description *string `json:"description,omitempty"` - Metadata *map[string]string `json:"metadata,omitempty"` - Name string `json:"name"` - PolicyId *string `json:"policyId,omitempty"` - ReleaseChannels *[]string `json:"releaseChannels,omitempty"` - ResourceFilter *map[string]interface{} `json:"resourceFilter,omitempty"` - SystemId string `json:"systemId"` + DeploymentVersionChannels *[]string `json:"deploymentVersionChannels,omitempty"` + Description *string `json:"description,omitempty"` + + // Directory The directory path of the environment + Directory *string `json:"directory,omitempty"` + Metadata *map[string]string `json:"metadata,omitempty"` + Name string `json:"name"` + PolicyId *string `json:"policyId,omitempty"` + ResourceFilter *map[string]interface{} `json:"resourceFilter,omitempty"` + SystemId string `json:"systemId"` } // UpsertJobAgentJSONBody defines parameters for UpsertJobAgent. @@ -484,6 +528,9 @@ type UpdateSystemJSONBody struct { // CreateDeploymentJSONRequestBody defines body for CreateDeployment for application/json ContentType. type CreateDeploymentJSONRequestBody CreateDeploymentJSONBody +// UpdateDeploymentJSONRequestBody defines body for UpdateDeployment for application/json ContentType. +type UpdateDeploymentJSONRequestBody = UpdateDeployment + // CreateEnvironmentJSONRequestBody defines body for CreateEnvironment for application/json ContentType. type CreateEnvironmentJSONRequestBody CreateEnvironmentJSONBody @@ -523,6 +570,182 @@ type CreateSystemJSONRequestBody CreateSystemJSONBody // UpdateSystemJSONRequestBody defines body for UpdateSystem for application/json ContentType. type UpdateSystemJSONRequestBody UpdateSystemJSONBody +// Getter for additional properties for UpdateDeployment. Returns the specified +// element and whether it was found +func (a UpdateDeployment) Get(fieldName string) (value interface{}, found bool) { + if a.AdditionalProperties != nil { + value, found = a.AdditionalProperties[fieldName] + } + return +} + +// Setter for additional properties for UpdateDeployment +func (a *UpdateDeployment) Set(fieldName string, value interface{}) { + if a.AdditionalProperties == nil { + a.AdditionalProperties = make(map[string]interface{}) + } + a.AdditionalProperties[fieldName] = value +} + +// Override default JSON handling for UpdateDeployment to handle AdditionalProperties +func (a *UpdateDeployment) UnmarshalJSON(b []byte) error { + object := make(map[string]json.RawMessage) + err := json.Unmarshal(b, &object) + if err != nil { + return err + } + + if raw, found := object["description"]; found { + err = json.Unmarshal(raw, &a.Description) + if err != nil { + return fmt.Errorf("error reading 'description': %w", err) + } + delete(object, "description") + } + + if raw, found := object["id"]; found { + err = json.Unmarshal(raw, &a.Id) + if err != nil { + return fmt.Errorf("error reading 'id': %w", err) + } + delete(object, "id") + } + + if raw, found := object["jobAgentConfig"]; found { + err = json.Unmarshal(raw, &a.JobAgentConfig) + if err != nil { + return fmt.Errorf("error reading 'jobAgentConfig': %w", err) + } + delete(object, "jobAgentConfig") + } + + if raw, found := object["jobAgentId"]; found { + err = json.Unmarshal(raw, &a.JobAgentId) + if err != nil { + return fmt.Errorf("error reading 'jobAgentId': %w", err) + } + delete(object, "jobAgentId") + } + + if raw, found := object["name"]; found { + err = json.Unmarshal(raw, &a.Name) + if err != nil { + return fmt.Errorf("error reading 'name': %w", err) + } + delete(object, "name") + } + + if raw, found := object["retryCount"]; found { + err = json.Unmarshal(raw, &a.RetryCount) + if err != nil { + return fmt.Errorf("error reading 'retryCount': %w", err) + } + delete(object, "retryCount") + } + + if raw, found := object["slug"]; found { + err = json.Unmarshal(raw, &a.Slug) + if err != nil { + return fmt.Errorf("error reading 'slug': %w", err) + } + delete(object, "slug") + } + + if raw, found := object["systemId"]; found { + err = json.Unmarshal(raw, &a.SystemId) + if err != nil { + return fmt.Errorf("error reading 'systemId': %w", err) + } + delete(object, "systemId") + } + + if raw, found := object["timeout"]; found { + err = json.Unmarshal(raw, &a.Timeout) + if err != nil { + return fmt.Errorf("error reading 'timeout': %w", err) + } + delete(object, "timeout") + } + + if len(object) != 0 { + a.AdditionalProperties = make(map[string]interface{}) + for fieldName, fieldBuf := range object { + var fieldVal interface{} + err := json.Unmarshal(fieldBuf, &fieldVal) + if err != nil { + return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err) + } + a.AdditionalProperties[fieldName] = fieldVal + } + } + return nil +} + +// Override default JSON handling for UpdateDeployment to handle AdditionalProperties +func (a UpdateDeployment) MarshalJSON() ([]byte, error) { + var err error + object := make(map[string]json.RawMessage) + + object["description"], err = json.Marshal(a.Description) + if err != nil { + return nil, fmt.Errorf("error marshaling 'description': %w", err) + } + + object["id"], err = json.Marshal(a.Id) + if err != nil { + return nil, fmt.Errorf("error marshaling 'id': %w", err) + } + + object["jobAgentConfig"], err = json.Marshal(a.JobAgentConfig) + if err != nil { + return nil, fmt.Errorf("error marshaling 'jobAgentConfig': %w", err) + } + + if a.JobAgentId != nil { + object["jobAgentId"], err = json.Marshal(a.JobAgentId) + if err != nil { + return nil, fmt.Errorf("error marshaling 'jobAgentId': %w", err) + } + } + + object["name"], err = json.Marshal(a.Name) + if err != nil { + return nil, fmt.Errorf("error marshaling 'name': %w", err) + } + + if a.RetryCount != nil { + object["retryCount"], err = json.Marshal(a.RetryCount) + if err != nil { + return nil, fmt.Errorf("error marshaling 'retryCount': %w", err) + } + } + + object["slug"], err = json.Marshal(a.Slug) + if err != nil { + return nil, fmt.Errorf("error marshaling 'slug': %w", err) + } + + object["systemId"], err = json.Marshal(a.SystemId) + if err != nil { + return nil, fmt.Errorf("error marshaling 'systemId': %w", err) + } + + if a.Timeout != nil { + object["timeout"], err = json.Marshal(a.Timeout) + if err != nil { + return nil, fmt.Errorf("error marshaling 'timeout': %w", err) + } + } + + for fieldName, field := range a.AdditionalProperties { + object[fieldName], err = json.Marshal(field) + if err != nil { + return nil, fmt.Errorf("error marshaling '%s': %w", fieldName, err) + } + } + return json.Marshal(object) +} + // AsVariableValue0 returns the union data inside the Variable_Value as a VariableValue0 func (t Variable_Value) AsVariableValue0() (VariableValue0, error) { var body VariableValue0 @@ -684,6 +907,9 @@ func WithRequestEditorFn(fn RequestEditorFn) ClientOption { // The interface specification for the client above. type ClientInterface interface { + // GetCloudProviderRegions request + GetCloudProviderRegions(ctx context.Context, provider GetCloudProviderRegionsParamsProvider, reqEditors ...RequestEditorFn) (*http.Response, error) + // CreateDeploymentWithBody request with any body CreateDeploymentWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -695,6 +921,11 @@ type ClientInterface interface { // GetDeployment request GetDeployment(ctx context.Context, deploymentId openapi_types.UUID, reqEditors ...RequestEditorFn) (*http.Response, error) + // UpdateDeploymentWithBody request with any body + UpdateDeploymentWithBody(ctx context.Context, deploymentId openapi_types.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + UpdateDeployment(ctx context.Context, deploymentId openapi_types.UUID, body UpdateDeploymentJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // DeleteReleaseChannel request DeleteReleaseChannel(ctx context.Context, deploymentId string, name string, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -805,14 +1036,41 @@ type ClientInterface interface { // GetWorkspace request GetWorkspace(ctx context.Context, workspaceId openapi_types.UUID, reqEditors ...RequestEditorFn) (*http.Response, error) + // ListDeployments request + ListDeployments(ctx context.Context, workspaceId string, reqEditors ...RequestEditorFn) (*http.Response, error) + + // ListEnvironments request + ListEnvironments(ctx context.Context, workspaceId string, reqEditors ...RequestEditorFn) (*http.Response, error) + // UpsertResourceProvider request UpsertResourceProvider(ctx context.Context, workspaceId string, name string, reqEditors ...RequestEditorFn) (*http.Response, error) + // ListResources request + ListResources(ctx context.Context, workspaceId string, reqEditors ...RequestEditorFn) (*http.Response, error) + // DeleteResourceByIdentifier request DeleteResourceByIdentifier(ctx context.Context, workspaceId string, identifier string, reqEditors ...RequestEditorFn) (*http.Response, error) // GetResourceByIdentifier request GetResourceByIdentifier(ctx context.Context, workspaceId string, identifier string, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetResourcesByFilter request + GetResourcesByFilter(ctx context.Context, workspaceId string, filter string, reqEditors ...RequestEditorFn) (*http.Response, error) + + // ListSystems request + ListSystems(ctx context.Context, workspaceId string, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +func (c *Client) GetCloudProviderRegions(ctx context.Context, provider GetCloudProviderRegionsParamsProvider, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetCloudProviderRegionsRequest(c.Server, provider) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) } func (c *Client) CreateDeploymentWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { @@ -863,6 +1121,30 @@ func (c *Client) GetDeployment(ctx context.Context, deploymentId openapi_types.U return c.Client.Do(req) } +func (c *Client) UpdateDeploymentWithBody(ctx context.Context, deploymentId openapi_types.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateDeploymentRequestWithBody(c.Server, deploymentId, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) UpdateDeployment(ctx context.Context, deploymentId openapi_types.UUID, body UpdateDeploymentJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateDeploymentRequest(c.Server, deploymentId, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) DeleteReleaseChannel(ctx context.Context, deploymentId string, name string, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewDeleteReleaseChannelRequest(c.Server, deploymentId, name) if err != nil { @@ -1355,6 +1637,30 @@ func (c *Client) GetWorkspace(ctx context.Context, workspaceId openapi_types.UUI return c.Client.Do(req) } +func (c *Client) ListDeployments(ctx context.Context, workspaceId string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewListDeploymentsRequest(c.Server, workspaceId) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) ListEnvironments(ctx context.Context, workspaceId string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewListEnvironmentsRequest(c.Server, workspaceId) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) UpsertResourceProvider(ctx context.Context, workspaceId string, name string, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewUpsertResourceProviderRequest(c.Server, workspaceId, name) if err != nil { @@ -1367,6 +1673,18 @@ func (c *Client) UpsertResourceProvider(ctx context.Context, workspaceId string, return c.Client.Do(req) } +func (c *Client) ListResources(ctx context.Context, workspaceId string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewListResourcesRequest(c.Server, workspaceId) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) DeleteResourceByIdentifier(ctx context.Context, workspaceId string, identifier string, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewDeleteResourceByIdentifierRequest(c.Server, workspaceId, identifier) if err != nil { @@ -1391,6 +1709,64 @@ func (c *Client) GetResourceByIdentifier(ctx context.Context, workspaceId string return c.Client.Do(req) } +func (c *Client) GetResourcesByFilter(ctx context.Context, workspaceId string, filter string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetResourcesByFilterRequest(c.Server, workspaceId, filter) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) ListSystems(ctx context.Context, workspaceId string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewListSystemsRequest(c.Server, workspaceId) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// NewGetCloudProviderRegionsRequest generates requests for GetCloudProviderRegions +func NewGetCloudProviderRegionsRequest(server string, provider GetCloudProviderRegionsParamsProvider) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "provider", runtime.ParamLocationPath, provider) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/api/v1/cloud-locations/%s", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewCreateDeploymentRequest calls the generic CreateDeployment builder with application/json body func NewCreateDeploymentRequest(server string, body CreateDeploymentJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader @@ -1499,20 +1875,24 @@ func NewGetDeploymentRequest(server string, deploymentId openapi_types.UUID) (*h return req, nil } -// NewDeleteReleaseChannelRequest generates requests for DeleteReleaseChannel -func NewDeleteReleaseChannelRequest(server string, deploymentId string, name string) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "deploymentId", runtime.ParamLocationPath, deploymentId) +// NewUpdateDeploymentRequest calls the generic UpdateDeployment builder with application/json body +func NewUpdateDeploymentRequest(server string, deploymentId openapi_types.UUID, body UpdateDeploymentJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) if err != nil { return nil, err } + bodyReader = bytes.NewReader(buf) + return NewUpdateDeploymentRequestWithBody(server, deploymentId, "application/json", bodyReader) +} - var pathParam1 string +// NewUpdateDeploymentRequestWithBody generates requests for UpdateDeployment with any type of body +func NewUpdateDeploymentRequestWithBody(server string, deploymentId openapi_types.UUID, contentType string, body io.Reader) (*http.Request, error) { + var err error - pathParam1, err = runtime.StyleParamWithLocation("simple", false, "name", runtime.ParamLocationPath, name) + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "deploymentId", runtime.ParamLocationPath, deploymentId) if err != nil { return nil, err } @@ -1522,7 +1902,7 @@ func NewDeleteReleaseChannelRequest(server string, deploymentId string, name str return nil, err } - operationPath := fmt.Sprintf("/v1/deployments/%s/release-channels/name/%s", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/v1/deployments/%s", pathParam0) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -1532,24 +1912,67 @@ func NewDeleteReleaseChannelRequest(server string, deploymentId string, name str return nil, err } - req, err := http.NewRequest("DELETE", queryURL.String(), nil) + req, err := http.NewRequest("PATCH", queryURL.String(), body) if err != nil { return nil, err } + req.Header.Add("Content-Type", contentType) + return req, nil } -// NewCreateEnvironmentRequest calls the generic CreateEnvironment builder with application/json body -func NewCreateEnvironmentRequest(server string, body CreateEnvironmentJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewCreateEnvironmentRequestWithBody(server, "application/json", bodyReader) -} +// NewDeleteReleaseChannelRequest generates requests for DeleteReleaseChannel +func NewDeleteReleaseChannelRequest(server string, deploymentId string, name string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "deploymentId", runtime.ParamLocationPath, deploymentId) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "name", runtime.ParamLocationPath, name) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/v1/deployments/%s/release-channels/name/%s", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("DELETE", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewCreateEnvironmentRequest calls the generic CreateEnvironment builder with application/json body +func NewCreateEnvironmentRequest(server string, body CreateEnvironmentJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewCreateEnvironmentRequestWithBody(server, "application/json", bodyReader) +} // NewCreateEnvironmentRequestWithBody generates requests for CreateEnvironment with any type of body func NewCreateEnvironmentRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { @@ -2578,6 +3001,74 @@ func NewGetWorkspaceRequest(server string, workspaceId openapi_types.UUID) (*htt return req, nil } +// NewListDeploymentsRequest generates requests for ListDeployments +func NewListDeploymentsRequest(server string, workspaceId string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "workspaceId", runtime.ParamLocationPath, workspaceId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/v1/workspaces/%s/deployments", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewListEnvironmentsRequest generates requests for ListEnvironments +func NewListEnvironmentsRequest(server string, workspaceId string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "workspaceId", runtime.ParamLocationPath, workspaceId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/v1/workspaces/%s/environments", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewUpsertResourceProviderRequest generates requests for UpsertResourceProvider func NewUpsertResourceProviderRequest(server string, workspaceId string, name string) (*http.Request, error) { var err error @@ -2619,6 +3110,40 @@ func NewUpsertResourceProviderRequest(server string, workspaceId string, name st return req, nil } +// NewListResourcesRequest generates requests for ListResources +func NewListResourcesRequest(server string, workspaceId string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "workspaceId", runtime.ParamLocationPath, workspaceId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/v1/workspaces/%s/resources", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewDeleteResourceByIdentifierRequest generates requests for DeleteResourceByIdentifier func NewDeleteResourceByIdentifierRequest(server string, workspaceId string, identifier string) (*http.Request, error) { var err error @@ -2701,6 +3226,81 @@ func NewGetResourceByIdentifierRequest(server string, workspaceId string, identi return req, nil } +// NewGetResourcesByFilterRequest generates requests for GetResourcesByFilter +func NewGetResourcesByFilterRequest(server string, workspaceId string, filter string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "workspaceId", runtime.ParamLocationPath, workspaceId) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "filter", runtime.ParamLocationPath, filter) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/v1/workspaces/%s/resources/%s", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewListSystemsRequest generates requests for ListSystems +func NewListSystemsRequest(server string, workspaceId string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "workspaceId", runtime.ParamLocationPath, workspaceId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/v1/workspaces/%s/systems", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { for _, r := range c.RequestEditors { if err := r(ctx, req); err != nil { @@ -2744,6 +3344,9 @@ func WithBaseURL(baseURL string) ClientOption { // ClientWithResponsesInterface is the interface specification for the client with responses above. type ClientWithResponsesInterface interface { + // GetCloudProviderRegionsWithResponse request + GetCloudProviderRegionsWithResponse(ctx context.Context, provider GetCloudProviderRegionsParamsProvider, reqEditors ...RequestEditorFn) (*GetCloudProviderRegionsResponse, error) + // CreateDeploymentWithBodyWithResponse request with any body CreateDeploymentWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateDeploymentResponse, error) @@ -2755,6 +3358,11 @@ type ClientWithResponsesInterface interface { // GetDeploymentWithResponse request GetDeploymentWithResponse(ctx context.Context, deploymentId openapi_types.UUID, reqEditors ...RequestEditorFn) (*GetDeploymentResponse, error) + // UpdateDeploymentWithBodyWithResponse request with any body + UpdateDeploymentWithBodyWithResponse(ctx context.Context, deploymentId openapi_types.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateDeploymentResponse, error) + + UpdateDeploymentWithResponse(ctx context.Context, deploymentId openapi_types.UUID, body UpdateDeploymentJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateDeploymentResponse, error) + // DeleteReleaseChannelWithResponse request DeleteReleaseChannelWithResponse(ctx context.Context, deploymentId string, name string, reqEditors ...RequestEditorFn) (*DeleteReleaseChannelResponse, error) @@ -2865,14 +3473,54 @@ type ClientWithResponsesInterface interface { // GetWorkspaceWithResponse request GetWorkspaceWithResponse(ctx context.Context, workspaceId openapi_types.UUID, reqEditors ...RequestEditorFn) (*GetWorkspaceResponse, error) + // ListDeploymentsWithResponse request + ListDeploymentsWithResponse(ctx context.Context, workspaceId string, reqEditors ...RequestEditorFn) (*ListDeploymentsResponse, error) + + // ListEnvironmentsWithResponse request + ListEnvironmentsWithResponse(ctx context.Context, workspaceId string, reqEditors ...RequestEditorFn) (*ListEnvironmentsResponse, error) + // UpsertResourceProviderWithResponse request UpsertResourceProviderWithResponse(ctx context.Context, workspaceId string, name string, reqEditors ...RequestEditorFn) (*UpsertResourceProviderResponse, error) + // ListResourcesWithResponse request + ListResourcesWithResponse(ctx context.Context, workspaceId string, reqEditors ...RequestEditorFn) (*ListResourcesResponse, error) + // DeleteResourceByIdentifierWithResponse request DeleteResourceByIdentifierWithResponse(ctx context.Context, workspaceId string, identifier string, reqEditors ...RequestEditorFn) (*DeleteResourceByIdentifierResponse, error) // GetResourceByIdentifierWithResponse request GetResourceByIdentifierWithResponse(ctx context.Context, workspaceId string, identifier string, reqEditors ...RequestEditorFn) (*GetResourceByIdentifierResponse, error) + + // GetResourcesByFilterWithResponse request + GetResourcesByFilterWithResponse(ctx context.Context, workspaceId string, filter string, reqEditors ...RequestEditorFn) (*GetResourcesByFilterResponse, error) + + // ListSystemsWithResponse request + ListSystemsWithResponse(ctx context.Context, workspaceId string, reqEditors ...RequestEditorFn) (*ListSystemsResponse, error) +} + +type GetCloudProviderRegionsResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *map[string]CloudRegionGeoData + JSON404 *struct { + Error *string `json:"error,omitempty"` + } +} + +// Status returns HTTPResponse.Status +func (r GetCloudProviderRegionsResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetCloudProviderRegionsResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 } type CreateDeploymentResponse struct { @@ -2957,6 +3605,34 @@ func (r GetDeploymentResponse) StatusCode() int { return 0 } +type UpdateDeploymentResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Deployment + JSON404 *struct { + Error string `json:"error"` + } + JSON500 *struct { + Error string `json:"error"` + } +} + +// Status returns HTTPResponse.Status +func (r UpdateDeploymentResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r UpdateDeploymentResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type DeleteReleaseChannelResponse struct { Body []byte HTTPResponse *http.Response @@ -3765,6 +4441,54 @@ func (r GetWorkspaceResponse) StatusCode() int { return 0 } +type ListDeploymentsResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *struct { + Data *[]Deployment `json:"data,omitempty"` + } +} + +// Status returns HTTPResponse.Status +func (r ListDeploymentsResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r ListDeploymentsResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type ListEnvironmentsResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *struct { + Data *[]Environment `json:"data,omitempty"` + } +} + +// Status returns HTTPResponse.Status +func (r ListEnvironmentsResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r ListEnvironmentsResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type UpsertResourceProviderResponse struct { Body []byte HTTPResponse *http.Response @@ -3791,6 +4515,30 @@ func (r UpsertResourceProviderResponse) StatusCode() int { return 0 } +type ListResourcesResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *struct { + Data *[]Resource `json:"data,omitempty"` + } +} + +// Status returns HTTPResponse.Status +func (r ListResourcesResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r ListResourcesResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type DeleteResourceByIdentifierResponse struct { Body []byte HTTPResponse *http.Response @@ -3838,13 +4586,62 @@ type GetResourceByIdentifierResponse struct { } `json:"variables,omitempty"` WorkspaceId string `json:"workspaceId"` } - JSON404 *struct { - Error *string `json:"error,omitempty"` + JSON404 *struct { + Error *string `json:"error,omitempty"` + } +} + +// Status returns HTTPResponse.Status +func (r GetResourceByIdentifierResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetResourceByIdentifierResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetResourcesByFilterResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *[]Resource + JSON400 *struct { + Error *string `json:"error,omitempty"` + } +} + +// Status returns HTTPResponse.Status +func (r GetResourcesByFilterResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetResourcesByFilterResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type ListSystemsResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *struct { + Data *[]System `json:"data,omitempty"` } } // Status returns HTTPResponse.Status -func (r GetResourceByIdentifierResponse) Status() string { +func (r ListSystemsResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -3852,13 +4649,22 @@ func (r GetResourceByIdentifierResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetResourceByIdentifierResponse) StatusCode() int { +func (r ListSystemsResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } +// GetCloudProviderRegionsWithResponse request returning *GetCloudProviderRegionsResponse +func (c *ClientWithResponses) GetCloudProviderRegionsWithResponse(ctx context.Context, provider GetCloudProviderRegionsParamsProvider, reqEditors ...RequestEditorFn) (*GetCloudProviderRegionsResponse, error) { + rsp, err := c.GetCloudProviderRegions(ctx, provider, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetCloudProviderRegionsResponse(rsp) +} + // CreateDeploymentWithBodyWithResponse request with arbitrary body returning *CreateDeploymentResponse func (c *ClientWithResponses) CreateDeploymentWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateDeploymentResponse, error) { rsp, err := c.CreateDeploymentWithBody(ctx, contentType, body, reqEditors...) @@ -3894,6 +4700,23 @@ func (c *ClientWithResponses) GetDeploymentWithResponse(ctx context.Context, dep return ParseGetDeploymentResponse(rsp) } +// UpdateDeploymentWithBodyWithResponse request with arbitrary body returning *UpdateDeploymentResponse +func (c *ClientWithResponses) UpdateDeploymentWithBodyWithResponse(ctx context.Context, deploymentId openapi_types.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateDeploymentResponse, error) { + rsp, err := c.UpdateDeploymentWithBody(ctx, deploymentId, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdateDeploymentResponse(rsp) +} + +func (c *ClientWithResponses) UpdateDeploymentWithResponse(ctx context.Context, deploymentId openapi_types.UUID, body UpdateDeploymentJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateDeploymentResponse, error) { + rsp, err := c.UpdateDeployment(ctx, deploymentId, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdateDeploymentResponse(rsp) +} + // DeleteReleaseChannelWithResponse request returning *DeleteReleaseChannelResponse func (c *ClientWithResponses) DeleteReleaseChannelWithResponse(ctx context.Context, deploymentId string, name string, reqEditors ...RequestEditorFn) (*DeleteReleaseChannelResponse, error) { rsp, err := c.DeleteReleaseChannel(ctx, deploymentId, name, reqEditors...) @@ -4250,6 +5073,24 @@ func (c *ClientWithResponses) GetWorkspaceWithResponse(ctx context.Context, work return ParseGetWorkspaceResponse(rsp) } +// ListDeploymentsWithResponse request returning *ListDeploymentsResponse +func (c *ClientWithResponses) ListDeploymentsWithResponse(ctx context.Context, workspaceId string, reqEditors ...RequestEditorFn) (*ListDeploymentsResponse, error) { + rsp, err := c.ListDeployments(ctx, workspaceId, reqEditors...) + if err != nil { + return nil, err + } + return ParseListDeploymentsResponse(rsp) +} + +// ListEnvironmentsWithResponse request returning *ListEnvironmentsResponse +func (c *ClientWithResponses) ListEnvironmentsWithResponse(ctx context.Context, workspaceId string, reqEditors ...RequestEditorFn) (*ListEnvironmentsResponse, error) { + rsp, err := c.ListEnvironments(ctx, workspaceId, reqEditors...) + if err != nil { + return nil, err + } + return ParseListEnvironmentsResponse(rsp) +} + // UpsertResourceProviderWithResponse request returning *UpsertResourceProviderResponse func (c *ClientWithResponses) UpsertResourceProviderWithResponse(ctx context.Context, workspaceId string, name string, reqEditors ...RequestEditorFn) (*UpsertResourceProviderResponse, error) { rsp, err := c.UpsertResourceProvider(ctx, workspaceId, name, reqEditors...) @@ -4259,6 +5100,15 @@ func (c *ClientWithResponses) UpsertResourceProviderWithResponse(ctx context.Con return ParseUpsertResourceProviderResponse(rsp) } +// ListResourcesWithResponse request returning *ListResourcesResponse +func (c *ClientWithResponses) ListResourcesWithResponse(ctx context.Context, workspaceId string, reqEditors ...RequestEditorFn) (*ListResourcesResponse, error) { + rsp, err := c.ListResources(ctx, workspaceId, reqEditors...) + if err != nil { + return nil, err + } + return ParseListResourcesResponse(rsp) +} + // DeleteResourceByIdentifierWithResponse request returning *DeleteResourceByIdentifierResponse func (c *ClientWithResponses) DeleteResourceByIdentifierWithResponse(ctx context.Context, workspaceId string, identifier string, reqEditors ...RequestEditorFn) (*DeleteResourceByIdentifierResponse, error) { rsp, err := c.DeleteResourceByIdentifier(ctx, workspaceId, identifier, reqEditors...) @@ -4277,6 +5127,59 @@ func (c *ClientWithResponses) GetResourceByIdentifierWithResponse(ctx context.Co return ParseGetResourceByIdentifierResponse(rsp) } +// GetResourcesByFilterWithResponse request returning *GetResourcesByFilterResponse +func (c *ClientWithResponses) GetResourcesByFilterWithResponse(ctx context.Context, workspaceId string, filter string, reqEditors ...RequestEditorFn) (*GetResourcesByFilterResponse, error) { + rsp, err := c.GetResourcesByFilter(ctx, workspaceId, filter, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetResourcesByFilterResponse(rsp) +} + +// ListSystemsWithResponse request returning *ListSystemsResponse +func (c *ClientWithResponses) ListSystemsWithResponse(ctx context.Context, workspaceId string, reqEditors ...RequestEditorFn) (*ListSystemsResponse, error) { + rsp, err := c.ListSystems(ctx, workspaceId, reqEditors...) + if err != nil { + return nil, err + } + return ParseListSystemsResponse(rsp) +} + +// ParseGetCloudProviderRegionsResponse parses an HTTP response from a GetCloudProviderRegionsWithResponse call +func ParseGetCloudProviderRegionsResponse(rsp *http.Response) (*GetCloudProviderRegionsResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetCloudProviderRegionsResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest map[string]CloudRegionGeoData + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest struct { + Error *string `json:"error,omitempty"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + } + + return response, nil +} + // ParseCreateDeploymentResponse parses an HTTP response from a CreateDeploymentWithResponse call func ParseCreateDeploymentResponse(rsp *http.Response) (*CreateDeploymentResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -4401,6 +5304,50 @@ func ParseGetDeploymentResponse(rsp *http.Response) (*GetDeploymentResponse, err return response, nil } +// ParseUpdateDeploymentResponse parses an HTTP response from a UpdateDeploymentWithResponse call +func ParseUpdateDeploymentResponse(rsp *http.Response) (*UpdateDeploymentResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &UpdateDeploymentResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Deployment + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest struct { + Error string `json:"error"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest struct { + Error string `json:"error"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + // ParseDeleteReleaseChannelResponse parses an HTTP response from a DeleteReleaseChannelWithResponse call func ParseDeleteReleaseChannelResponse(rsp *http.Response) (*DeleteReleaseChannelResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -5504,6 +6451,62 @@ func ParseGetWorkspaceResponse(rsp *http.Response) (*GetWorkspaceResponse, error return response, nil } +// ParseListDeploymentsResponse parses an HTTP response from a ListDeploymentsWithResponse call +func ParseListDeploymentsResponse(rsp *http.Response) (*ListDeploymentsResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &ListDeploymentsResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest struct { + Data *[]Deployment `json:"data,omitempty"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseListEnvironmentsResponse parses an HTTP response from a ListEnvironmentsWithResponse call +func ParseListEnvironmentsResponse(rsp *http.Response) (*ListEnvironmentsResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &ListEnvironmentsResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest struct { + Data *[]Environment `json:"data,omitempty"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + // ParseUpsertResourceProviderResponse parses an HTTP response from a UpsertResourceProviderWithResponse call func ParseUpsertResourceProviderResponse(rsp *http.Response) (*UpsertResourceProviderResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -5534,6 +6537,34 @@ func ParseUpsertResourceProviderResponse(rsp *http.Response) (*UpsertResourcePro return response, nil } +// ParseListResourcesResponse parses an HTTP response from a ListResourcesWithResponse call +func ParseListResourcesResponse(rsp *http.Response) (*ListResourcesResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &ListResourcesResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest struct { + Data *[]Resource `json:"data,omitempty"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + // ParseDeleteResourceByIdentifierResponse parses an HTTP response from a DeleteResourceByIdentifierWithResponse call func ParseDeleteResourceByIdentifierResponse(rsp *http.Response) (*DeleteResourceByIdentifierResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -5621,3 +6652,66 @@ func ParseGetResourceByIdentifierResponse(rsp *http.Response) (*GetResourceByIde return response, nil } + +// ParseGetResourcesByFilterResponse parses an HTTP response from a GetResourcesByFilterWithResponse call +func ParseGetResourcesByFilterResponse(rsp *http.Response) (*GetResourcesByFilterResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetResourcesByFilterResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest []Resource + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest struct { + Error *string `json:"error,omitempty"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + } + + return response, nil +} + +// ParseListSystemsResponse parses an HTTP response from a ListSystemsWithResponse call +func ParseListSystemsResponse(rsp *http.Response) (*ListSystemsResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &ListSystemsResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest struct { + Data *[]System `json:"data,omitempty"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} diff --git a/docs/data-sources/deployment.md b/docs/data-sources/deployment.md new file mode 100644 index 0000000..dde7295 --- /dev/null +++ b/docs/data-sources/deployment.md @@ -0,0 +1,32 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "ctrlplane_deployment Data Source - ctrlplane" +subcategory: "" +description: |- + Fetch a deployment resource by ID or other attributes +--- + +# ctrlplane_deployment (Data Source) + +Fetch a deployment resource by ID or other attributes + + + + +## Schema + +### Optional + +- `id` (String) Deployment identifier +- `name` (String) Name of the deployment +- `slug` (String) Slug identifier for the deployment +- `system_id` (String) System ID this deployment belongs to + +### Read-Only + +- `description` (String) Description of the deployment +- `job_agent_config` (Map of String) Job agent configuration +- `job_agent_id` (String) Job agent ID used for this deployment +- `resource_filter` (Map of String) Resource filter configuration +- `retry_count` (Number) Number of retry attempts +- `timeout` (Number) Timeout in seconds diff --git a/docs/data-sources/environment.md b/docs/data-sources/environment.md index a42e83f..2d0fe3d 100644 --- a/docs/data-sources/environment.md +++ b/docs/data-sources/environment.md @@ -22,11 +22,11 @@ Data source for a CtrlPlane environment. ### Read-Only +- `deployment_version_channels` (List of String) Deployment version channels for the environment. - `description` (String) Description of the environment. - `id` (String) Unique identifier for the environment. - `metadata` (Map of String) Metadata for the environment. - `policy_id` (String) ID of the policy associated with this environment. -- `release_channels` (List of String) Release channels for the environment. - `resource_filter` (Attributes) Resource filter for the environment. (see [below for nested schema](#nestedatt--resource_filter)) diff --git a/docs/resources/deployment.md b/docs/resources/deployment.md new file mode 100644 index 0000000..4d0363a --- /dev/null +++ b/docs/resources/deployment.md @@ -0,0 +1,77 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "ctrlplane_deployment Resource - ctrlplane" +subcategory: "" +description: |- + Manages a deployment +--- + +# ctrlplane_deployment (Resource) + +Manages a deployment + +## Example Usage + +```terraform +terraform { + required_providers { + ctrlplane = { + source = "registry.terraform.io/ctrlplanedev/ctrlplane" + } + } +} + +provider "ctrlplane" {} + + +resource "ctrlplane_system" "example" { + name = "example-system" + slug = "example-system" + description = "Example system for deployment" +} + +resource "ctrlplane_deployment" "example" { + name = "example-deployment" + slug = "example-deployment" + description = "Example deployment for Terraform provider" + system_id = ctrlplane_system.example.id + + # Required job agent configuration + job_agent_config = { + "agent_type" = "kubernetes" + "namespace" = "default" + } + + # Optional fields + retry_count = 3 + timeout = 600 + + # Optional resource filter configuration + resource_filter = { + "environment" = "dev" + "region" = "us-west" + } +} +``` + + +## Schema + +### Required + +- `job_agent_config` (Map of String) Job agent configuration +- `name` (String) Name of the deployment +- `slug` (String) Slug identifier for the deployment +- `system_id` (String) System ID this deployment belongs to + +### Optional + +- `description` (String) Description of the deployment +- `job_agent_id` (String) Job agent ID to use for this deployment +- `resource_filter` (Map of String) Resource filter configuration +- `retry_count` (Number) Number of retry attempts +- `timeout` (Number) Timeout in seconds + +### Read-Only + +- `id` (String) Deployment identifier diff --git a/docs/resources/environment.md b/docs/resources/environment.md index c9f1577..c9d5cf0 100644 --- a/docs/resources/environment.md +++ b/docs/resources/environment.md @@ -22,9 +22,9 @@ Manages a CtrlPlane environment. ### Optional +- `deployment_version_channels` (List of String) Deployment version channels for the environment. - `description` (String) Description of the environment. - `metadata` (Map of String) Metadata for the environment. -- `release_channels` (List of String) Release channels for the environment. - `resource_filter` (Attributes) Inline resource filter for the environment. Cannot be specified together with resource_filter_id. (see [below for nested schema](#nestedatt--resource_filter)) - `resource_filter_id` (String) ID of a ctrlplane_resource_filter resource to use. Cannot be specified together with resource_filter. diff --git a/examples/resources/ctrlplane_deployment/datadsource.tf b/examples/resources/ctrlplane_deployment/datadsource.tf new file mode 100644 index 0000000..343630f --- /dev/null +++ b/examples/resources/ctrlplane_deployment/datadsource.tf @@ -0,0 +1,51 @@ +terraform { + required_providers { + ctrlplane = { + source = "registry.terraform.io/ctrlplanedev/ctrlplane" + } + } +} + +provider "ctrlplane" {} + +# Example: Retrieve a deployment using its ID +data "ctrlplane_deployment" "example" { + id = "9d1a9d15-6faf-426e-9657-5e7115588226" +} + +# Usage example: Use retrieved deployment data +output "deployment_name" { + value = data.ctrlplane_deployment.example.name +} + +output "deployment_system_id" { + value = data.ctrlplane_deployment.example.system_id +} + +# Example: Retrieve a deployment that was created in this configuration +resource "ctrlplane_system" "example" { + name = "example-system" + slug = "example-system" + description = "Example system for deployment" +} + +resource "ctrlplane_deployment" "example" { + name = "example-deployment" + slug = "example-deployment" + description = "Example deployment for Terraform provider" + system_id = ctrlplane_system.example.id + + job_agent_config = { + "agent_type" = "kubernetes" + "namespace" = "default" + } +} + +# Reference the created deployment +data "ctrlplane_deployment" "created_example" { + id = ctrlplane_deployment.example.id +} + +output "created_deployment_name" { + value = data.ctrlplane_deployment.created_example.name +} diff --git a/examples/resources/ctrlplane_deployment/resource.tf b/examples/resources/ctrlplane_deployment/resource.tf new file mode 100644 index 0000000..15ee768 --- /dev/null +++ b/examples/resources/ctrlplane_deployment/resource.tf @@ -0,0 +1,39 @@ +terraform { + required_providers { + ctrlplane = { + source = "registry.terraform.io/ctrlplanedev/ctrlplane" + } + } +} + +provider "ctrlplane" {} + + +resource "ctrlplane_system" "example" { + name = "example-system" + slug = "example-system" + description = "Example system for deployment" +} + +resource "ctrlplane_deployment" "example" { + name = "example-deployment" + slug = "example-deployment" + description = "Example deployment for Terraform provider" + system_id = ctrlplane_system.example.id + + # Required job agent configuration + job_agent_config = { + "agent_type" = "kubernetes" + "namespace" = "default" + } + + # Optional fields + retry_count = 3 + timeout = 600 + + # Optional resource filter configuration + resource_filter = { + "environment" = "dev" + "region" = "us-west" + } +} diff --git a/go.mod b/go.mod index e5e7fa6..d181b85 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,7 @@ require ( github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-checkpoint v0.5.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-cty v1.4.1 // indirect + github.com/hashicorp/go-cty v1.5.0 // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-plugin v1.6.3 // indirect @@ -72,7 +72,7 @@ require ( golang.org/x/text v0.23.0 // indirect golang.org/x/tools v0.31.0 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect google.golang.org/grpc v1.71.0 // indirect google.golang.org/protobuf v1.36.5 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect diff --git a/go.sum b/go.sum index f14f6cf..1520623 100644 --- a/go.sum +++ b/go.sum @@ -65,8 +65,8 @@ github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuD github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-cty v1.4.1 h1:T4i4kbEKuyMoe4Ujh52Ud07VXr05dnP/Si9JiVDpx3Y= -github.com/hashicorp/go-cty v1.4.1/go.mod h1:EiZBMaudVLy8fmjf9Npq1dq9RalhveqZG5w/yz3mHWs= +github.com/hashicorp/go-cty v1.5.0 h1:EkQ/v+dDNUqnuVpmS5fPqyY71NXVgT5gf32+57xY8g0= +github.com/hashicorp/go-cty v1.5.0/go.mod h1:lFUCG5kd8exDobgSfyj4ONE/dc822kiYMguVKdHGMLM= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= @@ -233,7 +233,6 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= @@ -249,8 +248,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 h1:iK2jbkWL86DXjEx0qiHcRE9dE4/Ahua5k6V8OWFb//c= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= diff --git a/internal/integration/environment_test.go b/internal/integration/environment_test.go index ba8e556..0435839 100644 --- a/internal/integration/environment_test.go +++ b/internal/integration/environment_test.go @@ -55,20 +55,20 @@ var _ = Describe("Environment API", func() { }) It("should create an environment without a resource filter", func() { - releaseChannels := []string{} + deploymentVersionChannels := []string{} metadata := map[string]string{ "test": "true", "env": "integration", } description := "Test environment without resource filter" envResp, err := apiClient.CreateEnvironmentWithResponse(ctx, client.CreateEnvironmentJSONRequestBody{ - Name: envName, - Description: &description, - SystemId: systemID.String(), - PolicyId: nil, - Metadata: &metadata, - ResourceFilter: nil, - ReleaseChannels: &releaseChannels, + Name: envName, + Description: &description, + SystemId: systemID.String(), + PolicyId: nil, + Metadata: &metadata, + ResourceFilter: nil, + DeploymentVersionChannels: &deploymentVersionChannels, }) Expect(err).NotTo(HaveOccurred(), "Failed to create environment") @@ -117,20 +117,20 @@ var _ = Describe("Environment API", func() { shortUUID := uuid.New().String()[:6] envName = fmt.Sprintf("env-empty-filter-%s", shortUUID) - releaseChannels := []string{} + deploymentVersionChannels := []string{} metadata := map[string]string{ "test": "true", "env": "integration", } description := "Test environment with resource filter" envResp, err := apiClient.CreateEnvironmentWithResponse(ctx, client.CreateEnvironmentJSONRequestBody{ - Name: envName, - Description: &description, - SystemId: systemID.String(), - PolicyId: nil, - Metadata: &metadata, - ResourceFilter: &filter, - ReleaseChannels: &releaseChannels, + Name: envName, + Description: &description, + SystemId: systemID.String(), + PolicyId: nil, + Metadata: &metadata, + ResourceFilter: &filter, + DeploymentVersionChannels: &deploymentVersionChannels, }) Expect(err).NotTo(HaveOccurred(), "Failed to create environment") @@ -175,7 +175,7 @@ var _ = Describe("Environment API", func() { defer func() { safeDeleteTestSystem(ctx, apiClient, systemID) }() - releaseChannels := []string{} + deploymentVersionChannels := []string{} envName := fmt.Sprintf("env-comparison-%s", uuid.New().String()[:6]) filter := map[string]interface{}{ "type": "kind", @@ -188,13 +188,13 @@ var _ = Describe("Environment API", func() { } description := "Test environment with comparison filter" envReq := client.CreateEnvironmentJSONRequestBody{ - Name: envName, - Description: &description, - SystemId: systemID.String(), - PolicyId: nil, - Metadata: &metadata, - ResourceFilter: &filter, - ReleaseChannels: &releaseChannels, + Name: envName, + Description: &description, + SystemId: systemID.String(), + PolicyId: nil, + Metadata: &metadata, + ResourceFilter: &filter, + DeploymentVersionChannels: &deploymentVersionChannels, } Logger.Debug("creating environment with comparison filter", @@ -249,7 +249,7 @@ var _ = Describe("Environment API", func() { Expect(err).NotTo(HaveOccurred()) defer safeDeleteTestSystem(ctx, apiClient, systemID) - releaseChannels := []string{} + deploymentVersionChannels := []string{} envName := fmt.Sprintf("env-metadata-%s", uuid.New().String()[:6]) filter := map[string]interface{}{ "type": "metadata", @@ -263,13 +263,13 @@ var _ = Describe("Environment API", func() { } description := "Test environment with metadata filter" envReq := client.CreateEnvironmentJSONRequestBody{ - Name: envName, - Description: &description, - SystemId: systemID.String(), - PolicyId: nil, - Metadata: &metadata, - ResourceFilter: &filter, - ReleaseChannels: &releaseChannels, + Name: envName, + Description: &description, + SystemId: systemID.String(), + PolicyId: nil, + Metadata: &metadata, + ResourceFilter: &filter, + DeploymentVersionChannels: &deploymentVersionChannels, } Logger.Debug("creating environment with metadata filter", @@ -326,7 +326,7 @@ var _ = Describe("Environment API", func() { defer func() { safeDeleteTestSystem(ctx, apiClient, systemID) }() - releaseChannels := []string{} + deploymentVersionChannels := []string{} envName := fmt.Sprintf("env-simple-filter-%s", uuid.New().String()[:6]) filter := map[string]interface{}{ "type": "metadata", @@ -340,13 +340,13 @@ var _ = Describe("Environment API", func() { } description := "Test environment with simple filter" envReq := client.CreateEnvironmentJSONRequestBody{ - Name: envName, - Description: &description, - SystemId: systemID.String(), - PolicyId: nil, - Metadata: &metadata, - ResourceFilter: &filter, - ReleaseChannels: &releaseChannels, + Name: envName, + Description: &description, + SystemId: systemID.String(), + PolicyId: nil, + Metadata: &metadata, + ResourceFilter: &filter, + DeploymentVersionChannels: &deploymentVersionChannels, } Logger.Debug("creating environment with simple filter", @@ -399,7 +399,7 @@ var _ = Describe("Environment API", func() { Expect(err).NotTo(HaveOccurred()) defer safeDeleteTestSystem(ctx, apiClient, systemID) - releaseChannels := []string{} + deploymentVersionChannels := []string{} envName := fmt.Sprintf("env-complex-filter-%s", uuid.New().String()[:6]) filter := map[string]interface{}{ "not": false, @@ -425,13 +425,13 @@ var _ = Describe("Environment API", func() { } description := "Test environment with complex filter" envReq := client.CreateEnvironmentJSONRequestBody{ - Name: envName, - Description: &description, - SystemId: systemID.String(), - PolicyId: nil, - Metadata: &metadata, - ResourceFilter: &filter, - ReleaseChannels: &releaseChannels, + Name: envName, + Description: &description, + SystemId: systemID.String(), + PolicyId: nil, + Metadata: &metadata, + ResourceFilter: &filter, + DeploymentVersionChannels: &deploymentVersionChannels, } Logger.Debug("creating environment with complex filter", @@ -500,7 +500,7 @@ var _ = Describe("Environment API", func() { Expect(err).NotTo(HaveOccurred()) defer safeDeleteTestSystem(ctx, apiClient, systemID) - releaseChannels := []string{} + deploymentVersionChannels := []string{} envName := fmt.Sprintf("env-date-filter-%s", uuid.New().String()[:6]) // Create a resource filter with a date condition @@ -533,13 +533,13 @@ var _ = Describe("Environment API", func() { } description := "Test environment with date condition filter" envReq := client.CreateEnvironmentJSONRequestBody{ - Name: envName, - Description: &description, - SystemId: systemID.String(), - PolicyId: nil, - Metadata: &metadata, - ResourceFilter: &filter, - ReleaseChannels: &releaseChannels, + Name: envName, + Description: &description, + SystemId: systemID.String(), + PolicyId: nil, + Metadata: &metadata, + ResourceFilter: &filter, + DeploymentVersionChannels: &deploymentVersionChannels, } Logger.Debug("creating environment with date condition filter", @@ -650,7 +650,7 @@ var _ = Describe("Environment API", func() { } createdEnvs := make([]uuid.UUID, 0, len(environments)) - releaseChannels := []string{} + deploymentVersionChannels := []string{} for _, env := range environments { description := fmt.Sprintf("Test environment for %s", env.name) @@ -660,13 +660,13 @@ var _ = Describe("Environment API", func() { } envReq := client.CreateEnvironmentJSONRequestBody{ - Name: env.name, - Description: &description, - SystemId: systemID.String(), - PolicyId: nil, - Metadata: &metadata, - ResourceFilter: &env.filter, - ReleaseChannels: &releaseChannels, + Name: env.name, + Description: &description, + SystemId: systemID.String(), + PolicyId: nil, + Metadata: &metadata, + ResourceFilter: &env.filter, + DeploymentVersionChannels: &deploymentVersionChannels, } Logger.Debug("creating environment", @@ -697,7 +697,7 @@ var _ = Describe("Environment API", func() { Expect(err).NotTo(HaveOccurred()) defer safeDeleteTestSystem(ctx, apiClient, systemID) - releaseChannels := []string{} + deploymentVersionChannels := []string{} envName := fmt.Sprintf("env-nested-comp-%s", uuid.New().String()[:6]) // Create a resource filter with nested comparison conditions @@ -737,13 +737,13 @@ var _ = Describe("Environment API", func() { } description := "Test environment with nested comparison conditions" envReq := client.CreateEnvironmentJSONRequestBody{ - Name: envName, - Description: &description, - SystemId: systemID.String(), - PolicyId: nil, - Metadata: &metadata, - ResourceFilter: &filter, - ReleaseChannels: &releaseChannels, + Name: envName, + Description: &description, + SystemId: systemID.String(), + PolicyId: nil, + Metadata: &metadata, + ResourceFilter: &filter, + DeploymentVersionChannels: &deploymentVersionChannels, } Logger.Debug("creating environment with nested comparison conditions", @@ -800,7 +800,7 @@ var _ = Describe("Environment API", func() { Expect(err).NotTo(HaveOccurred()) defer safeDeleteTestSystem(ctx, apiClient, systemID) - releaseChannels := []string{} + deploymentVersionChannels := []string{} envName := fmt.Sprintf("env-mixed-conditions-%s", uuid.New().String()[:6]) // Create a resource filter with mixed condition types @@ -834,13 +834,13 @@ var _ = Describe("Environment API", func() { } description := "Test environment with mixed condition types" envReq := client.CreateEnvironmentJSONRequestBody{ - Name: envName, - Description: &description, - SystemId: systemID.String(), - PolicyId: nil, - Metadata: &metadata, - ResourceFilter: &filter, - ReleaseChannels: &releaseChannels, + Name: envName, + Description: &description, + SystemId: systemID.String(), + PolicyId: nil, + Metadata: &metadata, + ResourceFilter: &filter, + DeploymentVersionChannels: &deploymentVersionChannels, } Logger.Debug("creating environment with mixed condition types", @@ -906,7 +906,7 @@ var _ = Describe("Environment API", func() { Expect(err).NotTo(HaveOccurred()) defer safeDeleteTestSystem(ctx, apiClient, systemID) - releaseChannels := []string{} + deploymentVersionChannels := []string{} envName := fmt.Sprintf("env-deep-nesting-%s", uuid.New().String()[:6]) // Create a resource filter with 3 levels of nesting @@ -957,13 +957,13 @@ var _ = Describe("Environment API", func() { } description := "Test environment with deeply nested conditions" envReq := client.CreateEnvironmentJSONRequestBody{ - Name: envName, - Description: &description, - SystemId: systemID.String(), - PolicyId: nil, - Metadata: &metadata, - ResourceFilter: &filter, - ReleaseChannels: &releaseChannels, + Name: envName, + Description: &description, + SystemId: systemID.String(), + PolicyId: nil, + Metadata: &metadata, + ResourceFilter: &filter, + DeploymentVersionChannels: &deploymentVersionChannels, } Logger.Debug("creating environment with deeply nested conditions", diff --git a/internal/provider/environment_data_source.go b/internal/provider/environment_data_source.go index ed35239..e7999df 100644 --- a/internal/provider/environment_data_source.go +++ b/internal/provider/environment_data_source.go @@ -172,5 +172,7 @@ func (d *EnvironmentDataSource) Read(ctx context.Context, req datasource.ReadReq } } + data.DeploymentVersionChannels = []types.String{} + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } diff --git a/internal/provider/environment_model.go b/internal/provider/environment_model.go index d8614e2..fb87381 100644 --- a/internal/provider/environment_model.go +++ b/internal/provider/environment_model.go @@ -8,26 +8,26 @@ import ( ) type EnvironmentModel struct { - ID types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - Description types.String `tfsdk:"description"` - SystemID types.String `tfsdk:"system_id"` - PolicyID types.String `tfsdk:"policy_id"` - Metadata types.Map `tfsdk:"metadata"` - ResourceFilter *ResourceFilterModel `tfsdk:"resource_filter"` - ResourceFilterID types.String `tfsdk:"resource_filter_id"` - ReleaseChannels []types.String `tfsdk:"release_channels"` + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + SystemID types.String `tfsdk:"system_id"` + PolicyID types.String `tfsdk:"policy_id"` + Metadata types.Map `tfsdk:"metadata"` + ResourceFilter *ResourceFilterModel `tfsdk:"resource_filter"` + ResourceFilterID types.String `tfsdk:"resource_filter_id"` + DeploymentVersionChannels []types.String `tfsdk:"deployment_version_channels"` } type EnvironmentDataSourceModel struct { - ID types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - Description types.String `tfsdk:"description"` - SystemID types.String `tfsdk:"system_id"` - PolicyID types.String `tfsdk:"policy_id"` - Metadata types.Map `tfsdk:"metadata"` - ResourceFilter *ResourceFilterModel `tfsdk:"resource_filter"` - ReleaseChannels []types.String `tfsdk:"release_channels"` + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + SystemID types.String `tfsdk:"system_id"` + PolicyID types.String `tfsdk:"policy_id"` + Metadata types.Map `tfsdk:"metadata"` + ResourceFilter *ResourceFilterModel `tfsdk:"resource_filter"` + DeploymentVersionChannels []types.String `tfsdk:"deployment_version_channels"` } func (e *EnvironmentModel) SetDefaults() { @@ -37,8 +37,8 @@ func (e *EnvironmentModel) SetDefaults() { if e.Metadata.IsNull() { e.Metadata = types.MapNull(types.StringType) } - if e.ReleaseChannels == nil { - e.ReleaseChannels = []types.String{} + if e.DeploymentVersionChannels == nil { + e.DeploymentVersionChannels = []types.String{} } } @@ -49,7 +49,7 @@ func (e *EnvironmentDataSourceModel) SetDefaults() { if e.Metadata.IsNull() { e.Metadata = types.MapNull(types.StringType) } - if e.ReleaseChannels == nil { - e.ReleaseChannels = []types.String{} + if e.DeploymentVersionChannels == nil { + e.DeploymentVersionChannels = []types.String{} } } diff --git a/internal/provider/environment_resource.go b/internal/provider/environment_resource.go index 66091a2..2a2b587 100644 --- a/internal/provider/environment_resource.go +++ b/internal/provider/environment_resource.go @@ -170,29 +170,29 @@ func (r *EnvironmentResource) Create(ctx context.Context, req resource.CreateReq }) } - releaseChannels := make([]string, 0) - for _, ch := range data.ReleaseChannels { + deploymentVersionChannels := make([]string, 0) + for _, ch := range data.DeploymentVersionChannels { if !ch.IsNull() && !ch.IsUnknown() { - releaseChannels = append(releaseChannels, ch.ValueString()) + deploymentVersionChannels = append(deploymentVersionChannels, ch.ValueString()) } } createReq := client.CreateEnvironmentJSONRequestBody{ - Name: data.Name.ValueString(), - Description: stringToPtr(data.Description.ValueString()), - SystemId: data.SystemID.ValueString(), - Metadata: &metadata, - ResourceFilter: resourceFilter, - ReleaseChannels: &releaseChannels, + Name: data.Name.ValueString(), + Description: stringToPtr(data.Description.ValueString()), + SystemId: data.SystemID.ValueString(), + Metadata: &metadata, + ResourceFilter: resourceFilter, + DeploymentVersionChannels: &deploymentVersionChannels, } tflog.Info(ctx, "API request details", map[string]interface{}{ - "name": data.Name.ValueString(), - "description": data.Description.ValueString(), - "system_id": data.SystemID.ValueString(), - "metadata": metadata, - "resource_filter": fmt.Sprintf("%+v", resourceFilter), - "release_channels": releaseChannels, + "name": data.Name.ValueString(), + "description": data.Description.ValueString(), + "system_id": data.SystemID.ValueString(), + "metadata": metadata, + "resource_filter": fmt.Sprintf("%+v", resourceFilter), + "deployment_version_channels": deploymentVersionChannels, }) createResp, err := r.client.CreateEnvironmentWithResponse(ctx, createReq) diff --git a/internal/provider/environment_schema.go b/internal/provider/environment_schema.go index dc3d82d..c75c287 100644 --- a/internal/provider/environment_schema.go +++ b/internal/provider/environment_schema.go @@ -58,8 +58,8 @@ func GetEnvironmentResourceSchema() resourceschema.Schema { }, Attributes: GetResourceFilterSchema(), // This function is defined in resource_filter_schema.go }, - "release_channels": resourceschema.ListAttribute{ - Description: "Release channels for the environment.", + "deployment_version_channels": resourceschema.ListAttribute{ + Description: "Deployment version channels for the environment.", Optional: true, ElementType: types.StringType, }, @@ -130,8 +130,8 @@ func GetEnvironmentDataSourceSchema() dschema.Schema { }, }, }, - "release_channels": dschema.ListAttribute{ - Description: "Release channels for the environment.", + "deployment_version_channels": dschema.ListAttribute{ + Description: "Deployment version channels for the environment.", Computed: true, ElementType: types.StringType, }, diff --git a/internal/provider/provider.go b/internal/provider/provider.go index f83c514..94ac9a9 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -12,6 +12,7 @@ import ( "strings" "terraform-provider-ctrlplane/client" + "terraform-provider-ctrlplane/internal/resources/deployment" "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-framework/datasource" @@ -32,6 +33,9 @@ type CtrlplaneProvider struct { // version is set to the provider version on release, "dev" when built locally, and "test" during acceptance testing. version string } +type ClientProvider interface { + GetClient() *client.ClientWithResponses +} // CtrlplaneProviderModel describes the provider configuration. type CtrlplaneProviderModel struct { @@ -233,12 +237,14 @@ func (p *CtrlplaneProvider) Resources(ctx context.Context) []func() resource.Res NewSystemResource, NewEnvironmentResource, NewResourceFilterResource, + deployment.NewResource, } } func (p *CtrlplaneProvider) DataSources(ctx context.Context) []func() datasource.DataSource { return []func() datasource.DataSource{ NewEnvironmentDataSource, + deployment.NewDataSource, } } diff --git a/internal/resources/deployment/create.go b/internal/resources/deployment/create.go new file mode 100644 index 0000000..c19d126 --- /dev/null +++ b/internal/resources/deployment/create.go @@ -0,0 +1,140 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package deployment + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + + "terraform-provider-ctrlplane/client" +) + +func (r *Resource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + tflog.Debug(ctx, "Creating deployment resource") + + var plan DeploymentModel + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + if plan.Name.IsNull() || plan.SystemID.IsNull() || plan.Slug.IsNull() || plan.JobAgentConfig.IsNull() { + resp.Diagnostics.AddError( + "Missing Required Fields", + "One or more required fields are missing. Required fields are: name, system_id, slug, job_agent_config", + ) + return + } + + systemID, err := uuid.Parse(plan.SystemID.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + "Invalid System ID", + fmt.Sprintf("Cannot parse system ID as UUID: %s", err), + ) + return + } + + var jobAgentConfigMap map[string]string + diags = plan.JobAgentConfig.ElementsAs(ctx, &jobAgentConfigMap, false) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + jobAgentConfig := make(map[string]interface{}) + for k, v := range jobAgentConfigMap { + jobAgentConfig[k] = v + } + + var resourceFilter map[string]interface{} + if !plan.ResourceFilter.IsNull() { + diags = plan.ResourceFilter.ElementsAs(ctx, &resourceFilter, false) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + } + + createRequest := client.CreateDeploymentJSONRequestBody{ + Name: plan.Name.ValueString(), + SystemId: systemID, + Slug: plan.Slug.ValueString(), + } + + if !plan.Description.IsNull() { + desc := plan.Description.ValueString() + createRequest.Description = &desc + } + + createRequest.JobAgentConfig = &jobAgentConfig + + if !plan.JobAgentID.IsNull() { + jobAgentID, err := uuid.Parse(plan.JobAgentID.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + "Invalid Job Agent ID", + fmt.Sprintf("Cannot parse job agent ID as UUID: %s", err), + ) + return + } + createRequest.JobAgentId = &jobAgentID + } + + if !plan.RetryCount.IsNull() { + retryCountInt := int(plan.RetryCount.ValueInt64()) + retryCountFloat := float32(retryCountInt) + createRequest.RetryCount = &retryCountFloat + } + + if !plan.Timeout.IsNull() { + timeoutInt := int(plan.Timeout.ValueInt64()) + timeoutFloat := float32(timeoutInt) + createRequest.Timeout = &timeoutFloat + } + + if resourceFilter != nil { + createRequest.ResourceFilter = &resourceFilter + } + + response, err := r.client.CreateDeploymentWithResponse(ctx, createRequest) + if err != nil { + resp.Diagnostics.AddError( + "API Error", + fmt.Sprintf("Unable to create deployment: %s", err), + ) + return + } + + if response.StatusCode() != http.StatusCreated { + resp.Diagnostics.AddError( + "API Error", + fmt.Sprintf("Failed to create deployment. Status: %d, Body: %s", + response.StatusCode(), string(response.Body)), + ) + return + } + + var deployment client.Deployment + if err := json.Unmarshal(response.Body, &deployment); err != nil { + resp.Diagnostics.AddError( + "API Response Error", + fmt.Sprintf("Unable to unmarshal deployment creation response: %s", err), + ) + return + } + + plan.ID = types.StringValue(deployment.Id.String()) + + diags = resp.State.Set(ctx, plan) + resp.Diagnostics.Append(diags...) +} diff --git a/internal/resources/deployment/create_test.go b/internal/resources/deployment/create_test.go new file mode 100644 index 0000000..0b1126f --- /dev/null +++ b/internal/resources/deployment/create_test.go @@ -0,0 +1,70 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package deployment_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + + "terraform-provider-ctrlplane/internal/provider" + ctrlacctest "terraform-provider-ctrlplane/testing/acctest" + "terraform-provider-ctrlplane/testing/testutils" +) + +func TestAccDeploymentResource_create(t *testing.T) { + resourceName := "ctrlplane_deployment.test" + rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { ctrlacctest.PreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccDeploymentConfig(rName), + Check: resource.ComposeTestCheckFunc( + testutils.CheckResourceExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", fmt.Sprintf("test-deployment-%s", rName)), + resource.TestCheckResourceAttr(resourceName, "slug", fmt.Sprintf("test-deployment-%s", rName)), + resource.TestCheckResourceAttrSet(resourceName, "system_id"), + resource.TestCheckResourceAttrSet(resourceName, "id"), + // You can add more attribute checks as needed + ), + }, + }, + }) +} + +var testAccProtoV6ProviderFactories = map[string]func() (tfprotov6.ProviderServer, error){ + "ctrlplane": providerserver.NewProtocol6WithError(provider.New("test")()), +} + +func testAccDeploymentConfig(rName string) string { + return ctrlacctest.ProviderConfig() + fmt.Sprintf(` + # First create a system + resource "ctrlplane_system" "test" { + name = "test-system-%[1]s" + description = "Test system for deployment tests" + slug = "test-system-%[1]s" + } + + # Create the deployment + resource "ctrlplane_deployment" "test" { + name = "test-deployment-%[1]s" + description = "Test deployment created by acceptance tests" + system_id = ctrlplane_system.test.id + slug = "test-deployment-%[1]s" + job_agent_config = { + "key1" = "value1" + "key2" = "value2" + } + retry_count = 3 + timeout = 300 + } + `, rName) +} diff --git a/internal/resources/deployment/data_source.go b/internal/resources/deployment/data_source.go new file mode 100644 index 0000000..4b9ee7f --- /dev/null +++ b/internal/resources/deployment/data_source.go @@ -0,0 +1,178 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package deployment + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/types" + + "terraform-provider-ctrlplane/client" +) + +var ( + _ datasource.DataSource = &DataSource{} +) + +func NewDataSource() datasource.DataSource { + return &DataSource{} +} + +type DataSource struct { + client *client.ClientWithResponses +} + +func (d *DataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_deployment" +} + +func (d *DataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = GetDeploymentDataSourceSchema() +} + +func (d *DataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + // Use reflection to safely extract the Client field from the provider data + // without needing to import the provider package directly + providerValue := reflect.ValueOf(req.ProviderData).Elem() + clientField := providerValue.FieldByName("Client") + + if !clientField.IsValid() { + resp.Diagnostics.AddError( + "Invalid Provider Data", + "Provider data does not contain a Client field", + ) + return + } + + client, ok := clientField.Interface().(*client.ClientWithResponses) + if !ok { + resp.Diagnostics.AddError( + "Invalid Client Type", + fmt.Sprintf("Expected *client.ClientWithResponses, got: %T", clientField.Interface()), + ) + return + } + + d.client = client +} + +func (d *DataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var config DeploymentModel + diags := req.Config.Get(ctx, &config) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + if config.ID.IsNull() && config.Name.IsNull() && config.SystemID.IsNull() && config.Slug.IsNull() { + resp.Diagnostics.AddError( + "Missing Required Attribute", + "At least one of id, name, system_id, or slug must be provided to find a deployment", + ) + return + } + + if !config.ID.IsNull() { + deploymentID, err := uuid.Parse(config.ID.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + "Invalid Deployment ID", + fmt.Sprintf("Cannot parse deployment ID as UUID: %s", err), + ) + return + } + + response, err := d.client.GetDeploymentWithResponse(ctx, deploymentID) + if err != nil { + resp.Diagnostics.AddError( + "API Error", + fmt.Sprintf("Unable to read deployment by ID: %s", err), + ) + return + } + + if response.StatusCode() == http.StatusNotFound { + resp.Diagnostics.AddError( + "Deployment Not Found", + fmt.Sprintf("No deployment found with ID %s", deploymentID.String()), + ) + return + } + + if response.StatusCode() != http.StatusOK { + resp.Diagnostics.AddError( + "API Error", + fmt.Sprintf("Received status %d: %s", response.StatusCode(), string(response.Body)), + ) + return + } + + var deployment client.Deployment + if err := json.Unmarshal(response.Body, &deployment); err != nil { + resp.Diagnostics.AddError( + "API Response Error", + fmt.Sprintf("Unable to unmarshal deployment response: %s", err), + ) + return + } + + config = mapDeploymentToModel(ctx, deployment, resp, config) + if resp.Diagnostics.HasError() { + return + } + } else { + resp.Diagnostics.AddError( + "ID Required", + "Deployment lookup requires an ID. Please specify the 'id' attribute.", + ) + return + } + + diags = resp.State.Set(ctx, &config) + resp.Diagnostics.Append(diags...) +} + +func mapDeploymentToModel(ctx context.Context, deployment client.Deployment, resp *datasource.ReadResponse, model DeploymentModel) DeploymentModel { + model.ID = types.StringValue(deployment.Id.String()) + model.Name = types.StringValue(deployment.Name) + model.Description = types.StringValue(deployment.Description) + model.SystemID = types.StringValue(deployment.SystemId.String()) + model.Slug = types.StringValue(deployment.Slug) + + if deployment.JobAgentId != nil { + model.JobAgentID = types.StringValue(deployment.JobAgentId.String()) + } else { + model.JobAgentID = types.StringNull() + } + + jobAgentConfigMap, diags := types.MapValueFrom(ctx, types.StringType, deployment.JobAgentConfig) + resp.Diagnostics.Append(diags...) + model.JobAgentConfig = jobAgentConfigMap + + if deployment.RetryCount != nil { + model.RetryCount = types.Int64Value(int64(*deployment.RetryCount)) + } else { + model.RetryCount = types.Int64Null() + } + + if deployment.Timeout != nil { + model.Timeout = types.Int64Value(int64(*deployment.Timeout)) + } else { + model.Timeout = types.Int64Null() + } + + model.ResourceFilter = types.MapNull(types.StringType) + + return model +} diff --git a/internal/resources/deployment/data_source_test.go b/internal/resources/deployment/data_source_test.go new file mode 100644 index 0000000..5353ec1 --- /dev/null +++ b/internal/resources/deployment/data_source_test.go @@ -0,0 +1,54 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package deployment_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + + ctrlacctest "terraform-provider-ctrlplane/testing/acctest" + "terraform-provider-ctrlplane/testing/testutils" +) + +func TestAccDeploymentDataSource_basic(t *testing.T) { + rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + resourceName := "ctrlplane_deployment.test" + dataSourceName := "data.ctrlplane_deployment.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { ctrlacctest.PreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccDeploymentConfig(rName), + Check: resource.ComposeTestCheckFunc( + testutils.CheckResourceExists(resourceName), + ), + }, + { + Config: testAccDeploymentDataSourceConfig(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair(dataSourceName, "id", resourceName, "id"), + resource.TestCheckResourceAttrPair(dataSourceName, "name", resourceName, "name"), + resource.TestCheckResourceAttrPair(dataSourceName, "description", resourceName, "description"), + resource.TestCheckResourceAttrPair(dataSourceName, "system_id", resourceName, "system_id"), + resource.TestCheckResourceAttrPair(dataSourceName, "slug", resourceName, "slug"), + resource.TestCheckResourceAttrPair(dataSourceName, "job_agent_id", resourceName, "job_agent_id"), + resource.TestCheckResourceAttrPair(dataSourceName, "retry_count", resourceName, "retry_count"), + resource.TestCheckResourceAttrPair(dataSourceName, "timeout", resourceName, "timeout"), + ), + }, + }, + }) +} + +func testAccDeploymentDataSourceConfig(rName string) string { + return testAccDeploymentConfig(rName) + ` + data "ctrlplane_deployment" "test" { + id = ctrlplane_deployment.test.id + } + ` +} diff --git a/internal/resources/deployment/delete.go b/internal/resources/deployment/delete.go new file mode 100644 index 0000000..c117b5d --- /dev/null +++ b/internal/resources/deployment/delete.go @@ -0,0 +1,83 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package deployment + +import ( + "context" + "fmt" + "net/http" + + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +// Delete deletes the deployment resource. +func (r *Resource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + tflog.Debug(ctx, "Deleting deployment resource") + + // Get the current state + var state DeploymentModel + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Check if ID is set + if state.ID.IsNull() { + resp.Diagnostics.AddError( + "Missing Deployment ID", + "Cannot delete deployment without ID. This is a bug in the provider.", + ) + return + } + + // Parse ID to UUID + deploymentID, err := uuid.Parse(state.ID.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + "Invalid Deployment ID", + fmt.Sprintf("Cannot parse deployment ID as UUID: %s", err), + ) + return + } + + tflog.Debug(ctx, "Deleting deployment", map[string]interface{}{ + "id": deploymentID.String(), + }) + + // Call API to delete deployment + response, err := r.client.DeleteDeploymentWithResponse(ctx, deploymentID) + if err != nil { + resp.Diagnostics.AddError( + "API Error", + fmt.Sprintf("Unable to delete deployment: %s", err), + ) + return + } + + // Check the response status + if response.StatusCode() != http.StatusNoContent && response.StatusCode() != http.StatusOK { + // If resource does not exist, consider it "deleted" + if response.StatusCode() == http.StatusNotFound { + tflog.Warn(ctx, "Deployment already deleted or does not exist", map[string]interface{}{ + "id": deploymentID.String(), + }) + return + } + + resp.Diagnostics.AddError( + "API Error", + fmt.Sprintf("Failed to delete deployment. Status: %d, Body: %s", + response.StatusCode(), string(response.Body)), + ) + return + } + + // Successfully deleted + tflog.Info(ctx, "Deployment deleted successfully", map[string]interface{}{ + "id": deploymentID.String(), + }) +} diff --git a/internal/resources/deployment/delete_test.go b/internal/resources/deployment/delete_test.go new file mode 100644 index 0000000..2f772ee --- /dev/null +++ b/internal/resources/deployment/delete_test.go @@ -0,0 +1,60 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package deployment_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + + ctrlacctest "terraform-provider-ctrlplane/testing/acctest" + "terraform-provider-ctrlplane/testing/testutils" +) + +func TestAccDeploymentResource_delete(t *testing.T) { + resourceName := "ctrlplane_deployment.test" + rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { ctrlacctest.PreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + CheckDestroy: testAccCheckDeploymentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDeploymentConfig(rName), + Check: resource.ComposeTestCheckFunc( + testutils.CheckResourceExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", fmt.Sprintf("test-deployment-%s", rName)), + resource.TestCheckResourceAttrSet(resourceName, "id"), + ), + }, + { + Config: testAccDeploymentEmptyConfig(), + Check: resource.ComposeTestCheckFunc(), + }, + }, + }) +} + +func testAccDeploymentEmptyConfig() string { + return ctrlacctest.ProviderConfig() +} + +// testAccCheckDeploymentDestroy verifies the deployment no longer exists. +func testAccCheckDeploymentDestroy(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "ctrlplane_deployment" { + continue + } + + // We intentionally don't query the API here to confirm destruction, as that would + // require setting up a client which is outside the scope of our test framework. + // The actual API call is tested in the resource's Delete method. + } + + return nil +} diff --git a/internal/resources/deployment/import_test.go b/internal/resources/deployment/import_test.go new file mode 100644 index 0000000..dded304 --- /dev/null +++ b/internal/resources/deployment/import_test.go @@ -0,0 +1,37 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package deployment_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + + ctrlacctest "terraform-provider-ctrlplane/testing/acctest" +) + +func TestAccDeploymentResource_import(t *testing.T) { + resourceName := "ctrlplane_deployment.test" + rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + name := fmt.Sprintf("test-deployment-%s", rName) + description := "Test deployment for import" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { ctrlacctest.PreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccDeploymentConfigWithDesc(rName, name, description), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"job_agent_config"}, + }, + }, + }) +} diff --git a/internal/resources/deployment/model.go b/internal/resources/deployment/model.go new file mode 100644 index 0000000..2465a00 --- /dev/null +++ b/internal/resources/deployment/model.go @@ -0,0 +1,24 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package deployment + +import ( + "github.com/hashicorp/terraform-plugin-framework/types" +) + +type DeploymentModel struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + SystemID types.String `tfsdk:"system_id"` + Slug types.String `tfsdk:"slug"` + JobAgentID types.String `tfsdk:"job_agent_id"` + JobAgentConfig types.Map `tfsdk:"job_agent_config"` + RetryCount types.Int64 `tfsdk:"retry_count"` + Timeout types.Int64 `tfsdk:"timeout"` + ResourceFilter types.Map `tfsdk:"resource_filter"` +} + +type DeploymentResourceModel = DeploymentModel +type DeploymentDataSourceModel = DeploymentModel diff --git a/internal/resources/deployment/read.go b/internal/resources/deployment/read.go new file mode 100644 index 0000000..cf173ff --- /dev/null +++ b/internal/resources/deployment/read.go @@ -0,0 +1,117 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package deployment + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + + "terraform-provider-ctrlplane/client" +) + +func (r *Resource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state DeploymentModel + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + if state.ID.IsNull() { + resp.Diagnostics.AddError( + "Missing Deployment ID", + "Cannot read deployment without ID. This is a bug in the provider.", + ) + return + } + + deploymentID, err := uuid.Parse(state.ID.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + "Invalid Deployment ID", + fmt.Sprintf("Cannot parse deployment ID as UUID: %s", err), + ) + return + } + + tflog.Debug(ctx, "Reading deployment", map[string]interface{}{ + "id": deploymentID.String(), + }) + + response, err := r.client.GetDeploymentWithResponse(ctx, deploymentID) + if err != nil { + resp.Diagnostics.AddError( + "API Error", + fmt.Sprintf("Unable to read deployment: %s", err), + ) + return + } + + if response.StatusCode() == http.StatusNotFound { + resp.State.RemoveResource(ctx) + return + } + + if response.StatusCode() != http.StatusOK { + resp.Diagnostics.AddError( + "API Error", + fmt.Sprintf("Received status %d: %s", response.StatusCode(), string(response.Body)), + ) + return + } + + var deployment client.Deployment + if err := json.Unmarshal(response.Body, &deployment); err != nil { + resp.Diagnostics.AddError( + "API Response Error", + fmt.Sprintf("Unable to unmarshal deployment response: %s", err), + ) + return + } + + state.ID = types.StringValue(deployment.Id.String()) + state.Name = types.StringValue(deployment.Name) + state.Description = types.StringValue(deployment.Description) + state.SystemID = types.StringValue(deployment.SystemId.String()) + state.Slug = types.StringValue(deployment.Slug) + + if deployment.JobAgentId != nil { + state.JobAgentID = types.StringValue(deployment.JobAgentId.String()) + } else { + state.JobAgentID = types.StringNull() + } + + jobAgentConfigMap, diags := types.MapValueFrom(ctx, types.StringType, deployment.JobAgentConfig) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + state.JobAgentConfig = jobAgentConfigMap + + if deployment.RetryCount != nil { + state.RetryCount = types.Int64Value(int64(*deployment.RetryCount)) + } else { + state.RetryCount = types.Int64Null() + } + + if deployment.Timeout != nil { + state.Timeout = types.Int64Value(int64(*deployment.Timeout)) + } else { + state.Timeout = types.Int64Null() + } + + // The ResourceFilter field is not returned by the API's Get operation + // We preserve the value from the existing state if it's a Read after Apply + // We only set it to null if it doesn't exist in the current state (like during import) + + diags = resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) +} diff --git a/internal/resources/deployment/read_test.go b/internal/resources/deployment/read_test.go new file mode 100644 index 0000000..b3e7520 --- /dev/null +++ b/internal/resources/deployment/read_test.go @@ -0,0 +1,76 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package deployment_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + + ctrlacctest "terraform-provider-ctrlplane/testing/acctest" + "terraform-provider-ctrlplane/testing/testutils" +) + +func TestAccDeploymentResource_read(t *testing.T) { + resourceName := "ctrlplane_deployment.test" + rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { ctrlacctest.PreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccDeploymentConfigBasic(rName), + Check: resource.ComposeTestCheckFunc( + testutils.CheckResourceExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", fmt.Sprintf("test-deployment-%s", rName)), + resource.TestCheckResourceAttr(resourceName, "slug", fmt.Sprintf("test-deployment-%s", rName)), + resource.TestCheckResourceAttrSet(resourceName, "system_id"), + resource.TestCheckResourceAttrSet(resourceName, "id"), + ), + }, + { + Config: testAccDeploymentConfigBasic(rName), + Check: resource.ComposeTestCheckFunc( + testutils.CheckResourceExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", fmt.Sprintf("test-deployment-%s", rName)), + resource.TestCheckResourceAttr(resourceName, "description", "Test deployment for read test"), + resource.TestCheckResourceAttr(resourceName, "slug", fmt.Sprintf("test-deployment-%s", rName)), + resource.TestCheckResourceAttrSet(resourceName, "system_id"), + resource.TestCheckResourceAttr(resourceName, "job_agent_config.key1", "value1"), + resource.TestCheckResourceAttr(resourceName, "job_agent_config.key2", "value2"), + resource.TestCheckResourceAttr(resourceName, "retry_count", "3"), + resource.TestCheckResourceAttr(resourceName, "timeout", "300"), + ), + }, + }, + }) +} + +func testAccDeploymentConfigBasic(rName string) string { + return ctrlacctest.ProviderConfig() + fmt.Sprintf(` + # First create a system + resource "ctrlplane_system" "test" { + name = "test-system-%[1]s" + description = "Test system for deployment read test" + slug = "test-system-%[1]s" + } + + # Create the deployment + resource "ctrlplane_deployment" "test" { + name = "test-deployment-%[1]s" + description = "Test deployment for read test" + system_id = ctrlplane_system.test.id + slug = "test-deployment-%[1]s" + job_agent_config = { + "key1" = "value1" + "key2" = "value2" + } + retry_count = 3 + timeout = 300 + } + `, rName) +} diff --git a/internal/resources/deployment/resource.go b/internal/resources/deployment/resource.go new file mode 100644 index 0000000..15e995b --- /dev/null +++ b/internal/resources/deployment/resource.go @@ -0,0 +1,126 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package deployment + +import ( + "context" + "fmt" + "reflect" + + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + + "terraform-provider-ctrlplane/client" +) + +var ( + _ resource.Resource = &Resource{} + _ resource.ResourceWithImportState = &Resource{} +) + +type Resource struct { + client *client.ClientWithResponses +} + +func NewResource() resource.Resource { + return &Resource{} +} + +func (r *Resource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + providerValue := reflect.ValueOf(req.ProviderData).Elem() + clientField := providerValue.FieldByName("Client") + + if !clientField.IsValid() { + resp.Diagnostics.AddError( + "Invalid Provider Data", + "Provider data does not contain a Client field", + ) + return + } + + client, ok := clientField.Interface().(*client.ClientWithResponses) + if !ok { + resp.Diagnostics.AddError( + "Invalid Client Type", + fmt.Sprintf("Expected *client.ClientWithResponses, got: %T", clientField.Interface()), + ) + return + } + + r.client = client +} + +func (r *Resource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_deployment" +} + +func (r *Resource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Manages a deployment", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Deployment identifier", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Required: true, + MarkdownDescription: "Name of the deployment", + }, + "description": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Description of the deployment", + }, + "system_id": schema.StringAttribute{ + Required: true, + MarkdownDescription: "System ID this deployment belongs to", + }, + "slug": schema.StringAttribute{ + Required: true, + MarkdownDescription: "Slug identifier for the deployment", + }, + "job_agent_id": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Job agent ID to use for this deployment", + }, + "job_agent_config": schema.MapAttribute{ + Required: true, + ElementType: types.StringType, + MarkdownDescription: "Job agent configuration", + }, + "retry_count": schema.Int64Attribute{ + Optional: true, + MarkdownDescription: "Number of retry attempts", + }, + "timeout": schema.Int64Attribute{ + Optional: true, + MarkdownDescription: "Timeout in seconds", + }, + "resource_filter": schema.MapAttribute{ + Optional: true, + ElementType: types.StringType, + MarkdownDescription: "Resource filter configuration", + }, + }, + } +} + +func (r *Resource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + tflog.Debug(ctx, "Importing deployment resource", map[string]interface{}{ + "id": req.ID, + }) + + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} diff --git a/internal/resources/deployment/schema.go b/internal/resources/deployment/schema.go new file mode 100644 index 0000000..5b799e0 --- /dev/null +++ b/internal/resources/deployment/schema.go @@ -0,0 +1,59 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package deployment + +import ( + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func GetDeploymentDataSourceSchema() schema.Schema { + return schema.Schema{ + MarkdownDescription: "Fetch a deployment resource by ID or other attributes", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Deployment identifier", + }, + "name": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Name of the deployment", + }, + "description": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Description of the deployment", + }, + "system_id": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "System ID this deployment belongs to", + }, + "slug": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Slug identifier for the deployment", + }, + "job_agent_id": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Job agent ID used for this deployment", + }, + "job_agent_config": schema.MapAttribute{ + Computed: true, + ElementType: types.StringType, + MarkdownDescription: "Job agent configuration", + }, + "retry_count": schema.Int64Attribute{ + Computed: true, + MarkdownDescription: "Number of retry attempts", + }, + "timeout": schema.Int64Attribute{ + Computed: true, + MarkdownDescription: "Timeout in seconds", + }, + "resource_filter": schema.MapAttribute{ + Computed: true, + ElementType: types.StringType, + MarkdownDescription: "Resource filter configuration", + }, + }, + } +} diff --git a/internal/resources/deployment/update.go b/internal/resources/deployment/update.go new file mode 100644 index 0000000..cfa544c --- /dev/null +++ b/internal/resources/deployment/update.go @@ -0,0 +1,157 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package deployment + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-log/tflog" + + "terraform-provider-ctrlplane/client" +) + +func (r *Resource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan DeploymentModel + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + var state DeploymentModel + diags = req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, "Updating deployment", map[string]interface{}{ + "id": state.ID.ValueString(), + }) + + if state.ID.IsNull() { + resp.Diagnostics.AddError( + "Missing Deployment ID", + "Cannot update deployment without ID. This is a bug in the provider.", + ) + return + } + + deploymentID, err := uuid.Parse(state.ID.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + "Invalid Deployment ID", + fmt.Sprintf("Cannot parse deployment ID as UUID: %s", err), + ) + return + } + + var jobAgentConfigMap map[string]string + diags = plan.JobAgentConfig.ElementsAs(ctx, &jobAgentConfigMap, false) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + jobAgentConfig := make(map[string]interface{}) + for k, v := range jobAgentConfigMap { + jobAgentConfig[k] = v + } + + // var resourceFilter map[string]interface{} + // if !plan.ResourceFilter.IsNull() { + // diags = plan.ResourceFilter.ElementsAs(ctx, &resourceFilter, false) + // resp.Diagnostics.Append(diags...) + // if resp.Diagnostics.HasError() { + // return + // } + // } + + systemID, err := uuid.Parse(plan.SystemID.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + "Invalid System ID", + fmt.Sprintf("Cannot parse system ID as UUID: %s", err), + ) + return + } + + updateRequest := client.UpdateDeploymentJSONRequestBody{ + Id: deploymentID, + Name: plan.Name.ValueString(), + SystemId: systemID, + Slug: plan.Slug.ValueString(), + } + + if !plan.Description.IsNull() { + desc := plan.Description.ValueString() + updateRequest.Description = desc + } + + updateRequest.JobAgentConfig = jobAgentConfig + + if !plan.JobAgentID.IsNull() { + agentID, err := uuid.Parse(plan.JobAgentID.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + "Invalid Job Agent ID", + fmt.Sprintf("Cannot parse job agent ID as UUID: %s", err), + ) + return + } + updateRequest.JobAgentId = &agentID + } + + if !plan.RetryCount.IsNull() { + retryCount := int(plan.RetryCount.ValueInt64()) + updateRequest.RetryCount = &retryCount + } + + if !plan.Timeout.IsNull() { + timeout := int(plan.Timeout.ValueInt64()) + updateRequest.Timeout = &timeout + } + + // TODO: ResourceFilter will be migrated to ResourceSelector + // if resourceFilter != nil { + // updateRequest.Set("resourceFilter", &resourceFilter) + // } + + response, err := r.client.UpdateDeploymentWithResponse(ctx, deploymentID, updateRequest) + if err != nil { + resp.Diagnostics.AddError( + "API Error", + fmt.Sprintf("Unable to update deployment: %s", err), + ) + return + } + + if response.StatusCode() != http.StatusOK { + resp.Diagnostics.AddError( + "API Error", + fmt.Sprintf("Failed to update deployment. Status: %d, Body: %s", + response.StatusCode(), string(response.Body)), + ) + return + } + + var deployment client.Deployment + if err := json.Unmarshal(response.Body, &deployment); err != nil { + resp.Diagnostics.AddError( + "API Response Error", + fmt.Sprintf("Unable to unmarshal deployment update response: %s", err), + ) + return + } + + plan.ID = state.ID + + diags = resp.State.Set(ctx, plan) + resp.Diagnostics.Append(diags...) +} diff --git a/internal/resources/deployment/update_test.go b/internal/resources/deployment/update_test.go new file mode 100644 index 0000000..4b9a15f --- /dev/null +++ b/internal/resources/deployment/update_test.go @@ -0,0 +1,72 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package deployment_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + + ctrlacctest "terraform-provider-ctrlplane/testing/acctest" + "terraform-provider-ctrlplane/testing/testutils" +) + +func TestAccDeploymentResource_update(t *testing.T) { + resourceName := "ctrlplane_deployment.test" + rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + initialName := fmt.Sprintf("test-deployment-%s", rName) + updatedName := fmt.Sprintf("updated-deployment-%s", rName) + initialDescription := "Initial deployment description" + updatedDescription := "Updated deployment description" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { ctrlacctest.PreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccDeploymentConfigWithDesc(rName, initialName, initialDescription), + Check: resource.ComposeTestCheckFunc( + testutils.CheckResourceExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", initialName), + resource.TestCheckResourceAttr(resourceName, "description", initialDescription), + ), + }, + { + Config: testAccDeploymentConfigWithDesc(rName, updatedName, updatedDescription), + Check: resource.ComposeTestCheckFunc( + testutils.CheckResourceExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", updatedName), + resource.TestCheckResourceAttr(resourceName, "description", updatedDescription), + ), + }, + }, + }) +} + +func testAccDeploymentConfigWithDesc(rName, name, description string) string { + return ctrlacctest.ProviderConfig() + fmt.Sprintf(` + # First create a system + resource "ctrlplane_system" "test" { + name = "test-system-%[1]s" + description = "Test system for deployment tests" + slug = "test-system-%[1]s" + } + + # Create the deployment + resource "ctrlplane_deployment" "test" { + name = %[2]q + description = %[3]q + system_id = ctrlplane_system.test.id + slug = "deployment-%[1]s" + job_agent_config = { + "key1" = "value1" + "key2" = "value2" + } + retry_count = 3 + timeout = 300 + } + `, rName, name, description) +}