diff --git a/internal/provider/buildingblock.go b/client/buildingblock.go similarity index 68% rename from internal/provider/buildingblock.go rename to client/buildingblock.go index 9e6ec34..5580e7b 100644 --- a/internal/provider/buildingblock.go +++ b/client/buildingblock.go @@ -1,4 +1,12 @@ -package provider +package client + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "net/http" +) type MeshBuildingBlock struct { ApiVersion string `json:"apiVersion" tfsdk:"api_version"` @@ -40,3 +48,43 @@ type MeshBuildingBlockStatus struct { Status string `json:"status" tfsdk:"status"` Outputs []MeshBuildingBlockIO `json:"outputs" tfsdk:"outputs"` } + +func (c *MeshStackProviderClient) ReadBuildingBlock(uuid string) (*MeshBuildingBlock, error) { + if c.ensureValidToken() != nil { + return nil, errors.New(ERROR_AUTHENTICATION_FAILURE) + } + + targetUrl := c.endpoints.BuildingBlocks.JoinPath(uuid) + req, err := http.NewRequest("GET", targetUrl.String(), nil) + if err != nil { + return nil, err + } + + res, err := c.doAuthenticatedRequest(req) + if err != nil { + return nil, err + } + + defer res.Body.Close() + + data, err := io.ReadAll(res.Body) + if err != nil { + return nil, err + } + + if res.StatusCode == 404 { + return nil, nil + } + + if res.StatusCode != 200 { + return nil, fmt.Errorf("unexpected status code: %d, %s", res.StatusCode, data) + } + + var bb MeshBuildingBlock + err = json.Unmarshal(data, &bb) + if err != nil { + return nil, err + } + + return &bb, nil +} diff --git a/client/client.go b/client/client.go new file mode 100644 index 0000000..9158f69 --- /dev/null +++ b/client/client.go @@ -0,0 +1,216 @@ +package client + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "log" + "net/http" + "net/url" + "strings" + "time" +) + +const ( + apiMeshObjectsRoot = "/api/meshobjects" + loginEndpoint = "/api/login" + + ERROR_GENERIC_CLIENT_ERROR = "client error" + ERROR_GENERIC_API_ERROR = "api error" + ERROR_AUTHENTICATION_FAILURE = "Not authorized. Check api key and secret." + ERROR_ENDPOINT_LOOKUP = "Could not fetch endpoints for meshStack." + + CONTENT_TYPE_PROJECT = "application/vnd.meshcloud.api.meshproject.v2.hal+json" + CONTENT_TYPE_TENANT = "application/vnd.meshcloud.api.meshtenant.v3.hal+json" + CONTENT_TYPE_PROJECT_USER_BINDINGS = "application/vnd.meshcloud.api.meshprojectuserbinding.v1.hal+json" + CONTENT_TYPE_PROJECT_USER_BINDING = "application/vnd.meshcloud.api.meshprojectuserbinding.v3.hal+json" +) + +type MeshStackProviderClient struct { + url *url.URL + httpClient *http.Client + apiKey string + apiSecret string + token string + tokenExpiry time.Time + endpoints endpoints +} + +type endpoints struct { + BuildingBlocks *url.URL `json:"meshbuildingblocks"` + Projects *url.URL `json:"meshprojects"` + ProjectUserBindings *url.URL `json:"meshprojectuserbindings"` + Tenants *url.URL `json:"meshtenants"` +} + +type loginResponse struct { + Token string `json:"access_token"` + ExpireSec int `json:"expires_in"` +} + +func NewClient(rootUrl *url.URL, apiKey string, apiSecret string) (*MeshStackProviderClient, error) { + client := &MeshStackProviderClient{ + url: rootUrl, + httpClient: &http.Client{ + Timeout: time.Minute * 5, + }, + apiKey: apiKey, + apiSecret: apiSecret, + token: "", + } + + // TODO: lookup endpoints + client.endpoints = endpoints{ + BuildingBlocks: rootUrl.JoinPath(apiMeshObjectsRoot, "meshbuildingblocks"), + Projects: rootUrl.JoinPath(apiMeshObjectsRoot, "meshprojects"), + ProjectUserBindings: rootUrl.JoinPath(apiMeshObjectsRoot, "meshprojectbindings", "userbindings"), + Tenants: rootUrl.JoinPath(apiMeshObjectsRoot, "meshtenants"), + } + + return client, nil +} + +func (c *MeshStackProviderClient) login() error { + loginPath, err := url.JoinPath(c.url.String(), loginEndpoint) + if err != nil { + return err + } + + formData := url.Values{} + formData.Set("client_id", c.apiKey) + formData.Set("client_secret", c.apiSecret) + formData.Set("grant_type", "client_credentials") + + req, _ := http.NewRequest(http.MethodPost, loginPath, strings.NewReader(formData.Encode())) + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + + res, err := c.httpClient.Do(req) + + if err != nil || res.StatusCode != 200 { + return errors.New(ERROR_AUTHENTICATION_FAILURE) + } + + defer res.Body.Close() + + data, err := io.ReadAll(res.Body) + if err != nil { + return err + } + + var loginResult loginResponse + err = json.Unmarshal(data, &loginResult) + if err != nil { + return err + } + + c.token = fmt.Sprintf("Bearer %s", loginResult.Token) + c.tokenExpiry = time.Now().Add(time.Second * time.Duration(loginResult.ExpireSec)) + + return nil +} + +func (c *MeshStackProviderClient) ensureValidToken() error { + if c.token == "" || time.Now().Add(time.Second*30).After(c.tokenExpiry) { + return c.login() + } + return nil +} + +// nolint: unused +func (c *MeshStackProviderClient) lookUpEndpoints() error { + if c.ensureValidToken() != nil { + return errors.New(ERROR_AUTHENTICATION_FAILURE) + } + + meshObjectsPath, err := url.JoinPath(c.url.String(), apiMeshObjectsRoot) + if err != nil { + return err + } + meshObjects, _ := url.Parse(meshObjectsPath) + + res, err := c.httpClient.Do( + &http.Request{ + URL: meshObjects, + Method: "GET", + Header: http.Header{ + "Authorization": {c.token}, + }, + }, + ) + + if err != nil { + return errors.New(ERROR_GENERIC_CLIENT_ERROR) + } + + defer res.Body.Close() + + if res.StatusCode != 200 { + return errors.New(ERROR_AUTHENTICATION_FAILURE) + } + + data, err := io.ReadAll(res.Body) + if err != nil { + return err + } + + var endpoints endpoints + err = json.Unmarshal(data, &endpoints) + if err != nil { + return err + } + + c.endpoints = endpoints + return nil +} + +func (c *MeshStackProviderClient) doAuthenticatedRequest(req *http.Request) (*http.Response, error) { + // ensure that headeres are initialized + if req.Header == nil { + req.Header = map[string][]string{} + } + req.Header.Set("User-Agent", "meshStack Terraform Provider") + + // log request before adding auth + log.Println(req) + + // add authentication + if c.ensureValidToken() != nil { + return nil, errors.New(ERROR_AUTHENTICATION_FAILURE) + } + req.Header.Set("Authorization", c.token) + + res, err := c.httpClient.Do(req) + if err != nil { + return nil, err + } + log.Println(res) + + return res, nil +} + +func (c *MeshStackProviderClient) deleteMeshObject(targetUrl url.URL, expectedStatus int) error { + req, err := http.NewRequest("DELETE", targetUrl.String(), nil) + if err != nil { + return err + } + + res, err := c.doAuthenticatedRequest(req) + + if err != nil { + return errors.New(ERROR_GENERIC_CLIENT_ERROR) + } + + defer res.Body.Close() + + data, err := io.ReadAll(res.Body) + if err != nil { + return err + } + + if res.StatusCode != expectedStatus { + return fmt.Errorf("unexpected status code: %d, %s", res.StatusCode, data) + } + + return nil +} diff --git a/client/project.go b/client/project.go new file mode 100644 index 0000000..f9d97f8 --- /dev/null +++ b/client/project.go @@ -0,0 +1,236 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" +) + +type MeshProject struct { + ApiVersion string `json:"apiVersion" tfsdk:"api_version"` + Kind string `json:"kind" tfsdk:"kind"` + Metadata MeshProjectMetadata `json:"metadata" tfsdk:"metadata"` + Spec MeshProjectSpec `json:"spec" tfsdk:"spec"` +} + +type MeshProjectMetadata struct { + Name string `json:"name" tfsdk:"name"` + OwnedByWorkspace string `json:"ownedByWorkspace" tfsdk:"owned_by_workspace"` + CreatedOn string `json:"createdOn" tfsdk:"created_on"` + DeletedOn *string `json:"deletedOn" tfsdk:"deleted_on"` +} + +type MeshProjectSpec struct { + DisplayName string `json:"displayName" tfsdk:"display_name"` + Tags map[string][]string `json:"tags" tfsdk:"tags"` + PaymentMethodIdentifier *string `json:"paymentMethodIdentifier" tfsdk:"payment_method_identifier"` + SubstitutePaymentMethodIdentifier *string `json:"substitutePaymentMethodIdentifier" tfsdk:"substitute_payment_method_identifier"` +} + +type MeshProjectCreate struct { + Metadata MeshProjectCreateMetadata `json:"metadata" tfsdk:"metadata"` + Spec MeshProjectSpec `json:"spec" tfsdk:"spec"` +} + +type MeshProjectCreateMetadata struct { + Name string `json:"name" tfsdk:"name"` + OwnedByWorkspace string `json:"ownedByWorkspace" tfsdk:"owned_by_workspace"` +} + +func (c *MeshStackProviderClient) urlForProject(workspace string, name string) *url.URL { + identifier := workspace + "." + name + return c.endpoints.Projects.JoinPath(identifier) +} + +func (c *MeshStackProviderClient) ReadProject(workspace string, name string) (*MeshProject, error) { + targetUrl := c.urlForProject(workspace, name) + req, err := http.NewRequest("GET", targetUrl.String(), nil) + if err != nil { + return nil, err + } + req.Header.Set("Accept", CONTENT_TYPE_PROJECT) + + res, err := c.doAuthenticatedRequest(req) + if err != nil { + return nil, err + } + + defer res.Body.Close() + + data, err := io.ReadAll(res.Body) + if err != nil { + return nil, err + } + + if res.StatusCode == 404 { + return nil, nil + } + + if res.StatusCode != 200 { + return nil, fmt.Errorf("unexpected status code: %d, %s", res.StatusCode, data) + } + + var project MeshProject + err = json.Unmarshal(data, &project) + if err != nil { + return nil, err + } + + return &project, nil +} + +func (c *MeshStackProviderClient) ReadProjects(workspaceIdentifier string, paymentMethodIdentifier *string) (*[]MeshProject, error) { + var allProjects []MeshProject + + pageNumber := 0 + targetUrl := c.endpoints.Projects + query := targetUrl.Query() + query.Set("workspaceIdentifier", workspaceIdentifier) + if paymentMethodIdentifier != nil { + query.Set("paymentIdentifier", *paymentMethodIdentifier) + } + + for { + query.Set("page", fmt.Sprintf("%d", pageNumber)) + + targetUrl.RawQuery = query.Encode() + + req, err := http.NewRequest("GET", targetUrl.String(), nil) + if err != nil { + return nil, err + } + + req.Header.Set("Accept", CONTENT_TYPE_PROJECT) + + res, err := c.doAuthenticatedRequest(req) + if err != nil { + return nil, err + } + + defer res.Body.Close() + + data, err := io.ReadAll(res.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response body: %w", err) + } + + if res.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected status code: %d, %s", res.StatusCode, data) + } + + var response struct { + Embedded struct { + MeshProjects []MeshProject `json:"meshProjects"` + } `json:"_embedded"` + Page struct { + Size int `json:"size"` + TotalElements int `json:"totalElements"` + TotalPages int `json:"totalPages"` + Number int `json:"number"` + } `json:"page"` + } + + err = json.Unmarshal(data, &response) + if err != nil { + return nil, err + } + + allProjects = append(allProjects, response.Embedded.MeshProjects...) + + // Check if there are more pages + if response.Page.Number >= response.Page.TotalPages-1 { + break + } + + pageNumber++ + } + + return &allProjects, nil +} + +func (c *MeshStackProviderClient) CreateProject(project *MeshProjectCreate) (*MeshProject, error) { + payload, err := json.Marshal(project) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", c.endpoints.Projects.String(), bytes.NewBuffer(payload)) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", CONTENT_TYPE_PROJECT) + req.Header.Set("Accept", CONTENT_TYPE_PROJECT) + + res, err := c.doAuthenticatedRequest(req) + if err != nil { + return nil, err + } + + defer res.Body.Close() + + data, err := io.ReadAll(res.Body) + if err != nil { + return nil, err + } + + if res.StatusCode != 201 { + return nil, fmt.Errorf("unexpected status code: %d, %s", res.StatusCode, data) + } + + var createdProject MeshProject + err = json.Unmarshal(data, &createdProject) + if err != nil { + return nil, err + } + + return &createdProject, nil +} + +func (c *MeshStackProviderClient) UpdateProject(project *MeshProjectCreate) (*MeshProject, error) { + targetUrl := c.urlForProject(project.Metadata.OwnedByWorkspace, project.Metadata.Name) + + payload, err := json.Marshal(project) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("PUT", targetUrl.String(), bytes.NewBuffer(payload)) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", CONTENT_TYPE_PROJECT) + req.Header.Set("Accept", CONTENT_TYPE_PROJECT) + + res, err := c.doAuthenticatedRequest(req) + + if err != nil { + return nil, err + } + + defer res.Body.Close() + + data, err := io.ReadAll(res.Body) + if err != nil { + return nil, err + } + + if res.StatusCode != 200 { + return nil, fmt.Errorf("unexpected status code: %d, %s", res.StatusCode, data) + } + + var updatedProject MeshProject + err = json.Unmarshal(data, &updatedProject) + if err != nil { + return nil, err + } + + return &updatedProject, nil +} + +func (c *MeshStackProviderClient) DeleteProject(workspace string, name string) error { + targetUrl := c.urlForProject(workspace, name) + return c.deleteMeshObject(*targetUrl, 202) +} diff --git a/client/project_user_binding.go b/client/project_user_binding.go new file mode 100644 index 0000000..0c6f601 --- /dev/null +++ b/client/project_user_binding.go @@ -0,0 +1,120 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" +) + +type MeshProjectUserBinding struct { + ApiVersion string `json:"apiVersion" tfsdk:"api_version"` + Kind string `json:"kind" tfsdk:"kind"` + Metadata MeshProjectUserBindingMetadata `json:"metadata" tfsdk:"metadata"` + RoleRef MeshProjectRoleRef `json:"roleRef" tfsdk:"role_ref"` + TargetRef MeshProjectTargetRef `json:"targetRef" tfsdk:"target_ref"` + Subject MeshSubject `json:"subject" tfsdk:"subject"` +} + +type MeshProjectUserBindingMetadata struct { + Name string `json:"name" tfsdk:"name"` +} + +type MeshProjectRoleRef struct { + Name string `json:"name" tfsdk:"name"` +} + +type MeshProjectTargetRef struct { + Name string `json:"name" tfsdk:"name"` + OwnedByWorkspace string `json:"ownedByWorkspace" tfsdk:"owned_by_workspace"` +} + +type MeshSubject struct { + Name string `json:"name" tfsdk:"name"` +} + +func (c *MeshStackProviderClient) urlForPojectUserBinding(name string) *url.URL { + return c.endpoints.ProjectUserBindings.JoinPath(name) +} + +func (c *MeshStackProviderClient) ReadProjectUserBinding(name string) (*MeshProjectUserBinding, error) { + targetUrl := c.urlForPojectUserBinding(name) + req, err := http.NewRequest("GET", targetUrl.String(), nil) + if err != nil { + return nil, err + } + req.Header.Set("Accept", CONTENT_TYPE_PROJECT_USER_BINDING) + + res, err := c.doAuthenticatedRequest(req) + if err != nil { + return nil, err + } + + defer res.Body.Close() + + data, err := io.ReadAll(res.Body) + if err != nil { + return nil, err + } + + if res.StatusCode == 404 { + return nil, nil + } + + if res.StatusCode != 200 { + return nil, fmt.Errorf("unexpected status code: %d, %s", res.StatusCode, data) + } + + var binding MeshProjectUserBinding + err = json.Unmarshal(data, &binding) + if err != nil { + return nil, err + } + + return &binding, nil +} + +func (c *MeshStackProviderClient) CreateProjectUserBinding(binding *MeshProjectUserBinding) (*MeshProjectUserBinding, error) { + payload, err := json.Marshal(binding) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", c.endpoints.ProjectUserBindings.String(), bytes.NewBuffer(payload)) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", CONTENT_TYPE_PROJECT_USER_BINDING) + req.Header.Set("Accept", CONTENT_TYPE_PROJECT_USER_BINDING) + + res, err := c.doAuthenticatedRequest(req) + if err != nil { + return nil, err + } + + defer res.Body.Close() + + data, err := io.ReadAll(res.Body) + if err != nil { + return nil, err + } + + if res.StatusCode != 200 { + return nil, fmt.Errorf("unexpected status code: %d, %s", res.StatusCode, data) + } + + var createdBinding MeshProjectUserBinding + err = json.Unmarshal(data, &createdBinding) + if err != nil { + return nil, err + } + + return &createdBinding, nil +} + +func (c *MeshStackProviderClient) DeleteProjecUserBinding(name string) error { + targetUrl := c.urlForPojectUserBinding(name) + return c.deleteMeshObject(*targetUrl, 204) +} diff --git a/client/tenant.go b/client/tenant.go new file mode 100644 index 0000000..b8026aa --- /dev/null +++ b/client/tenant.go @@ -0,0 +1,138 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" +) + +type MeshTenant struct { + ApiVersion string `json:"apiVersion" tfsdk:"api_version"` + Kind string `json:"kind" tfsdk:"kind"` + Metadata MeshTenantMetadata `json:"metadata" tfsdk:"metadata"` + Spec MeshTenantSpec `json:"spec" tfsdk:"spec"` +} + +type MeshTenantMetadata struct { + OwnedByProject string `json:"ownedByProject" tfsdk:"owned_by_project"` + OwnedByWorkspace string `json:"ownedByWorkspace" tfsdk:"owned_by_workspace"` + PlatformIdentifier string `json:"platformIdentifier" tfsdk:"platform_identifier"` + AssignedTags map[string][]string `json:"assignedTags" tfsdk:"assigned_tags"` + DeletedOn *string `json:"deletedOn" tfsdk:"deleted_on"` +} + +type MeshTenantSpec struct { + LocalId *string `json:"localId" tfsdk:"local_id"` + LandingZoneIdentifier string `json:"landingZoneIdentifier" tfsdk:"landing_zone_identifier"` + Quotas []MeshTenantQuota `json:"quotas" tfsdk:"quotas"` +} + +type MeshTenantQuota struct { + Key string `json:"key" tfsdk:"key"` + Value int64 `json:"value" tfsdk:"value"` +} + +type MeshTenantCreate struct { + Metadata MeshTenantCreateMetadata `json:"metadata" tfsdk:"metadata"` + Spec MeshTenantCreateSpec `json:"spec" tfsdk:"spec"` +} + +type MeshTenantCreateMetadata struct { + OwnedByProject string `json:"ownedByProject" tfsdk:"owned_by_project"` + OwnedByWorkspace string `json:"ownedByWorkspace" tfsdk:"owned_by_workspace"` + PlatformIdentifier string `json:"platformIdentifier" tfsdk:"platform_identifier"` +} + +type MeshTenantCreateSpec struct { + LocalId *string `json:"localId" tfsdk:"local_id"` + LandingZoneIdentifier *string `json:"landingZoneIdentifier" tfsdk:"landing_zone_identifier"` + Quotas *[]MeshTenantQuota `json:"quotas" tfsdk:"quotas"` +} + +func (c *MeshStackProviderClient) urlForTenant(workspace string, project string, platform string) *url.URL { + identifier := workspace + "." + project + "." + platform + return c.endpoints.Tenants.JoinPath(identifier) +} + +func (c *MeshStackProviderClient) ReadTenant(workspace string, project string, platform string) (*MeshTenant, error) { + targetUrl := c.urlForTenant(workspace, project, platform) + req, err := http.NewRequest("GET", targetUrl.String(), nil) + if err != nil { + return nil, err + } + req.Header.Set("Accept", CONTENT_TYPE_TENANT) + + res, err := c.doAuthenticatedRequest(req) + if err != nil { + return nil, err + } + + defer res.Body.Close() + + data, err := io.ReadAll(res.Body) + if err != nil { + return nil, err + } + + if res.StatusCode == 404 { + return nil, nil + } + + if res.StatusCode != 200 { + return nil, fmt.Errorf("unexpected status code: %d, %s", res.StatusCode, data) + } + + var tenant MeshTenant + err = json.Unmarshal(data, &tenant) + if err != nil { + return nil, err + } + + return &tenant, nil +} + +func (c *MeshStackProviderClient) CreateTenant(tenant *MeshTenantCreate) (*MeshTenant, error) { + payload, err := json.Marshal(tenant) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", c.endpoints.Tenants.String(), bytes.NewBuffer(payload)) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", CONTENT_TYPE_TENANT) + req.Header.Set("Accept", CONTENT_TYPE_TENANT) + + res, err := c.doAuthenticatedRequest(req) + if err != nil { + return nil, err + } + + defer res.Body.Close() + + data, err := io.ReadAll(res.Body) + if err != nil { + return nil, err + } + + if res.StatusCode != 201 { + return nil, fmt.Errorf("unexpected status code: %d, %s", res.StatusCode, data) + } + + var createdTenant MeshTenant + err = json.Unmarshal(data, &createdTenant) + if err != nil { + return nil, err + } + + return &createdTenant, nil +} + +func (c *MeshStackProviderClient) DeleteTenant(workspace string, project string, platform string) error { + targetUrl := c.urlForTenant(workspace, project, platform) + return c.deleteMeshObject(*targetUrl, 202) +} diff --git a/internal/provider/buildingblock_data_source.go b/internal/provider/buildingblock_data_source.go index 15a978b..e410bd0 100644 --- a/internal/provider/buildingblock_data_source.go +++ b/internal/provider/buildingblock_data_source.go @@ -5,6 +5,8 @@ import ( "errors" "fmt" + "github.com/meshcloud/terraform-provider-meshstack/client" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" @@ -24,7 +26,7 @@ func NewBuildingBlockDataSource() datasource.DataSource { } type buildingBlockDataSource struct { - client *MeshStackProviderClient + client *client.MeshStackProviderClient } func (d *buildingBlockDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { @@ -127,7 +129,7 @@ func (d *buildingBlockDataSource) Configure(ctx context.Context, req datasource. return } - client, ok := req.ProviderData.(*MeshStackProviderClient) + client, ok := req.ProviderData.(*client.MeshStackProviderClient) if !ok { resp.Diagnostics.AddError( @@ -150,7 +152,7 @@ func (d *buildingBlockDataSource) Read(ctx context.Context, req datasource.ReadR ValueType types.String `tfsdk:"value_type"` } - mkIoList := func(ios *[]MeshBuildingBlockIO) (*[]io, error) { + mkIoList := func(ios *[]client.MeshBuildingBlockIO) (*[]io, error) { result := make([]io, 0) var err error for _, input := range *ios { diff --git a/internal/provider/buildingblock_resource.go b/internal/provider/buildingblock_resource.go index 6bd10dd..6e88fe1 100644 --- a/internal/provider/buildingblock_resource.go +++ b/internal/provider/buildingblock_resource.go @@ -4,6 +4,8 @@ import ( "context" "fmt" + "github.com/meshcloud/terraform-provider-meshstack/client" + "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" @@ -22,7 +24,7 @@ func NewBuildingBlockResource() resource.Resource { } type BuildingBlockResource struct { - client *MeshStackProviderClient + client *client.MeshStackProviderClient } type BuildingBlockResourceModel struct { @@ -84,7 +86,7 @@ func (r *BuildingBlockResource) Configure(ctx context.Context, req resource.Conf return } - client, ok := req.ProviderData.(*MeshStackProviderClient) + client, ok := req.ProviderData.(*client.MeshStackProviderClient) if !ok { resp.Diagnostics.AddError( diff --git a/internal/provider/client.go b/internal/provider/client.go deleted file mode 100644 index 8aa43d2..0000000 --- a/internal/provider/client.go +++ /dev/null @@ -1,622 +0,0 @@ -package provider - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "io" - "log" - "net/http" - "net/url" - "strings" - "time" -) - -const ( - apiMeshObjectsRoot = "/api/meshobjects" - loginEndpoint = "/api/login" - - ERROR_GENERIC_CLIENT_ERROR = "client error" - ERROR_GENERIC_API_ERROR = "api error" - ERROR_AUTHENTICATION_FAILURE = "Not authorized. Check api key and secret." - ERROR_ENDPOINT_LOOKUP = "Could not fetch endpoints for meshStack." - - CONTENT_TYPE_PROJECT = "application/vnd.meshcloud.api.meshproject.v2.hal+json" - CONTENT_TYPE_TENANT = "application/vnd.meshcloud.api.meshtenant.v3.hal+json" - CONTENT_TYPE_PROJECT_USER_BINDINGS = "application/vnd.meshcloud.api.meshprojectuserbinding.v1.hal+json" - CONTENT_TYPE_PROJECT_USER_BINDING = "application/vnd.meshcloud.api.meshprojectuserbinding.v3.hal+json" -) - -// TODO this will be an abstraction that does the login call, get a token and then use this token in the Auth header. -type MeshStackProviderClient struct { - url *url.URL - httpClient *http.Client - apiKey string - apiSecret string - token string - tokenExpiry time.Time - endpoints endpoints -} - -type endpoints struct { - BuildingBlocks *url.URL `json:"meshbuildingblocks"` - Projects *url.URL `json:"meshprojects"` - ProjectUserBindings *url.URL `json:"meshprojectuserbindings"` - Tenants *url.URL `json:"meshtenants"` -} - -type loginResponse struct { - Token string `json:"access_token"` - ExpireSec int `json:"expires_in"` -} - -func NewClient(rootUrl *url.URL, apiKey string, apiSecret string) (*MeshStackProviderClient, error) { - client := &MeshStackProviderClient{ - url: rootUrl, - httpClient: &http.Client{ - Timeout: time.Minute * 5, - }, - apiKey: apiKey, - apiSecret: apiSecret, - token: "", - } - - // TODO: lookup endpoints - client.endpoints = endpoints{ - BuildingBlocks: rootUrl.JoinPath(apiMeshObjectsRoot, "meshbuildingblocks"), - Projects: rootUrl.JoinPath(apiMeshObjectsRoot, "meshprojects"), - ProjectUserBindings: rootUrl.JoinPath(apiMeshObjectsRoot, "meshprojectbindings", "userbindings"), - Tenants: rootUrl.JoinPath(apiMeshObjectsRoot, "meshtenants"), - } - - return client, nil -} - -func (c *MeshStackProviderClient) login() error { - loginPath, err := url.JoinPath(c.url.String(), loginEndpoint) - if err != nil { - return err - } - - formData := url.Values{} - formData.Set("client_id", c.apiKey) - formData.Set("client_secret", c.apiSecret) - formData.Set("grant_type", "client_credentials") - - req, _ := http.NewRequest(http.MethodPost, loginPath, strings.NewReader(formData.Encode())) - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") - - res, err := c.httpClient.Do(req) - - if err != nil || res.StatusCode != 200 { - return errors.New(ERROR_AUTHENTICATION_FAILURE) - } - - defer res.Body.Close() - - data, err := io.ReadAll(res.Body) - if err != nil { - return err - } - - var loginResult loginResponse - err = json.Unmarshal(data, &loginResult) - if err != nil { - return err - } - - c.token = fmt.Sprintf("Bearer %s", loginResult.Token) - c.tokenExpiry = time.Now().Add(time.Second * time.Duration(loginResult.ExpireSec)) - - return nil -} - -func (c *MeshStackProviderClient) ensureValidToken() error { - if c.token == "" || time.Now().Add(time.Second*30).After(c.tokenExpiry) { - return c.login() - } - return nil -} - -// nolint: unused -func (c *MeshStackProviderClient) lookUpEndpoints() error { - if c.ensureValidToken() != nil { - return errors.New(ERROR_AUTHENTICATION_FAILURE) - } - - meshObjectsPath, err := url.JoinPath(c.url.String(), apiMeshObjectsRoot) - if err != nil { - return err - } - meshObjects, _ := url.Parse(meshObjectsPath) - - res, err := c.httpClient.Do( - &http.Request{ - URL: meshObjects, - Method: "GET", - Header: http.Header{ - "Authorization": {c.token}, - }, - }, - ) - - if err != nil { - return errors.New(ERROR_GENERIC_CLIENT_ERROR) - } - - defer res.Body.Close() - - if res.StatusCode != 200 { - return errors.New(ERROR_AUTHENTICATION_FAILURE) - } - - data, err := io.ReadAll(res.Body) - if err != nil { - return err - } - - var endpoints endpoints - err = json.Unmarshal(data, &endpoints) - if err != nil { - return err - } - - c.endpoints = endpoints - return nil -} - -func (c *MeshStackProviderClient) doAuthenticatedRequest(req *http.Request) (*http.Response, error) { - // ensure that headeres are initialized - if req.Header == nil { - req.Header = map[string][]string{} - } - req.Header.Set("User-Agent", "meshStack Terraform Provider") - - // log request before adding auth - log.Println(req) - - // add authentication - if c.ensureValidToken() != nil { - return nil, errors.New(ERROR_AUTHENTICATION_FAILURE) - } - req.Header.Set("Authorization", c.token) - - res, err := c.httpClient.Do(req) - if err != nil { - return nil, err - } - log.Println(res) - - return res, nil -} - -func (c *MeshStackProviderClient) DeleteMeshObject(targetUrl url.URL, expectedStatus int) error { - req, err := http.NewRequest("DELETE", targetUrl.String(), nil) - if err != nil { - return err - } - - res, err := c.doAuthenticatedRequest(req) - - if err != nil { - return errors.New(ERROR_GENERIC_CLIENT_ERROR) - } - - defer res.Body.Close() - - data, err := io.ReadAll(res.Body) - if err != nil { - return err - } - - if res.StatusCode != expectedStatus { - return fmt.Errorf("unexpected status code: %d, %s", res.StatusCode, data) - } - - return nil -} - -func (c *MeshStackProviderClient) ReadBuildingBlock(uuid string) (*MeshBuildingBlock, error) { - if c.ensureValidToken() != nil { - return nil, errors.New(ERROR_AUTHENTICATION_FAILURE) - } - - targetUrl := c.endpoints.BuildingBlocks.JoinPath(uuid) - req, err := http.NewRequest("GET", targetUrl.String(), nil) - if err != nil { - return nil, err - } - - res, err := c.doAuthenticatedRequest(req) - if err != nil { - return nil, err - } - - defer res.Body.Close() - - data, err := io.ReadAll(res.Body) - if err != nil { - return nil, err - } - - if res.StatusCode == 404 { - return nil, nil - } - - if res.StatusCode != 200 { - return nil, fmt.Errorf("unexpected status code: %d, %s", res.StatusCode, data) - } - - var bb MeshBuildingBlock - err = json.Unmarshal(data, &bb) - if err != nil { - return nil, err - } - - return &bb, nil -} - -func (c *MeshStackProviderClient) urlForProject(workspace string, name string) *url.URL { - identifier := workspace + "." + name - return c.endpoints.Projects.JoinPath(identifier) -} - -func (c *MeshStackProviderClient) urlForTenant(workspace string, project string, platform string) *url.URL { - identifier := workspace + "." + project + "." + platform - return c.endpoints.Tenants.JoinPath(identifier) -} - -func (c *MeshStackProviderClient) ReadProject(workspace string, name string) (*MeshProject, error) { - targetUrl := c.urlForProject(workspace, name) - req, err := http.NewRequest("GET", targetUrl.String(), nil) - if err != nil { - return nil, err - } - req.Header.Set("Accept", CONTENT_TYPE_PROJECT) - - res, err := c.doAuthenticatedRequest(req) - if err != nil { - return nil, err - } - - defer res.Body.Close() - - data, err := io.ReadAll(res.Body) - if err != nil { - return nil, err - } - - if res.StatusCode == 404 { - return nil, nil - } - - if res.StatusCode != 200 { - return nil, fmt.Errorf("unexpected status code: %d, %s", res.StatusCode, data) - } - - var project MeshProject - err = json.Unmarshal(data, &project) - if err != nil { - return nil, err - } - - return &project, nil -} - -func (c *MeshStackProviderClient) ReadProjects(workspaceIdentifier string, paymentMethodIdentifier *string) (*[]MeshProject, error) { - var allProjects []MeshProject - - pageNumber := 0 - targetUrl := c.endpoints.Projects - query := targetUrl.Query() - query.Set("workspaceIdentifier", workspaceIdentifier) - if paymentMethodIdentifier != nil { - query.Set("paymentIdentifier", *paymentMethodIdentifier) - } - - for { - query.Set("page", fmt.Sprintf("%d", pageNumber)) - - targetUrl.RawQuery = query.Encode() - - req, err := http.NewRequest("GET", targetUrl.String(), nil) - if err != nil { - return nil, err - } - - req.Header.Set("Accept", CONTENT_TYPE_PROJECT) - - res, err := c.doAuthenticatedRequest(req) - if err != nil { - return nil, err - } - - defer res.Body.Close() - - data, err := io.ReadAll(res.Body) - if err != nil { - return nil, fmt.Errorf("failed to read response body: %w", err) - } - - if res.StatusCode != http.StatusOK { - return nil, fmt.Errorf("unexpected status code: %d, %s", res.StatusCode, data) - } - - var response struct { - Embedded struct { - MeshProjects []MeshProject `json:"meshProjects"` - } `json:"_embedded"` - Page struct { - Size int `json:"size"` - TotalElements int `json:"totalElements"` - TotalPages int `json:"totalPages"` - Number int `json:"number"` - } `json:"page"` - } - - err = json.Unmarshal(data, &response) - if err != nil { - return nil, err - } - - allProjects = append(allProjects, response.Embedded.MeshProjects...) - - // Check if there are more pages - if response.Page.Number >= response.Page.TotalPages-1 { - break - } - - pageNumber++ - } - - return &allProjects, nil -} - -func (c *MeshStackProviderClient) CreateProject(project *MeshProjectCreate) (*MeshProject, error) { - payload, err := json.Marshal(project) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("POST", c.endpoints.Projects.String(), bytes.NewBuffer(payload)) - if err != nil { - return nil, err - } - req.Header.Set("Content-Type", CONTENT_TYPE_PROJECT) - req.Header.Set("Accept", CONTENT_TYPE_PROJECT) - - res, err := c.doAuthenticatedRequest(req) - if err != nil { - return nil, err - } - - defer res.Body.Close() - - data, err := io.ReadAll(res.Body) - if err != nil { - return nil, err - } - - if res.StatusCode != 201 { - return nil, fmt.Errorf("unexpected status code: %d, %s", res.StatusCode, data) - } - - var createdProject MeshProject - err = json.Unmarshal(data, &createdProject) - if err != nil { - return nil, err - } - - return &createdProject, nil -} - -func (c *MeshStackProviderClient) UpdateProject(project *MeshProjectCreate) (*MeshProject, error) { - targetUrl := c.urlForProject(project.Metadata.OwnedByWorkspace, project.Metadata.Name) - - payload, err := json.Marshal(project) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("PUT", targetUrl.String(), bytes.NewBuffer(payload)) - if err != nil { - return nil, err - } - req.Header.Set("Content-Type", CONTENT_TYPE_PROJECT) - req.Header.Set("Accept", CONTENT_TYPE_PROJECT) - - res, err := c.doAuthenticatedRequest(req) - - if err != nil { - return nil, err - } - - defer res.Body.Close() - - data, err := io.ReadAll(res.Body) - if err != nil { - return nil, err - } - - if res.StatusCode != 200 { - return nil, fmt.Errorf("unexpected status code: %d, %s", res.StatusCode, data) - } - - var updatedProject MeshProject - err = json.Unmarshal(data, &updatedProject) - if err != nil { - return nil, err - } - - return &updatedProject, nil -} - -func (c *MeshStackProviderClient) DeleteProject(workspace string, name string) error { - targetUrl := c.urlForProject(workspace, name) - return c.DeleteMeshObject(*targetUrl, 202) -} - -func (c *MeshStackProviderClient) ReadTenant(workspace string, project string, platform string) (*MeshTenant, error) { - targetUrl := c.urlForTenant(workspace, project, platform) - req, err := http.NewRequest("GET", targetUrl.String(), nil) - if err != nil { - return nil, err - } - req.Header.Set("Accept", CONTENT_TYPE_TENANT) - - res, err := c.doAuthenticatedRequest(req) - if err != nil { - return nil, err - } - - defer res.Body.Close() - - data, err := io.ReadAll(res.Body) - if err != nil { - return nil, err - } - - if res.StatusCode == 404 { - return nil, nil - } - - if res.StatusCode != 200 { - return nil, fmt.Errorf("unexpected status code: %d, %s", res.StatusCode, data) - } - - var tenant MeshTenant - err = json.Unmarshal(data, &tenant) - if err != nil { - return nil, err - } - - return &tenant, nil -} - -func (c *MeshStackProviderClient) CreateTenant(tenant *MeshTenantCreate) (*MeshTenant, error) { - payload, err := json.Marshal(tenant) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("POST", c.endpoints.Tenants.String(), bytes.NewBuffer(payload)) - if err != nil { - return nil, err - } - req.Header.Set("Content-Type", CONTENT_TYPE_TENANT) - req.Header.Set("Accept", CONTENT_TYPE_TENANT) - - res, err := c.doAuthenticatedRequest(req) - if err != nil { - return nil, err - } - - defer res.Body.Close() - - data, err := io.ReadAll(res.Body) - if err != nil { - return nil, err - } - - if res.StatusCode != 201 { - return nil, fmt.Errorf("unexpected status code: %d, %s", res.StatusCode, data) - } - - var createdTenant MeshTenant - err = json.Unmarshal(data, &createdTenant) - if err != nil { - return nil, err - } - - return &createdTenant, nil -} - -func (c *MeshStackProviderClient) DeleteTenant(workspace string, project string, platform string) error { - targetUrl := c.urlForTenant(workspace, project, platform) - return c.DeleteMeshObject(*targetUrl, 202) -} - -func (c *MeshStackProviderClient) urlForPojectUserBinding(name string) *url.URL { - return c.endpoints.ProjectUserBindings.JoinPath(name) -} - -func (c *MeshStackProviderClient) ReadProjectUserBinding(name string) (*MeshProjectUserBinding, error) { - targetUrl := c.urlForPojectUserBinding(name) - req, err := http.NewRequest("GET", targetUrl.String(), nil) - if err != nil { - return nil, err - } - req.Header.Set("Accept", CONTENT_TYPE_PROJECT_USER_BINDING) - - res, err := c.doAuthenticatedRequest(req) - if err != nil { - return nil, err - } - - defer res.Body.Close() - - data, err := io.ReadAll(res.Body) - if err != nil { - return nil, err - } - - if res.StatusCode == 404 { - return nil, nil - } - - if res.StatusCode != 200 { - return nil, fmt.Errorf("unexpected status code: %d, %s", res.StatusCode, data) - } - - var binding MeshProjectUserBinding - err = json.Unmarshal(data, &binding) - if err != nil { - return nil, err - } - - return &binding, nil -} - -func (c *MeshStackProviderClient) CreateProjectUserBinding(binding *MeshProjectUserBinding) (*MeshProjectUserBinding, error) { - payload, err := json.Marshal(binding) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("POST", c.endpoints.ProjectUserBindings.String(), bytes.NewBuffer(payload)) - if err != nil { - return nil, err - } - req.Header.Set("Content-Type", CONTENT_TYPE_PROJECT_USER_BINDING) - req.Header.Set("Accept", CONTENT_TYPE_PROJECT_USER_BINDING) - - res, err := c.doAuthenticatedRequest(req) - if err != nil { - return nil, err - } - - defer res.Body.Close() - - data, err := io.ReadAll(res.Body) - if err != nil { - return nil, err - } - - if res.StatusCode != 200 { - return nil, fmt.Errorf("unexpected status code: %d, %s", res.StatusCode, data) - } - - var createdBinding MeshProjectUserBinding - err = json.Unmarshal(data, &createdBinding) - if err != nil { - return nil, err - } - - return &createdBinding, nil -} - -func (c *MeshStackProviderClient) DeleteProjecUserBinding(name string) error { - targetUrl := c.urlForPojectUserBinding(name) - return c.DeleteMeshObject(*targetUrl, 204) -} diff --git a/internal/provider/project.go b/internal/provider/project.go deleted file mode 100644 index aebd490..0000000 --- a/internal/provider/project.go +++ /dev/null @@ -1,32 +0,0 @@ -package provider - -type MeshProject struct { - ApiVersion string `json:"apiVersion" tfsdk:"api_version"` - Kind string `json:"kind" tfsdk:"kind"` - Metadata MeshProjectMetadata `json:"metadata" tfsdk:"metadata"` - Spec MeshProjectSpec `json:"spec" tfsdk:"spec"` -} - -type MeshProjectMetadata struct { - Name string `json:"name" tfsdk:"name"` - OwnedByWorkspace string `json:"ownedByWorkspace" tfsdk:"owned_by_workspace"` - CreatedOn string `json:"createdOn" tfsdk:"created_on"` - DeletedOn *string `json:"deletedOn" tfsdk:"deleted_on"` -} - -type MeshProjectSpec struct { - DisplayName string `json:"displayName" tfsdk:"display_name"` - Tags map[string][]string `json:"tags" tfsdk:"tags"` - PaymentMethodIdentifier *string `json:"paymentMethodIdentifier" tfsdk:"payment_method_identifier"` - SubstitutePaymentMethodIdentifier *string `json:"substitutePaymentMethodIdentifier" tfsdk:"substitute_payment_method_identifier"` -} - -type MeshProjectCreate struct { - Metadata MeshProjectCreateMetadata `json:"metadata" tfsdk:"metadata"` - Spec MeshProjectSpec `json:"spec" tfsdk:"spec"` -} - -type MeshProjectCreateMetadata struct { - Name string `json:"name" tfsdk:"name"` - OwnedByWorkspace string `json:"ownedByWorkspace" tfsdk:"owned_by_workspace"` -} diff --git a/internal/provider/project_data_source.go b/internal/provider/project_data_source.go index 869fef8..7fcb9f2 100644 --- a/internal/provider/project_data_source.go +++ b/internal/provider/project_data_source.go @@ -4,6 +4,8 @@ import ( "context" "fmt" + "github.com/meshcloud/terraform-provider-meshstack/client" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" @@ -23,7 +25,7 @@ func NewProjectDataSource() datasource.DataSource { } type projectDataSource struct { - client *MeshStackProviderClient + client *client.MeshStackProviderClient } func (d *projectDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { @@ -82,7 +84,7 @@ func (d *projectDataSource) Configure(ctx context.Context, req datasource.Config return } - client, ok := req.ProviderData.(*MeshStackProviderClient) + client, ok := req.ProviderData.(*client.MeshStackProviderClient) if !ok { resp.Diagnostics.AddError( diff --git a/internal/provider/project_resource.go b/internal/provider/project_resource.go index fe988b2..1023ea9 100644 --- a/internal/provider/project_resource.go +++ b/internal/provider/project_resource.go @@ -5,6 +5,8 @@ import ( "fmt" "strings" + "github.com/meshcloud/terraform-provider-meshstack/client" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" @@ -30,7 +32,7 @@ func NewProjectResource() resource.Resource { // projectResource is the resource implementation. type projectResource struct { - client *MeshStackProviderClient + client *client.MeshStackProviderClient } // Metadata returns the resource type name. @@ -44,7 +46,7 @@ func (r *projectResource) Configure(_ context.Context, req resource.ConfigureReq return } - client, ok := req.ProviderData.(*MeshStackProviderClient) + client, ok := req.ProviderData.(*client.MeshStackProviderClient) if !ok { resp.Diagnostics.AddError( @@ -178,12 +180,12 @@ func (r *projectResource) Create(ctx context.Context, req resource.CreateRequest paymentMethodIdentifier = plan.Spec.SubstitutePaymentMethodIdentifier.ValueStringPointer() } - create := MeshProjectCreate{ - Metadata: MeshProjectCreateMetadata{ + create := client.MeshProjectCreate{ + Metadata: client.MeshProjectCreateMetadata{ Name: plan.Metadata.Name.ValueString(), OwnedByWorkspace: plan.Metadata.OwnedByWorkspace.ValueString(), }, - Spec: MeshProjectSpec{ + Spec: client.MeshProjectSpec{ DisplayName: plan.Spec.DisplayName.ValueString(), Tags: tags, PaymentMethodIdentifier: paymentMethodIdentifier, @@ -259,12 +261,12 @@ func (r *projectResource) Update(ctx context.Context, req resource.UpdateRequest paymentMethodIdentifier = plan.Spec.SubstitutePaymentMethodIdentifier.ValueStringPointer() } - create := MeshProjectCreate{ - Metadata: MeshProjectCreateMetadata{ + create := client.MeshProjectCreate{ + Metadata: client.MeshProjectCreateMetadata{ Name: plan.Metadata.Name.ValueString(), OwnedByWorkspace: plan.Metadata.OwnedByWorkspace.ValueString(), }, - Spec: MeshProjectSpec{ + Spec: client.MeshProjectSpec{ DisplayName: plan.Spec.DisplayName.ValueString(), Tags: tags, PaymentMethodIdentifier: paymentMethodIdentifier, @@ -287,7 +289,7 @@ func (r *projectResource) Update(ctx context.Context, req resource.UpdateRequest // Delete deletes the resource and removes the Terraform state on success. func (r *projectResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - var state MeshProject + var state client.MeshProject diags := req.State.Get(ctx, &state) resp.Diagnostics.Append(diags...) diff --git a/internal/provider/project_user_binding.go b/internal/provider/project_user_binding.go deleted file mode 100644 index 6add7a0..0000000 --- a/internal/provider/project_user_binding.go +++ /dev/null @@ -1,27 +0,0 @@ -package provider - -type MeshProjectUserBinding struct { - ApiVersion string `json:"apiVersion" tfsdk:"api_version"` - Kind string `json:"kind" tfsdk:"kind"` - Metadata MeshProjectUserBindingMetadata `json:"metadata" tfsdk:"metadata"` - RoleRef MeshProjectRoleRef `json:"roleRef" tfsdk:"role_ref"` - TargetRef MeshProjectTargetRef `json:"targetRef" tfsdk:"target_ref"` - Subject MeshSubject `json:"subject" tfsdk:"subject"` -} - -type MeshProjectUserBindingMetadata struct { - Name string `json:"name" tfsdk:"name"` -} - -type MeshProjectRoleRef struct { - Name string `json:"name" tfsdk:"name"` -} - -type MeshProjectTargetRef struct { - Name string `json:"name" tfsdk:"name"` - OwnedByWorkspace string `json:"ownedByWorkspace" tfsdk:"owned_by_workspace"` -} - -type MeshSubject struct { - Name string `json:"name" tfsdk:"name"` -} diff --git a/internal/provider/project_user_binding_data_source.go b/internal/provider/project_user_binding_data_source.go index c3e6369..2afa059 100644 --- a/internal/provider/project_user_binding_data_source.go +++ b/internal/provider/project_user_binding_data_source.go @@ -4,6 +4,8 @@ import ( "context" "fmt" + "github.com/meshcloud/terraform-provider-meshstack/client" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" @@ -21,7 +23,7 @@ func NewProjectUserBindingsDataSource() datasource.DataSource { } type projectUserBindingsDataSource struct { - client *MeshStackProviderClient + client *client.MeshStackProviderClient } func (d *projectUserBindingsDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { @@ -96,7 +98,7 @@ func (d *projectUserBindingsDataSource) Configure(ctx context.Context, req datas return } - client, ok := req.ProviderData.(*MeshStackProviderClient) + client, ok := req.ProviderData.(*client.MeshStackProviderClient) if !ok { resp.Diagnostics.AddError( diff --git a/internal/provider/project_user_binding_resource.go b/internal/provider/project_user_binding_resource.go index 6c7592b..a79d37e 100644 --- a/internal/provider/project_user_binding_resource.go +++ b/internal/provider/project_user_binding_resource.go @@ -4,6 +4,8 @@ import ( "context" "fmt" + "github.com/meshcloud/terraform-provider-meshstack/client" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" @@ -28,7 +30,7 @@ func NewProjectUserBindingResource() resource.Resource { // projectUserBindingResource is the resource implementation. type projectUserBindingResource struct { - client *MeshStackProviderClient + client *client.MeshStackProviderClient } // Metadata returns the resource type name. @@ -42,7 +44,7 @@ func (r *projectUserBindingResource) Configure(_ context.Context, req resource.C return } - client, ok := req.ProviderData.(*MeshStackProviderClient) + client, ok := req.ProviderData.(*client.MeshStackProviderClient) if !ok { resp.Diagnostics.AddError( @@ -137,28 +139,9 @@ func (r *projectUserBindingResource) Schema(_ context.Context, _ resource.Schema } } -type projectUserBindingCreate struct { - Metadata struct { - Name *string `tfsdk:"name"` - } `tfsdk:"metadata"` - - RoleRef struct { - Name string `tfsdk:"name"` - } `tfsdk:"role_ref"` - - TargetRef struct { - Name string `tfsdk:"name"` - OwnedByWorkspace string `tfsdk:"owned_by_workspace"` - } `tfsdk:"target_ref"` - - Subject struct { - Name string `tfsdk:"name"` - } `tfsdk:"subject"` -} - // Create creates the resource and sets the initial Terraform state. func (r *projectUserBindingResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - var plan MeshProjectUserBinding + var plan client.MeshProjectUserBinding diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) diff --git a/internal/provider/projects_data_source.go b/internal/provider/projects_data_source.go index 309dfcc..57eedcc 100644 --- a/internal/provider/projects_data_source.go +++ b/internal/provider/projects_data_source.go @@ -4,6 +4,8 @@ import ( "context" "fmt" + "github.com/meshcloud/terraform-provider-meshstack/client" + "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/path" @@ -21,7 +23,7 @@ func NewProjectsDataSource() datasource.DataSource { } type projectsDataSource struct { - client *MeshStackProviderClient + client *client.MeshStackProviderClient } func (d *projectsDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { @@ -111,7 +113,7 @@ func (d *projectsDataSource) Configure(ctx context.Context, req datasource.Confi return } - client, ok := req.ProviderData.(*MeshStackProviderClient) + client, ok := req.ProviderData.(*client.MeshStackProviderClient) if !ok { resp.Diagnostics.AddError( diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 5999c02..4d48261 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -4,6 +4,8 @@ import ( "context" "net/url" + "github.com/meshcloud/terraform-provider-meshstack/client" + "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/provider" "github.com/hashicorp/terraform-plugin-framework/provider/schema" @@ -59,7 +61,7 @@ func (p *MeshStackProvider) Configure(ctx context.Context, req provider.Configur if err != nil { resp.Diagnostics.AddError("Provider endpoint not valid.", "The value provided as the providers endpoint is not a valid URL.") } else { - client, err := NewClient(url, data.ApiKey.ValueString(), data.ApiSecret.ValueString()) // TODO handle err + client, err := client.NewClient(url, data.ApiKey.ValueString(), data.ApiSecret.ValueString()) // TODO handle err if err != nil { resp.Diagnostics.AddError("Failed to create client.", err.Error()) } diff --git a/internal/provider/tenant.go b/internal/provider/tenant.go deleted file mode 100644 index 5843fb8..0000000 --- a/internal/provider/tenant.go +++ /dev/null @@ -1,44 +0,0 @@ -package provider - -type MeshTenant struct { - ApiVersion string `json:"apiVersion" tfsdk:"api_version"` - Kind string `json:"kind" tfsdk:"kind"` - Metadata MeshTenantMetadata `json:"metadata" tfsdk:"metadata"` - Spec MeshTenantSpec `json:"spec" tfsdk:"spec"` -} - -type MeshTenantMetadata struct { - OwnedByProject string `json:"ownedByProject" tfsdk:"owned_by_project"` - OwnedByWorkspace string `json:"ownedByWorkspace" tfsdk:"owned_by_workspace"` - PlatformIdentifier string `json:"platformIdentifier" tfsdk:"platform_identifier"` - AssignedTags map[string][]string `json:"assignedTags" tfsdk:"assigned_tags"` - DeletedOn *string `json:"deletedOn" tfsdk:"deleted_on"` -} - -type MeshTenantSpec struct { - LocalId *string `json:"localId" tfsdk:"local_id"` - LandingZoneIdentifier string `json:"landingZoneIdentifier" tfsdk:"landing_zone_identifier"` - Quotas []MeshTenantQuota `json:"quotas" tfsdk:"quotas"` -} - -type MeshTenantQuota struct { - Key string `json:"key" tfsdk:"key"` - Value int64 `json:"value" tfsdk:"value"` -} - -type MeshTenantCreate struct { - Metadata MeshTenantCreateMetadata `json:"metadata" tfsdk:"metadata"` - Spec MeshTenantCreateSpec `json:"spec" tfsdk:"spec"` -} - -type MeshTenantCreateMetadata struct { - OwnedByProject string `json:"ownedByProject" tfsdk:"owned_by_project"` - OwnedByWorkspace string `json:"ownedByWorkspace" tfsdk:"owned_by_workspace"` - PlatformIdentifier string `json:"platformIdentifier" tfsdk:"platform_identifier"` -} - -type MeshTenantCreateSpec struct { - LocalId *string `json:"localId" tfsdk:"local_id"` - LandingZoneIdentifier *string `json:"landingZoneIdentifier" tfsdk:"landing_zone_identifier"` - Quotas *[]MeshTenantQuota `json:"quotas" tfsdk:"quotas"` -} diff --git a/internal/provider/tenant_data_source.go b/internal/provider/tenant_data_source.go index 8db348c..1f2791a 100644 --- a/internal/provider/tenant_data_source.go +++ b/internal/provider/tenant_data_source.go @@ -4,6 +4,8 @@ import ( "context" "fmt" + "github.com/meshcloud/terraform-provider-meshstack/client" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" @@ -23,7 +25,7 @@ func NewTenantDataSource() datasource.DataSource { } type tenantDataSource struct { - client *MeshStackProviderClient + client *client.MeshStackProviderClient } func (d *tenantDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { @@ -90,7 +92,7 @@ func (d *tenantDataSource) Configure(ctx context.Context, req datasource.Configu return } - client, ok := req.ProviderData.(*MeshStackProviderClient) + client, ok := req.ProviderData.(*client.MeshStackProviderClient) if !ok { resp.Diagnostics.AddError( diff --git a/internal/provider/tenant_resource.go b/internal/provider/tenant_resource.go index f948229..3f8e7aa 100644 --- a/internal/provider/tenant_resource.go +++ b/internal/provider/tenant_resource.go @@ -5,6 +5,8 @@ import ( "fmt" "strings" + "github.com/meshcloud/terraform-provider-meshstack/client" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" @@ -27,7 +29,7 @@ func NewTenantResource() resource.Resource { } type tenantResource struct { - client *MeshStackProviderClient + client *client.MeshStackProviderClient } func (r *tenantResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { @@ -39,7 +41,7 @@ func (r *tenantResource) Configure(_ context.Context, req resource.ConfigureRequ return } - client, ok := req.ProviderData.(*MeshStackProviderClient) + client, ok := req.ProviderData.(*client.MeshStackProviderClient) if !ok { resp.Diagnostics.AddError( @@ -165,7 +167,7 @@ func (r *tenantResource) Create(ctx context.Context, req resource.CreateRequest, landing_zone_identifier = spec.LandingZoneIdentifier.ValueStringPointer() } - var quotas []MeshTenantQuota + var quotas []client.MeshTenantQuota if !spec.Quotas.IsNull() && !spec.Quotas.IsUnknown() { resp.Diagnostics.Append(spec.Quotas.ElementsAs(ctx, "as, false)...) } @@ -174,13 +176,13 @@ func (r *tenantResource) Create(ctx context.Context, req resource.CreateRequest, return } - create := MeshTenantCreate{ - Metadata: MeshTenantCreateMetadata{ + create := client.MeshTenantCreate{ + Metadata: client.MeshTenantCreateMetadata{ OwnedByProject: metadata.OwnedByProject.ValueString(), OwnedByWorkspace: metadata.OwnedByWorkspace.ValueString(), PlatformIdentifier: metadata.PlatformIdentifier.ValueString(), }, - Spec: MeshTenantCreateSpec{ + Spec: client.MeshTenantCreateSpec{ LocalId: local_id, LandingZoneIdentifier: landing_zone_identifier, Quotas: "as, @@ -231,7 +233,7 @@ func (r *tenantResource) Update(ctx context.Context, req resource.UpdateRequest, } func (r *tenantResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - var state MeshTenant + var state client.MeshTenant diags := req.State.Get(ctx, &state) resp.Diagnostics.Append(diags...)