From 93684fa5128efa7dcb6aa4b28e8da8617d1b7200 Mon Sep 17 00:00:00 2001 From: Wilson de Carvalho <796900+wcmjunior@users.noreply.github.com> Date: Sat, 30 Mar 2024 00:13:56 -0700 Subject: [PATCH 01/22] Refactor cyral_repository --- cyral/core/README.md | 1 - cyral/internal/repository/constants.go | 91 ++++ ...rce_cyral_repository.go => data_source.go} | 48 +- ...repository_test.go => data_source_test.go} | 8 +- cyral/internal/repository/model.go | 201 +++++++ cyral/internal/repository/resource.go | 179 +++++++ .../repository/resource_cyral_repository.go | 502 ------------------ ...al_repository_test.go => resource_test.go} | 16 +- cyral/internal/repository/schema_loader.go | 31 ++ cyral/provider/provider.go | 3 - cyral/provider/schema_loader.go | 2 + 11 files changed, 540 insertions(+), 542 deletions(-) create mode 100644 cyral/internal/repository/constants.go rename cyral/internal/repository/{data_source_cyral_repository.go => data_source.go} (83%) rename cyral/internal/repository/{data_source_cyral_repository_test.go => data_source_test.go} (97%) create mode 100644 cyral/internal/repository/model.go create mode 100644 cyral/internal/repository/resource.go delete mode 100644 cyral/internal/repository/resource_cyral_repository.go rename cyral/internal/repository/{resource_cyral_repository_test.go => resource_test.go} (96%) create mode 100644 cyral/internal/repository/schema_loader.go diff --git a/cyral/core/README.md b/cyral/core/README.md index fc815dd8..7d9bf18b 100644 --- a/cyral/core/README.md +++ b/cyral/core/README.md @@ -76,7 +76,6 @@ package newfeature var dsContextHandler = core.DefaultContextHandler{ ResourceName: dataSourceName, ResourceType: resourcetype.DataSource, - SchemaReaderFactory: func() core.SchemaReader { return &NewFeature{} }, SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &NewFeature{} }, BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/NewFeature", c.ControlPlane) diff --git a/cyral/internal/repository/constants.go b/cyral/internal/repository/constants.go new file mode 100644 index 00000000..851bd085 --- /dev/null +++ b/cyral/internal/repository/constants.go @@ -0,0 +1,91 @@ +package repository + +const ( + resourceName = "cyral_repository" + dataSourceName = "cyral_repository" +) + +const ( + // Schema keys. + RepoIDKey = "id" + RepoTypeKey = "type" + RepoNameKey = "name" + RepoLabelsKey = "labels" + // Connection draining keys. + RepoConnDrainingKey = "connection_draining" + RepoConnDrainingAutoKey = "auto" + RepoConnDrainingWaitTimeKey = "wait_time" + // Repo node keys. + RepoNodesKey = "repo_node" + RepoHostKey = "host" + RepoPortKey = "port" + RepoNodeDynamicKey = "dynamic" + // MongoDB settings keys. + RepoMongoDBSettingsKey = "mongodb_settings" + RepoMongoDBReplicaSetNameKey = "replica_set_name" + RepoMongoDBServerTypeKey = "server_type" + RepoMongoDBSRVRecordName = "srv_record_name" + RepoMongoDBFlavorKey = "flavor" +) + +const ( + Denodo = "denodo" + Dremio = "dremio" + DynamoDB = "dynamodb" + DynamoDBStreams = "dynamodbstreams" + Galera = "galera" + MariaDB = "mariadb" + MongoDB = "mongodb" + MySQL = "mysql" + Oracle = "oracle" + PostgreSQL = "postgresql" + Redshift = "redshift" + S3 = "s3" + Snowflake = "snowflake" + SQLServer = "sqlserver" +) + +func RepositoryTypes() []string { + return []string{ + Denodo, + Dremio, + DynamoDB, + DynamoDBStreams, + Galera, + MariaDB, + MongoDB, + MySQL, + Oracle, + PostgreSQL, + Redshift, + S3, + Snowflake, + SQLServer, + } +} + +const ( + ReplicaSet = "replicaset" + Standalone = "standalone" + Sharded = "sharded" +) + +const ( + MongoDBFlavorMongoDB = "mongodb" + MongoDBFlavorDocumentDB = "documentdb" +) + +func mongoServerTypes() []string { + return []string{ + ReplicaSet, + Standalone, + Sharded, + } +} + +func mongoFlavors() []string { + return []string{ + MongoDBFlavorMongoDB, + MongoDBFlavorDocumentDB, + } +} diff --git a/cyral/internal/repository/data_source_cyral_repository.go b/cyral/internal/repository/data_source.go similarity index 83% rename from cyral/internal/repository/data_source_cyral_repository.go rename to cyral/internal/repository/data_source.go index d6a25138..ee0623ff 100644 --- a/cyral/internal/repository/data_source_cyral_repository.go +++ b/cyral/internal/repository/data_source.go @@ -2,7 +2,6 @@ package repository import ( "fmt" - "net/http" "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -10,7 +9,7 @@ import ( "github.com/cyralinc/terraform-provider-cyral/cyral/client" "github.com/cyralinc/terraform-provider-cyral/cyral/core" - "github.com/cyralinc/terraform-provider-cyral/cyral/core/types/operationtype" + "github.com/cyralinc/terraform-provider-cyral/cyral/core/types/resourcetype" "github.com/cyralinc/terraform-provider-cyral/cyral/utils" ) @@ -21,6 +20,10 @@ const ( // GetReposSubResponse is different from GetRepoByIDResponse. For the by-id // response, we expect the ids to be embedded in the RepoInfo struct. For // GetReposSubResponse, the ids come outside of RepoInfo. +// +// Needles to say we need a new API version to fix these issues. For the +// time being, I'm keeping the model here to isolate it from the rest of +// the code - Wilson. type GetReposSubResponse struct { ID string `json:"id"` Repo RepoInfo `json:"repo"` @@ -37,10 +40,10 @@ func (resp *GetReposResponse) WriteToSchema(d *schema.ResourceData) error { RepoIDKey: repo.ID, RepoNameKey: repo.Repo.Name, RepoTypeKey: repo.Repo.Type, - RepoLabelsKey: repo.Repo.LabelsAsInterface(), - RepoConnDrainingKey: repo.Repo.ConnDrainingAsInterface(), - RepoNodesKey: repo.Repo.RepoNodesAsInterface(), - RepoMongoDBSettingsKey: repo.Repo.MongoDBSettingsAsInterface(), + RepoLabelsKey: repo.Repo.Labels.AsInterface(), + RepoConnDrainingKey: repo.Repo.ConnParams.AsInterface(), + RepoNodesKey: repo.Repo.RepoNodes.AsInterface(), + RepoMongoDBSettingsKey: repo.Repo.MongoDBSettings.AsInterface(), } repoList = append(repoList, argumentVals) } @@ -54,29 +57,26 @@ func (resp *GetReposResponse) WriteToSchema(d *schema.ResourceData) error { return nil } -func dataSourceRepositoryReadConfig() core.ResourceOperationConfig { - return core.ResourceOperationConfig{ - ResourceName: "RepositoryDataSourceRead", - Type: operationtype.Read, - HttpMethod: http.MethodGet, - URLFactory: func(d *schema.ResourceData, c *client.Client) string { - nameFilter := d.Get("name").(string) - typeFilter := d.Get("type").(string) - urlParams := utils.UrlQuery(map[string]string{ - "name": nameFilter, - "type": typeFilter, - }) +var dsContextHandler = core.DefaultContextHandler{ + ResourceName: dataSourceName, + ResourceType: resourcetype.DataSource, + SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &GetReposResponse{} }, + BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { + nameFilter := d.Get("name").(string) + typeFilter := d.Get("type").(string) + urlParams := utils.UrlQuery(map[string]string{ + "name": nameFilter, + "type": typeFilter, + }) - return fmt.Sprintf("https://%s/v1/repos%s", c.ControlPlane, urlParams) - }, - SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &GetReposResponse{} }, - } + return fmt.Sprintf("https://%s/v1/repos%s", c.ControlPlane, urlParams) + }, } -func DataSourceRepository() *schema.Resource { +func dataSourceSchema() *schema.Resource { return &schema.Resource{ Description: "Retrieves a list of repositories. See [`repository_list`](#nestedatt--repository_list).", - ReadContext: core.ReadResource(dataSourceRepositoryReadConfig()), + ReadContext: dsContextHandler.ReadContext(), Schema: map[string]*schema.Schema{ RepoNameKey: { Description: "Filter the results by a regular expression (regex) that matches names of existing repositories.", diff --git a/cyral/internal/repository/data_source_cyral_repository_test.go b/cyral/internal/repository/data_source_test.go similarity index 97% rename from cyral/internal/repository/data_source_cyral_repository_test.go rename to cyral/internal/repository/data_source_test.go index a576f324..8c79d87c 100644 --- a/cyral/internal/repository/data_source_cyral_repository_test.go +++ b/cyral/internal/repository/data_source_test.go @@ -20,8 +20,8 @@ func repositoryDataSourceTestRepos() []repository.RepoInfo { { Name: utils.AccTestName(repositoryDataSourceName, "sqlserver-1"), Type: "sqlserver", - Labels: []string{"rds", "us-east-2"}, - RepoNodes: []*repository.RepoNode{ + Labels: repository.Labels{"rds", "us-east-2"}, + RepoNodes: repository.RepoNodes{ { Host: "sql.local", Port: 3333, @@ -31,8 +31,8 @@ func repositoryDataSourceTestRepos() []repository.RepoInfo { { Name: utils.AccTestName(repositoryDataSourceName, "mongodb-1"), Type: "mongodb", - Labels: []string{"rds", "us-east-1"}, - RepoNodes: []*repository.RepoNode{ + Labels: repository.Labels{"rds", "us-east-1"}, + RepoNodes: repository.RepoNodes{ { Host: "mongo.local", Port: 27017, diff --git a/cyral/internal/repository/model.go b/cyral/internal/repository/model.go new file mode 100644 index 00000000..494bec6b --- /dev/null +++ b/cyral/internal/repository/model.go @@ -0,0 +1,201 @@ +package repository + +import ( + "fmt" + + "github.com/cyralinc/terraform-provider-cyral/cyral/utils" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +type Labels []string +type RepoNodes []*RepoNode + +type RepoInfo struct { + ID string `json:"id"` + Name string `json:"name"` + Type string `json:"type"` + Host string `json:"repoHost"` + Port uint32 `json:"repoPort"` + ConnParams *ConnParams `json:"connParams"` + Labels Labels `json:"labels"` + RepoNodes RepoNodes `json:"repoNodes,omitempty"` + MongoDBSettings *MongoDBSettings `json:"mongoDbSettings,omitempty"` +} + +type ConnParams struct { + ConnDraining *ConnDraining `json:"connDraining"` +} + +type ConnDraining struct { + Auto bool `json:"auto"` + WaitTime uint32 `json:"waitTime"` +} + +type MongoDBSettings struct { + ReplicaSetName string `json:"replicaSetName,omitempty"` + ServerType string `json:"serverType,omitempty"` + SRVRecordName string `json:"srvRecordName,omitempty"` + Flavor string `json:"flavor,omitempty"` +} + +type RepoNode struct { + Name string `json:"name"` + Host string `json:"host"` + Port uint32 `json:"port"` + Dynamic bool `json:"dynamic"` +} + +type GetRepoByIDResponse struct { + Repo RepoInfo `json:"repo"` +} + +func (res *GetRepoByIDResponse) WriteToSchema(d *schema.ResourceData) error { + return res.Repo.WriteToSchema(d) +} + +func (res *RepoInfo) WriteToSchema(d *schema.ResourceData) error { + d.Set(RepoTypeKey, res.Type) + d.Set(RepoNameKey, res.Name) + d.Set(RepoLabelsKey, res.Labels.AsInterface()) + d.Set(RepoConnDrainingKey, res.ConnParams.AsInterface()) + d.Set(RepoNodesKey, res.RepoNodes.AsInterface()) + d.Set(RepoMongoDBSettingsKey, res.MongoDBSettings.AsInterface()) + return nil +} + +func (r *RepoInfo) ReadFromSchema(d *schema.ResourceData) error { + r.ID = d.Id() + r.Name = d.Get(RepoNameKey).(string) + r.Type = d.Get(RepoTypeKey).(string) + r.Labels = labelsFromInterface(d.Get(RepoLabelsKey).([]interface{})) + r.RepoNodes = repoNodesFromInterface(d.Get(RepoNodesKey).([]interface{})) + r.ConnParams = connDrainingFromInterface(d.Get(RepoConnDrainingKey).(*schema.Set).List()) + var mongoDBSettings = d.Get(RepoMongoDBSettingsKey).(*schema.Set).List() + if r.Type == MongoDB && len(mongoDBSettings) == 0 { + return fmt.Errorf("'%s' block must be provided when '%s=%s'", RepoMongoDBSettingsKey, utils.TypeKey, MongoDB) + } else if r.Type != MongoDB && len(mongoDBSettings) > 0 { + return fmt.Errorf("'%s' block is only allowed when '%s=%s'", RepoMongoDBSettingsKey, utils.TypeKey, MongoDB) + } + m, err := mongoDBSettingsFromInterface(mongoDBSettings) + r.MongoDBSettings = m + return err +} + +func (l *Labels) AsInterface() []interface{} { + if l == nil { + return nil + } + labels := make([]interface{}, len(*l)) + for i, label := range *l { + labels[i] = label + } + return labels +} + +func labelsFromInterface(i []interface{}) Labels { + labels := make([]string, len(i)) + for index, v := range i { + labels[index] = v.(string) + } + return labels +} + +func (c *ConnParams) AsInterface() []interface{} { + if c == nil || c.ConnDraining == nil { + return nil + } + return []interface{}{map[string]interface{}{ + RepoConnDrainingAutoKey: c.ConnDraining.Auto, + RepoConnDrainingWaitTimeKey: c.ConnDraining.WaitTime, + }} +} + +func connDrainingFromInterface(i []interface{}) *ConnParams { + if len(i) == 0 { + return nil + } + return &ConnParams{ + ConnDraining: &ConnDraining{ + Auto: i[0].(map[string]interface{})[RepoConnDrainingAutoKey].(bool), + WaitTime: uint32(i[0].(map[string]interface{})[RepoConnDrainingWaitTimeKey].(int)), + }, + } +} + +func (r *RepoNodes) AsInterface() []interface{} { + if r == nil { + return nil + } + repoNodes := make([]interface{}, len(*r)) + for i, node := range *r { + repoNodes[i] = map[string]interface{}{ + RepoNameKey: node.Name, + RepoHostKey: node.Host, + RepoPortKey: node.Port, + RepoNodeDynamicKey: node.Dynamic, + } + } + return repoNodes +} + +func repoNodesFromInterface(i []interface{}) RepoNodes { + if len(i) == 0 { + return nil + } + repoNodes := make(RepoNodes, len(i)) + for index, nodeInterface := range i { + nodeMap := nodeInterface.(map[string]interface{}) + node := &RepoNode{ + Name: nodeMap[RepoNameKey].(string), + Host: nodeMap[RepoHostKey].(string), + Port: uint32(nodeMap[RepoPortKey].(int)), + Dynamic: nodeMap[RepoNodeDynamicKey].(bool), + } + repoNodes[index] = node + } + return repoNodes +} + +func (m *MongoDBSettings) AsInterface() []interface{} { + if m == nil { + return nil + } + + return []interface{}{map[string]interface{}{ + RepoMongoDBReplicaSetNameKey: m.ReplicaSetName, + RepoMongoDBServerTypeKey: m.ServerType, + RepoMongoDBSRVRecordName: m.SRVRecordName, + RepoMongoDBFlavorKey: m.Flavor, + }} +} + +func mongoDBSettingsFromInterface(i []interface{}) (*MongoDBSettings, error) { + if len(i) == 0 { + return nil, nil + } + var replicaSetName = i[0].(map[string]interface{})[RepoMongoDBReplicaSetNameKey].(string) + var serverType = i[0].(map[string]interface{})[RepoMongoDBServerTypeKey].(string) + var srvRecordName = i[0].(map[string]interface{})[RepoMongoDBSRVRecordName].(string) + if serverType == ReplicaSet && replicaSetName == "" { + return nil, fmt.Errorf("'%s' must be provided when '%s=\"%s\"'", RepoMongoDBReplicaSetNameKey, + RepoMongoDBServerTypeKey, ReplicaSet) + } + if serverType != ReplicaSet && replicaSetName != "" { + return nil, fmt.Errorf("'%s' cannot be provided when '%s=\"%s\"'", RepoMongoDBReplicaSetNameKey, + RepoMongoDBServerTypeKey, serverType) + } + if serverType == Standalone && srvRecordName != "" { + return nil, fmt.Errorf( + "'%s' cannot be provided when '%s=\"%s\"'", + RepoMongoDBSRVRecordName, + RepoMongoDBServerTypeKey, + Standalone, + ) + } + return &MongoDBSettings{ + ReplicaSetName: i[0].(map[string]interface{})[RepoMongoDBReplicaSetNameKey].(string), + ServerType: i[0].(map[string]interface{})[RepoMongoDBServerTypeKey].(string), + SRVRecordName: i[0].(map[string]interface{})[RepoMongoDBSRVRecordName].(string), + Flavor: i[0].(map[string]interface{})[RepoMongoDBFlavorKey].(string), + }, nil +} diff --git a/cyral/internal/repository/resource.go b/cyral/internal/repository/resource.go new file mode 100644 index 00000000..f100a4b4 --- /dev/null +++ b/cyral/internal/repository/resource.go @@ -0,0 +1,179 @@ +package repository + +import ( + "fmt" + + "github.com/cyralinc/terraform-provider-cyral/cyral/client" + "github.com/cyralinc/terraform-provider-cyral/cyral/core" + "github.com/cyralinc/terraform-provider-cyral/cyral/core/types/resourcetype" + "github.com/cyralinc/terraform-provider-cyral/cyral/utils" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +var resourceContextHandler = core.DefaultContextHandler{ + ResourceName: resourceName, + ResourceType: resourcetype.Resource, + SchemaReaderFactory: func() core.SchemaReader { return &RepoInfo{} }, + SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &GetRepoByIDResponse{} }, + BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { + return fmt.Sprintf( + "https://%s/v1/repos", + c.ControlPlane, + ) + }, +} + +func resourceSchema() *schema.Resource { + return &schema.Resource{ + Description: "Manages [repositories](https://cyral.com/docs/manage-repositories/repo-track)." + + "\n\nSee also [Cyral Repository Configuration Module](https://github.com/cyralinc/terraform-cyral-repository-config)." + + "\nThis module provides the repository configuration options as shown in Cyral UI.", + CreateContext: resourceContextHandler.CreateContext(), + ReadContext: resourceContextHandler.ReadContext(), + UpdateContext: resourceContextHandler.UpdateContext(), + DeleteContext: resourceContextHandler.DeleteContext(), + Schema: map[string]*schema.Schema{ + RepoIDKey: { + Description: "ID of this resource in Cyral environment.", + Type: schema.TypeString, + Computed: true, + }, + RepoTypeKey: { + Description: "Repository type. List of supported types:" + utils.SupportedValuesAsMarkdown(RepositoryTypes()), + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice(RepositoryTypes(), false), + }, + RepoNameKey: { + Description: "Repository name that will be used internally in the control plane (ex: `your_repo_name`).", + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + RepoLabelsKey: { + Description: "Labels enable you to categorize your repository.", + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + RepoConnDrainingKey: { + Description: "Parameters related to connection draining.", + Type: schema.TypeSet, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + RepoConnDrainingAutoKey: { + Description: "Whether connections should be drained automatically after a listener dies.", + Type: schema.TypeBool, + Optional: true, + }, + RepoConnDrainingWaitTimeKey: { + Description: "Seconds to wait to let connections drain before starting to kill all the connections, " + + "if auto is set to true.", + Type: schema.TypeInt, + Optional: true, + }, + }, + }, + }, + RepoNodesKey: { + Description: "List of nodes for this repository.", + Type: schema.TypeList, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + RepoNameKey: { + Description: "Name of the repo node.", + Type: schema.TypeString, + Optional: true, + }, + RepoHostKey: { + Description: "Repo node host (ex: `somerepo.cyral.com`). Can be empty if node is dynamic.", + Type: schema.TypeString, + Optional: true, + }, + RepoPortKey: { + Description: "Repository access port (ex: `3306`). Can be empty if node is dynamic.", + Type: schema.TypeInt, + Optional: true, + }, + RepoNodeDynamicKey: { + Description: "*Only supported for MongoDB in cluster configurations.*\n" + + "Indicates if the node is dynamically discovered, meaning that the sidecar " + + "will query the cluster to get the topology information and discover the " + + "addresses of the dynamic nodes. If set to `true`, `host` and `port` must " + + "be empty. A node with value of this field as false considered `static`.\n" + + "The following conditions apply: \n" + + " - The total number of declared `" + RepoNodesKey + "` blocks must match " + + "the actual number of nodes in the cluster.\n" + + " - If there are static nodes in the configuration, they must be declared " + + "before all dynamic nodes.\n" + + " - See the MongoDB-specific configuration in the [" + RepoMongoDBSettingsKey + + "](#nested-schema-for-" + RepoMongoDBSettingsKey + ").", + Type: schema.TypeBool, + Optional: true, + }, + }, + }, + }, + RepoMongoDBSettingsKey: { + Description: "Parameters related to MongoDB repositories.", + Type: schema.TypeSet, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + RepoMongoDBReplicaSetNameKey: { + Description: "Name of the replica set, if applicable.", + Type: schema.TypeString, + Optional: true, + }, + RepoMongoDBServerTypeKey: { + Description: "Type of the MongoDB server. Allowed values: " + utils.SupportedValuesAsMarkdown(mongoServerTypes()) + + "\n\n The following conditions apply:\n" + + " - If `" + Sharded + "` and `" + RepoMongoDBSRVRecordName + "` *not* provided, then all `" + + RepoNodesKey + "` blocks must be static (see [`" + RepoNodeDynamicKey + "`](#" + RepoNodeDynamicKey + ")).\n" + + " - If `" + Sharded + "` and `" + RepoMongoDBSRVRecordName + "` provided, then all `" + + RepoNodesKey + "` blocks must be dynamic (see [`" + RepoNodeDynamicKey + "`](#" + RepoNodeDynamicKey + ")).\n" + + " - If `" + Standalone + "`, then only one `" + RepoNodesKey + + "` block can be declared and it must be static (see [`" + RepoNodeDynamicKey + "`](#" + RepoNodeDynamicKey + ")). The `" + + RepoMongoDBSRVRecordName + "` is not supported in this configuration.\n" + + " - If `" + ReplicaSet + "` and `" + RepoMongoDBSRVRecordName + "` *not* provided, then `" + + RepoNodesKey + "` blocks may mix dynamic and static nodes (see [`" + RepoNodeDynamicKey + "`](#" + RepoNodeDynamicKey + ")).\n" + + " - If `" + ReplicaSet + "` and `" + RepoMongoDBSRVRecordName + "` provided, then `" + + RepoNodesKey + "` blocks must be dynamic (see [`" + RepoNodeDynamicKey + "`](#" + RepoNodeDynamicKey + ")).\n", + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(mongoServerTypes(), false), + }, + RepoMongoDBSRVRecordName: { + Description: "Name of a DNS SRV record which contains cluster topology details. " + + "If specified, then all `" + RepoNodesKey + "` blocks must be declared dynamic " + + "(see [`" + RepoNodeDynamicKey + "`](#" + RepoNodeDynamicKey + ")). " + + "Only supported for `" + RepoMongoDBServerTypeKey + "=\"" + Sharded + "\"` or `" + + RepoMongoDBServerTypeKey + "=\"" + ReplicaSet + "\".", + Type: schema.TypeString, + Optional: true, + }, + RepoMongoDBFlavorKey: { + Description: "The flavor of the MongoDB deployment. Allowed values: " + utils.SupportedValuesAsMarkdown(mongoFlavors()) + + "\n\n The following conditions apply:\n" + + " - The `" + MongoDBFlavorDocumentDB + "` flavor cannot be combined with the MongoDB Server type `" + Sharded + "`.\n", + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice(mongoFlavors(), false), + }, + }, + }, + }, + }, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + } +} diff --git a/cyral/internal/repository/resource_cyral_repository.go b/cyral/internal/repository/resource_cyral_repository.go deleted file mode 100644 index 1d1631fb..00000000 --- a/cyral/internal/repository/resource_cyral_repository.go +++ /dev/null @@ -1,502 +0,0 @@ -package repository - -import ( - "fmt" - "net/http" - - "github.com/cyralinc/terraform-provider-cyral/cyral/client" - "github.com/cyralinc/terraform-provider-cyral/cyral/core" - "github.com/cyralinc/terraform-provider-cyral/cyral/core/types/operationtype" - "github.com/cyralinc/terraform-provider-cyral/cyral/utils" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" -) - -const ( - // Schema keys. - RepoIDKey = "id" - RepoTypeKey = "type" - RepoNameKey = "name" - RepoLabelsKey = "labels" - // Connection draining keys. - RepoConnDrainingKey = "connection_draining" - RepoConnDrainingAutoKey = "auto" - RepoConnDrainingWaitTimeKey = "wait_time" - // Repo node keys. - RepoNodesKey = "repo_node" - RepoHostKey = "host" - RepoPortKey = "port" - RepoNodeDynamicKey = "dynamic" - // MongoDB settings keys. - RepoMongoDBSettingsKey = "mongodb_settings" - RepoMongoDBReplicaSetNameKey = "replica_set_name" - RepoMongoDBServerTypeKey = "server_type" - RepoMongoDBSRVRecordName = "srv_record_name" - RepoMongoDBFlavorKey = "flavor" -) - -const ( - Denodo = "denodo" - Dremio = "dremio" - DynamoDB = "dynamodb" - DynamoDBStreams = "dynamodbstreams" - Galera = "galera" - MariaDB = "mariadb" - MongoDB = "mongodb" - MySQL = "mysql" - Oracle = "oracle" - PostgreSQL = "postgresql" - Redshift = "redshift" - S3 = "s3" - Snowflake = "snowflake" - SQLServer = "sqlserver" -) - -func RepositoryTypes() []string { - return []string{ - Denodo, - Dremio, - DynamoDB, - DynamoDBStreams, - Galera, - MariaDB, - MongoDB, - MySQL, - Oracle, - PostgreSQL, - Redshift, - S3, - Snowflake, - SQLServer, - } -} - -const ( - ReplicaSet = "replicaset" - Standalone = "standalone" - Sharded = "sharded" -) - -const ( - MongoDBFlavorMongoDB = "mongodb" - MongoDBFlavorDocumentDB = "documentdb" -) - -func mongoServerTypes() []string { - return []string{ - ReplicaSet, - Standalone, - Sharded, - } -} - -func mongoFlavors() []string { - return []string{ - MongoDBFlavorMongoDB, - MongoDBFlavorDocumentDB, - } -} - -type RepoInfo struct { - ID string `json:"id"` - Name string `json:"name"` - Type string `json:"type"` - Host string `json:"repoHost"` - Port uint32 `json:"repoPort"` - ConnParams *ConnParams `json:"connParams"` - Labels []string `json:"labels"` - RepoNodes []*RepoNode `json:"repoNodes,omitempty"` - MongoDBSettings *MongoDBSettings `json:"mongoDbSettings,omitempty"` -} - -type ConnParams struct { - ConnDraining *ConnDraining `json:"connDraining"` -} - -type ConnDraining struct { - Auto bool `json:"auto"` - WaitTime uint32 `json:"waitTime"` -} - -type MongoDBSettings struct { - ReplicaSetName string `json:"replicaSetName,omitempty"` - ServerType string `json:"serverType,omitempty"` - SRVRecordName string `json:"srvRecordName,omitempty"` - Flavor string `json:"flavor,omitempty"` -} - -type RepoNode struct { - Name string `json:"name"` - Host string `json:"host"` - Port uint32 `json:"port"` - Dynamic bool `json:"dynamic"` -} - -type GetRepoByIDResponse struct { - Repo RepoInfo `json:"repo"` -} - -func (res *GetRepoByIDResponse) WriteToSchema(d *schema.ResourceData) error { - return res.Repo.WriteToSchema(d) -} - -func (res *RepoInfo) WriteToSchema(d *schema.ResourceData) error { - d.Set(RepoTypeKey, res.Type) - d.Set(RepoNameKey, res.Name) - d.Set(RepoLabelsKey, res.LabelsAsInterface()) - d.Set(RepoConnDrainingKey, res.ConnDrainingAsInterface()) - d.Set(RepoNodesKey, res.RepoNodesAsInterface()) - d.Set(RepoMongoDBSettingsKey, res.MongoDBSettingsAsInterface()) - return nil -} - -func (r *RepoInfo) ReadFromSchema(d *schema.ResourceData) error { - r.ID = d.Id() - r.Name = d.Get(RepoNameKey).(string) - r.Type = d.Get(RepoTypeKey).(string) - r.LabelsFromInterface(d.Get(RepoLabelsKey).([]interface{})) - r.RepoNodesFromInterface(d.Get(RepoNodesKey).([]interface{})) - r.ConnDrainingFromInterface(d.Get(RepoConnDrainingKey).(*schema.Set).List()) - var mongoDBSettings = d.Get(RepoMongoDBSettingsKey).(*schema.Set).List() - if r.Type == MongoDB && (mongoDBSettings == nil || len(mongoDBSettings) == 0) { - return fmt.Errorf("'%s' block must be provided when '%s=%s'", RepoMongoDBSettingsKey, utils.TypeKey, MongoDB) - } else if r.Type != MongoDB && len(mongoDBSettings) > 0 { - return fmt.Errorf("'%s' block is only allowed when '%s=%s'", RepoMongoDBSettingsKey, utils.TypeKey, MongoDB) - } - return r.MongoDBSettingsFromInterface(mongoDBSettings) -} - -func (r *RepoInfo) LabelsAsInterface() []interface{} { - if r.Labels == nil { - return nil - } - labels := make([]interface{}, len(r.Labels)) - for i, label := range r.Labels { - labels[i] = label - } - return labels -} - -func (r *RepoInfo) LabelsFromInterface(i []interface{}) { - labels := make([]string, len(i)) - for index, v := range i { - labels[index] = v.(string) - } - r.Labels = labels -} - -func (r *RepoInfo) ConnDrainingAsInterface() []interface{} { - if r.ConnParams == nil || r.ConnParams.ConnDraining == nil { - return nil - } - - return []interface{}{map[string]interface{}{ - RepoConnDrainingAutoKey: r.ConnParams.ConnDraining.Auto, - RepoConnDrainingWaitTimeKey: r.ConnParams.ConnDraining.WaitTime, - }} -} - -func (r *RepoInfo) ConnDrainingFromInterface(i []interface{}) { - if len(i) == 0 { - return - } - r.ConnParams = &ConnParams{ - ConnDraining: &ConnDraining{ - Auto: i[0].(map[string]interface{})[RepoConnDrainingAutoKey].(bool), - WaitTime: uint32(i[0].(map[string]interface{})[RepoConnDrainingWaitTimeKey].(int)), - }, - } -} - -func (r *RepoInfo) RepoNodesAsInterface() []interface{} { - if r.RepoNodes == nil { - return nil - } - repoNodes := make([]interface{}, len(r.RepoNodes)) - for i, node := range r.RepoNodes { - repoNodes[i] = map[string]interface{}{ - RepoNameKey: node.Name, - RepoHostKey: node.Host, - RepoPortKey: node.Port, - RepoNodeDynamicKey: node.Dynamic, - } - } - return repoNodes -} - -func (r *RepoInfo) RepoNodesFromInterface(i []interface{}) { - if len(i) == 0 { - return - } - repoNodes := make([]*RepoNode, len(i)) - for index, nodeInterface := range i { - nodeMap := nodeInterface.(map[string]interface{}) - node := &RepoNode{ - Name: nodeMap[RepoNameKey].(string), - Host: nodeMap[RepoHostKey].(string), - Port: uint32(nodeMap[RepoPortKey].(int)), - Dynamic: nodeMap[RepoNodeDynamicKey].(bool), - } - repoNodes[index] = node - } - r.RepoNodes = repoNodes -} - -func (r *RepoInfo) MongoDBSettingsAsInterface() []interface{} { - if r.MongoDBSettings == nil { - return nil - } - - return []interface{}{map[string]interface{}{ - RepoMongoDBReplicaSetNameKey: r.MongoDBSettings.ReplicaSetName, - RepoMongoDBServerTypeKey: r.MongoDBSettings.ServerType, - RepoMongoDBSRVRecordName: r.MongoDBSettings.SRVRecordName, - RepoMongoDBFlavorKey: r.MongoDBSettings.Flavor, - }} -} - -func (r *RepoInfo) MongoDBSettingsFromInterface(i []interface{}) error { - if len(i) == 0 { - return nil - } - var replicaSetName = i[0].(map[string]interface{})[RepoMongoDBReplicaSetNameKey].(string) - var serverType = i[0].(map[string]interface{})[RepoMongoDBServerTypeKey].(string) - var srvRecordName = i[0].(map[string]interface{})[RepoMongoDBSRVRecordName].(string) - if serverType == ReplicaSet && replicaSetName == "" { - return fmt.Errorf("'%s' must be provided when '%s=\"%s\"'", RepoMongoDBReplicaSetNameKey, - RepoMongoDBServerTypeKey, ReplicaSet) - } - if serverType != ReplicaSet && replicaSetName != "" { - return fmt.Errorf("'%s' cannot be provided when '%s=\"%s\"'", RepoMongoDBReplicaSetNameKey, - RepoMongoDBServerTypeKey, serverType) - } - if serverType == Standalone && srvRecordName != "" { - return fmt.Errorf( - "'%s' cannot be provided when '%s=\"%s\"'", - RepoMongoDBSRVRecordName, - RepoMongoDBServerTypeKey, - Standalone, - ) - } - r.MongoDBSettings = &MongoDBSettings{ - ReplicaSetName: i[0].(map[string]interface{})[RepoMongoDBReplicaSetNameKey].(string), - ServerType: i[0].(map[string]interface{})[RepoMongoDBServerTypeKey].(string), - SRVRecordName: i[0].(map[string]interface{})[RepoMongoDBSRVRecordName].(string), - Flavor: i[0].(map[string]interface{})[RepoMongoDBFlavorKey].(string), - } - return nil -} - -var ReadRepositoryConfig = core.ResourceOperationConfig{ - ResourceName: "RepositoryRead", - Type: operationtype.Read, - HttpMethod: http.MethodGet, - URLFactory: func(d *schema.ResourceData, c *client.Client) string { - return fmt.Sprintf( - "https://%s/v1/repos/%s", - c.ControlPlane, - d.Id(), - ) - }, - SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { - return &GetRepoByIDResponse{} - }, - RequestErrorHandler: &core.ReadIgnoreHttpNotFound{ResName: "Repository"}, -} - -func ResourceRepository() *schema.Resource { - return &schema.Resource{ - Description: "Manages [repositories](https://cyral.com/docs/manage-repositories/repo-track)." + - "\n\nSee also [Cyral Repository Configuration Module](https://github.com/cyralinc/terraform-cyral-repository-config)." + - "\nThis module provides the repository configuration options as shown in Cyral UI.", - CreateContext: core.CreateResource( - core.ResourceOperationConfig{ - ResourceName: "RepositoryCreate", - Type: operationtype.Create, - HttpMethod: http.MethodPost, - URLFactory: func(d *schema.ResourceData, c *client.Client) string { - return fmt.Sprintf( - "https://%s/v1/repos", - c.ControlPlane, - ) - }, - SchemaReaderFactory: func() core.SchemaReader { return &RepoInfo{} }, - }, - ReadRepositoryConfig, - ), - ReadContext: core.ReadResource(ReadRepositoryConfig), - UpdateContext: core.UpdateResource( - core.ResourceOperationConfig{ - ResourceName: "RepositoryUpdate", - Type: operationtype.Update, - HttpMethod: http.MethodPut, - URLFactory: func(d *schema.ResourceData, c *client.Client) string { - return fmt.Sprintf( - "https://%s/v1/repos/%s", - c.ControlPlane, - d.Id(), - ) - }, - SchemaReaderFactory: func() core.SchemaReader { return &RepoInfo{} }, - }, - ReadRepositoryConfig, - ), - DeleteContext: core.DeleteResource( - core.ResourceOperationConfig{ - ResourceName: "RepositoryDelete", - Type: operationtype.Delete, - HttpMethod: http.MethodDelete, - URLFactory: func(d *schema.ResourceData, c *client.Client) string { - return fmt.Sprintf( - "https://%s/v1/repos/%s", - c.ControlPlane, - d.Id(), - ) - }, - }, - ), - - Schema: map[string]*schema.Schema{ - RepoIDKey: { - Description: "ID of this resource in Cyral environment.", - Type: schema.TypeString, - Computed: true, - }, - RepoTypeKey: { - Description: "Repository type. List of supported types:" + utils.SupportedValuesAsMarkdown(RepositoryTypes()), - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validation.StringInSlice(RepositoryTypes(), false), - }, - RepoNameKey: { - Description: "Repository name that will be used internally in the control plane (ex: `your_repo_name`).", - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - RepoLabelsKey: { - Description: "Labels enable you to categorize your repository.", - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - RepoConnDrainingKey: { - Description: "Parameters related to connection draining.", - Type: schema.TypeSet, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - RepoConnDrainingAutoKey: { - Description: "Whether connections should be drained automatically after a listener dies.", - Type: schema.TypeBool, - Optional: true, - }, - RepoConnDrainingWaitTimeKey: { - Description: "Seconds to wait to let connections drain before starting to kill all the connections, " + - "if auto is set to true.", - Type: schema.TypeInt, - Optional: true, - }, - }, - }, - }, - RepoNodesKey: { - Description: "List of nodes for this repository.", - Type: schema.TypeList, - Required: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - RepoNameKey: { - Description: "Name of the repo node.", - Type: schema.TypeString, - Optional: true, - }, - RepoHostKey: { - Description: "Repo node host (ex: `somerepo.cyral.com`). Can be empty if node is dynamic.", - Type: schema.TypeString, - Optional: true, - }, - RepoPortKey: { - Description: "Repository access port (ex: `3306`). Can be empty if node is dynamic.", - Type: schema.TypeInt, - Optional: true, - }, - RepoNodeDynamicKey: { - Description: "*Only supported for MongoDB in cluster configurations.*\n" + - "Indicates if the node is dynamically discovered, meaning that the sidecar " + - "will query the cluster to get the topology information and discover the " + - "addresses of the dynamic nodes. If set to `true`, `host` and `port` must " + - "be empty. A node with value of this field as false considered `static`.\n" + - "The following conditions apply: \n" + - " - The total number of declared `" + RepoNodesKey + "` blocks must match " + - "the actual number of nodes in the cluster.\n" + - " - If there are static nodes in the configuration, they must be declared " + - "before all dynamic nodes.\n" + - " - See the MongoDB-specific configuration in the [" + RepoMongoDBSettingsKey + - "](#nested-schema-for-" + RepoMongoDBSettingsKey + ").", - Type: schema.TypeBool, - Optional: true, - }, - }, - }, - }, - RepoMongoDBSettingsKey: { - Description: "Parameters related to MongoDB repositories.", - Type: schema.TypeSet, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - RepoMongoDBReplicaSetNameKey: { - Description: "Name of the replica set, if applicable.", - Type: schema.TypeString, - Optional: true, - }, - RepoMongoDBServerTypeKey: { - Description: "Type of the MongoDB server. Allowed values: " + utils.SupportedValuesAsMarkdown(mongoServerTypes()) + - "\n\n The following conditions apply:\n" + - " - If `" + Sharded + "` and `" + RepoMongoDBSRVRecordName + "` *not* provided, then all `" + - RepoNodesKey + "` blocks must be static (see [`" + RepoNodeDynamicKey + "`](#" + RepoNodeDynamicKey + ")).\n" + - " - If `" + Sharded + "` and `" + RepoMongoDBSRVRecordName + "` provided, then all `" + - RepoNodesKey + "` blocks must be dynamic (see [`" + RepoNodeDynamicKey + "`](#" + RepoNodeDynamicKey + ")).\n" + - " - If `" + Standalone + "`, then only one `" + RepoNodesKey + - "` block can be declared and it must be static (see [`" + RepoNodeDynamicKey + "`](#" + RepoNodeDynamicKey + ")). The `" + - RepoMongoDBSRVRecordName + "` is not supported in this configuration.\n" + - " - If `" + ReplicaSet + "` and `" + RepoMongoDBSRVRecordName + "` *not* provided, then `" + - RepoNodesKey + "` blocks may mix dynamic and static nodes (see [`" + RepoNodeDynamicKey + "`](#" + RepoNodeDynamicKey + ")).\n" + - " - If `" + ReplicaSet + "` and `" + RepoMongoDBSRVRecordName + "` provided, then `" + - RepoNodesKey + "` blocks must be dynamic (see [`" + RepoNodeDynamicKey + "`](#" + RepoNodeDynamicKey + ")).\n", - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice(mongoServerTypes(), false), - }, - RepoMongoDBSRVRecordName: { - Description: "Name of a DNS SRV record which contains cluster topology details. " + - "If specified, then all `" + RepoNodesKey + "` blocks must be declared dynamic " + - "(see [`" + RepoNodeDynamicKey + "`](#" + RepoNodeDynamicKey + ")). " + - "Only supported for `" + RepoMongoDBServerTypeKey + "=\"" + Sharded + "\"` or `" + - RepoMongoDBServerTypeKey + "=\"" + ReplicaSet + "\".", - Type: schema.TypeString, - Optional: true, - }, - RepoMongoDBFlavorKey: { - Description: "The flavor of the MongoDB deployment. Allowed values: " + utils.SupportedValuesAsMarkdown(mongoFlavors()) + - "\n\n The following conditions apply:\n" + - " - The `" + MongoDBFlavorDocumentDB + "` flavor cannot be combined with the MongoDB Server type `" + Sharded + "`.\n", - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice(mongoFlavors(), false), - }, - }, - }, - }, - }, - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, - } -} diff --git a/cyral/internal/repository/resource_cyral_repository_test.go b/cyral/internal/repository/resource_test.go similarity index 96% rename from cyral/internal/repository/resource_cyral_repository_test.go rename to cyral/internal/repository/resource_test.go index 1d91d02f..db71fb0a 100644 --- a/cyral/internal/repository/resource_cyral_repository_test.go +++ b/cyral/internal/repository/resource_test.go @@ -15,8 +15,8 @@ var ( initialRepoConfig = repository.RepoInfo{ Name: utils.AccTestName(utils.RepositoryResourceName, "repo"), Type: repository.MongoDB, - Labels: []string{"rds", "us-east-2"}, - RepoNodes: []*repository.RepoNode{ + Labels: repository.Labels{"rds", "us-east-2"}, + RepoNodes: repository.RepoNodes{ { Host: "mongo.local", Port: 3333, @@ -30,8 +30,8 @@ var ( updatedRepoConfig = repository.RepoInfo{ Name: utils.AccTestName(utils.RepositoryResourceName, "repo-updated"), Type: repository.MongoDB, - Labels: []string{"rds", "us-east-1"}, - RepoNodes: []*repository.RepoNode{ + Labels: repository.Labels{"rds", "us-east-1"}, + RepoNodes: repository.RepoNodes{ { Host: "mongo.local", Port: 3334, @@ -48,7 +48,7 @@ var ( ConnParams: &repository.ConnParams{ ConnDraining: &repository.ConnDraining{}, }, - RepoNodes: []*repository.RepoNode{ + RepoNodes: repository.RepoNodes{ { Host: "mongo-cluster.local", Port: 27017, @@ -68,7 +68,7 @@ var ( WaitTime: 20, }, }, - RepoNodes: []*repository.RepoNode{ + RepoNodes: repository.RepoNodes{ { Host: "mongo-cluster.local", Port: 27017, @@ -88,7 +88,7 @@ var ( WaitTime: 20, }, }, - RepoNodes: []*repository.RepoNode{ + RepoNodes: repository.RepoNodes{ { Name: "node1", Host: "mongo-cluster.local.node1", @@ -122,7 +122,7 @@ var ( allRepoNodesAreDynamic = repository.RepoInfo{ Name: utils.AccTestName(utils.RepositoryResourceName, "repo-all-repo-nodes-are-dynamic"), Type: "mongodb", - RepoNodes: []*repository.RepoNode{ + RepoNodes: repository.RepoNodes{ { Dynamic: true, }, diff --git a/cyral/internal/repository/schema_loader.go b/cyral/internal/repository/schema_loader.go new file mode 100644 index 00000000..afee33f9 --- /dev/null +++ b/cyral/internal/repository/schema_loader.go @@ -0,0 +1,31 @@ +package repository + +import ( + "github.com/cyralinc/terraform-provider-cyral/cyral/core" +) + +type packageSchema struct { +} + +func (p *packageSchema) Name() string { + return "repository" +} + +func (p *packageSchema) Schemas() []*core.SchemaDescriptor { + return []*core.SchemaDescriptor{ + { + Name: dataSourceName, + Type: core.DataSourceSchemaType, + Schema: dataSourceSchema, + }, + { + Name: resourceName, + Type: core.ResourceSchemaType, + Schema: resourceSchema, + }, + } +} + +func PackageSchema() core.PackageSchema { + return &packageSchema{} +} diff --git a/cyral/provider/provider.go b/cyral/provider/provider.go index 0a7dbcd0..e3ef9619 100644 --- a/cyral/provider/provider.go +++ b/cyral/provider/provider.go @@ -21,7 +21,6 @@ import ( "github.com/cyralinc/terraform-provider-cyral/cyral/internal/policy" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/policy/rule" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/regopolicy" - "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/accessgateway" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/accessrules" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/binding" @@ -112,7 +111,6 @@ func getDataSourceMap(ps []core.PackageSchema) map[string]*schema.Resource { schemaMap["cyral_integration_idp_saml"] = idpsaml.DataSourceIntegrationIdPSAML() schemaMap["cyral_integration_logging"] = logging.DataSourceIntegrationLogging() schemaMap["cyral_permission"] = permission.DataSourcePermission() - schemaMap["cyral_repository"] = repository.DataSourceRepository() schemaMap["cyral_role"] = role.DataSourceRole() schemaMap["cyral_saml_configuration"] = samlconfiguration.DataSourceSAMLConfiguration() schemaMap["cyral_sidecar_bound_ports"] = sidecar.DataSourceSidecarBoundPorts() @@ -168,7 +166,6 @@ func getResourceMap(ps []core.PackageSchema) map[string]*schema.Resource { schemaMap["cyral_policy"] = policy.ResourcePolicy() schemaMap["cyral_policy_rule"] = rule.ResourcePolicyRule() schemaMap["cyral_rego_policy_instance"] = regopolicy.ResourceRegoPolicyInstance() - schemaMap["cyral_repository"] = repository.ResourceRepository() schemaMap["cyral_repository_access_rules"] = accessrules.ResourceRepositoryAccessRules() schemaMap["cyral_repository_access_gateway"] = accessgateway.ResourceRepositoryAccessGateway() schemaMap["cyral_repository_binding"] = binding.ResourceRepositoryBinding() diff --git a/cyral/provider/schema_loader.go b/cyral/provider/schema_loader.go index f74ebf4b..816c3d80 100644 --- a/cyral/provider/schema_loader.go +++ b/cyral/provider/schema_loader.go @@ -6,6 +6,7 @@ import ( "github.com/cyralinc/terraform-provider-cyral/cyral/internal/integration/hcvault" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/integration/slack" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/integration/teams" + "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/datamap" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/useraccount" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/samlcertificate" @@ -17,6 +18,7 @@ func packagesSchemas() []core.PackageSchema { datalabel.PackageSchema(), datamap.PackageSchema(), hcvault.PackageSchema(), + repository.PackageSchema(), samlcertificate.PackageSchema(), slack.PackageSchema(), teams.PackageSchema(), From a2367517ab489ff269f4dc5304b13b723485c436 Mon Sep 17 00:00:00 2001 From: Wilson de Carvalho <796900+wcmjunior@users.noreply.github.com> Date: Sat, 30 Mar 2024 00:37:43 -0700 Subject: [PATCH 02/22] Refactor cyral_repository_access_gateway --- .../repository/accessgateway/constants.go | 5 + .../repository/accessgateway/model.go | 30 ++++ .../repository/accessgateway/resource.go | 101 +++++++++++++ ...esource_cyral_repository_access_gateway.go | 140 ------------------ ...ccess_gateway_test.go => resource_test.go} | 0 .../repository/accessgateway/schema_loader.go | 26 ++++ cyral/provider/provider.go | 2 - cyral/provider/schema_loader.go | 2 + 8 files changed, 164 insertions(+), 142 deletions(-) create mode 100644 cyral/internal/repository/accessgateway/constants.go create mode 100644 cyral/internal/repository/accessgateway/model.go create mode 100644 cyral/internal/repository/accessgateway/resource.go delete mode 100644 cyral/internal/repository/accessgateway/resource_cyral_repository_access_gateway.go rename cyral/internal/repository/accessgateway/{resource_cyral_repository_access_gateway_test.go => resource_test.go} (100%) create mode 100644 cyral/internal/repository/accessgateway/schema_loader.go diff --git a/cyral/internal/repository/accessgateway/constants.go b/cyral/internal/repository/accessgateway/constants.go new file mode 100644 index 00000000..2c8c78c2 --- /dev/null +++ b/cyral/internal/repository/accessgateway/constants.go @@ -0,0 +1,5 @@ +package accessgateway + +const ( + resourceName = "cyral_repository_access_gateway" +) diff --git a/cyral/internal/repository/accessgateway/model.go b/cyral/internal/repository/accessgateway/model.go new file mode 100644 index 00000000..6c17e7b4 --- /dev/null +++ b/cyral/internal/repository/accessgateway/model.go @@ -0,0 +1,30 @@ +package accessgateway + +import ( + "github.com/cyralinc/terraform-provider-cyral/cyral/utils" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +type AGData struct { + SidecarId string `json:"sidecarId,omitempty"` + BindingId string `json:"bindingId,omitempty"` +} + +type AccessGateway struct { + AGData *AGData `json:"accessGateway,omitempty"` +} + +func (r *AccessGateway) WriteToSchema(d *schema.ResourceData) error { + d.SetId(d.Get(utils.RepositoryIDKey).(string)) + d.Set(utils.SidecarIDKey, r.AGData.SidecarId) + d.Set(utils.BindingIDKey, r.AGData.BindingId) + return nil +} + +func (r *AccessGateway) ReadFromSchema(d *schema.ResourceData) error { + r.AGData = &AGData{ + BindingId: d.Get(utils.BindingIDKey).(string), + SidecarId: d.Get(utils.SidecarIDKey).(string), + } + return nil +} diff --git a/cyral/internal/repository/accessgateway/resource.go b/cyral/internal/repository/accessgateway/resource.go new file mode 100644 index 00000000..fd46267f --- /dev/null +++ b/cyral/internal/repository/accessgateway/resource.go @@ -0,0 +1,101 @@ +package accessgateway + +import ( + "context" + "fmt" + "net/http" + + "github.com/cyralinc/terraform-provider-cyral/cyral/client" + "github.com/cyralinc/terraform-provider-cyral/cyral/core" + "github.com/cyralinc/terraform-provider-cyral/cyral/core/types/operationtype" + "github.com/cyralinc/terraform-provider-cyral/cyral/utils" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +var urlFactory = func(d *schema.ResourceData, c *client.Client) string { + return fmt.Sprintf( + "https://%s/v1/repos/%s/accessGateway", + c.ControlPlane, + d.Get(utils.RepositoryIDKey).(string), + ) +} + +var readRepositoryAccessGatewayConfig = core.ResourceOperationConfig{ + ResourceName: resourceName, + Type: operationtype.Read, + HttpMethod: http.MethodGet, + URLFactory: urlFactory, + SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { + return &AccessGateway{} + }, + RequestErrorHandler: &core.ReadIgnoreHttpNotFound{ResName: resourceName}, +} + +func resourceSchema() *schema.Resource { + return &schema.Resource{ + Description: "Manages the sidecar and binding set as the access gateway for [cyral_repositories](./repositories.md).", + CreateContext: core.CreateResource( + core.ResourceOperationConfig{ + ResourceName: resourceName, + Type: operationtype.Create, + HttpMethod: http.MethodPut, + URLFactory: urlFactory, + SchemaReaderFactory: func() core.SchemaReader { return &AccessGateway{} }, + }, + readRepositoryAccessGatewayConfig, + ), + ReadContext: core.ReadResource(readRepositoryAccessGatewayConfig), + UpdateContext: core.UpdateResource( + core.ResourceOperationConfig{ + ResourceName: resourceName, + Type: operationtype.Update, + HttpMethod: http.MethodPut, + URLFactory: urlFactory, + SchemaReaderFactory: func() core.SchemaReader { return &AccessGateway{} }, + }, + readRepositoryAccessGatewayConfig, + ), + DeleteContext: core.DeleteResource( + core.ResourceOperationConfig{ + ResourceName: resourceName, + Type: operationtype.Delete, + HttpMethod: http.MethodDelete, + URLFactory: urlFactory, + RequestErrorHandler: &core.DeleteIgnoreHttpNotFound{ResName: resourceName}, + }, + ), + + Schema: map[string]*schema.Schema{ + utils.RepositoryIDKey: { + Description: "ID of the repository the access gateway is associated with. This is also the " + + "import ID for this resource.", + Type: schema.TypeString, + ForceNew: true, + Required: true, + }, + utils.SidecarIDKey: { + Description: "ID of the sidecar that will be set as the access gateway for the given repository.", + Type: schema.TypeString, + Required: true, + }, + utils.BindingIDKey: { + Description: "ID of the binding that will be set as the access gateway for the given repository. " + + "Note that modifications to this field will result in terraform replacing the given " + + "access gateway resource, since the access gateway must be deleted before binding. ", + Type: schema.TypeString, + ForceNew: true, + Required: true, + }, + }, + Importer: &schema.ResourceImporter{ + StateContext: func( + ctx context.Context, + d *schema.ResourceData, + m interface{}, + ) ([]*schema.ResourceData, error) { + d.Set(utils.RepositoryIDKey, d.Id()) + return []*schema.ResourceData{d}, nil + }, + }, + } +} diff --git a/cyral/internal/repository/accessgateway/resource_cyral_repository_access_gateway.go b/cyral/internal/repository/accessgateway/resource_cyral_repository_access_gateway.go deleted file mode 100644 index 0fa03651..00000000 --- a/cyral/internal/repository/accessgateway/resource_cyral_repository_access_gateway.go +++ /dev/null @@ -1,140 +0,0 @@ -package accessgateway - -import ( - "context" - "fmt" - "net/http" - - "github.com/cyralinc/terraform-provider-cyral/cyral/client" - "github.com/cyralinc/terraform-provider-cyral/cyral/core" - "github.com/cyralinc/terraform-provider-cyral/cyral/core/types/operationtype" - "github.com/cyralinc/terraform-provider-cyral/cyral/utils" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -) - -type AGData struct { - SidecarId string `json:"sidecarId,omitempty"` - BindingId string `json:"bindingId,omitempty"` -} - -type AccessGateway struct { - AGData *AGData `json:"accessGateway,omitempty"` -} - -func (r *AccessGateway) WriteToSchema(d *schema.ResourceData) error { - d.SetId(d.Get(utils.RepositoryIDKey).(string)) - d.Set(utils.SidecarIDKey, r.AGData.SidecarId) - d.Set(utils.BindingIDKey, r.AGData.BindingId) - return nil -} - -func (r *AccessGateway) ReadFromSchema(d *schema.ResourceData) error { - r.AGData = &AGData{ - BindingId: d.Get(utils.BindingIDKey).(string), - SidecarId: d.Get(utils.SidecarIDKey).(string), - } - return nil -} - -var ReadRepositoryAccessGatewayConfig = core.ResourceOperationConfig{ - ResourceName: "RepositoryAccessGatewayRead", - Type: operationtype.Read, - HttpMethod: http.MethodGet, - URLFactory: func(d *schema.ResourceData, c *client.Client) string { - return fmt.Sprintf( - "https://%s/v1/repos/%s/accessGateway", - c.ControlPlane, - d.Get(utils.RepositoryIDKey).(string), - ) - }, - SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { - return &AccessGateway{} - }, - RequestErrorHandler: &core.ReadIgnoreHttpNotFound{ResName: "Repository access gateway"}, -} - -func ResourceRepositoryAccessGateway() *schema.Resource { - return &schema.Resource{ - Description: "Manages the sidecar and binding set as the access gateway for [cyral_repositories](./repositories.md).", - CreateContext: core.CreateResource( - core.ResourceOperationConfig{ - ResourceName: "RepositoryAccessGatewayCreate", - Type: operationtype.Update, - HttpMethod: http.MethodPut, - URLFactory: func(d *schema.ResourceData, c *client.Client) string { - return fmt.Sprintf( - "https://%s/v1/repos/%s/accessGateway", - c.ControlPlane, - d.Get(utils.RepositoryIDKey).(string), - ) - }, - SchemaReaderFactory: func() core.SchemaReader { return &AccessGateway{} }, - }, - ReadRepositoryAccessGatewayConfig, - ), - ReadContext: core.ReadResource(ReadRepositoryAccessGatewayConfig), - UpdateContext: core.UpdateResource( - core.ResourceOperationConfig{ - ResourceName: "RepositoryAccessGatewayUpdate", - Type: operationtype.Update, - HttpMethod: http.MethodPut, - URLFactory: func(d *schema.ResourceData, c *client.Client) string { - return fmt.Sprintf( - "https://%s/v1/repos/%s/accessGateway", - c.ControlPlane, - d.Get(utils.RepositoryIDKey).(string), - ) - }, - SchemaReaderFactory: func() core.SchemaReader { return &AccessGateway{} }, - }, - ReadRepositoryAccessGatewayConfig, - ), - DeleteContext: core.DeleteResource( - core.ResourceOperationConfig{ - ResourceName: "RepositoryAccessGatewayDelete", - Type: operationtype.Delete, - HttpMethod: http.MethodDelete, - URLFactory: func(d *schema.ResourceData, c *client.Client) string { - return fmt.Sprintf( - "https://%s/v1/repos/%s/accessGateway", - c.ControlPlane, - d.Get(utils.RepositoryIDKey).(string), - ) - }, - }, - ), - - Schema: map[string]*schema.Schema{ - utils.RepositoryIDKey: { - Description: "ID of the repository the access gateway is associated with. This is also the " + - "import ID for this resource.", - Type: schema.TypeString, - ForceNew: true, - Required: true, - }, - utils.SidecarIDKey: { - Description: "ID of the sidecar that will be set as the access gateway for the given repository.", - Type: schema.TypeString, - Required: true, - }, - utils.BindingIDKey: { - Description: "ID of the binding that will be set as the access gateway for the given repository. " + - "Note that modifications to this field will result in terraform replacing the given " + - "access gateway resource, since the access gateway must be deleted before binding. ", - Type: schema.TypeString, - ForceNew: true, - Required: true, - }, - }, - Importer: &schema.ResourceImporter{ - StateContext: func( - ctx context.Context, - d *schema.ResourceData, - m interface{}, - ) ([]*schema.ResourceData, error) { - d.Set(utils.RepositoryIDKey, d.Id()) - return []*schema.ResourceData{d}, nil - }, - }, - } -} diff --git a/cyral/internal/repository/accessgateway/resource_cyral_repository_access_gateway_test.go b/cyral/internal/repository/accessgateway/resource_test.go similarity index 100% rename from cyral/internal/repository/accessgateway/resource_cyral_repository_access_gateway_test.go rename to cyral/internal/repository/accessgateway/resource_test.go diff --git a/cyral/internal/repository/accessgateway/schema_loader.go b/cyral/internal/repository/accessgateway/schema_loader.go new file mode 100644 index 00000000..6c1fdf43 --- /dev/null +++ b/cyral/internal/repository/accessgateway/schema_loader.go @@ -0,0 +1,26 @@ +package accessgateway + +import ( + "github.com/cyralinc/terraform-provider-cyral/cyral/core" +) + +type packageSchema struct { +} + +func (p *packageSchema) Name() string { + return "repository_access_gateway" +} + +func (p *packageSchema) Schemas() []*core.SchemaDescriptor { + return []*core.SchemaDescriptor{ + { + Name: resourceName, + Type: core.ResourceSchemaType, + Schema: resourceSchema, + }, + } +} + +func PackageSchema() core.PackageSchema { + return &packageSchema{} +} diff --git a/cyral/provider/provider.go b/cyral/provider/provider.go index e3ef9619..32ab6cf5 100644 --- a/cyral/provider/provider.go +++ b/cyral/provider/provider.go @@ -21,7 +21,6 @@ import ( "github.com/cyralinc/terraform-provider-cyral/cyral/internal/policy" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/policy/rule" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/regopolicy" - "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/accessgateway" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/accessrules" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/binding" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/confanalysis" @@ -167,7 +166,6 @@ func getResourceMap(ps []core.PackageSchema) map[string]*schema.Resource { schemaMap["cyral_policy_rule"] = rule.ResourcePolicyRule() schemaMap["cyral_rego_policy_instance"] = regopolicy.ResourceRegoPolicyInstance() schemaMap["cyral_repository_access_rules"] = accessrules.ResourceRepositoryAccessRules() - schemaMap["cyral_repository_access_gateway"] = accessgateway.ResourceRepositoryAccessGateway() schemaMap["cyral_repository_binding"] = binding.ResourceRepositoryBinding() schemaMap["cyral_repository_conf_auth"] = confauth.ResourceRepositoryConfAuth() schemaMap["cyral_repository_conf_analysis"] = confanalysis.ResourceRepositoryConfAnalysis() diff --git a/cyral/provider/schema_loader.go b/cyral/provider/schema_loader.go index 816c3d80..dbf82444 100644 --- a/cyral/provider/schema_loader.go +++ b/cyral/provider/schema_loader.go @@ -7,6 +7,7 @@ import ( "github.com/cyralinc/terraform-provider-cyral/cyral/internal/integration/slack" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/integration/teams" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository" + "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/accessgateway" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/datamap" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/useraccount" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/samlcertificate" @@ -15,6 +16,7 @@ import ( func packagesSchemas() []core.PackageSchema { v := []core.PackageSchema{ + accessgateway.PackageSchema(), datalabel.PackageSchema(), datamap.PackageSchema(), hcvault.PackageSchema(), From f11c32a7bafef5669cd74189f2401dee35546a58 Mon Sep 17 00:00:00 2001 From: Wilson de Carvalho <796900+wcmjunior@users.noreply.github.com> Date: Sat, 30 Mar 2024 00:46:14 -0700 Subject: [PATCH 03/22] Refactor cyral_repository_access_rules --- .../repository/accessrules/constants.go | 5 + .../internal/repository/accessrules/model.go | 123 +++++++++++++ ...repository_access_rules.go => resource.go} | 173 +++--------------- ..._access_rules_test.go => resource_test.go} | 0 .../repository/accessrules/schema_loader.go | 26 +++ cyral/provider/provider.go | 2 - cyral/provider/schema_loader.go | 2 + 7 files changed, 179 insertions(+), 152 deletions(-) create mode 100644 cyral/internal/repository/accessrules/constants.go create mode 100644 cyral/internal/repository/accessrules/model.go rename cyral/internal/repository/accessrules/{resource_cyral_repository_access_rules.go => resource.go} (53%) rename cyral/internal/repository/accessrules/{resource_cyral_repository_access_rules_test.go => resource_test.go} (100%) create mode 100644 cyral/internal/repository/accessrules/schema_loader.go diff --git a/cyral/internal/repository/accessrules/constants.go b/cyral/internal/repository/accessrules/constants.go new file mode 100644 index 00000000..ec0bee53 --- /dev/null +++ b/cyral/internal/repository/accessrules/constants.go @@ -0,0 +1,5 @@ +package accessrules + +const ( + resourceName = "cyral_repository_access_rules" +) diff --git a/cyral/internal/repository/accessrules/model.go b/cyral/internal/repository/accessrules/model.go new file mode 100644 index 00000000..216bc93f --- /dev/null +++ b/cyral/internal/repository/accessrules/model.go @@ -0,0 +1,123 @@ +package accessrules + +import ( + "github.com/cyralinc/terraform-provider-cyral/cyral/utils" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +type AccessRulesIdentity struct { + Type string `json:"type"` + Name string `json:"name"` +} + +type AccessRulesConfig struct { + AuthorizationPolicyInstanceIDs []string `json:"authorizationPolicyInstanceIDs"` +} + +type AccessRule struct { + Identity *AccessRulesIdentity `json:"identity"` + ValidFrom *string `json:"validFrom"` + ValidUntil *string `json:"validUntil"` + Config *AccessRulesConfig `json:"config"` +} + +type AccessRulesResource struct { + AccessRules []*AccessRule `json:"accessRules"` +} + +type AccessRulesResponse struct { + AccessRules []*AccessRule `json:"accessRules"` +} + +// WriteToSchema is used when reading a resource. It takes whatever the API +// read call returned and translates it into the Terraform schema. +func (arr *AccessRulesResponse) WriteToSchema(d *schema.ResourceData) error { + d.SetId( + utils.MarshalComposedID( + []string{ + d.Get("repository_id").(string), + d.Get("user_account_id").(string), + }, + "/", + ), + ) + // We'll have to build the access rule set in the format expected by Terraform, + // which boils down to doing a bunch of type casts + rules := make([]interface{}, 0, len(arr.AccessRules)) + for _, rule := range arr.AccessRules { + m := make(map[string]interface{}) + + m["identity"] = []interface{}{ + map[string]interface{}{ + "type": rule.Identity.Type, + "name": rule.Identity.Name, + }, + } + + m["valid_from"] = rule.ValidFrom + m["valid_until"] = rule.ValidUntil + + if rule.Config != nil && len(rule.Config.AuthorizationPolicyInstanceIDs) > 0 { + m["config"] = []interface{}{ + map[string]interface{}{ + "policy_ids": rule.Config.AuthorizationPolicyInstanceIDs, + }, + } + } + + rules = append(rules, m) + } + return d.Set("rule", rules) +} + +// ReadFromSchema is called when *creating* or *updating* a resource. +// Essentially, it translates the stuff from the .tf file into whatever the +// API expects. The `AccessRulesResource` will be marshalled verbatim, so +// make sure that it matches *exactly* what the API needs. +func (arr *AccessRulesResource) ReadFromSchema(d *schema.ResourceData) error { + rules := d.Get("rule").([]interface{}) + var accessRules []*AccessRule + + for _, rule := range rules { + ruleMap := rule.(map[string]interface{}) + + accessRule := &AccessRule{} + + identity := ruleMap["identity"].(*schema.Set).List()[0].(map[string]interface{}) + accessRule.Identity = &AccessRulesIdentity{ + Type: identity["type"].(string), + Name: identity["name"].(string), + } + + validFrom := ruleMap["valid_from"].(string) + if validFrom != "" { + accessRule.ValidFrom = &validFrom + } + + validUntil := ruleMap["valid_until"].(string) + if validUntil != "" { + accessRule.ValidUntil = &validUntil + } + + conf, ok := ruleMap["config"] + if ok { + confList := conf.(*schema.Set).List() + if len(confList) > 0 { + config := confList[0].(map[string]interface{}) + policyIDs := config["policy_ids"].([]interface{}) + ids := make([]string, 0, len(policyIDs)) + for _, policyID := range policyIDs { + ids = append(ids, policyID.(string)) + } + accessRule.Config = &AccessRulesConfig{ + AuthorizationPolicyInstanceIDs: ids, + } + } + } + + accessRules = append(accessRules, accessRule) + } + + arr.AccessRules = accessRules + return nil +} diff --git a/cyral/internal/repository/accessrules/resource_cyral_repository_access_rules.go b/cyral/internal/repository/accessrules/resource.go similarity index 53% rename from cyral/internal/repository/accessrules/resource_cyral_repository_access_rules.go rename to cyral/internal/repository/accessrules/resource.go index 81ff32ed..547171fe 100644 --- a/cyral/internal/repository/accessrules/resource_cyral_repository_access_rules.go +++ b/cyral/internal/repository/accessrules/resource.go @@ -12,179 +12,50 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -type AccessRulesIdentity struct { - Type string `json:"type"` - Name string `json:"name"` -} - -type AccessRulesConfig struct { - AuthorizationPolicyInstanceIDs []string `json:"authorizationPolicyInstanceIDs"` -} - -type AccessRule struct { - Identity *AccessRulesIdentity `json:"identity"` - ValidFrom *string `json:"validFrom"` - ValidUntil *string `json:"validUntil"` - Config *AccessRulesConfig `json:"config"` -} - -type AccessRulesResource struct { - AccessRules []*AccessRule `json:"accessRules"` -} - -type AccessRulesResponse struct { - AccessRules []*AccessRule `json:"accessRules"` -} - -// WriteToSchema is used when reading a resource. It takes whatever the API -// read call returned and translates it into the Terraform schema. -func (arr *AccessRulesResponse) WriteToSchema(d *schema.ResourceData) error { - d.SetId( - utils.MarshalComposedID( - []string{ - d.Get("repository_id").(string), - d.Get("user_account_id").(string), - }, - "/", - ), +var urlFactory = func(d *schema.ResourceData, c *client.Client) string { + return fmt.Sprintf("https://%s/v1/repos/%s/userAccounts/%s/accessRules", + c.ControlPlane, + d.Get("repository_id").(string), + d.Get("user_account_id").(string), ) - // We'll have to build the access rule set in the format expected by Terraform, - // which boils down to doing a bunch of type casts - rules := make([]interface{}, 0, len(arr.AccessRules)) - for _, rule := range arr.AccessRules { - m := make(map[string]interface{}) - - m["identity"] = []interface{}{ - map[string]interface{}{ - "type": rule.Identity.Type, - "name": rule.Identity.Name, - }, - } - - m["valid_from"] = rule.ValidFrom - m["valid_until"] = rule.ValidUntil - - if rule.Config != nil && len(rule.Config.AuthorizationPolicyInstanceIDs) > 0 { - m["config"] = []interface{}{ - map[string]interface{}{ - "policy_ids": rule.Config.AuthorizationPolicyInstanceIDs, - }, - } - } - - rules = append(rules, m) - } - return d.Set("rule", rules) -} - -// ReadFromSchema is called when *creating* or *updating* a resource. -// Essentially, it translates the stuff from the .tf file into whatever the -// API expects. The `AccessRulesResource` will be marshalled verbatim, so -// make sure that it matches *exactly* what the API needs. -func (arr *AccessRulesResource) ReadFromSchema(d *schema.ResourceData) error { - rules := d.Get("rule").([]interface{}) - var accessRules []*AccessRule - - for _, rule := range rules { - ruleMap := rule.(map[string]interface{}) - - accessRule := &AccessRule{} - - identity := ruleMap["identity"].(*schema.Set).List()[0].(map[string]interface{}) - accessRule.Identity = &AccessRulesIdentity{ - Type: identity["type"].(string), - Name: identity["name"].(string), - } - - validFrom := ruleMap["valid_from"].(string) - if validFrom != "" { - accessRule.ValidFrom = &validFrom - } - - validUntil := ruleMap["valid_until"].(string) - if validUntil != "" { - accessRule.ValidUntil = &validUntil - } - - conf, ok := ruleMap["config"] - if ok { - confList := conf.(*schema.Set).List() - if len(confList) > 0 { - config := confList[0].(map[string]interface{}) - policyIDs := config["policy_ids"].([]interface{}) - ids := make([]string, 0, len(policyIDs)) - for _, policyID := range policyIDs { - ids = append(ids, policyID.(string)) - } - accessRule.Config = &AccessRulesConfig{ - AuthorizationPolicyInstanceIDs: ids, - } - } - } - - accessRules = append(accessRules, accessRule) - } - - arr.AccessRules = accessRules - return nil } -var ReadRepositoryAccessRulesConfig = core.ResourceOperationConfig{ - ResourceName: "RepositoryAccessRulesRead", +var readRepositoryAccessRulesConfig = core.ResourceOperationConfig{ + ResourceName: resourceName, Type: operationtype.Read, HttpMethod: http.MethodGet, - URLFactory: func(d *schema.ResourceData, c *client.Client) string { - return fmt.Sprintf("https://%s/v1/repos/%s/userAccounts/%s/accessRules", - c.ControlPlane, - d.Get("repository_id").(string), - d.Get("user_account_id").(string), - ) - }, + URLFactory: urlFactory, SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &AccessRulesResponse{} }, RequestErrorHandler: &core.ReadIgnoreHttpNotFound{ResName: "Repository access rule"}, } -func ResourceRepositoryAccessRules() *schema.Resource { +func resourceSchema() *schema.Resource { return &schema.Resource{ Description: "Manage access rules", CreateContext: core.CreateResource( core.ResourceOperationConfig{ - ResourceName: "RepositoryAccessRulesCreate", - Type: operationtype.Create, - HttpMethod: http.MethodPut, - URLFactory: func(d *schema.ResourceData, c *client.Client) string { - repoID := d.Get("repository_id").(string) - userAccountID := d.Get("user_account_id").(string) - return fmt.Sprintf("https://%s/v1/repos/%s/userAccounts/%s/accessRules", - c.ControlPlane, - repoID, - userAccountID, - ) - }, + ResourceName: resourceName, + Type: operationtype.Create, + HttpMethod: http.MethodPut, + URLFactory: urlFactory, SchemaReaderFactory: func() core.SchemaReader { return &AccessRulesResource{} }, SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &AccessRulesResponse{} }, }, - ReadRepositoryAccessRulesConfig, + readRepositoryAccessRulesConfig, ), - ReadContext: core.ReadResource(ReadRepositoryAccessRulesConfig), + ReadContext: core.ReadResource(readRepositoryAccessRulesConfig), UpdateContext: core.UpdateResource( core.ResourceOperationConfig{ - ResourceName: "RepositoryAccessRulesUpdate", - Type: operationtype.Update, - HttpMethod: http.MethodPut, - URLFactory: func(d *schema.ResourceData, c *client.Client) string { - return fmt.Sprintf("https://%s/v1/repos/%s/userAccounts/%s/accessRules", - c.ControlPlane, - d.Get("repository_id").(string), - d.Get("user_account_id").(string), - ) - }, + ResourceName: resourceName, + Type: operationtype.Update, + HttpMethod: http.MethodPut, + URLFactory: urlFactory, SchemaReaderFactory: func() core.SchemaReader { return &AccessRulesResource{} }, SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &AccessRulesResponse{} }, }, - ReadRepositoryAccessRulesConfig, + readRepositoryAccessRulesConfig, ), DeleteContext: core.DeleteResource( core.ResourceOperationConfig{ @@ -192,7 +63,8 @@ func ResourceRepositoryAccessRules() *schema.Resource { Type: operationtype.Delete, HttpMethod: http.MethodDelete, URLFactory: func(d *schema.ResourceData, c *client.Client) string { - + // TODO Discuss why this is really necessary. We should be able + // to use the same factory for all operations. idPieces, err := utils.UnMarshalComposedID(d.Id(), "/", 2) if err != nil { panic(fmt.Sprintf("Failed to unmarshal access rules ID: %v", err)) @@ -206,6 +78,7 @@ func ResourceRepositoryAccessRules() *schema.Resource { userAccountID, ) }, + RequestErrorHandler: &core.DeleteIgnoreHttpNotFound{ResName: resourceName}, }, ), diff --git a/cyral/internal/repository/accessrules/resource_cyral_repository_access_rules_test.go b/cyral/internal/repository/accessrules/resource_test.go similarity index 100% rename from cyral/internal/repository/accessrules/resource_cyral_repository_access_rules_test.go rename to cyral/internal/repository/accessrules/resource_test.go diff --git a/cyral/internal/repository/accessrules/schema_loader.go b/cyral/internal/repository/accessrules/schema_loader.go new file mode 100644 index 00000000..6b3c5dd7 --- /dev/null +++ b/cyral/internal/repository/accessrules/schema_loader.go @@ -0,0 +1,26 @@ +package accessrules + +import ( + "github.com/cyralinc/terraform-provider-cyral/cyral/core" +) + +type packageSchema struct { +} + +func (p *packageSchema) Name() string { + return "repository_access_rules" +} + +func (p *packageSchema) Schemas() []*core.SchemaDescriptor { + return []*core.SchemaDescriptor{ + { + Name: resourceName, + Type: core.ResourceSchemaType, + Schema: resourceSchema, + }, + } +} + +func PackageSchema() core.PackageSchema { + return &packageSchema{} +} diff --git a/cyral/provider/provider.go b/cyral/provider/provider.go index 32ab6cf5..843e80b4 100644 --- a/cyral/provider/provider.go +++ b/cyral/provider/provider.go @@ -21,7 +21,6 @@ import ( "github.com/cyralinc/terraform-provider-cyral/cyral/internal/policy" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/policy/rule" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/regopolicy" - "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/accessrules" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/binding" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/confanalysis" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/confauth" @@ -165,7 +164,6 @@ func getResourceMap(ps []core.PackageSchema) map[string]*schema.Resource { schemaMap["cyral_policy"] = policy.ResourcePolicy() schemaMap["cyral_policy_rule"] = rule.ResourcePolicyRule() schemaMap["cyral_rego_policy_instance"] = regopolicy.ResourceRegoPolicyInstance() - schemaMap["cyral_repository_access_rules"] = accessrules.ResourceRepositoryAccessRules() schemaMap["cyral_repository_binding"] = binding.ResourceRepositoryBinding() schemaMap["cyral_repository_conf_auth"] = confauth.ResourceRepositoryConfAuth() schemaMap["cyral_repository_conf_analysis"] = confanalysis.ResourceRepositoryConfAnalysis() diff --git a/cyral/provider/schema_loader.go b/cyral/provider/schema_loader.go index dbf82444..8c94c7fa 100644 --- a/cyral/provider/schema_loader.go +++ b/cyral/provider/schema_loader.go @@ -8,6 +8,7 @@ import ( "github.com/cyralinc/terraform-provider-cyral/cyral/internal/integration/teams" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/accessgateway" + "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/accessrules" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/datamap" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/useraccount" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/samlcertificate" @@ -17,6 +18,7 @@ import ( func packagesSchemas() []core.PackageSchema { v := []core.PackageSchema{ accessgateway.PackageSchema(), + accessrules.PackageSchema(), datalabel.PackageSchema(), datamap.PackageSchema(), hcvault.PackageSchema(), From ab89d445ee539712564378b04a5fd5f3dd63d48b Mon Sep 17 00:00:00 2001 From: Wilson de Carvalho <796900+wcmjunior@users.noreply.github.com> Date: Sun, 31 Mar 2024 23:00:55 -0700 Subject: [PATCH 04/22] Refactor cyral_repository_binding --- cyral/core/default_context_handler.go | 79 +++--- cyral/core/resource.go | 18 +- cyral/internal/datalabel/resource.go | 10 +- .../resource_cyral_integration_datadog.go | 8 +- .../resource_cyral_integration_elk.go | 8 +- .../resource_cyral_integration_logstash.go | 8 +- .../resource_cyral_integration_looker.go | 8 +- .../resource_cyral_integration_splunk.go | 8 +- .../resource_cyral_integration_sumo_logic.go | 8 +- .../resource_cyral_integration_aws_iam.go | 8 +- .../model_integration_confextension.go | 1 + .../internal/integration/hcvault/resource.go | 8 +- .../integration/hcvault/schema_loader.go | 2 +- .../resource_cyral_integration_logging.go | 1 + cyral/internal/integration/slack/resource.go | 8 +- .../integration/slack/schema_loader.go | 2 +- cyral/internal/integration/teams/resource.go | 8 +- .../integration/teams/schema_loader.go | 2 +- .../repository/accessgateway/schema_loader.go | 2 +- .../internal/repository/binding/constants.go | 9 + cyral/internal/repository/binding/model.go | 97 +++++++ cyral/internal/repository/binding/resource.go | 102 ++++++++ .../resource_cyral_repository_binding.go | 239 ------------------ ...itory_binding_test.go => resource_test.go} | 0 .../repository/binding/schema_loader.go | 26 ++ cyral/internal/repository/data_source.go | 8 +- cyral/internal/repository/resource.go | 8 +- .../repository/useraccount/schema_loader.go | 2 +- .../internal/samlcertificate/schema_loader.go | 2 +- cyral/provider/provider.go | 2 - cyral/provider/schema_loader.go | 2 + 31 files changed, 351 insertions(+), 343 deletions(-) create mode 100644 cyral/internal/repository/binding/constants.go create mode 100644 cyral/internal/repository/binding/model.go create mode 100644 cyral/internal/repository/binding/resource.go delete mode 100644 cyral/internal/repository/binding/resource_cyral_repository_binding.go rename cyral/internal/repository/binding/{resource_cyral_repository_binding_test.go => resource_test.go} (100%) create mode 100644 cyral/internal/repository/binding/schema_loader.go diff --git a/cyral/core/default_context_handler.go b/cyral/core/default_context_handler.go index 952c3d10..d7419a02 100644 --- a/cyral/core/default_context_handler.go +++ b/cyral/core/default_context_handler.go @@ -12,13 +12,19 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -// Implementation of a default context handler that can be used by all resources -// which API follows these principles: -// 1. The resource is backed by an ID coming from the API. -// 2. The creation is a POST that returns a JSON with an `id` field, meaning -// it can be used with the `IDBasedResponse` struct. -// 3. The endpoint to perform GET, PUT and DELETE calls are composed by the -// POST endpoint plus the ID specification like the following: +// Implementation of a default context handler that can be used by all resources. +// +// 1. `SchemaWriterFactoryGetMethod“ must be provided. +// 2. In case `SchemaWriterFactoryPostMethod“ is not provided, +// it will assume that a call to POST returns a JSON with +// an `id` field, meaning it will use the +// `IDBasedResponse` struct in such cases. +// 3. `BaseURLFactory` must be provided. It will be used to +// create the POST endpoint and others in case `IdBasedURLFactory` +// is not provided. +// 4. If `IdBasedURLFactory` is NOT provided, the endpoint to +// perform GET, PUT and DELETE calls are composed by the +// `BaseURLFactory` endpoint plus the ID specification as follows: // - POST: https://// // - GET: https:///// // - PUT: https:///// @@ -27,19 +33,25 @@ type DefaultContextHandler struct { ResourceName string ResourceType rt.ResourceType SchemaReaderFactory SchemaReaderFactoryFunc - SchemaWriterFactory SchemaWriterFactoryFunc - BaseURLFactory URLFactoryFunc + // SchemaWriterFactoryGetMethod defines how the schema will be + // written in GET operations. + SchemaWriterFactoryGetMethod SchemaWriterFactoryFunc + // SchemaWriterFactoryPostMethod defines how the schema will be + // written in POST operations. + SchemaWriterFactoryPostMethod SchemaWriterFactoryFunc + // BaseURLFactory provides the base URL used for POSTs and that + // will also be used to compose the ID URL in case the later + // is not provided. + BaseURLFactory URLFactoryFunc + IdBasedURLFactory URLFactoryFunc } -func defaultSchemaWriterFactory(d *schema.ResourceData) SchemaWriter { +func DefaultSchemaWriterFactory(d *schema.ResourceData) SchemaWriter { return &IDBasedResponse{} } -func defaultOperationHandler( - resourceName string, - resourceType rt.ResourceType, +func (dch DefaultContextHandler) defaultOperationHandler( operationType ot.OperationType, - baseURLFactory URLFactoryFunc, httpMethod string, schemaReaderFactory SchemaReaderFactoryFunc, schemaWriterFactory SchemaWriterFactoryFunc, @@ -47,25 +59,29 @@ func defaultOperationHandler( // POST = https://// // GET, PUT and DELETE = https:///// endpoint := func(d *schema.ResourceData, c *client.Client) string { - url := baseURLFactory(d, c) - if d.Id() != "" { - url = fmt.Sprintf("%s/%s", baseURLFactory(d, c), d.Id()) + var url string + if httpMethod == http.MethodPost { + url = dch.BaseURLFactory(d, c) + } else if dch.IdBasedURLFactory != nil { + url = dch.IdBasedURLFactory(d, c) + } else { + url = fmt.Sprintf("%s/%s", dch.BaseURLFactory(d, c), d.Id()) } tflog.Debug(context.Background(), fmt.Sprintf("Returning base URL for %s '%s' operation '%s' and httpMethod %s: %s", - resourceType, resourceName, operationType, httpMethod, url)) + dch.ResourceType, dch.ResourceName, operationType, httpMethod, url)) return url } var errorHandler RequestErrorHandler if httpMethod == http.MethodGet { - errorHandler = &ReadIgnoreHttpNotFound{ResName: resourceName} + errorHandler = &ReadIgnoreHttpNotFound{ResName: dch.ResourceName} } else if httpMethod == http.MethodDelete { - errorHandler = &DeleteIgnoreHttpNotFound{ResName: resourceName} + errorHandler = &DeleteIgnoreHttpNotFound{ResName: dch.ResourceName} } result := ResourceOperationConfig{ - ResourceName: resourceName, + ResourceName: dch.ResourceName, Type: operationType, - ResourceType: resourceType, + ResourceType: dch.ResourceType, HttpMethod: httpMethod, URLFactory: endpoint, SchemaReaderFactory: schemaReaderFactory, @@ -77,9 +93,15 @@ func defaultOperationHandler( } func (dch DefaultContextHandler) CreateContext() schema.CreateContextFunc { + // By default, assumes that if no SchemaWriterFactoryPostMethod is provided, + // the POST api will return an ID + schemaWriterPost := DefaultSchemaWriterFactory + if dch.SchemaWriterFactoryPostMethod != nil { + schemaWriterPost = dch.SchemaWriterFactoryPostMethod + } return CreateResource( - defaultOperationHandler(dch.ResourceName, dch.ResourceType, ot.Create, dch.BaseURLFactory, http.MethodPost, dch.SchemaReaderFactory, nil), - defaultOperationHandler(dch.ResourceName, dch.ResourceType, ot.Create, dch.BaseURLFactory, http.MethodGet, nil, dch.SchemaWriterFactory), + dch.defaultOperationHandler(ot.Create, http.MethodPost, dch.SchemaReaderFactory, schemaWriterPost), + dch.defaultOperationHandler(ot.Create, http.MethodGet, nil, dch.SchemaWriterFactoryGetMethod), ) } @@ -87,16 +109,15 @@ func (dch DefaultContextHandler) ReadContext() schema.ReadContextFunc { return ReadResource(dch.ReadResourceOperationConfig()) } func (dch DefaultContextHandler) ReadResourceOperationConfig() ResourceOperationConfig { - return defaultOperationHandler(dch.ResourceName, dch.ResourceType, ot.Read, dch.BaseURLFactory, http.MethodGet, nil, dch.SchemaWriterFactory) + return dch.defaultOperationHandler(ot.Read, http.MethodGet, nil, dch.SchemaWriterFactoryGetMethod) } func (dch DefaultContextHandler) UpdateContext() schema.UpdateContextFunc { return UpdateResource( - defaultOperationHandler(dch.ResourceName, dch.ResourceType, ot.Update, dch.BaseURLFactory, http.MethodPut, dch.SchemaReaderFactory, nil), - defaultOperationHandler(dch.ResourceName, dch.ResourceType, ot.Update, dch.BaseURLFactory, http.MethodGet, nil, dch.SchemaWriterFactory)) + dch.defaultOperationHandler(ot.Update, http.MethodPut, dch.SchemaReaderFactory, nil), + dch.defaultOperationHandler(ot.Update, http.MethodGet, nil, dch.SchemaWriterFactoryGetMethod)) } func (dch DefaultContextHandler) DeleteContext() schema.DeleteContextFunc { - return DeleteResource(defaultOperationHandler( - dch.ResourceName, dch.ResourceType, ot.Delete, dch.BaseURLFactory, http.MethodDelete, nil, nil)) + return DeleteResource(dch.defaultOperationHandler(ot.Delete, http.MethodDelete, nil, nil)) } diff --git a/cyral/core/resource.go b/cyral/core/resource.go index 7256de2f..8a0e4a59 100644 --- a/cyral/core/resource.go +++ b/cyral/core/resource.go @@ -139,20 +139,10 @@ func handleRequests(operations []ResourceOperationConfig) func(context.Context, ) } - // If a `SchemaWriterFactory` implementation is NOT provided and this is a creation operation, - // use the `defaultSchemaWriterFactory`, assuming the response is a JSON with an `id` field. - /// TODO: Remove this feature after refactoring all resources to use the `DefaultContext`. - var responseDataFunc SchemaWriterFactoryFunc - if body != nil { - if operation.SchemaWriterFactory == nil && operation.Type == operationtype.Create { - responseDataFunc = defaultSchemaWriterFactory - tflog.Debug(ctx, "NewResponseData function set to defaultSchemaWriterFactory.") - } else { - responseDataFunc = operation.SchemaWriterFactory - } - } - if responseDataFunc != nil { - if responseData := responseDataFunc(d); responseData != nil { + if operation.SchemaWriterFactory == nil { + tflog.Debug(ctx, fmt.Sprintf("No SchemaWriterFactory found to %s resource %s", operation.Type, operation.ResourceName)) + } else { + if responseData := operation.SchemaWriterFactory(d); responseData != nil { tflog.Debug(ctx, fmt.Sprintf("NewResponseData function call performed. d: %#v", d)) if err := json.Unmarshal(body, responseData); err != nil { return utils.CreateError("Unable to unmarshall JSON", err.Error()) diff --git a/cyral/internal/datalabel/resource.go b/cyral/internal/datalabel/resource.go index e3953636..332a8c65 100644 --- a/cyral/internal/datalabel/resource.go +++ b/cyral/internal/datalabel/resource.go @@ -17,10 +17,10 @@ import ( func resourceSchema() *schema.Resource { contextHandler := core.DefaultContextHandler{ - ResourceName: resourceName, - ResourceType: resourcetype.Resource, - SchemaReaderFactory: func() core.SchemaReader { return &DataLabel{} }, - SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &DataLabel{} }, + ResourceName: resourceName, + ResourceType: resourcetype.Resource, + SchemaReaderFactory: func() core.SchemaReader { return &DataLabel{} }, + SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &DataLabel{} }, BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/datalabels", c.ControlPlane) @@ -114,7 +114,7 @@ func resourceSchema() *schema.Resource { } var readDataLabelConfig = core.ResourceOperationConfig{ - ResourceName: "DataLabelResourceRead", + ResourceName: resourceName, Type: operationtype.Read, HttpMethod: http.MethodGet, URLFactory: func(d *schema.ResourceData, c *client.Client) string { diff --git a/cyral/internal/deprecated/resource_cyral_integration_datadog.go b/cyral/internal/deprecated/resource_cyral_integration_datadog.go index ebc3bf19..f1a067ae 100644 --- a/cyral/internal/deprecated/resource_cyral_integration_datadog.go +++ b/cyral/internal/deprecated/resource_cyral_integration_datadog.go @@ -30,10 +30,10 @@ func (data *DatadogIntegration) ReadFromSchema(d *schema.ResourceData) error { func ResourceIntegrationDatadog() *schema.Resource { contextHandler := core.DefaultContextHandler{ - ResourceName: "Datadog Integration", - ResourceType: resourcetype.Resource, - SchemaReaderFactory: func() core.SchemaReader { return &DatadogIntegration{} }, - SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &DatadogIntegration{} }, + ResourceName: "Datadog Integration", + ResourceType: resourcetype.Resource, + SchemaReaderFactory: func() core.SchemaReader { return &DatadogIntegration{} }, + SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &DatadogIntegration{} }, BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/integrations/datadog", c.ControlPlane) }, diff --git a/cyral/internal/deprecated/resource_cyral_integration_elk.go b/cyral/internal/deprecated/resource_cyral_integration_elk.go index 0293c427..f0031f9a 100644 --- a/cyral/internal/deprecated/resource_cyral_integration_elk.go +++ b/cyral/internal/deprecated/resource_cyral_integration_elk.go @@ -33,10 +33,10 @@ func (data *ELKIntegration) ReadFromSchema(d *schema.ResourceData) error { func ResourceIntegrationELK() *schema.Resource { contextHandler := core.DefaultContextHandler{ - ResourceName: "ELK Integration", - ResourceType: resourcetype.Resource, - SchemaReaderFactory: func() core.SchemaReader { return &ELKIntegration{} }, - SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &ELKIntegration{} }, + ResourceName: "ELK Integration", + ResourceType: resourcetype.Resource, + SchemaReaderFactory: func() core.SchemaReader { return &ELKIntegration{} }, + SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &ELKIntegration{} }, BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/integrations/elk", c.ControlPlane) }, diff --git a/cyral/internal/deprecated/resource_cyral_integration_logstash.go b/cyral/internal/deprecated/resource_cyral_integration_logstash.go index a4198d0e..88ba38a0 100644 --- a/cyral/internal/deprecated/resource_cyral_integration_logstash.go +++ b/cyral/internal/deprecated/resource_cyral_integration_logstash.go @@ -37,10 +37,10 @@ func (data *LogstashIntegration) ReadFromSchema(d *schema.ResourceData) error { func ResourceIntegrationLogstash() *schema.Resource { contextHandler := core.DefaultContextHandler{ - ResourceName: "Logstash Integration", - ResourceType: resourcetype.Resource, - SchemaReaderFactory: func() core.SchemaReader { return &LogstashIntegration{} }, - SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &LogstashIntegration{} }, + ResourceName: "Logstash Integration", + ResourceType: resourcetype.Resource, + SchemaReaderFactory: func() core.SchemaReader { return &LogstashIntegration{} }, + SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &LogstashIntegration{} }, BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/integrations/logstash", c.ControlPlane) }, diff --git a/cyral/internal/deprecated/resource_cyral_integration_looker.go b/cyral/internal/deprecated/resource_cyral_integration_looker.go index 34751e29..f67f6cf8 100644 --- a/cyral/internal/deprecated/resource_cyral_integration_looker.go +++ b/cyral/internal/deprecated/resource_cyral_integration_looker.go @@ -31,10 +31,10 @@ func (data *LookerIntegration) ReadFromSchema(d *schema.ResourceData) error { func ResourceIntegrationLooker() *schema.Resource { contextHandler := core.DefaultContextHandler{ - ResourceName: "Looker Integration", - ResourceType: resourcetype.Resource, - SchemaReaderFactory: func() core.SchemaReader { return &LookerIntegration{} }, - SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &LookerIntegration{} }, + ResourceName: "Looker Integration", + ResourceType: resourcetype.Resource, + SchemaReaderFactory: func() core.SchemaReader { return &LookerIntegration{} }, + SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &LookerIntegration{} }, BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/integrations/looker", c.ControlPlane) }, diff --git a/cyral/internal/deprecated/resource_cyral_integration_splunk.go b/cyral/internal/deprecated/resource_cyral_integration_splunk.go index 8db2ff53..50c41d96 100644 --- a/cyral/internal/deprecated/resource_cyral_integration_splunk.go +++ b/cyral/internal/deprecated/resource_cyral_integration_splunk.go @@ -41,10 +41,10 @@ func (data *SplunkIntegration) ReadFromSchema(d *schema.ResourceData) error { func ResourceIntegrationSplunk() *schema.Resource { contextHandler := core.DefaultContextHandler{ - ResourceName: "Splunk Integration", - ResourceType: resourcetype.Resource, - SchemaReaderFactory: func() core.SchemaReader { return &SplunkIntegration{} }, - SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &SplunkIntegration{} }, + ResourceName: "Splunk Integration", + ResourceType: resourcetype.Resource, + SchemaReaderFactory: func() core.SchemaReader { return &SplunkIntegration{} }, + SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &SplunkIntegration{} }, BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/integrations/splunk", c.ControlPlane) }, diff --git a/cyral/internal/deprecated/resource_cyral_integration_sumo_logic.go b/cyral/internal/deprecated/resource_cyral_integration_sumo_logic.go index 4a4b1d05..9459bef1 100644 --- a/cyral/internal/deprecated/resource_cyral_integration_sumo_logic.go +++ b/cyral/internal/deprecated/resource_cyral_integration_sumo_logic.go @@ -28,10 +28,10 @@ func (data *SumoLogicIntegration) ReadFromSchema(d *schema.ResourceData) error { func ResourceIntegrationSumoLogic() *schema.Resource { contextHandler := core.DefaultContextHandler{ - ResourceName: "SumoLogic Integration", - ResourceType: resourcetype.Resource, - SchemaReaderFactory: func() core.SchemaReader { return &SumoLogicIntegration{} }, - SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &SumoLogicIntegration{} }, + ResourceName: "SumoLogic Integration", + ResourceType: resourcetype.Resource, + SchemaReaderFactory: func() core.SchemaReader { return &SumoLogicIntegration{} }, + SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &SumoLogicIntegration{} }, BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/integrations/sumologic", c.ControlPlane) }, diff --git a/cyral/internal/integration/awsiam/resource_cyral_integration_aws_iam.go b/cyral/internal/integration/awsiam/resource_cyral_integration_aws_iam.go index 49b2e5c9..81eaaeed 100644 --- a/cyral/internal/integration/awsiam/resource_cyral_integration_aws_iam.go +++ b/cyral/internal/integration/awsiam/resource_cyral_integration_aws_iam.go @@ -64,10 +64,10 @@ func (wrapper *AWSIAMIntegrationWrapper) ReadFromSchema(d *schema.ResourceData) func ResourceIntegrationAWSIAM() *schema.Resource { contextHandler := core.DefaultContextHandler{ - ResourceName: "AWS IAM Integration", - ResourceType: resourcetype.Resource, - SchemaReaderFactory: func() core.SchemaReader { return &AWSIAMIntegrationWrapper{} }, - SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &AWSIAMIntegrationWrapper{} }, + ResourceName: "AWS IAM Integration", + ResourceType: resourcetype.Resource, + SchemaReaderFactory: func() core.SchemaReader { return &AWSIAMIntegrationWrapper{} }, + SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &AWSIAMIntegrationWrapper{} }, BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/integrations/aws/iam", c.ControlPlane) }, diff --git a/cyral/internal/integration/confextension/model_integration_confextension.go b/cyral/internal/integration/confextension/model_integration_confextension.go index 8bdc93a1..3fa3e8fa 100644 --- a/cyral/internal/integration/confextension/model_integration_confextension.go +++ b/cyral/internal/integration/confextension/model_integration_confextension.go @@ -102,6 +102,7 @@ func ConfExtensionIntegrationCreate(templateType string) core.ResourceOperationC SchemaReaderFactory: func() core.SchemaReader { return NewIntegrationConfExtension(templateType) }, + SchemaWriterFactory: core.DefaultSchemaWriterFactory, } } diff --git a/cyral/internal/integration/hcvault/resource.go b/cyral/internal/integration/hcvault/resource.go index d717859e..f0fd8b77 100644 --- a/cyral/internal/integration/hcvault/resource.go +++ b/cyral/internal/integration/hcvault/resource.go @@ -10,10 +10,10 @@ import ( ) var resourceContextHandler = core.DefaultContextHandler{ - ResourceName: resourceName, - ResourceType: resourcetype.Resource, - SchemaReaderFactory: func() core.SchemaReader { return &HCVaultIntegration{} }, - SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &HCVaultIntegration{} }, + ResourceName: resourceName, + ResourceType: resourcetype.Resource, + SchemaReaderFactory: func() core.SchemaReader { return &HCVaultIntegration{} }, + SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &HCVaultIntegration{} }, BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/integrations/secretProviders/hcvault", c.ControlPlane) }, diff --git a/cyral/internal/integration/hcvault/schema_loader.go b/cyral/internal/integration/hcvault/schema_loader.go index 9499b202..159aa501 100644 --- a/cyral/internal/integration/hcvault/schema_loader.go +++ b/cyral/internal/integration/hcvault/schema_loader.go @@ -6,7 +6,7 @@ type packageSchema struct { } func (p *packageSchema) Name() string { - return "HC Vault Integration" + return "hcvault" } func (p *packageSchema) Schemas() []*core.SchemaDescriptor { diff --git a/cyral/internal/integration/logging/resource_cyral_integration_logging.go b/cyral/internal/integration/logging/resource_cyral_integration_logging.go index d03988c5..e860e2d7 100644 --- a/cyral/internal/integration/logging/resource_cyral_integration_logging.go +++ b/cyral/internal/integration/logging/resource_cyral_integration_logging.go @@ -181,6 +181,7 @@ func CreateLoggingIntegration() core.ResourceOperationConfig { return fmt.Sprintf("https://%s/v1/integrations/logging", c.ControlPlane) }, SchemaReaderFactory: func() core.SchemaReader { return &LoggingIntegration{} }, + SchemaWriterFactory: core.DefaultSchemaWriterFactory, } } diff --git a/cyral/internal/integration/slack/resource.go b/cyral/internal/integration/slack/resource.go index fe711b33..9bfb8ff3 100644 --- a/cyral/internal/integration/slack/resource.go +++ b/cyral/internal/integration/slack/resource.go @@ -10,10 +10,10 @@ import ( ) var resourceContextHandler = core.DefaultContextHandler{ - ResourceName: resourceName, - ResourceType: resourcetype.Resource, - SchemaReaderFactory: func() core.SchemaReader { return &SlackAlertsIntegration{} }, - SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &SlackAlertsIntegration{} }, + ResourceName: resourceName, + ResourceType: resourcetype.Resource, + SchemaReaderFactory: func() core.SchemaReader { return &SlackAlertsIntegration{} }, + SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &SlackAlertsIntegration{} }, BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/integrations/notifications/slack", c.ControlPlane) }, diff --git a/cyral/internal/integration/slack/schema_loader.go b/cyral/internal/integration/slack/schema_loader.go index 458a3e91..c08b2fcb 100644 --- a/cyral/internal/integration/slack/schema_loader.go +++ b/cyral/internal/integration/slack/schema_loader.go @@ -6,7 +6,7 @@ type packageSchema struct { } func (p *packageSchema) Name() string { - return "Slack Integration" + return "slack" } func (p *packageSchema) Schemas() []*core.SchemaDescriptor { diff --git a/cyral/internal/integration/teams/resource.go b/cyral/internal/integration/teams/resource.go index 4d513a66..bd1e6a67 100644 --- a/cyral/internal/integration/teams/resource.go +++ b/cyral/internal/integration/teams/resource.go @@ -12,10 +12,10 @@ import ( ) var resourceContextHandler = core.DefaultContextHandler{ - ResourceName: resourceName, - ResourceType: resourcetype.Resource, - SchemaReaderFactory: func() core.SchemaReader { return &MsTeamsIntegration{} }, - SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &MsTeamsIntegration{} }, + ResourceName: resourceName, + ResourceType: resourcetype.Resource, + SchemaReaderFactory: func() core.SchemaReader { return &MsTeamsIntegration{} }, + SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &MsTeamsIntegration{} }, BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/integrations/notifications/teams", c.ControlPlane) }, diff --git a/cyral/internal/integration/teams/schema_loader.go b/cyral/internal/integration/teams/schema_loader.go index 75c24700..196bcaf8 100644 --- a/cyral/internal/integration/teams/schema_loader.go +++ b/cyral/internal/integration/teams/schema_loader.go @@ -6,7 +6,7 @@ type packageSchema struct { } func (p *packageSchema) Name() string { - return "Microsoft Teams Integration" + return "teams" } func (p *packageSchema) Schemas() []*core.SchemaDescriptor { diff --git a/cyral/internal/repository/accessgateway/schema_loader.go b/cyral/internal/repository/accessgateway/schema_loader.go index 6c1fdf43..ce0734f9 100644 --- a/cyral/internal/repository/accessgateway/schema_loader.go +++ b/cyral/internal/repository/accessgateway/schema_loader.go @@ -8,7 +8,7 @@ type packageSchema struct { } func (p *packageSchema) Name() string { - return "repository_access_gateway" + return "accessgateway" } func (p *packageSchema) Schemas() []*core.SchemaDescriptor { diff --git a/cyral/internal/repository/binding/constants.go b/cyral/internal/repository/binding/constants.go new file mode 100644 index 00000000..90517f12 --- /dev/null +++ b/cyral/internal/repository/binding/constants.go @@ -0,0 +1,9 @@ +package binding + +const ( + resourceName = "cyral_repository_binding" + + BindingEnabledKey = "enabled" + ListenerBindingKey = "listener_binding" + NodeIndexKey = "node_index" +) diff --git a/cyral/internal/repository/binding/model.go b/cyral/internal/repository/binding/model.go new file mode 100644 index 00000000..0d58d899 --- /dev/null +++ b/cyral/internal/repository/binding/model.go @@ -0,0 +1,97 @@ +package binding + +import ( + "github.com/cyralinc/terraform-provider-cyral/cyral/utils" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +type ListenerBindings []*ListenerBinding + +type Binding struct { + BindingID string `json:"id,omitempty"` + RepoId string `json:"repoId,omitempty"` + Enabled bool `json:"enabled,omitempty"` + ListenerBindings ListenerBindings `json:"listenerBindings,omitempty"` +} + +type ListenerBinding struct { + ListenerID string `json:"listenerId,omitempty"` + NodeIndex uint32 `json:"nodeIndex,omitempty"` +} + +type CreateBindingRequest struct { + SidecarID string `json:"sidecarId,omitempty"` + Binding *Binding `json:"binding,omitempty"` +} + +type CreateBindingResponse struct { + BindingID string `json:"bindingId,omitempty"` +} + +type GetBindingResponse struct { + Binding *Binding `json:"binding,omitempty"` +} + +func (r *CreateBindingResponse) WriteToSchema(d *schema.ResourceData) error { + d.Set(utils.BindingIDKey, r.BindingID) + d.SetId(utils.MarshalComposedID( + []string{ + d.Get(utils.SidecarIDKey).(string), + r.BindingID, + }, "/")) + return nil +} + +func (r *GetBindingResponse) WriteToSchema(d *schema.ResourceData) error { + return r.Binding.WriteToSchema(d) +} + +func (r *Binding) WriteToSchema(d *schema.ResourceData) error { + d.Set(utils.BindingIDKey, r.BindingID) + d.Set(BindingEnabledKey, r.Enabled) + d.Set(utils.RepositoryIDKey, r.RepoId) + d.Set(ListenerBindingKey, r.ListenerBindings.AsInterface()) + return nil +} + +func (r *CreateBindingRequest) ReadFromSchema(d *schema.ResourceData) error { + r.SidecarID = d.Get(utils.SidecarIDKey).(string) + r.Binding = &Binding{} + return r.Binding.ReadFromSchema(d) +} + +func (r *Binding) ReadFromSchema(d *schema.ResourceData) error { + r.BindingID = d.Get(utils.BindingIDKey).(string) + r.Enabled = d.Get(BindingEnabledKey).(bool) + r.RepoId = d.Get(utils.RepositoryIDKey).(string) + r.ListenerBindingsFromInterface(d.Get(ListenerBindingKey).([]interface{})) + return nil +} + +func (r *ListenerBindings) AsInterface() []interface{} { + if r == nil { + return nil + } + listenerBindings := make([]interface{}, len(*r)) + for i, listenerBinding := range *r { + listenerBindings[i] = map[string]interface{}{ + utils.ListenerIDKey: listenerBinding.ListenerID, + NodeIndexKey: listenerBinding.NodeIndex, + } + } + return listenerBindings +} + +func (r *Binding) ListenerBindingsFromInterface(i []interface{}) { + if len(i) == 0 { + return + } + listenerBindings := make([]*ListenerBinding, len(i)) + for index, listenerBinding := range i { + listenerBindings[index] = &ListenerBinding{ + ListenerID: listenerBinding.(map[string]interface{})[utils.ListenerIDKey].(string), + NodeIndex: uint32(listenerBinding.(map[string]interface{})[NodeIndexKey].(int)), + } + } + r.ListenerBindings = listenerBindings +} diff --git a/cyral/internal/repository/binding/resource.go b/cyral/internal/repository/binding/resource.go new file mode 100644 index 00000000..78d1fa8a --- /dev/null +++ b/cyral/internal/repository/binding/resource.go @@ -0,0 +1,102 @@ +package binding + +import ( + "context" + "fmt" + + "github.com/cyralinc/terraform-provider-cyral/cyral/client" + "github.com/cyralinc/terraform-provider-cyral/cyral/core" + "github.com/cyralinc/terraform-provider-cyral/cyral/core/types/resourcetype" + "github.com/cyralinc/terraform-provider-cyral/cyral/utils" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceSchema() *schema.Resource { + contextHandler := core.DefaultContextHandler{ + ResourceName: resourceName, + ResourceType: resourcetype.Resource, + SchemaReaderFactory: func() core.SchemaReader { return &CreateBindingRequest{} }, + SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &GetBindingResponse{} }, + SchemaWriterFactoryPostMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &CreateBindingResponse{} }, + BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { + return fmt.Sprintf("https://%s/v1/sidecars/%s/bindings", + c.ControlPlane, + d.Get(utils.SidecarIDKey).(string)) + }, + IdBasedURLFactory: func(d *schema.ResourceData, c *client.Client) string { + return fmt.Sprintf("https://%s/v1/sidecars/%s/bindings/%s", + c.ControlPlane, + d.Get(utils.SidecarIDKey).(string), + d.Get(utils.BindingIDKey).(string), + ) + }, + } + return &schema.Resource{ + Description: "Manages [cyral repository to sidecar bindings](https://cyral.com/docs/sidecars/sidecar-assign-repo).", + CreateContext: contextHandler.CreateContext(), + ReadContext: contextHandler.ReadContext(), + UpdateContext: contextHandler.UpdateContext(), + DeleteContext: contextHandler.DeleteContext(), + SchemaVersion: 2, + Schema: map[string]*schema.Schema{ + utils.BindingIDKey: { + Description: "ID of the binding. Computed and assigned to binding at the time of creation.", + Computed: true, + Type: schema.TypeString, + }, + utils.SidecarIDKey: { + Description: "ID of the sidecar that will be bound to the given repository.", + Required: true, + ForceNew: true, + Type: schema.TypeString, + }, + utils.RepositoryIDKey: { + Description: "ID of the repository that will be bound to the sidecar.", + Required: true, + ForceNew: true, + Type: schema.TypeString, + }, + BindingEnabledKey: { + Description: "Enable or disable all listener bindings.", + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + ListenerBindingKey: { + Description: "The configuration for listeners associated with the binding. At least one `listener_binding` is required.", + Type: schema.TypeList, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + utils.ListenerIDKey: { + Description: "The sidecar listener that this binding is associated with.", + Required: true, + Type: schema.TypeString, + }, + + NodeIndexKey: { + Description: "The index of the repo node that this binding is associated with.", + Optional: true, + Type: schema.TypeInt, + }, + }, + }, + }, + }, + Importer: &schema.ResourceImporter{ + StateContext: func( + ctx context.Context, + d *schema.ResourceData, + m interface{}, + ) ([]*schema.ResourceData, error) { + ids, err := utils.UnMarshalComposedID(d.Id(), "/", 2) + if err != nil { + return nil, err + } + d.Set(utils.SidecarIDKey, ids[0]) + d.Set(utils.BindingIDKey, ids[1]) + return []*schema.ResourceData{d}, nil + }, + }, + } +} diff --git a/cyral/internal/repository/binding/resource_cyral_repository_binding.go b/cyral/internal/repository/binding/resource_cyral_repository_binding.go deleted file mode 100644 index 217702a8..00000000 --- a/cyral/internal/repository/binding/resource_cyral_repository_binding.go +++ /dev/null @@ -1,239 +0,0 @@ -package binding - -import ( - "context" - "fmt" - "net/http" - - "github.com/cyralinc/terraform-provider-cyral/cyral/client" - "github.com/cyralinc/terraform-provider-cyral/cyral/core" - "github.com/cyralinc/terraform-provider-cyral/cyral/core/types/operationtype" - "github.com/cyralinc/terraform-provider-cyral/cyral/utils" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -) - -const ( - BindingEnabledKey = "enabled" - ListenerBindingKey = "listener_binding" - NodeIndexKey = "node_index" -) - -type Binding struct { - BindingID string `json:"id,omitempty"` - RepoId string `json:"repoId,omitempty"` - Enabled bool `json:"enabled,omitempty"` - ListenerBindings []*ListenerBinding `json:"listenerBindings,omitempty"` -} - -type ListenerBinding struct { - ListenerID string `json:"listenerId,omitempty"` - NodeIndex uint32 `json:"nodeIndex,omitempty"` -} - -type CreateBindingRequest struct { - SidecarID string `json:"sidecarId,omitempty"` - Binding *Binding `json:"binding,omitempty"` -} - -type CreateBindingResponse struct { - BindingID string `json:"bindingId,omitempty"` -} - -type GetBindingResponse struct { - Binding *Binding `json:"binding,omitempty"` -} - -func (r *CreateBindingResponse) WriteToSchema(d *schema.ResourceData) error { - d.Set(utils.BindingIDKey, r.BindingID) - d.SetId(utils.MarshalComposedID( - []string{ - d.Get(utils.SidecarIDKey).(string), - r.BindingID, - }, "/")) - return nil -} - -func (r *GetBindingResponse) WriteToSchema(d *schema.ResourceData) error { - return r.Binding.WriteToSchema(d) -} - -func (r *Binding) WriteToSchema(d *schema.ResourceData) error { - d.Set(utils.BindingIDKey, r.BindingID) - d.Set(BindingEnabledKey, r.Enabled) - d.Set(utils.RepositoryIDKey, r.RepoId) - d.Set(ListenerBindingKey, r.ListenerBindingsAsInterface()) - return nil -} - -func (r *CreateBindingRequest) ReadFromSchema(d *schema.ResourceData) error { - r.SidecarID = d.Get(utils.SidecarIDKey).(string) - r.Binding = &Binding{} - return r.Binding.ReadFromSchema(d) -} - -func (r *Binding) ReadFromSchema(d *schema.ResourceData) error { - r.BindingID = d.Get(utils.BindingIDKey).(string) - r.Enabled = d.Get(BindingEnabledKey).(bool) - r.RepoId = d.Get(utils.RepositoryIDKey).(string) - r.ListenerBindingsFromInterface(d.Get(ListenerBindingKey).([]interface{})) - return nil -} - -func (r *Binding) ListenerBindingsAsInterface() []interface{} { - if r.ListenerBindings == nil { - return nil - } - listenerBindings := make([]interface{}, len(r.ListenerBindings)) - for i, listenerBinding := range r.ListenerBindings { - listenerBindings[i] = map[string]interface{}{ - utils.ListenerIDKey: listenerBinding.ListenerID, - NodeIndexKey: listenerBinding.NodeIndex, - } - } - return listenerBindings -} - -func (r *Binding) ListenerBindingsFromInterface(i []interface{}) { - if len(i) == 0 { - return - } - listenerBindings := make([]*ListenerBinding, len(i)) - for index, listenerBinding := range i { - listenerBindings[index] = &ListenerBinding{ - ListenerID: listenerBinding.(map[string]interface{})[utils.ListenerIDKey].(string), - NodeIndex: uint32(listenerBinding.(map[string]interface{})[NodeIndexKey].(int)), - } - } - r.ListenerBindings = listenerBindings -} - -var ReadRepositoryBindingConfig = core.ResourceOperationConfig{ - ResourceName: "RepositoryBindingResourceRead", - Type: operationtype.Read, - HttpMethod: http.MethodGet, - URLFactory: func(d *schema.ResourceData, c *client.Client) string { - return fmt.Sprintf("https://%s/v1/sidecars/%s/bindings/%s", - c.ControlPlane, - d.Get(utils.SidecarIDKey).(string), - d.Get(utils.BindingIDKey).(string), - ) - }, - SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { - return &GetBindingResponse{} - }, - RequestErrorHandler: &core.ReadIgnoreHttpNotFound{ResName: "Repository binding"}, -} - -func ResourceRepositoryBinding() *schema.Resource { - return &schema.Resource{ - Description: "Manages [cyral repository to sidecar bindings](https://cyral.com/docs/sidecars/sidecar-assign-repo).", - CreateContext: core.CreateResource( - core.ResourceOperationConfig{ - ResourceName: "RepositoryBindingResourceCreate", - Type: operationtype.Create, - HttpMethod: http.MethodPost, - URLFactory: func(d *schema.ResourceData, c *client.Client) string { - return fmt.Sprintf("https://%s/v1/sidecars/%s/bindings", - c.ControlPlane, - d.Get(utils.SidecarIDKey).(string)) - - }, - SchemaReaderFactory: func() core.SchemaReader { return &CreateBindingRequest{} }, - SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &CreateBindingResponse{} }, - }, ReadRepositoryBindingConfig, - ), - ReadContext: core.ReadResource(ReadRepositoryBindingConfig), - UpdateContext: core.UpdateResource( - core.ResourceOperationConfig{ - ResourceName: "RepositoryBindingResourceUpdate", - Type: operationtype.Update, - HttpMethod: http.MethodPut, - URLFactory: func(d *schema.ResourceData, c *client.Client) string { - return fmt.Sprintf("https://%s/v1/sidecars/%s/bindings/%s", - c.ControlPlane, - d.Get(utils.SidecarIDKey).(string), - d.Get(utils.BindingIDKey).(string), - ) - - }, - SchemaReaderFactory: func() core.SchemaReader { return &CreateBindingRequest{} }, - }, ReadRepositoryBindingConfig, - ), - DeleteContext: core.DeleteResource( - core.ResourceOperationConfig{ - ResourceName: "RepositoryBindingResourceDelete", - Type: operationtype.Delete, - HttpMethod: http.MethodDelete, - URLFactory: func(d *schema.ResourceData, c *client.Client) string { - return fmt.Sprintf("https://%s/v1/sidecars/%s/bindings/%s", - c.ControlPlane, - d.Get(utils.SidecarIDKey).(string), - d.Get(utils.BindingIDKey).(string), - ) - }, - }, - ), - - SchemaVersion: 2, - Schema: map[string]*schema.Schema{ - utils.BindingIDKey: { - Description: "ID of the binding. Computed and assigned to binding at the time of creation.", - Computed: true, - Type: schema.TypeString, - }, - utils.SidecarIDKey: { - Description: "ID of the sidecar that will be bound to the given repository.", - Required: true, - ForceNew: true, - Type: schema.TypeString, - }, - utils.RepositoryIDKey: { - Description: "ID of the repository that will be bound to the sidecar.", - Required: true, - ForceNew: true, - Type: schema.TypeString, - }, - BindingEnabledKey: { - Description: "Enable or disable all listener bindings.", - Type: schema.TypeBool, - Optional: true, - Default: true, - }, - ListenerBindingKey: { - Description: "The configuration for listeners associated with the binding. At least one `listener_binding` is required.", - Type: schema.TypeList, - Required: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - utils.ListenerIDKey: { - Description: "The sidecar listener that this binding is associated with.", - Required: true, - Type: schema.TypeString, - }, - - NodeIndexKey: { - Description: "The index of the repo node that this binding is associated with.", - Optional: true, - Type: schema.TypeInt, - }, - }, - }, - }, - }, - Importer: &schema.ResourceImporter{ - StateContext: func( - ctx context.Context, - d *schema.ResourceData, - m interface{}, - ) ([]*schema.ResourceData, error) { - ids, err := utils.UnMarshalComposedID(d.Id(), "/", 2) - if err != nil { - return nil, err - } - d.Set(utils.SidecarIDKey, ids[0]) - d.Set(utils.BindingIDKey, ids[1]) - return []*schema.ResourceData{d}, nil - }, - }, - } -} diff --git a/cyral/internal/repository/binding/resource_cyral_repository_binding_test.go b/cyral/internal/repository/binding/resource_test.go similarity index 100% rename from cyral/internal/repository/binding/resource_cyral_repository_binding_test.go rename to cyral/internal/repository/binding/resource_test.go diff --git a/cyral/internal/repository/binding/schema_loader.go b/cyral/internal/repository/binding/schema_loader.go new file mode 100644 index 00000000..6f551f7b --- /dev/null +++ b/cyral/internal/repository/binding/schema_loader.go @@ -0,0 +1,26 @@ +package binding + +import ( + "github.com/cyralinc/terraform-provider-cyral/cyral/core" +) + +type packageSchema struct { +} + +func (p *packageSchema) Name() string { + return "binding" +} + +func (p *packageSchema) Schemas() []*core.SchemaDescriptor { + return []*core.SchemaDescriptor{ + { + Name: resourceName, + Type: core.ResourceSchemaType, + Schema: resourceSchema, + }, + } +} + +func PackageSchema() core.PackageSchema { + return &packageSchema{} +} diff --git a/cyral/internal/repository/data_source.go b/cyral/internal/repository/data_source.go index ee0623ff..d5f5d1c7 100644 --- a/cyral/internal/repository/data_source.go +++ b/cyral/internal/repository/data_source.go @@ -58,10 +58,10 @@ func (resp *GetReposResponse) WriteToSchema(d *schema.ResourceData) error { } var dsContextHandler = core.DefaultContextHandler{ - ResourceName: dataSourceName, - ResourceType: resourcetype.DataSource, - SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &GetReposResponse{} }, - BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { + ResourceName: dataSourceName, + ResourceType: resourcetype.DataSource, + SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &GetReposResponse{} }, + IdBasedURLFactory: func(d *schema.ResourceData, c *client.Client) string { nameFilter := d.Get("name").(string) typeFilter := d.Get("type").(string) urlParams := utils.UrlQuery(map[string]string{ diff --git a/cyral/internal/repository/resource.go b/cyral/internal/repository/resource.go index f100a4b4..37a3ac4c 100644 --- a/cyral/internal/repository/resource.go +++ b/cyral/internal/repository/resource.go @@ -12,10 +12,10 @@ import ( ) var resourceContextHandler = core.DefaultContextHandler{ - ResourceName: resourceName, - ResourceType: resourcetype.Resource, - SchemaReaderFactory: func() core.SchemaReader { return &RepoInfo{} }, - SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &GetRepoByIDResponse{} }, + ResourceName: resourceName, + ResourceType: resourcetype.Resource, + SchemaReaderFactory: func() core.SchemaReader { return &RepoInfo{} }, + SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &GetRepoByIDResponse{} }, BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf( "https://%s/v1/repos", diff --git a/cyral/internal/repository/useraccount/schema_loader.go b/cyral/internal/repository/useraccount/schema_loader.go index a57e0e8d..86b59280 100644 --- a/cyral/internal/repository/useraccount/schema_loader.go +++ b/cyral/internal/repository/useraccount/schema_loader.go @@ -6,7 +6,7 @@ type packageSchema struct { } func (p *packageSchema) Name() string { - return "User Account" + return "useraccount" } func (p *packageSchema) Schemas() []*core.SchemaDescriptor { diff --git a/cyral/internal/samlcertificate/schema_loader.go b/cyral/internal/samlcertificate/schema_loader.go index 10234985..5535706d 100644 --- a/cyral/internal/samlcertificate/schema_loader.go +++ b/cyral/internal/samlcertificate/schema_loader.go @@ -6,7 +6,7 @@ type packageSchema struct { } func (p *packageSchema) Name() string { - return "SAML Certificate" + return "samlcertificate" } func (p *packageSchema) Schemas() []*core.SchemaDescriptor { diff --git a/cyral/provider/provider.go b/cyral/provider/provider.go index 843e80b4..2f6b564f 100644 --- a/cyral/provider/provider.go +++ b/cyral/provider/provider.go @@ -21,7 +21,6 @@ import ( "github.com/cyralinc/terraform-provider-cyral/cyral/internal/policy" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/policy/rule" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/regopolicy" - "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/binding" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/confanalysis" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/confauth" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/network" @@ -164,7 +163,6 @@ func getResourceMap(ps []core.PackageSchema) map[string]*schema.Resource { schemaMap["cyral_policy"] = policy.ResourcePolicy() schemaMap["cyral_policy_rule"] = rule.ResourcePolicyRule() schemaMap["cyral_rego_policy_instance"] = regopolicy.ResourceRegoPolicyInstance() - schemaMap["cyral_repository_binding"] = binding.ResourceRepositoryBinding() schemaMap["cyral_repository_conf_auth"] = confauth.ResourceRepositoryConfAuth() schemaMap["cyral_repository_conf_analysis"] = confanalysis.ResourceRepositoryConfAnalysis() schemaMap["cyral_repository_network_access_policy"] = network.ResourceRepositoryNetworkAccessPolicy() diff --git a/cyral/provider/schema_loader.go b/cyral/provider/schema_loader.go index 8c94c7fa..7516dfb6 100644 --- a/cyral/provider/schema_loader.go +++ b/cyral/provider/schema_loader.go @@ -9,6 +9,7 @@ import ( "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/accessgateway" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/accessrules" + "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/binding" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/datamap" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/useraccount" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/samlcertificate" @@ -19,6 +20,7 @@ func packagesSchemas() []core.PackageSchema { v := []core.PackageSchema{ accessgateway.PackageSchema(), accessrules.PackageSchema(), + binding.PackageSchema(), datalabel.PackageSchema(), datamap.PackageSchema(), hcvault.PackageSchema(), From 1cc3531a6bf1954333119306e5e993af505f9a8a Mon Sep 17 00:00:00 2001 From: Wilson de Carvalho <796900+wcmjunior@users.noreply.github.com> Date: Mon, 1 Apr 2024 15:46:33 -0700 Subject: [PATCH 05/22] Refactor cyral_sidecar --- ...esource_cyral_integration_idp_saml_test.go | 32 +- .../integration/slack/resource_test.go | 2 +- cyral/internal/integration/teams/resource.go | 13 - .../integration/teams/resource_test.go | 2 +- .../repository/accessrules/resource.go | 2 +- cyral/internal/repository/binding/resource.go | 47 +- cyral/internal/sidecar/constants.go | 7 + cyral/internal/sidecar/model.go | 192 ++++++++ cyral/internal/sidecar/resource.go | 239 ++++++++++ .../sidecar/resource_cyral_sidecar.go | 433 ------------------ ...cyral_sidecar_test.go => resource_test.go} | 76 ++- cyral/internal/sidecar/schema_loader.go | 31 ++ cyral/provider/provider.go | 1 - cyral/provider/schema_loader.go | 2 + 14 files changed, 570 insertions(+), 509 deletions(-) create mode 100644 cyral/internal/sidecar/constants.go create mode 100644 cyral/internal/sidecar/model.go create mode 100644 cyral/internal/sidecar/resource.go delete mode 100644 cyral/internal/sidecar/resource_cyral_sidecar.go rename cyral/internal/sidecar/{resource_cyral_sidecar_test.go => resource_test.go} (74%) create mode 100644 cyral/internal/sidecar/schema_loader.go diff --git a/cyral/internal/integration/idpsaml/resource_cyral_integration_idp_saml_test.go b/cyral/internal/integration/idpsaml/resource_cyral_integration_idp_saml_test.go index b7904acb..44c6f84f 100644 --- a/cyral/internal/integration/idpsaml/resource_cyral_integration_idp_saml_test.go +++ b/cyral/internal/integration/idpsaml/resource_cyral_integration_idp_saml_test.go @@ -46,8 +46,12 @@ func TestAccIntegrationIdPSAMLResource(t *testing.T) { "upgrade_test", samlMetadataDocumentSample("fakeCertificateUpdated")) updatedAgainConfig, updatedAgainChecks := setupIntegrationIdPSAMLTest( "upgrade_test", samlMetadataDocumentSample("fakeCertificateUpdated")) - newConfig, newChecks := setupIntegrationIdPSAMLTest( - "new_test", samlMetadataDocumentSample("fakeCertificateNew")) + + println("========> initialConfig: " + initialConfig) + println("========> updatedConfig: " + updatedConfig) + println("========> updatedAgainConfig: " + updatedAgainConfig) + // newConfig, newChecks := setupIntegrationIdPSAMLTest( + // "new_test", samlMetadataDocumentSample("fakeCertificateNew")) resource.ParallelTest(t, resource.TestCase{ ProviderFactories: provider.ProviderFactories, @@ -68,18 +72,18 @@ func TestAccIntegrationIdPSAMLResource(t *testing.T) { // If user runs apply again, it should work. Check: updatedAgainChecks, }, - { - Config: newConfig, - // When a new SAML draft and a new integration - // are created, there should be no no problem. - Check: newChecks, - }, - { - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"idp_metadata_xml", "saml_draft_id"}, - ResourceName: "cyral_integration_idp_saml.new_test", - }, + // { + // Config: newConfig, + // // When a new SAML draft and a new integration + // // are created, there should be no no problem. + // Check: newChecks, + // }, + // { + // ImportState: true, + // ImportStateVerify: true, + // ImportStateVerifyIgnore: []string{"idp_metadata_xml", "saml_draft_id"}, + // ResourceName: "cyral_integration_idp_saml.new_test", + // }, }, }) } diff --git a/cyral/internal/integration/slack/resource_test.go b/cyral/internal/integration/slack/resource_test.go index 30a3d332..04c5e1b3 100644 --- a/cyral/internal/integration/slack/resource_test.go +++ b/cyral/internal/integration/slack/resource_test.go @@ -26,7 +26,7 @@ var updatedSlackAlertsConfig slack.SlackAlertsIntegration = slack.SlackAlertsInt func TestAccSlackAlertsIntegrationResource(t *testing.T) { testConfig, testFunc := setupSlackAlertTest(initialSlackAlertsConfig) - testUpdateConfig, testUpdateFunc := setupSlackAlertTest(initialSlackAlertsConfig) + testUpdateConfig, testUpdateFunc := setupSlackAlertTest(updatedSlackAlertsConfig) resource.ParallelTest(t, resource.TestCase{ ProviderFactories: provider.ProviderFactories, diff --git a/cyral/internal/integration/teams/resource.go b/cyral/internal/integration/teams/resource.go index bd1e6a67..945a2082 100644 --- a/cyral/internal/integration/teams/resource.go +++ b/cyral/internal/integration/teams/resource.go @@ -2,11 +2,9 @@ package teams import ( "fmt" - "net/http" "github.com/cyralinc/terraform-provider-cyral/cyral/client" "github.com/cyralinc/terraform-provider-cyral/cyral/core" - "github.com/cyralinc/terraform-provider-cyral/cyral/core/types/operationtype" "github.com/cyralinc/terraform-provider-cyral/cyral/core/types/resourcetype" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) @@ -21,17 +19,6 @@ var resourceContextHandler = core.DefaultContextHandler{ }, } -var ReadMsTeamsConfig = core.ResourceOperationConfig{ - ResourceName: resourceName, - Type: operationtype.Read, - HttpMethod: http.MethodGet, - URLFactory: func(d *schema.ResourceData, c *client.Client) string { - return fmt.Sprintf("https://%s/v1/integrations/notifications/teams/%s", c.ControlPlane, d.Id()) - }, - SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &MsTeamsIntegration{} }, - RequestErrorHandler: &core.ReadIgnoreHttpNotFound{ResName: "Integration Teams"}, -} - func resourceSchema() *schema.Resource { return &schema.Resource{ Description: "Manages [integration with Microsoft Teams](https://cyral.com/docs/integrations/messaging/microsoft-teams/).", diff --git a/cyral/internal/integration/teams/resource_test.go b/cyral/internal/integration/teams/resource_test.go index e53b575f..21374322 100644 --- a/cyral/internal/integration/teams/resource_test.go +++ b/cyral/internal/integration/teams/resource_test.go @@ -26,7 +26,7 @@ var updatedTeamsConfig teams.MsTeamsIntegration = teams.MsTeamsIntegration{ func TestAccMsTeamsIntegrationResource(t *testing.T) { testConfig, testFunc := setupTeamsTest(initialTeamsConfig) - testUpdateConfig, testUpdateFunc := setupTeamsTest(initialTeamsConfig) + testUpdateConfig, testUpdateFunc := setupTeamsTest(updatedTeamsConfig) resource.ParallelTest(t, resource.TestCase{ ProviderFactories: provider.ProviderFactories, diff --git a/cyral/internal/repository/accessrules/resource.go b/cyral/internal/repository/accessrules/resource.go index 547171fe..fef95c73 100644 --- a/cyral/internal/repository/accessrules/resource.go +++ b/cyral/internal/repository/accessrules/resource.go @@ -59,7 +59,7 @@ func resourceSchema() *schema.Resource { ), DeleteContext: core.DeleteResource( core.ResourceOperationConfig{ - ResourceName: "RepositoryAccessRulesDelete", + ResourceName: resourceName, Type: operationtype.Delete, HttpMethod: http.MethodDelete, URLFactory: func(d *schema.ResourceData, c *client.Client) string { diff --git a/cyral/internal/repository/binding/resource.go b/cyral/internal/repository/binding/resource.go index 78d1fa8a..b60e336d 100644 --- a/cyral/internal/repository/binding/resource.go +++ b/cyral/internal/repository/binding/resource.go @@ -11,32 +11,33 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) +var resourceContextHandler = core.DefaultContextHandler{ + ResourceName: resourceName, + ResourceType: resourcetype.Resource, + SchemaReaderFactory: func() core.SchemaReader { return &CreateBindingRequest{} }, + SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &GetBindingResponse{} }, + SchemaWriterFactoryPostMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &CreateBindingResponse{} }, + BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { + return fmt.Sprintf("https://%s/v1/sidecars/%s/bindings", + c.ControlPlane, + d.Get(utils.SidecarIDKey).(string)) + }, + IdBasedURLFactory: func(d *schema.ResourceData, c *client.Client) string { + return fmt.Sprintf("https://%s/v1/sidecars/%s/bindings/%s", + c.ControlPlane, + d.Get(utils.SidecarIDKey).(string), + d.Get(utils.BindingIDKey).(string), + ) + }, +} + func resourceSchema() *schema.Resource { - contextHandler := core.DefaultContextHandler{ - ResourceName: resourceName, - ResourceType: resourcetype.Resource, - SchemaReaderFactory: func() core.SchemaReader { return &CreateBindingRequest{} }, - SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &GetBindingResponse{} }, - SchemaWriterFactoryPostMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &CreateBindingResponse{} }, - BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { - return fmt.Sprintf("https://%s/v1/sidecars/%s/bindings", - c.ControlPlane, - d.Get(utils.SidecarIDKey).(string)) - }, - IdBasedURLFactory: func(d *schema.ResourceData, c *client.Client) string { - return fmt.Sprintf("https://%s/v1/sidecars/%s/bindings/%s", - c.ControlPlane, - d.Get(utils.SidecarIDKey).(string), - d.Get(utils.BindingIDKey).(string), - ) - }, - } return &schema.Resource{ Description: "Manages [cyral repository to sidecar bindings](https://cyral.com/docs/sidecars/sidecar-assign-repo).", - CreateContext: contextHandler.CreateContext(), - ReadContext: contextHandler.ReadContext(), - UpdateContext: contextHandler.UpdateContext(), - DeleteContext: contextHandler.DeleteContext(), + CreateContext: resourceContextHandler.CreateContext(), + ReadContext: resourceContextHandler.ReadContext(), + UpdateContext: resourceContextHandler.UpdateContext(), + DeleteContext: resourceContextHandler.DeleteContext(), SchemaVersion: 2, Schema: map[string]*schema.Schema{ utils.BindingIDKey: { diff --git a/cyral/internal/sidecar/constants.go b/cyral/internal/sidecar/constants.go new file mode 100644 index 00000000..5443c83f --- /dev/null +++ b/cyral/internal/sidecar/constants.go @@ -0,0 +1,7 @@ +package sidecar + +const ( + resourceName = "cyral_sidecar" + dataSourceIdName = "cyral_sidecar_id" + dataSourceBoundPortsName = "cyral_sidecar_bound_ports" +) diff --git a/cyral/internal/sidecar/model.go b/cyral/internal/sidecar/model.go new file mode 100644 index 00000000..d8d07e79 --- /dev/null +++ b/cyral/internal/sidecar/model.go @@ -0,0 +1,192 @@ +package sidecar + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +type CreateSidecarResponse struct { + ID string `json:"ID"` +} + +type SidecarData struct { + ID string `json:"id"` + Name string `json:"name"` + Labels []string `json:"labels"` + SidecarProperties *SidecarProperties `json:"properties"` + ServicesConfig SidecarServicesConfig `json:"services"` + UserEndpoint string `json:"userEndpoint"` + CertificateBundleSecrets CertificateBundleSecrets `json:"certificateBundleSecrets,omitempty"` +} + +func (sd *SidecarData) BypassMode() string { + if sd.ServicesConfig != nil { + if dispConfig, ok := sd.ServicesConfig["dispatcher"]; ok { + if bypass_mode, ok := dispConfig["bypass"]; ok { + return bypass_mode + } + } + } + return "" +} + +func (r *SidecarData) WriteToSchema(d *schema.ResourceData) error { + if err := d.Set("name", r.Name); err != nil { + return fmt.Errorf("error setting 'name' field: %w", err) + } + if r.SidecarProperties != nil { + if err := d.Set("deployment_method", r.SidecarProperties.DeploymentMethod); err != nil { + return fmt.Errorf("error setting 'deployment_method' field: %w", err) + } + if err := d.Set("activity_log_integration_id", r.SidecarProperties.LogIntegrationID); err != nil { + return fmt.Errorf("error setting 'activity_log_integration_id' field: %w", err) + } + if err := d.Set("diagnostic_log_integration_id", r.SidecarProperties.DiagnosticLogIntegrationID); err != nil { + return fmt.Errorf("error setting 'diagnostic_log_integration_id' field: %w", err) + } + } + if err := d.Set("labels", r.Labels); err != nil { + return fmt.Errorf("error setting 'labels' field: %w", err) + } + if err := d.Set("user_endpoint", r.UserEndpoint); err != nil { + return fmt.Errorf("error setting 'user_endpoint' field: %w", err) + } + if bypassMode := r.BypassMode(); bypassMode != "" { + if err := d.Set("bypass_mode", bypassMode); err != nil { + return fmt.Errorf("error setting 'bypass_mode' field: %w", err) + } + } + + if err := d.Set("certificate_bundle_secrets", flattenCertificateBundleSecrets(r.CertificateBundleSecrets)); err != nil { + return fmt.Errorf("error setting 'certificate_bundle_secrets' field: %w", err) + } + return nil +} + +func (r *SidecarData) ReadFromSchema(d *schema.ResourceData) error { + activityLogIntegrationID := d.Get("activity_log_integration_id").(string) + if activityLogIntegrationID == "" { + activityLogIntegrationID = d.Get("log_integration_id").(string) + } + + labels := d.Get("labels").([]interface{}) + sidecarDataLabels := []string{} + for _, labelInterface := range labels { + if label, ok := labelInterface.(string); ok { + sidecarDataLabels = append(sidecarDataLabels, label) + } + } + + r.ID = d.Id() + r.Name = d.Get("name").(string) + r.Labels = sidecarDataLabels + r.SidecarProperties = &SidecarProperties{ + DeploymentMethod: d.Get("deployment_method").(string), + LogIntegrationID: activityLogIntegrationID, + DiagnosticLogIntegrationID: d.Get("diagnostic_log_integration_id").(string), + } + r.ServicesConfig = SidecarServicesConfig{ + "dispatcher": map[string]string{ + "bypass": d.Get("bypass_mode").(string), + }, + } + r.UserEndpoint = d.Get("user_endpoint").(string) + r.CertificateBundleSecrets = getCertificateBundleSecret(d) + + return nil +} + +type SidecarProperties struct { + DeploymentMethod string `json:"deploymentMethod"` + LogIntegrationID string `json:"logIntegrationID,omitempty"` + DiagnosticLogIntegrationID string `json:"diagnosticLogIntegrationID,omitempty"` +} + +type SidecarServicesConfig map[string]map[string]string + +type CertificateBundleSecrets map[string]*CertificateBundleSecret + +type CertificateBundleSecret struct { + Engine string `json:"engine,omitempty"` + SecretId string `json:"secretId,omitempty"` + Type string `json:"type,omitempty"` +} + +func flattenCertificateBundleSecrets(cbs CertificateBundleSecrets) []interface{} { + ctx := context.Background() + tflog.Debug(ctx, "Init flattenCertificateBundleSecrets") + var flatCBS []interface{} + if cbs != nil { + cb := make(map[string]interface{}) + + for key, val := range cbs { + // Ignore self-signed certificates + if key != "sidecar-generated-selfsigned" { + contentCB := make([]interface{}, 1) + + tflog.Debug(ctx, fmt.Sprintf("key: %v", key)) + tflog.Debug(ctx, fmt.Sprintf("val: %v", val)) + + contentCBMap := make(map[string]interface{}) + contentCBMap["secret_id"] = val.SecretId + contentCBMap["engine"] = val.Engine + contentCBMap["type"] = val.Type + + contentCB[0] = contentCBMap + cb[key] = contentCB + } + } + + if len(cb) > 0 { + flatCBS = make([]interface{}, 1) + flatCBS[0] = cb + } + } + + tflog.Debug(ctx, fmt.Sprintf("end flattenCertificateBundleSecrets %v", flatCBS)) + return flatCBS +} + +func getCertificateBundleSecret(d *schema.ResourceData) CertificateBundleSecrets { + ctx := context.Background() + tflog.Debug(ctx, "Init getCertificateBundleSecret") + rdCBS := d.Get("certificate_bundle_secrets").(*schema.Set).List() + ret := make(CertificateBundleSecrets) + + if len(rdCBS) > 0 { + cbsMap := rdCBS[0].(map[string]interface{}) + for k, v := range cbsMap { + vList := v.(*schema.Set).List() + // 1. k = "sidecar" or other direct internal elements of certificate_bundle_secrets + // 2. Also one element on this list due to MaxItems... + // 3. Ignore self signed certificates + if len(vList) > 0 && k != "sidecar-generated-selfsigned" { + vMap := vList[0].(map[string]interface{}) + engine := "" + if val, ok := vMap["engine"]; val != nil && ok { + engine = val.(string) + } + cbsType := vMap["type"].(string) + secretId := vMap["secret_id"].(string) + cbs := CertificateBundleSecret{ + SecretId: secretId, + Engine: engine, + Type: cbsType, + } + ret[k] = &cbs + } + } + } + + // If the occurrence of `sidecar` does not exist, set it to an empty certificate bundle + // so that the API can remove the `sidecar` key from the persisted certificate bundle map. + if _, ok := ret["sidecar"]; !ok { + ret["sidecar"] = &CertificateBundleSecret{} + } + + tflog.Debug(ctx, "end getCertificateBundleSecret") + return ret +} diff --git a/cyral/internal/sidecar/resource.go b/cyral/internal/sidecar/resource.go new file mode 100644 index 00000000..e68dfb95 --- /dev/null +++ b/cyral/internal/sidecar/resource.go @@ -0,0 +1,239 @@ +package sidecar + +import ( + "context" + "fmt" + "net/http" + "regexp" + + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + + "github.com/cyralinc/terraform-provider-cyral/cyral/client" + "github.com/cyralinc/terraform-provider-cyral/cyral/core" + "github.com/cyralinc/terraform-provider-cyral/cyral/core/types/operationtype" +) + +// Currently, the sidecar API always returns a status code of 500 for every error, +// so its not possible to distinguish if the error returned is related to +// a 404 Not Found or not by its status code. This way, a workaround for that is to +// check if the error message matches a 'Failed to extract info for wrapper' message, +// since thats the current message returned when the sidecar is not found. Once this +// issue is fixed in the sidecar API, we should be able to use core.DefaultContextHandler +// and remove SidecarDeleteIgnoreHttpNotFound and SidecarReadIgnoreHttpNotFound. +type SidecarDeleteIgnoreHttpNotFound struct { +} + +func (h *SidecarDeleteIgnoreHttpNotFound) HandleError( + ctx context.Context, + r *schema.ResourceData, + _ *client.Client, + err error, +) error { + httpError, ok := err.(*client.HttpError) + if !ok || httpError.StatusCode != http.StatusNotFound { + return err + } + + matched, regexpError := regexp.MatchString( + "NotFound", + err.Error(), + ) + if regexpError == nil && matched { + tflog.Debug(ctx, fmt.Sprintf("Sidecar not found. Skipping deletion. Error: %v", err)) + r.SetId("") + return nil + } + + return nil +} + +type SidecarReadIgnoreHttpNotFound struct { +} + +func (h *SidecarReadIgnoreHttpNotFound) HandleError( + ctx context.Context, + r *schema.ResourceData, + _ *client.Client, + err error, +) error { + httpError, ok := err.(*client.HttpError) + if !ok || httpError.StatusCode != http.StatusNotFound { + return err + } + + r.SetId("") + tflog.Debug(ctx, resourceName+" not found. Marking resource for recreation.") + return nil +} + +var urlFactory = func(d *schema.ResourceData, c *client.Client) string { + return fmt.Sprintf("https://%s/v1/sidecars/%s", c.ControlPlane, d.Id()) +} + +var readSidecarConfig = core.ResourceOperationConfig{ + ResourceName: resourceName, + Type: operationtype.Read, + HttpMethod: http.MethodGet, + URLFactory: urlFactory, + SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &SidecarData{} }, + RequestErrorHandler: &SidecarReadIgnoreHttpNotFound{}, +} + +func resourceSchema() *schema.Resource { + return &schema.Resource{ + Description: "Manages [sidecars](https://cyral.com/docs/sidecars/sidecar-manage).", + CreateContext: core.CreateResource( + core.ResourceOperationConfig{ + ResourceName: resourceName, + Type: operationtype.Create, + HttpMethod: http.MethodPost, + URLFactory: func(d *schema.ResourceData, c *client.Client) string { + return fmt.Sprintf("https://%s/v1/sidecars", c.ControlPlane) + }, + SchemaReaderFactory: func() core.SchemaReader { return &SidecarData{} }, + SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &core.IDBasedResponse{} }, + }, + readSidecarConfig, + ), + ReadContext: core.ReadResource(readSidecarConfig), + UpdateContext: core.UpdateResource( + core.ResourceOperationConfig{ + ResourceName: resourceName, + Type: operationtype.Update, + HttpMethod: http.MethodPut, + URLFactory: urlFactory, + SchemaReaderFactory: func() core.SchemaReader { return &SidecarData{} }, + SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &SidecarData{} }, + }, + readSidecarConfig, + ), + DeleteContext: core.DeleteResource( + core.ResourceOperationConfig{ + ResourceName: resourceName, + Type: operationtype.Delete, + HttpMethod: http.MethodDelete, + URLFactory: urlFactory, + RequestErrorHandler: &SidecarDeleteIgnoreHttpNotFound{}, + }, + ), + Schema: map[string]*schema.Schema{ + "id": { + Description: "ID of this resource in Cyral environment", + Type: schema.TypeString, + Computed: true, + }, + "name": { + Description: "Sidecar name that will be used internally in Control Plane (ex: `your_sidecar_name`).", + Type: schema.TypeString, + Required: true, + }, + "deployment_method": { + Description: "Deployment method that will be used by this sidecar (valid values: `docker`, `cft-ec2`, `terraform`, `helm3`, `automated`, `custom`, `terraformGKE`, `linux`, and `singleContainer`).", + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice( + []string{ + "docker", "cft-ec2", "terraform", "helm3", + "automated", "custom", "terraformGKE", "singleContainer", + "linux", + }, false, + ), + }, + "log_integration_id": { + Description: "ID of the log integration mapped to this sidecar, used for Cyral activity logs.", + Type: schema.TypeString, + Optional: true, + Deprecated: "Since sidecar v4.8. Use `activity_log_integration_id` instead.", + ConflictsWith: []string{"activity_log_integration_id"}, + }, + "activity_log_integration_id": { + Description: "ID of the log integration mapped to this sidecar, used for Cyral activity logs.", + Type: schema.TypeString, + Optional: true, + }, + "diagnostic_log_integration_id": { + Description: "ID of the log integration mapped to this sidecar, used for sidecar diagnostic logs.", + Type: schema.TypeString, + Optional: true, + }, + "labels": { + Description: "Labels that can be attached to the sidecar and shown in the `Tags` field in the UI.", + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "user_endpoint": { + Description: "User-defined endpoint (also referred as `alias`) that can be used to override the sidecar DNS endpoint shown in the UI.", + Type: schema.TypeString, + Optional: true, + }, + "bypass_mode": { + Description: "This argument lets you specify how to handle the connection in the event of an error in the sidecar during a user’s session. Valid modes are: `always`, `failover` or `never`. Defaults to `failover`. If `always` is specified, the sidecar will run in [passthrough mode](https://cyral.com/docs/sidecars/sidecar-manage#passthrough-mode). If `failover` is specified, the sidecar will run in [resiliency mode](https://cyral.com/docs/sidecars/sidecar-manage#resilient-mode-of-sidecar-operation). If `never` is specified and there is an error in the sidecar, connections to bound repositories will fail.", + Type: schema.TypeString, + Optional: true, + Default: "failover", + ValidateFunc: validation.StringInSlice( + []string{ + "always", + "failover", + "never", + }, false, + ), + }, + "certificate_bundle_secrets": { + Deprecated: "Since sidecar v4.7 the certificate is managed at deployment level. Refer" + + " to [our public docs](https://cyral.com/docs/sidecars/deployment/certificates)" + + " for more information.", + Description: "Certificate Bundle Secret is a configuration that holds data about the" + + " location of a particular TLS certificate bundle in a secrets manager.", + Type: schema.TypeSet, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "sidecar": { + Description: "Certificate Bundle Secret for sidecar.", + Type: schema.TypeSet, + MaxItems: 1, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "engine": { + Description: "Engine is the name of the engine used with the given secrets" + + " manager type, when applicable.", + Type: schema.TypeString, + Optional: true, + }, + "secret_id": { + Description: "Secret ID is the identifier or location for the secret that" + + " holds the certificate bundle.", + Type: schema.TypeString, + Required: true, + }, + "type": { + Description: "Type identifies the secret manager used to store the secret. Valid values are: `aws` and `k8s`.", + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice( + []string{ + "aws", + "k8s", + }, false, + ), + }, + }, + }, + }, + }, + }, + }, + }, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + } +} diff --git a/cyral/internal/sidecar/resource_cyral_sidecar.go b/cyral/internal/sidecar/resource_cyral_sidecar.go deleted file mode 100644 index bff5aa1d..00000000 --- a/cyral/internal/sidecar/resource_cyral_sidecar.go +++ /dev/null @@ -1,433 +0,0 @@ -package sidecar - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "regexp" - - "github.com/hashicorp/terraform-plugin-log/tflog" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - - "github.com/cyralinc/terraform-provider-cyral/cyral/client" - "github.com/cyralinc/terraform-provider-cyral/cyral/utils" -) - -type CreateSidecarResponse struct { - ID string `json:"ID"` -} - -type SidecarData struct { - ID string `json:"id"` - Name string `json:"name"` - Labels []string `json:"labels"` - SidecarProperties *SidecarProperties `json:"properties"` - ServicesConfig SidecarServicesConfig `json:"services"` - UserEndpoint string `json:"userEndpoint"` - CertificateBundleSecrets CertificateBundleSecrets `json:"certificateBundleSecrets,omitempty"` -} - -func (sd *SidecarData) BypassMode() string { - if sd.ServicesConfig != nil { - if dispConfig, ok := sd.ServicesConfig["dispatcher"]; ok { - if bypass_mode, ok := dispConfig["bypass"]; ok { - return bypass_mode - } - } - } - return "" -} - -type SidecarProperties struct { - DeploymentMethod string `json:"deploymentMethod"` - LogIntegrationID string `json:"logIntegrationID,omitempty"` - DiagnosticLogIntegrationID string `json:"diagnosticLogIntegrationID,omitempty"` -} - -func NewSidecarProperties(deploymentMethod, activityLogIntegrationID, diagnosticLogIntegrationID string) *SidecarProperties { - return &SidecarProperties{ - DeploymentMethod: deploymentMethod, - LogIntegrationID: activityLogIntegrationID, - DiagnosticLogIntegrationID: diagnosticLogIntegrationID, - } -} - -type SidecarServicesConfig map[string]map[string]string - -type CertificateBundleSecrets map[string]*CertificateBundleSecret - -type CertificateBundleSecret struct { - Engine string `json:"engine,omitempty"` - SecretId string `json:"secretId,omitempty"` - Type string `json:"type,omitempty"` -} - -func ResourceSidecar() *schema.Resource { - return &schema.Resource{ - Description: "Manages [sidecars](https://cyral.com/docs/sidecars/sidecar-manage).", - CreateContext: resourceSidecarCreate, - ReadContext: resourceSidecarRead, - UpdateContext: resourceSidecarUpdate, - DeleteContext: resourceSidecarDelete, - - Schema: map[string]*schema.Schema{ - "id": { - Description: "ID of this resource in Cyral environment", - Type: schema.TypeString, - Computed: true, - }, - "name": { - Description: "Sidecar name that will be used internally in Control Plane (ex: `your_sidecar_name`).", - Type: schema.TypeString, - Required: true, - }, - "deployment_method": { - Description: "Deployment method that will be used by this sidecar (valid values: `docker`, `cft-ec2`, `terraform`, `helm3`, `automated`, `custom`, `terraformGKE`, `linux`, and `singleContainer`).", - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice( - []string{ - "docker", "cft-ec2", "terraform", "helm3", - "automated", "custom", "terraformGKE", "singleContainer", - "linux", - }, false, - ), - }, - "log_integration_id": { - Description: "ID of the log integration mapped to this sidecar, used for Cyral activity logs.", - Type: schema.TypeString, - Optional: true, - Deprecated: "Since sidecar v4.8. Use `activity_log_integration_id` instead.", - ConflictsWith: []string{"activity_log_integration_id"}, - }, - "activity_log_integration_id": { - Description: "ID of the log integration mapped to this sidecar, used for Cyral activity logs.", - Type: schema.TypeString, - Optional: true, - }, - "diagnostic_log_integration_id": { - Description: "ID of the log integration mapped to this sidecar, used for sidecar diagnostic logs.", - Type: schema.TypeString, - Optional: true, - }, - "labels": { - Description: "Labels that can be attached to the sidecar and shown in the `Tags` field in the UI.", - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "user_endpoint": { - Description: "User-defined endpoint (also referred as `alias`) that can be used to override the sidecar DNS endpoint shown in the UI.", - Type: schema.TypeString, - Optional: true, - }, - "bypass_mode": { - Description: "This argument lets you specify how to handle the connection in the event of an error in the sidecar during a user’s session. Valid modes are: `always`, `failover` or `never`. Defaults to `failover`. If `always` is specified, the sidecar will run in [passthrough mode](https://cyral.com/docs/sidecars/sidecar-manage#passthrough-mode). If `failover` is specified, the sidecar will run in [resiliency mode](https://cyral.com/docs/sidecars/sidecar-manage#resilient-mode-of-sidecar-operation). If `never` is specified and there is an error in the sidecar, connections to bound repositories will fail.", - Type: schema.TypeString, - Optional: true, - Default: "failover", - ValidateFunc: validation.StringInSlice( - []string{ - "always", - "failover", - "never", - }, false, - ), - }, - "certificate_bundle_secrets": { - Deprecated: "Since sidecar v4.7 the certificate is managed at deployment level. Refer" + - " to [our public docs](https://cyral.com/docs/sidecars/deployment/certificates)" + - " for more information.", - Description: "Certificate Bundle Secret is a configuration that holds data about the" + - " location of a particular TLS certificate bundle in a secrets manager.", - Type: schema.TypeSet, - MaxItems: 1, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "sidecar": { - Description: "Certificate Bundle Secret for sidecar.", - Type: schema.TypeSet, - MaxItems: 1, - Required: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "engine": { - Description: "Engine is the name of the engine used with the given secrets" + - " manager type, when applicable.", - Type: schema.TypeString, - Optional: true, - }, - "secret_id": { - Description: "Secret ID is the identifier or location for the secret that" + - " holds the certificate bundle.", - Type: schema.TypeString, - Required: true, - }, - "type": { - Description: "Type identifies the secret manager used to store the secret. Valid values are: `aws` and `k8s`.", - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice( - []string{ - "aws", - "k8s", - }, false, - ), - }, - }, - }, - }, - }, - }, - }, - }, - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, - } -} - -func resourceSidecarCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - tflog.Debug(ctx, "Init resourceSidecarCreate") - c := m.(*client.Client) - - resourceData, err := getSidecarDataFromResource(c, d) - if err != nil { - return utils.CreateError("Unable to create sidecar", fmt.Sprintf("%v", err)) - } - - url := fmt.Sprintf("https://%s/v1/sidecars", c.ControlPlane) - - body, err := c.DoRequest(ctx, url, http.MethodPost, resourceData) - if err != nil { - return utils.CreateError("Unable to create sidecar", fmt.Sprintf("%v", err)) - } - - response := SidecarData{} - if err := json.Unmarshal(body, &response); err != nil { - return utils.CreateError("Unable to unmarshall JSON", fmt.Sprintf("%v", err)) - } - tflog.Debug(ctx, fmt.Sprintf("Response body (unmarshalled): %#v", response)) - - d.SetId(response.ID) - - return resourceSidecarRead(ctx, d, m) -} - -func resourceSidecarRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - tflog.Debug(ctx, "Init resourceSidecarRead") - c := m.(*client.Client) - - url := fmt.Sprintf("https://%s/v1/sidecars/%s", c.ControlPlane, d.Id()) - - body, err := c.DoRequest(ctx, url, http.MethodGet, nil) - if err != nil { - // Currently, the sidecar API always returns a status code of 500 for every error, - // so its not possible to distinguish if the error returned is related to - // a 404 Not Found or not by its status code. This way, a workaround for that is to - // check if the error message matches a 'Failed to extract info for wrapper' message, - // since thats the current message returned when the sidecar is not found. Once this - // issue is fixed in the sidecar API, we should handle the error here by its status - // code, and only remove the resource from the state (d.SetId("")) if it returns a 404 - // Not Found. - matched, regexpError := regexp.MatchString( - "Failed to extract info for wrapper", - err.Error(), - ) - if regexpError == nil && matched { - tflog.Debug(ctx, fmt.Sprintf("Sidecar not found. SidecarID: %s. "+ - "Removing it from state. Error: %v", d.Id(), err)) - d.SetId("") - return nil - } - - return utils.CreateError( - fmt.Sprintf( - "Unable to read sidecar. SidecarID: %s", - d.Id(), - ), fmt.Sprintf("%v", err), - ) - } - - response := SidecarData{} - if err := json.Unmarshal(body, &response); err != nil { - return utils.CreateError("Unable to unmarshall JSON", fmt.Sprintf("%v", err)) - } - tflog.Debug(ctx, fmt.Sprintf("Response body (unmarshalled): %#v", response)) - - d.Set("name", response.Name) - if properties := response.SidecarProperties; properties != nil { - d.Set("deployment_method", properties.DeploymentMethod) - d.Set("activity_log_integration_id", properties.LogIntegrationID) - d.Set("diagnostic_log_integration_id", properties.DiagnosticLogIntegrationID) - } - d.Set("labels", response.Labels) - d.Set("user_endpoint", response.UserEndpoint) - if bypassMode := response.BypassMode(); bypassMode != "" { - d.Set("bypass_mode", bypassMode) - } - d.Set("certificate_bundle_secrets", flattenCertificateBundleSecrets(response.CertificateBundleSecrets)) - - tflog.Debug(ctx, "End resourceSidecarRead") - - return diag.Diagnostics{} -} - -func resourceSidecarUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - tflog.Debug(ctx, "Init resourceSidecarUpdate") - c := m.(*client.Client) - - resourceData, err := getSidecarDataFromResource(c, d) - if err != nil { - return utils.CreateError("Unable to update sidecar", fmt.Sprintf("%v", err)) - } - - url := fmt.Sprintf("https://%s/v1/sidecars/%s", c.ControlPlane, d.Id()) - - if _, err = c.DoRequest(ctx, url, http.MethodPut, resourceData); err != nil { - return utils.CreateError("Unable to update sidecar", fmt.Sprintf("%v", err)) - } - - tflog.Debug(ctx, "End resourceSidecarUpdate") - - return resourceSidecarRead(ctx, d, m) -} - -func resourceSidecarDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - tflog.Debug(ctx, "Init resourceSidecarDelete") - c := m.(*client.Client) - - url := fmt.Sprintf("https://%s/v1/sidecars/%s", c.ControlPlane, d.Id()) - - if _, err := c.DoRequest(ctx, url, http.MethodDelete, nil); err != nil { - return utils.CreateError("Unable to delete sidecar", fmt.Sprintf("%v", err)) - } - - tflog.Debug(ctx, "End resourceSidecarDelete") - - return diag.Diagnostics{} -} - -func getSidecarDataFromResource(c *client.Client, d *schema.ResourceData) (*SidecarData, error) { - ctx := context.Background() - tflog.Debug(ctx, "Init getSidecarDataFromResource") - - deploymentMethod := d.Get("deployment_method").(string) - - activityLogIntegrationID := d.Get("activity_log_integration_id").(string) - if activityLogIntegrationID == "" { - activityLogIntegrationID = d.Get("log_integration_id").(string) - } - diagnosticLogIntegrationID := d.Get("diagnostic_log_integration_id").(string) - - properties := NewSidecarProperties(deploymentMethod, activityLogIntegrationID, diagnosticLogIntegrationID) - - svcconf := SidecarServicesConfig{ - "dispatcher": map[string]string{ - "bypass": d.Get("bypass_mode").(string), - }, - } - - labels := d.Get("labels").([]interface{}) - sidecarDataLabels := []string{} - for _, labelInterface := range labels { - if label, ok := labelInterface.(string); ok { - sidecarDataLabels = append(sidecarDataLabels, label) - } - } - - cbs := getCertificateBundleSecret(d) - - tflog.Debug(ctx, "end getSidecarDataFromResource") - return &SidecarData{ - ID: d.Id(), - Name: d.Get("name").(string), - Labels: sidecarDataLabels, - SidecarProperties: properties, - ServicesConfig: svcconf, - UserEndpoint: d.Get("user_endpoint").(string), - CertificateBundleSecrets: cbs, - }, nil -} - -func flattenCertificateBundleSecrets(cbs CertificateBundleSecrets) []interface{} { - ctx := context.Background() - tflog.Debug(ctx, "Init flattenCertificateBundleSecrets") - var flatCBS []interface{} - if cbs != nil { - cb := make(map[string]interface{}) - - for key, val := range cbs { - // Ignore self-signed certificates - if key != "sidecar-generated-selfsigned" { - contentCB := make([]interface{}, 1) - - tflog.Debug(ctx, fmt.Sprintf("key: %v", key)) - tflog.Debug(ctx, fmt.Sprintf("val: %v", val)) - - contentCBMap := make(map[string]interface{}) - contentCBMap["secret_id"] = val.SecretId - contentCBMap["engine"] = val.Engine - contentCBMap["type"] = val.Type - - contentCB[0] = contentCBMap - cb[key] = contentCB - } - } - - if len(cb) > 0 { - flatCBS = make([]interface{}, 1) - flatCBS[0] = cb - } - } - - tflog.Debug(ctx, fmt.Sprintf("end flattenCertificateBundleSecrets %v", flatCBS)) - return flatCBS -} - -func getCertificateBundleSecret(d *schema.ResourceData) CertificateBundleSecrets { - ctx := context.Background() - tflog.Debug(ctx, "Init getCertificateBundleSecret") - rdCBS := d.Get("certificate_bundle_secrets").(*schema.Set).List() - ret := make(CertificateBundleSecrets) - - if len(rdCBS) > 0 { - cbsMap := rdCBS[0].(map[string]interface{}) - for k, v := range cbsMap { - vList := v.(*schema.Set).List() - // 1. k = "sidecar" or other direct internal elements of certificate_bundle_secrets - // 2. Also one element on this list due to MaxItems... - // 3. Ignore self signed certificates - if len(vList) > 0 && k != "sidecar-generated-selfsigned" { - vMap := vList[0].(map[string]interface{}) - engine := "" - if val, ok := vMap["engine"]; val != nil && ok { - engine = val.(string) - } - cbsType := vMap["type"].(string) - secretId := vMap["secret_id"].(string) - cbs := CertificateBundleSecret{ - SecretId: secretId, - Engine: engine, - Type: cbsType, - } - ret[k] = &cbs - } - } - } - - // If the occurrence of `sidecar` does not exist, set it to an empty certificate bundle - // so that the API can remove the `sidecar` key from the persisted certificate bundle map. - if _, ok := ret["sidecar"]; !ok { - ret["sidecar"] = &CertificateBundleSecret{} - } - - tflog.Debug(ctx, "end getCertificateBundleSecret") - return ret -} diff --git a/cyral/internal/sidecar/resource_cyral_sidecar_test.go b/cyral/internal/sidecar/resource_test.go similarity index 74% rename from cyral/internal/sidecar/resource_cyral_sidecar_test.go rename to cyral/internal/sidecar/resource_test.go index 1eca02b2..18c5de15 100644 --- a/cyral/internal/sidecar/resource_cyral_sidecar_test.go +++ b/cyral/internal/sidecar/resource_test.go @@ -21,56 +21,84 @@ func getTestCBS() sidecar.CertificateBundleSecrets { } var cloudFormationSidecarConfig = sidecar.SidecarData{ - Name: utils.AccTestName(utils.SidecarResourceName, "cft"), - Labels: []string{"test1"}, - SidecarProperties: sidecar.NewSidecarProperties("cft-ec2", "foo", ""), + Name: utils.AccTestName(utils.SidecarResourceName, "cft"), + Labels: []string{"test1"}, + SidecarProperties: &sidecar.SidecarProperties{ + DeploymentMethod: "cft-ec2", + LogIntegrationID: "foo", + DiagnosticLogIntegrationID: "", + }, UserEndpoint: "some.cft.user.endpoint", CertificateBundleSecrets: getTestCBS(), } var dockerSidecarConfig = sidecar.SidecarData{ - Name: utils.AccTestName(utils.SidecarResourceName, "docker"), - Labels: []string{"test2"}, - SidecarProperties: sidecar.NewSidecarProperties("docker", "bar", ""), + Name: utils.AccTestName(utils.SidecarResourceName, "docker"), + Labels: []string{"test2"}, + SidecarProperties: &sidecar.SidecarProperties{ + DeploymentMethod: "docker", + LogIntegrationID: "bar", + DiagnosticLogIntegrationID: "", + }, UserEndpoint: "some.docker.user.endpoint", CertificateBundleSecrets: getTestCBS(), } var helmSidecarConfig = sidecar.SidecarData{ - Name: utils.AccTestName(utils.SidecarResourceName, "helm3"), - Labels: []string{"test3"}, - SidecarProperties: sidecar.NewSidecarProperties("helm3", "baz", ""), + Name: utils.AccTestName(utils.SidecarResourceName, "helm3"), + Labels: []string{"test3"}, + SidecarProperties: &sidecar.SidecarProperties{ + DeploymentMethod: "helm3", + LogIntegrationID: "baz", + DiagnosticLogIntegrationID: "", + }, UserEndpoint: "some.helm3.user.endpoint", CertificateBundleSecrets: getTestCBS(), } var tfSidecarConfig = sidecar.SidecarData{ - Name: utils.AccTestName(utils.SidecarResourceName, "tf"), - Labels: []string{"test4"}, - SidecarProperties: sidecar.NewSidecarProperties("terraform", "qux", ""), + Name: utils.AccTestName(utils.SidecarResourceName, "tf"), + Labels: []string{"test4"}, + SidecarProperties: &sidecar.SidecarProperties{ + DeploymentMethod: "terraform", + LogIntegrationID: "qux", + DiagnosticLogIntegrationID: "", + }, UserEndpoint: "some.tf.user.endpoint", CertificateBundleSecrets: getTestCBS(), } var singleContainerSidecarConfig = sidecar.SidecarData{ - Name: utils.AccTestName(utils.SidecarResourceName, "singleContainer"), - Labels: []string{"test5"}, - SidecarProperties: sidecar.NewSidecarProperties("singleContainer", "quxx", ""), + Name: utils.AccTestName(utils.SidecarResourceName, "singleContainer"), + Labels: []string{"test5"}, + SidecarProperties: &sidecar.SidecarProperties{ + DeploymentMethod: "singleContainer", + LogIntegrationID: "quxx", + DiagnosticLogIntegrationID: "", + }, UserEndpoint: "some.singleContainer.user.endpoint", CertificateBundleSecrets: getTestCBS(), } var linuxSidecarConfig = sidecar.SidecarData{ - Name: utils.AccTestName(utils.SidecarResourceName, "linux"), - Labels: []string{"test6"}, - SidecarProperties: sidecar.NewSidecarProperties("linux", "empty", ""), + Name: utils.AccTestName(utils.SidecarResourceName, "linux"), + Labels: []string{"test6"}, + SidecarProperties: &sidecar.SidecarProperties{ + DeploymentMethod: "linux", + LogIntegrationID: "empty", + DiagnosticLogIntegrationID: "", + }, UserEndpoint: "some.linux.user.endpoint", CertificateBundleSecrets: getTestCBS(), } var bypassNeverSidecarConfig = sidecar.SidecarData{ - Name: utils.AccTestName(utils.SidecarResourceName, "bypassNeverSidecar"), - SidecarProperties: sidecar.NewSidecarProperties("terraform", "a", ""), + Name: utils.AccTestName(utils.SidecarResourceName, "bypassNeverSidecar"), + SidecarProperties: &sidecar.SidecarProperties{ + DeploymentMethod: "terraform", + LogIntegrationID: "a", + DiagnosticLogIntegrationID: "", + }, ServicesConfig: sidecar.SidecarServicesConfig{ "dispatcher": map[string]string{ "bypass": "never", @@ -80,8 +108,12 @@ var bypassNeverSidecarConfig = sidecar.SidecarData{ } var bypassAlwaysSidecarConfig = sidecar.SidecarData{ - Name: utils.AccTestName(utils.SidecarResourceName, "bypassAlwaysSidecar"), - SidecarProperties: sidecar.NewSidecarProperties("terraform", "b", ""), + Name: utils.AccTestName(utils.SidecarResourceName, "bypassAlwaysSidecar"), + SidecarProperties: &sidecar.SidecarProperties{ + DeploymentMethod: "terraform", + LogIntegrationID: "b", + DiagnosticLogIntegrationID: "", + }, ServicesConfig: sidecar.SidecarServicesConfig{ "dispatcher": map[string]string{ "bypass": "always", diff --git a/cyral/internal/sidecar/schema_loader.go b/cyral/internal/sidecar/schema_loader.go new file mode 100644 index 00000000..8cc43233 --- /dev/null +++ b/cyral/internal/sidecar/schema_loader.go @@ -0,0 +1,31 @@ +package sidecar + +import ( + "github.com/cyralinc/terraform-provider-cyral/cyral/core" +) + +type packageSchema struct { +} + +func (p *packageSchema) Name() string { + return "sidecar" +} + +func (p *packageSchema) Schemas() []*core.SchemaDescriptor { + return []*core.SchemaDescriptor{ + // { + // Name: dataSourceName, + // Type: core.DataSourceSchemaType, + // Schema: dataSourceSchema, + // }, + { + Name: resourceName, + Type: core.ResourceSchemaType, + Schema: resourceSchema, + }, + } +} + +func PackageSchema() core.PackageSchema { + return &packageSchema{} +} diff --git a/cyral/provider/provider.go b/cyral/provider/provider.go index 2f6b564f..5bca46ad 100644 --- a/cyral/provider/provider.go +++ b/cyral/provider/provider.go @@ -169,7 +169,6 @@ func getResourceMap(ps []core.PackageSchema) map[string]*schema.Resource { schemaMap["cyral_role"] = role.ResourceRole() schemaMap["cyral_role_sso_groups"] = role.ResourceRoleSSOGroups() schemaMap["cyral_service_account"] = serviceaccount.ResourceServiceAccount() - schemaMap["cyral_sidecar"] = sidecar.ResourceSidecar() schemaMap["cyral_sidecar_credentials"] = credentials.ResourceSidecarCredentials() schemaMap["cyral_sidecar_listener"] = listener.ResourceSidecarListener() diff --git a/cyral/provider/schema_loader.go b/cyral/provider/schema_loader.go index 7516dfb6..be2a9b5f 100644 --- a/cyral/provider/schema_loader.go +++ b/cyral/provider/schema_loader.go @@ -13,6 +13,7 @@ import ( "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/datamap" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/useraccount" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/samlcertificate" + "github.com/cyralinc/terraform-provider-cyral/cyral/internal/sidecar" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/tokensettings" ) @@ -26,6 +27,7 @@ func packagesSchemas() []core.PackageSchema { hcvault.PackageSchema(), repository.PackageSchema(), samlcertificate.PackageSchema(), + sidecar.PackageSchema(), slack.PackageSchema(), teams.PackageSchema(), tokensettings.PackageSchema(), From 7fc290f46430eff0209b1670a0be8746c52769a1 Mon Sep 17 00:00:00 2001 From: Wilson de Carvalho <796900+wcmjunior@users.noreply.github.com> Date: Mon, 1 Apr 2024 18:03:48 -0700 Subject: [PATCH 06/22] Refactor cyral_sidecar_credentials --- .../internal/sidecar/credentials/constants.go | 5 + cyral/internal/sidecar/credentials/model.go | 38 ++++++ .../internal/sidecar/credentials/resource.go | 58 ++++++++ .../resource_cyral_sidecar_credentials.go | 128 ------------------ ...r_credentials_test.go => resource_test.go} | 0 .../sidecar/credentials/schema_loader.go | 26 ++++ cyral/provider/provider.go | 2 - cyral/provider/schema_loader.go | 2 + 8 files changed, 129 insertions(+), 130 deletions(-) create mode 100644 cyral/internal/sidecar/credentials/constants.go create mode 100644 cyral/internal/sidecar/credentials/model.go create mode 100644 cyral/internal/sidecar/credentials/resource.go delete mode 100644 cyral/internal/sidecar/credentials/resource_cyral_sidecar_credentials.go rename cyral/internal/sidecar/credentials/{resource_cyral_sidecar_credentials_test.go => resource_test.go} (100%) create mode 100644 cyral/internal/sidecar/credentials/schema_loader.go diff --git a/cyral/internal/sidecar/credentials/constants.go b/cyral/internal/sidecar/credentials/constants.go new file mode 100644 index 00000000..c30abaaf --- /dev/null +++ b/cyral/internal/sidecar/credentials/constants.go @@ -0,0 +1,5 @@ +package credentials + +const ( + resourceName = "cyral_sidecar_credentials" +) diff --git a/cyral/internal/sidecar/credentials/model.go b/cyral/internal/sidecar/credentials/model.go new file mode 100644 index 00000000..ac589424 --- /dev/null +++ b/cyral/internal/sidecar/credentials/model.go @@ -0,0 +1,38 @@ +package credentials + +import ( + "fmt" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +type CreateSidecarCredentialsRequest struct { + SidecarID string `json:"sidecarId"` +} + +func (r *CreateSidecarCredentialsRequest) ReadFromSchema(d *schema.ResourceData) error { + r.SidecarID = d.Get("sidecar_id").(string) + return nil +} + +type SidecarCredentialsData struct { + SidecarID string `json:"sidecarId"` + ClientID string `json:"clientId"` + ClientSecret string `json:"clientSecret"` +} + +func (r *SidecarCredentialsData) WriteToSchema(d *schema.ResourceData) error { + if err := d.Set("client_id", r.ClientID); err != nil { + return fmt.Errorf("error setting 'client_id' field: %w", err) + } + if r.ClientSecret != "" { + if err := d.Set("client_secret", r.ClientSecret); err != nil { + return fmt.Errorf("error setting 'client_secret' field: %w", err) + } + } + if err := d.Set("sidecar_id", r.SidecarID); err != nil { + return fmt.Errorf("error setting 'sidecar_id' field: %w", err) + } + d.SetId(r.ClientID) + return nil +} diff --git a/cyral/internal/sidecar/credentials/resource.go b/cyral/internal/sidecar/credentials/resource.go new file mode 100644 index 00000000..615b2f2f --- /dev/null +++ b/cyral/internal/sidecar/credentials/resource.go @@ -0,0 +1,58 @@ +package credentials + +import ( + "fmt" + + "github.com/cyralinc/terraform-provider-cyral/cyral/client" + "github.com/cyralinc/terraform-provider-cyral/cyral/core" + "github.com/cyralinc/terraform-provider-cyral/cyral/core/types/resourcetype" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +var resourceContextHandler = core.DefaultContextHandler{ + ResourceName: resourceName, + ResourceType: resourcetype.Resource, + SchemaReaderFactory: func() core.SchemaReader { return &CreateSidecarCredentialsRequest{} }, + SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &SidecarCredentialsData{} }, + SchemaWriterFactoryPostMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &SidecarCredentialsData{} }, + BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { + return fmt.Sprintf("https://%s/v1/users/sidecarAccounts", c.ControlPlane) + }, +} + +func resourceSchema() *schema.Resource { + return &schema.Resource{ + Description: "Create new [credentials for Cyral sidecar](https://cyral.com/docs/sidecars/sidecar-manage/#rotate-the-client-secret-for-a-sidecar).", + CreateContext: resourceContextHandler.CreateContext(), + ReadContext: resourceContextHandler.ReadContext(), + DeleteContext: resourceContextHandler.DeleteContext(), + + Schema: map[string]*schema.Schema{ + "id": { + Description: "Same as `client_id`.", + Type: schema.TypeString, + Computed: true, + }, + "sidecar_id": { + Description: "ID of the sidecar to create new credentials.", + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "client_id": { + Description: "Sidecar client ID.", + Type: schema.TypeString, + Computed: true, + }, + "client_secret": { + Description: "Sidecar client secret.", + Type: schema.TypeString, + Computed: true, + Sensitive: true, + }, + }, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + } +} diff --git a/cyral/internal/sidecar/credentials/resource_cyral_sidecar_credentials.go b/cyral/internal/sidecar/credentials/resource_cyral_sidecar_credentials.go deleted file mode 100644 index 2fe820cd..00000000 --- a/cyral/internal/sidecar/credentials/resource_cyral_sidecar_credentials.go +++ /dev/null @@ -1,128 +0,0 @@ -package credentials - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - - "github.com/cyralinc/terraform-provider-cyral/cyral/client" - "github.com/cyralinc/terraform-provider-cyral/cyral/utils" - "github.com/hashicorp/terraform-plugin-log/tflog" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -) - -type CreateSidecarCredentialsRequest struct { - SidecarID string `json:"sidecarId"` -} - -type SidecarCredentialsData struct { - SidecarID string `json:"sidecarId"` - ClientID string `json:"clientId"` - ClientSecret string `json:"clientSecret"` -} - -func ResourceSidecarCredentials() *schema.Resource { - return &schema.Resource{ - Description: "Create new [credentials for Cyral sidecar](https://cyral.com/docs/sidecars/sidecar-manage/#rotate-the-client-secret-for-a-sidecar).", - CreateContext: resourceSidecarCredentialsCreate, - ReadContext: resourceSidecarCredentialsRead, - DeleteContext: resourceSidecarCredentialsDelete, - - Schema: map[string]*schema.Schema{ - "id": { - Description: "Same as `client_id`.", - Type: schema.TypeString, - Computed: true, - }, - "sidecar_id": { - Description: "ID of the sidecar to create new credentials.", - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - "client_id": { - Description: "Sidecar client ID.", - Type: schema.TypeString, - Computed: true, - }, - "client_secret": { - Description: "Sidecar client secret.", - Type: schema.TypeString, - Computed: true, - Sensitive: true, - }, - }, - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, - } -} - -func resourceSidecarCredentialsCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - tflog.Debug(ctx, "Init resourceSidecarCredentialsCreate") - c := m.(*client.Client) - - payload := CreateSidecarCredentialsRequest{d.Get("sidecar_id").(string)} - - url := fmt.Sprintf("https://%s/v1/users/sidecarAccounts", c.ControlPlane) - - body, err := c.DoRequest(ctx, url, http.MethodPost, payload) - if err != nil { - return utils.CreateError("Unable to create sidecar credentials", fmt.Sprintf("%v", err)) - } - - response := SidecarCredentialsData{} - if err := json.Unmarshal(body, &response); err != nil { - return utils.CreateError("Unable to unmarshall JSON", fmt.Sprintf("%v", err)) - } - tflog.Debug(ctx, fmt.Sprintf("Response body (unmarshalled): %#v", response)) - - d.SetId(response.ClientID) - d.Set("client_id", response.ClientID) - d.Set("client_secret", response.ClientSecret) - - return resourceSidecarCredentialsRead(ctx, d, m) -} - -func resourceSidecarCredentialsRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - tflog.Debug(ctx, "Init resourceSidecarCredentialsRead") - c := m.(*client.Client) - - url := fmt.Sprintf("https://%s/v1/users/sidecarAccounts/%s", c.ControlPlane, d.Id()) - - body, err := c.DoRequest(ctx, url, http.MethodGet, nil) - if err != nil { - return utils.CreateError(fmt.Sprintf("Unable to read sidecar credentials. ClientID: %s", - d.Id()), fmt.Sprintf("%v", err)) - } - - response := SidecarCredentialsData{} - if err := json.Unmarshal(body, &response); err != nil { - return utils.CreateError("Unable to unmarshall JSON", fmt.Sprintf("%v", err)) - } - tflog.Debug(ctx, fmt.Sprintf("Response body (unmarshalled): %#v", response)) - - d.Set("sidecar_id", response.SidecarID) - d.Set("client_id", response.ClientID) - - tflog.Debug(ctx, "End resourceSidecarCredentialsRead") - - return diag.Diagnostics{} -} - -func resourceSidecarCredentialsDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - tflog.Debug(ctx, "Init resourceSidecarCredentialsDelete") - c := m.(*client.Client) - - url := fmt.Sprintf("https://%s/v1/users/sidecarAccounts/%s", c.ControlPlane, d.Id()) - - if _, err := c.DoRequest(ctx, url, http.MethodDelete, nil); err != nil { - return utils.CreateError("Unable to delete sidecar credentials", fmt.Sprintf("%v", err)) - } - - tflog.Debug(ctx, "End resourceSidecarCredentialsDelete") - - return diag.Diagnostics{} -} diff --git a/cyral/internal/sidecar/credentials/resource_cyral_sidecar_credentials_test.go b/cyral/internal/sidecar/credentials/resource_test.go similarity index 100% rename from cyral/internal/sidecar/credentials/resource_cyral_sidecar_credentials_test.go rename to cyral/internal/sidecar/credentials/resource_test.go diff --git a/cyral/internal/sidecar/credentials/schema_loader.go b/cyral/internal/sidecar/credentials/schema_loader.go new file mode 100644 index 00000000..17a4fe9e --- /dev/null +++ b/cyral/internal/sidecar/credentials/schema_loader.go @@ -0,0 +1,26 @@ +package credentials + +import ( + "github.com/cyralinc/terraform-provider-cyral/cyral/core" +) + +type packageSchema struct { +} + +func (p *packageSchema) Name() string { + return "credentials" +} + +func (p *packageSchema) Schemas() []*core.SchemaDescriptor { + return []*core.SchemaDescriptor{ + { + Name: resourceName, + Type: core.ResourceSchemaType, + Schema: resourceSchema, + }, + } +} + +func PackageSchema() core.PackageSchema { + return &packageSchema{} +} diff --git a/cyral/provider/provider.go b/cyral/provider/provider.go index 5bca46ad..6d79dfc7 100644 --- a/cyral/provider/provider.go +++ b/cyral/provider/provider.go @@ -28,7 +28,6 @@ import ( "github.com/cyralinc/terraform-provider-cyral/cyral/internal/samlconfiguration" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/serviceaccount" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/sidecar" - "github.com/cyralinc/terraform-provider-cyral/cyral/internal/sidecar/credentials" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/sidecar/health" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/sidecar/instance" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/sidecar/listener" @@ -169,7 +168,6 @@ func getResourceMap(ps []core.PackageSchema) map[string]*schema.Resource { schemaMap["cyral_role"] = role.ResourceRole() schemaMap["cyral_role_sso_groups"] = role.ResourceRoleSSOGroups() schemaMap["cyral_service_account"] = serviceaccount.ResourceServiceAccount() - schemaMap["cyral_sidecar_credentials"] = credentials.ResourceSidecarCredentials() schemaMap["cyral_sidecar_listener"] = listener.ResourceSidecarListener() tflog.Debug(ctx, "End getResourceMap") diff --git a/cyral/provider/schema_loader.go b/cyral/provider/schema_loader.go index be2a9b5f..3af6c113 100644 --- a/cyral/provider/schema_loader.go +++ b/cyral/provider/schema_loader.go @@ -14,6 +14,7 @@ import ( "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/useraccount" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/samlcertificate" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/sidecar" + "github.com/cyralinc/terraform-provider-cyral/cyral/internal/sidecar/credentials" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/tokensettings" ) @@ -22,6 +23,7 @@ func packagesSchemas() []core.PackageSchema { accessgateway.PackageSchema(), accessrules.PackageSchema(), binding.PackageSchema(), + credentials.PackageSchema(), datalabel.PackageSchema(), datamap.PackageSchema(), hcvault.PackageSchema(), From 867546f531236f3d82d4a51454eb1a2a3a0d2ee3 Mon Sep 17 00:00:00 2001 From: Wilson de Carvalho <796900+wcmjunior@users.noreply.github.com> Date: Tue, 2 Apr 2024 00:09:54 -0700 Subject: [PATCH 07/22] Refactor cyral_repository_conf_analysis --- .../repository/confanalysis/constants.go | 5 + .../internal/repository/confanalysis/model.go | 89 +++++ .../repository/confanalysis/resource.go | 193 +++++++++++ ...resource_cyral_repository_conf_analysis.go | 322 ------------------ ...conf_analysis_test.go => resource_test.go} | 0 .../repository/confanalysis/schema_loader.go | 26 ++ cyral/provider/provider.go | 2 - cyral/provider/schema_loader.go | 2 + 8 files changed, 315 insertions(+), 324 deletions(-) create mode 100644 cyral/internal/repository/confanalysis/constants.go create mode 100644 cyral/internal/repository/confanalysis/model.go create mode 100644 cyral/internal/repository/confanalysis/resource.go delete mode 100644 cyral/internal/repository/confanalysis/resource_cyral_repository_conf_analysis.go rename cyral/internal/repository/confanalysis/{resource_cyral_repository_conf_analysis_test.go => resource_test.go} (100%) create mode 100644 cyral/internal/repository/confanalysis/schema_loader.go diff --git a/cyral/internal/repository/confanalysis/constants.go b/cyral/internal/repository/confanalysis/constants.go new file mode 100644 index 00000000..5cb9c494 --- /dev/null +++ b/cyral/internal/repository/confanalysis/constants.go @@ -0,0 +1,5 @@ +package confanalysis + +const ( + resourceName = "cyral_repository_conf_analysis" +) diff --git a/cyral/internal/repository/confanalysis/model.go b/cyral/internal/repository/confanalysis/model.go new file mode 100644 index 00000000..386d3eaa --- /dev/null +++ b/cyral/internal/repository/confanalysis/model.go @@ -0,0 +1,89 @@ +package confanalysis + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +// TODO: v2 of this API should either return the repository ID +// or something else as an ID. Currently it accepts a `UserConfig` +// for the PUT payload, but returns a `RepositoryConfAnalysisData`. +// This makes the whole API confusing. + +type RepositoryConfAnalysisData struct { + UserConfig UserConfig `json:"userConfig"` +} + +type UserConfig struct { + AlertOnViolation bool `json:"alertOnViolation"` + BlockOnViolation bool `json:"blockOnViolation"` + CommentAnnotationGroups []string `json:"commentAnnotationGroups,omitempty"` + DisableFilterAnalysis bool `json:"disableFilterAnalysis"` + DisablePreConfiguredAlerts bool `json:"disablePreConfiguredAlerts"` + EnableDataMasking bool `json:"enableDataMasking"` + LogGroups []string `json:"logGroups,omitempty"` + Redact string `json:"redact"` + EnableDatasetRewrites bool `json:"enableDatasetRewrites"` +} + +func (r *RepositoryConfAnalysisData) WriteToSchema(d *schema.ResourceData) error { + d.SetId(d.Get("repository_id").(string)) + return r.UserConfig.WriteToSchema(d) +} + +func (r *UserConfig) WriteToSchema(d *schema.ResourceData) error { + logGroups := make([]interface{}, len(r.LogGroups)) + for index, logGroupItem := range r.LogGroups { + logGroups[index] = logGroupItem + } + logGroupsSet := schema.NewSet(schema.HashString, logGroups) + + annotationGroups := make([]interface{}, len(r.CommentAnnotationGroups)) + for index, annotationGroupItem := range r.CommentAnnotationGroups { + annotationGroups[index] = annotationGroupItem + } + annotationGroupsSet := schema.NewSet(schema.HashString, annotationGroups) + + d.Set("alert_on_violation", r.AlertOnViolation) + d.Set("block_on_violation", r.BlockOnViolation) + d.Set("comment_annotation_groups", annotationGroupsSet) + d.Set("disable_filter_analysis", r.DisableFilterAnalysis) + d.Set("disable_pre_configured_alerts", r.DisablePreConfiguredAlerts) + d.Set("enable_data_masking", r.EnableDataMasking) + d.Set("log_groups", logGroupsSet) + d.Set("redact", r.Redact) + d.Set("enable_dataset_rewrites", r.EnableDatasetRewrites) + + return nil +} + +func (r *RepositoryConfAnalysisData) ReadFromSchema(d *schema.ResourceData) error { + return r.UserConfig.ReadFromSchema(d) +} + +func (r *UserConfig) ReadFromSchema(d *schema.ResourceData) error { + var logGroups []string + if logGroupsSet, ok := d.GetOk("log_groups"); ok { + for _, logGroupItem := range logGroupsSet.(*schema.Set).List() { + logGroups = append(logGroups, logGroupItem.(string)) + } + } + + var annotationGroups []string + if annotationGroupsSet, ok := d.GetOk("comment_annotation_groups"); ok { + for _, annotationGroupItem := range annotationGroupsSet.(*schema.Set).List() { + annotationGroups = append(annotationGroups, annotationGroupItem.(string)) + } + } + + r.AlertOnViolation = d.Get("alert_on_violation").(bool) + r.BlockOnViolation = d.Get("block_on_violation").(bool) + r.DisableFilterAnalysis = d.Get("disable_filter_analysis").(bool) + r.DisablePreConfiguredAlerts = d.Get("disable_pre_configured_alerts").(bool) + r.EnableDataMasking = d.Get("enable_data_masking").(bool) + r.CommentAnnotationGroups = annotationGroups + r.LogGroups = logGroups + r.Redact = d.Get("redact").(string) + r.EnableDatasetRewrites = d.Get("enable_dataset_rewrites").(bool) + + return nil +} diff --git a/cyral/internal/repository/confanalysis/resource.go b/cyral/internal/repository/confanalysis/resource.go new file mode 100644 index 00000000..e52fc59e --- /dev/null +++ b/cyral/internal/repository/confanalysis/resource.go @@ -0,0 +1,193 @@ +package confanalysis + +import ( + "context" + "fmt" + "net/http" + + "github.com/cyralinc/terraform-provider-cyral/cyral/client" + "github.com/cyralinc/terraform-provider-cyral/cyral/core" + "github.com/cyralinc/terraform-provider-cyral/cyral/core/types/operationtype" + "github.com/cyralinc/terraform-provider-cyral/cyral/core/types/resourcetype" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +var urlFactory = func(d *schema.ResourceData, c *client.Client) string { + return fmt.Sprintf("https://%s/v1/repos/%s/conf/analysis", + c.ControlPlane, + d.Get("repository_id"), + ) +} + +var resourceContextHandler = core.DefaultContextHandler{ + ResourceName: resourceName, + ResourceType: resourcetype.Resource, + SchemaReaderFactory: func() core.SchemaReader { return &UserConfig{} }, + SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &RepositoryConfAnalysisData{} }, + BaseURLFactory: urlFactory, + IdBasedURLFactory: urlFactory, +} + +func resourceSchema() *schema.Resource { + return &schema.Resource{ + Description: "Manages Repository Analysis Configuration. This resource allows configuring both " + + "[Log Settings](https://cyral.com/docs/manage-repositories/repo-log-volume) " + + "and [Advanced settings](https://cyral.com/docs/manage-repositories/repo-advanced-settings) " + + "(Logs, Alerts, Analysis and Enforcement) configurations for Data Repositories.", + CreateContext: core.CreateResource( + core.ResourceOperationConfig{ + ResourceName: resourceName, + Type: operationtype.Create, + HttpMethod: http.MethodPut, + URLFactory: urlFactory, + SchemaReaderFactory: func() core.SchemaReader { return &UserConfig{} }, + SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &RepositoryConfAnalysisData{} }, + }, + core.ResourceOperationConfig{ + ResourceName: resourceName, + Type: operationtype.Read, + HttpMethod: http.MethodGet, + URLFactory: urlFactory, + SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &RepositoryConfAnalysisData{} }, + RequestErrorHandler: &core.ReadIgnoreHttpNotFound{}, + }, + ), + ReadContext: resourceContextHandler.ReadContext(), + UpdateContext: resourceContextHandler.UpdateContext(), + DeleteContext: resourceContextHandler.DeleteContext(), + + SchemaVersion: 1, + StateUpgraders: []schema.StateUpgrader{ + { + Version: 0, + Type: repositoryConfAnalysisResourceSchemaV0(). + CoreConfigSchema().ImpliedType(), + Upgrade: UpgradeRepositoryConfAnalysisV0, + }, + }, + + Schema: repositoryConfAnalysisResourceSchemaV0().Schema, + + Importer: &schema.ResourceImporter{ + StateContext: func( + ctx context.Context, + d *schema.ResourceData, + m interface{}, + ) ([]*schema.ResourceData, error) { + d.Set("repository_id", d.Id()) + return []*schema.ResourceData{d}, nil + }, + }, + } +} + +func repositoryConfAnalysisResourceSchemaV0() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Description: "ID of this resource in Cyral environment", + Type: schema.TypeString, + Computed: true, + }, + "repository_id": { + Description: "The ID of an existing data repository resource that will be configured.", + Type: schema.TypeString, + Required: true, + }, + "redact": { + Description: "Valid values are: `all`, `none` and `watched`. If set to `all` it will enable the redact of all literal values, `none` will disable it, and `watched` will only redact values from tracked fields set in the Datamap.", + Type: schema.TypeString, + Optional: true, + Default: "all", + ValidateFunc: client.ValidateRepositoryConfAnalysisRedact(), + }, + "alert_on_violation": { + Description: "If set to `true` it will enable alert on policy violations.", + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + "disable_pre_configured_alerts": { + Description: "If set to `true` it will *disable* preconfigured alerts.", + Type: schema.TypeBool, + Optional: true, + }, + "enable_data_masking": { + Description: "If set to `true` it will allow policies to force the masking " + + " of specified data fields in the results of queries. " + + "[Learn more](https://cyral.com/docs/using-cyral/masking/).", + Type: schema.TypeBool, + Optional: true, + }, + "block_on_violation": { + Description: "If set to `true` it will enable query blocking in case of a " + + "policy violation.", + Type: schema.TypeBool, + Optional: true, + }, + "disable_filter_analysis": { + Description: "If set to `true` it will *disable* filter analysis.", + Type: schema.TypeBool, + Optional: true, + }, + "enable_dataset_rewrites": { + Description: "If set to `true` it will enable rewriting queries.", + Type: schema.TypeBool, + Optional: true, + }, + "comment_annotation_groups": { + Description: "Valid values are: `identity`, `client`, `repo`, `sidecar`. The " + + "default behavior is to set only the `identity` when this option is " + + "enabled, but you can also opt to add the contents of `client`, `repo`, " + + " `sidecar` logging blocks as query comments. " + + " [Learn more](https://support.cyral.com/support/solutions/articles/44002218978).", + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: client.ValidateRepositoryConfAnalysisCommentAnnotationGroups(), + }, + }, + "log_groups": { + Description: "Responsible for configuring the Log Settings. Valid values are documented below. The `log_groups` list support the following values: " + + "\n - `everything` - Enables all the Log Settings." + + "\n - `dql` - Enables the `DQLs` setting for `all requests`." + + "\n - `dml` - Enables the `DMLs` setting for `all requests`." + + "\n - `ddl` - Enables the `DDLs` setting for `all requests`." + + "\n - `sensitive & dql` - Enables the `DQLs` setting for `logged fields`." + + "\n - `sensitive & dml` - Enables the `DMLs` setting for `logged fields`." + + "\n - `sensitive & ddl` - Enables the `DDLs` setting for `logged fields`." + + "\n - `privileged` - Enables the `Privileged commands` setting." + + "\n - `port-scan` - Enables the `Port scans` setting." + + "\n - `auth-failure` - Enables the `Authentication failures` setting." + + "\n - `full-table-scan` - Enables the `Full scans` setting." + + "\n - `violations` - Enables the `Policy violations` setting." + + "\n - `connections` - Enables the `Connection activity` setting." + + "\n - `sensitive` - Log all queries manipulating sensitive fields (watches)" + + "\n - `data-classification` - Log all queries whose response was automatically classified as sensitive (credit card numbers, emails and so on)." + + "\n - `audit` - Log `sensitive`, `DQLs`, `DDLs`, `DMLs` and `privileged`." + + "\n - `error` - Log analysis errors." + + "\n - `new-connections` - Log new connections." + + "\n - `closed-connections` - Log closed connections.", + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: client.ValidateRepositoryConfAnalysisLogSettings(), + }, + }, + }, + } +} + +// Previously, the ID for cyral_repository_conf_analysis had the format +// {repository_id}/ConfAnalysis. The goal of this state upgrade is to remove +// this suffix `ConfAnalysis`. +func UpgradeRepositoryConfAnalysisV0( + _ context.Context, + rawState map[string]interface{}, + _ interface{}, +) (map[string]interface{}, error) { + rawState["id"] = rawState["repository_id"] + return rawState, nil +} diff --git a/cyral/internal/repository/confanalysis/resource_cyral_repository_conf_analysis.go b/cyral/internal/repository/confanalysis/resource_cyral_repository_conf_analysis.go deleted file mode 100644 index 77762704..00000000 --- a/cyral/internal/repository/confanalysis/resource_cyral_repository_conf_analysis.go +++ /dev/null @@ -1,322 +0,0 @@ -package confanalysis - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - - "github.com/cyralinc/terraform-provider-cyral/cyral/client" - "github.com/cyralinc/terraform-provider-cyral/cyral/utils" - "github.com/hashicorp/terraform-plugin-log/tflog" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -) - -type RepositoryConfAnalysisData struct { - Config UserFacingConfig `json:"userConfig"` -} - -type UserFacingConfig struct { - AlertOnViolation bool `json:"alertOnViolation"` - BlockOnViolation bool `json:"blockOnViolation"` - CommentAnnotationGroups []string `json:"commentAnnotationGroups,omitempty"` - DisableFilterAnalysis bool `json:"disableFilterAnalysis"` - DisablePreConfiguredAlerts bool `json:"disablePreConfiguredAlerts"` - EnableDataMasking bool `json:"enableDataMasking"` - LogGroups []string `json:"logGroups,omitempty"` - Redact string `json:"redact"` - EnableDatasetRewrites bool `json:"enableDatasetRewrites"` -} - -func repositoryConfAnalysisResourceSchemaV0() *schema.Resource { - return &schema.Resource{ - Schema: map[string]*schema.Schema{ - "id": { - Description: "ID of this resource in Cyral environment", - Type: schema.TypeString, - Computed: true, - }, - "repository_id": { - Description: "The ID of an existing data repository resource that will be configured.", - Type: schema.TypeString, - Required: true, - }, - "redact": { - Description: "Valid values are: `all`, `none` and `watched`. If set to `all` it will enable the redact of all literal values, `none` will disable it, and `watched` will only redact values from tracked fields set in the Datamap.", - Type: schema.TypeString, - Optional: true, - Default: "all", - ValidateFunc: client.ValidateRepositoryConfAnalysisRedact(), - }, - "alert_on_violation": { - Description: "If set to `true` it will enable alert on policy violations.", - Type: schema.TypeBool, - Optional: true, - Default: true, - }, - "disable_pre_configured_alerts": { - Description: "If set to `true` it will *disable* preconfigured alerts.", - Type: schema.TypeBool, - Optional: true, - }, - "enable_data_masking": { - Description: "If set to `true` it will allow policies to force the masking " + - " of specified data fields in the results of queries. " + - "[Learn more](https://cyral.com/docs/using-cyral/masking/).", - Type: schema.TypeBool, - Optional: true, - }, - "block_on_violation": { - Description: "If set to `true` it will enable query blocking in case of a " + - "policy violation.", - Type: schema.TypeBool, - Optional: true, - }, - "disable_filter_analysis": { - Description: "If set to `true` it will *disable* filter analysis.", - Type: schema.TypeBool, - Optional: true, - }, - "enable_dataset_rewrites": { - Description: "If set to `true` it will enable rewriting queries.", - Type: schema.TypeBool, - Optional: true, - }, - "comment_annotation_groups": { - Description: "Valid values are: `identity`, `client`, `repo`, `sidecar`. The " + - "default behavior is to set only the `identity` when this option is " + - "enabled, but you can also opt to add the contents of `client`, `repo`, " + - " `sidecar` logging blocks as query comments. " + - " [Learn more](https://support.cyral.com/support/solutions/articles/44002218978).", - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: client.ValidateRepositoryConfAnalysisCommentAnnotationGroups(), - }, - }, - "log_groups": { - Description: "Responsible for configuring the Log Settings. Valid values are documented below. The `log_groups` list support the following values: " + - "\n - `everything` - Enables all the Log Settings." + - "\n - `dql` - Enables the `DQLs` setting for `all requests`." + - "\n - `dml` - Enables the `DMLs` setting for `all requests`." + - "\n - `ddl` - Enables the `DDLs` setting for `all requests`." + - "\n - `sensitive & dql` - Enables the `DQLs` setting for `logged fields`." + - "\n - `sensitive & dml` - Enables the `DMLs` setting for `logged fields`." + - "\n - `sensitive & ddl` - Enables the `DDLs` setting for `logged fields`." + - "\n - `privileged` - Enables the `Privileged commands` setting." + - "\n - `port-scan` - Enables the `Port scans` setting." + - "\n - `auth-failure` - Enables the `Authentication failures` setting." + - "\n - `full-table-scan` - Enables the `Full scans` setting." + - "\n - `violations` - Enables the `Policy violations` setting." + - "\n - `connections` - Enables the `Connection activity` setting." + - "\n - `sensitive` - Log all queries manipulating sensitive fields (watches)" + - "\n - `data-classification` - Log all queries whose response was automatically classified as sensitive (credit card numbers, emails and so on)." + - "\n - `audit` - Log `sensitive`, `DQLs`, `DDLs`, `DMLs` and `privileged`." + - "\n - `error` - Log analysis errors." + - "\n - `new-connections` - Log new connections." + - "\n - `closed-connections` - Log closed connections.", - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: client.ValidateRepositoryConfAnalysisLogSettings(), - }, - }, - }, - } -} - -// Previously, the ID for cyral_repository_conf_analysis had the format -// {repository_id}/ConfAnalysis. The goal of this state upgrade is to remove -// this suffix `ConfAnalysis`. -func UpgradeRepositoryConfAnalysisV0( - _ context.Context, - rawState map[string]interface{}, - _ interface{}, -) (map[string]interface{}, error) { - rawState["id"] = rawState["repository_id"] - return rawState, nil -} - -func ResourceRepositoryConfAnalysis() *schema.Resource { - return &schema.Resource{ - Description: "Manages Repository Analysis Configuration. This resource allows configuring both " + - "[Log Settings](https://cyral.com/docs/manage-repositories/repo-log-volume) " + - "and [Advanced settings](https://cyral.com/docs/manage-repositories/repo-advanced-settings) " + - "(Logs, Alerts, Analysis and Enforcement) configurations for Data Repositories.", - CreateContext: resourceRepositoryConfAnalysisCreate, - ReadContext: resourceRepositoryConfAnalysisRead, - UpdateContext: resourceRepositoryConfAnalysisUpdate, - DeleteContext: resourceRepositoryConfAnalysisDelete, - - SchemaVersion: 1, - StateUpgraders: []schema.StateUpgrader{ - { - Version: 0, - Type: repositoryConfAnalysisResourceSchemaV0(). - CoreConfigSchema().ImpliedType(), - Upgrade: UpgradeRepositoryConfAnalysisV0, - }, - }, - - Schema: repositoryConfAnalysisResourceSchemaV0().Schema, - - Importer: &schema.ResourceImporter{ - StateContext: func( - ctx context.Context, - d *schema.ResourceData, - m interface{}, - ) ([]*schema.ResourceData, error) { - d.Set("repository_id", d.Id()) - return []*schema.ResourceData{d}, nil - }, - }, - } -} - -func resourceRepositoryConfAnalysisCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - tflog.Debug(ctx, "Init resourceRepositoryConfAnalysisCreate") - c := m.(*client.Client) - - resourceData, err := getConfAnalysisDataFromResource(d) - if err != nil { - return utils.CreateError("Unable to create conf analysis", fmt.Sprintf("%v", err)) - } - - url := fmt.Sprintf("https://%s/v1/repos/%s/conf/analysis", c.ControlPlane, d.Get("repository_id")) - - body, err := c.DoRequest(ctx, url, http.MethodPut, resourceData.Config) - if err != nil { - return utils.CreateError("Unable to create conf analysis", fmt.Sprintf("%v", err)) - } - - response := RepositoryConfAnalysisData{} - if err := json.Unmarshal(body, &response); err != nil { - return utils.CreateError("Unable to unmarshall JSON", fmt.Sprintf("%v", err)) - } - - tflog.Debug(ctx, fmt.Sprintf("Response body (unmarshalled): %#v", response)) - - d.SetId(d.Get("repository_id").(string)) - - tflog.Debug(ctx, "End resourceRepositoryConfAnalysisCreate") - - return resourceRepositoryConfAnalysisRead(ctx, d, m) -} - -func resourceRepositoryConfAnalysisRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - tflog.Debug(ctx, "Init resourceRepositoryConfAnalysisRead") - c := m.(*client.Client) - - url := fmt.Sprintf("https://%s/v1/repos/%s/conf/analysis", c.ControlPlane, d.Get("repository_id")) - - body, err := c.DoRequest(ctx, url, http.MethodGet, nil) - if err != nil { - return utils.CreateError(fmt.Sprintf("Unable to read conf analysis. Conf Analysis Id: %s", - d.Id()), fmt.Sprintf("%v", err)) - } - - response := RepositoryConfAnalysisData{} - if err := json.Unmarshal(body, &response); err != nil { - return utils.CreateError("Unable to unmarshall JSON", fmt.Sprintf("%v", err)) - } - - tflog.Debug(ctx, fmt.Sprintf("Response body (unmarshalled): %#v", response)) - - setConfAnalysisDataToResource(d, response) - - tflog.Debug(ctx, "End resourceRepositoryConfAnalysisRead") - - return diag.Diagnostics{} -} - -func resourceRepositoryConfAnalysisUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - tflog.Debug(ctx, "Init resourceRepositoryConfAnalysisUpdate") - c := m.(*client.Client) - - resourceData, err := getConfAnalysisDataFromResource(d) - if err != nil { - return utils.CreateError("Unable to update conf analysis", fmt.Sprintf("%v", err)) - } - - url := fmt.Sprintf("https://%s/v1/repos/%s/conf/analysis", c.ControlPlane, d.Get("repository_id")) - - if _, err := c.DoRequest(ctx, url, http.MethodPut, resourceData.Config); err != nil { - return utils.CreateError("Unable to update conf analysis", fmt.Sprintf("%v", err)) - } - - tflog.Debug(ctx, "End resourceRepositoryConfAnalysisUpdate") - - return resourceRepositoryConfAnalysisRead(ctx, d, m) -} - -func resourceRepositoryConfAnalysisDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - tflog.Debug(ctx, "Init resourceRepositoryConfAnalysisDelete") - c := m.(*client.Client) - - url := fmt.Sprintf("https://%s/v1/repos/%s/conf/analysis", c.ControlPlane, d.Get("repository_id")) - - if _, err := c.DoRequest(ctx, url, http.MethodDelete, nil); err != nil { - return utils.CreateError("Unable to delete conf analysis", fmt.Sprintf("%v", err)) - } - - tflog.Debug(ctx, "End resourceRepositoryConfAnalysisDelete") - - return diag.Diagnostics{} -} - -func getConfAnalysisDataFromResource(d *schema.ResourceData) (RepositoryConfAnalysisData, error) { - var logGroups []string - if logGroupsSet, ok := d.GetOk("log_groups"); ok { - for _, logGroupItem := range logGroupsSet.(*schema.Set).List() { - logGroups = append(logGroups, logGroupItem.(string)) - } - } - - var annotationGroups []string - if annotationGroupsSet, ok := d.GetOk("comment_annotation_groups"); ok { - for _, annotationGroupItem := range annotationGroupsSet.(*schema.Set).List() { - annotationGroups = append(annotationGroups, annotationGroupItem.(string)) - } - } - - return RepositoryConfAnalysisData{ - Config: UserFacingConfig{ - AlertOnViolation: d.Get("alert_on_violation").(bool), - BlockOnViolation: d.Get("block_on_violation").(bool), - DisableFilterAnalysis: d.Get("disable_filter_analysis").(bool), - DisablePreConfiguredAlerts: d.Get("disable_pre_configured_alerts").(bool), - EnableDataMasking: d.Get("enable_data_masking").(bool), - CommentAnnotationGroups: annotationGroups, - LogGroups: logGroups, - Redact: d.Get("redact").(string), - EnableDatasetRewrites: d.Get("enable_dataset_rewrites").(bool), - }, - }, nil -} - -func setConfAnalysisDataToResource(d *schema.ResourceData, resourceData RepositoryConfAnalysisData) { - logGroups := make([]interface{}, len(resourceData.Config.LogGroups)) - for index, logGroupItem := range resourceData.Config.LogGroups { - logGroups[index] = logGroupItem - } - logGroupsSet := schema.NewSet(schema.HashString, logGroups) - - annotationGroups := make([]interface{}, len(resourceData.Config.CommentAnnotationGroups)) - for index, annotationGroupItem := range resourceData.Config.CommentAnnotationGroups { - annotationGroups[index] = annotationGroupItem - } - annotationGroupsSet := schema.NewSet(schema.HashString, annotationGroups) - - d.Set("alert_on_violation", resourceData.Config.AlertOnViolation) - d.Set("block_on_violation", resourceData.Config.BlockOnViolation) - d.Set("comment_annotation_groups", annotationGroupsSet) - d.Set("disable_filter_analysis", resourceData.Config.DisableFilterAnalysis) - d.Set("disable_pre_configured_alerts", resourceData.Config.DisablePreConfiguredAlerts) - d.Set("enable_data_masking", resourceData.Config.EnableDataMasking) - d.Set("log_groups", logGroupsSet) - d.Set("redact", resourceData.Config.Redact) - d.Set("enable_dataset_rewrites", resourceData.Config.EnableDatasetRewrites) -} diff --git a/cyral/internal/repository/confanalysis/resource_cyral_repository_conf_analysis_test.go b/cyral/internal/repository/confanalysis/resource_test.go similarity index 100% rename from cyral/internal/repository/confanalysis/resource_cyral_repository_conf_analysis_test.go rename to cyral/internal/repository/confanalysis/resource_test.go diff --git a/cyral/internal/repository/confanalysis/schema_loader.go b/cyral/internal/repository/confanalysis/schema_loader.go new file mode 100644 index 00000000..5eec9304 --- /dev/null +++ b/cyral/internal/repository/confanalysis/schema_loader.go @@ -0,0 +1,26 @@ +package confanalysis + +import ( + "github.com/cyralinc/terraform-provider-cyral/cyral/core" +) + +type packageSchema struct { +} + +func (p *packageSchema) Name() string { + return "confanalysis" +} + +func (p *packageSchema) Schemas() []*core.SchemaDescriptor { + return []*core.SchemaDescriptor{ + { + Name: resourceName, + Type: core.ResourceSchemaType, + Schema: resourceSchema, + }, + } +} + +func PackageSchema() core.PackageSchema { + return &packageSchema{} +} diff --git a/cyral/provider/provider.go b/cyral/provider/provider.go index 6d79dfc7..8ed9257c 100644 --- a/cyral/provider/provider.go +++ b/cyral/provider/provider.go @@ -21,7 +21,6 @@ import ( "github.com/cyralinc/terraform-provider-cyral/cyral/internal/policy" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/policy/rule" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/regopolicy" - "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/confanalysis" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/confauth" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/network" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/role" @@ -163,7 +162,6 @@ func getResourceMap(ps []core.PackageSchema) map[string]*schema.Resource { schemaMap["cyral_policy_rule"] = rule.ResourcePolicyRule() schemaMap["cyral_rego_policy_instance"] = regopolicy.ResourceRegoPolicyInstance() schemaMap["cyral_repository_conf_auth"] = confauth.ResourceRepositoryConfAuth() - schemaMap["cyral_repository_conf_analysis"] = confanalysis.ResourceRepositoryConfAnalysis() schemaMap["cyral_repository_network_access_policy"] = network.ResourceRepositoryNetworkAccessPolicy() schemaMap["cyral_role"] = role.ResourceRole() schemaMap["cyral_role_sso_groups"] = role.ResourceRoleSSOGroups() diff --git a/cyral/provider/schema_loader.go b/cyral/provider/schema_loader.go index 3af6c113..3e0fb792 100644 --- a/cyral/provider/schema_loader.go +++ b/cyral/provider/schema_loader.go @@ -10,6 +10,7 @@ import ( "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/accessgateway" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/accessrules" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/binding" + "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/confanalysis" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/datamap" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/useraccount" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/samlcertificate" @@ -23,6 +24,7 @@ func packagesSchemas() []core.PackageSchema { accessgateway.PackageSchema(), accessrules.PackageSchema(), binding.PackageSchema(), + confanalysis.PackageSchema(), credentials.PackageSchema(), datalabel.PackageSchema(), datamap.PackageSchema(), From eff8f84841e3179cbec48518b6926cb05a63b9e8 Mon Sep 17 00:00:00 2001 From: Wilson de Carvalho <796900+wcmjunior@users.noreply.github.com> Date: Tue, 2 Apr 2024 21:26:26 -0700 Subject: [PATCH 08/22] Ignore local tf test files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index bc4c4646..a84ead0f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ .terraform .terraform.lock.hcl terraform.tfstate* +*.tf # Provider binary terraform-provider-cyral* From 1b2095eba3162fc9694c4bdbc0f2012f42551be2 Mon Sep 17 00:00:00 2001 From: Wilson de Carvalho <796900+wcmjunior@users.noreply.github.com> Date: Tue, 2 Apr 2024 21:26:41 -0700 Subject: [PATCH 09/22] Improve debugging messages and fix error handling --- cyral/client/client.go | 30 +++++++++------ cyral/core/resource.go | 34 ++++++++++------- ...esource_cyral_integration_idp_saml_test.go | 32 +++++++--------- .../internal/repository/confanalysis/model.go | 2 +- cyral/internal/sidecar/resource.go | 38 +++++++++++++++---- 5 files changed, 85 insertions(+), 51 deletions(-) diff --git a/cyral/client/client.go b/cyral/client/client.go index 30f2317f..9bd703c8 100644 --- a/cyral/client/client.go +++ b/cyral/client/client.go @@ -71,23 +71,26 @@ func New(clientID, clientSecret, controlPlane string, tlsSkipVerify bool) (*Clie // DoRequest calls the httpMethod informed and delivers the resourceData as a payload, // filling the response parameter (if not nil) with the response body. func (c *Client) DoRequest(ctx context.Context, url, httpMethod string, resourceData interface{}) ([]byte, error) { - tflog.Debug(ctx, "Init DoRequest") - tflog.Debug(ctx, fmt.Sprintf("Resource info: %#v", resourceData)) - tflog.Debug(ctx, fmt.Sprintf("%s URL: %s", httpMethod, url)) + tflog.Debug(ctx, "=> Init DoRequest") + tflog.Debug(ctx, fmt.Sprintf("==> Resource info: %#v", resourceData)) + tflog.Debug(ctx, fmt.Sprintf("==> %s URL: %s", httpMethod, url)) var req *http.Request var err error if resourceData != nil { payloadBytes, err := json.Marshal(resourceData) if err != nil { + tflog.Debug(ctx, "=> End DoRequest - Error") return nil, fmt.Errorf("failed to encode payload: %v", err) } payload := string(payloadBytes) tflog.Debug(ctx, fmt.Sprintf("%s payload: %s", httpMethod, payload)) if req, err = http.NewRequest(httpMethod, url, strings.NewReader(payload)); err != nil { + tflog.Debug(ctx, "=> End DoRequest - Error") return nil, fmt.Errorf("unable to create request; err: %v", err) } } else { if req, err = http.NewRequest(httpMethod, url, nil); err != nil { + tflog.Debug(ctx, "=> End DoRequest - Error") return nil, fmt.Errorf("unable to create request; err: %v", err) } } @@ -96,24 +99,27 @@ func (c *Client) DoRequest(ctx context.Context, url, httpMethod string, resource token := &oauth2.Token{} if c.TokenSource != nil { if token, err = c.TokenSource.Token(); err != nil { + tflog.Debug(ctx, "=> End DoRequest - Error") return nil, fmt.Errorf("unable to retrieve authorization token. error: %v", err) } else { - tflog.Debug(ctx, fmt.Sprintf("Token Type: %s", token.Type())) - tflog.Debug(ctx, fmt.Sprintf("Access Token: %s", redactContent(token.AccessToken))) - tflog.Debug(ctx, fmt.Sprintf("Token Expiry: %s", token.Expiry)) + tflog.Debug(ctx, fmt.Sprintf("==> Token Type: %s", token.Type())) + tflog.Debug(ctx, fmt.Sprintf("==> Access Token: %s", redactContent(token.AccessToken))) + tflog.Debug(ctx, fmt.Sprintf("==> Token Expiry: %s", token.Expiry)) req.Header.Add("Authorization", fmt.Sprintf("%s %s", token.Type(), token.AccessToken)) } } - tflog.Debug(ctx, fmt.Sprintf("Executing %s", httpMethod)) + tflog.Debug(ctx, fmt.Sprintf("==> Executing %s", httpMethod)) res, err := c.client.Do(req) if err != nil { + tflog.Debug(ctx, "=> End DoRequest - Error") return nil, fmt.Errorf("unable to execute request. Check the control plane address; err: %v", err) } defer res.Body.Close() if res.StatusCode == http.StatusConflict || (httpMethod == http.MethodPost && strings.Contains(strings.ToLower(res.Status), "already exists")) { + tflog.Debug(ctx, "=> End DoRequest - Error") return nil, NewHttpError( fmt.Sprintf("resource possibly exists in the control plane. Response status: %s", res.Status), res.StatusCode) @@ -121,6 +127,7 @@ func (c *Client) DoRequest(ctx context.Context, url, httpMethod string, resource body, err := ioutil.ReadAll(res.Body) if err != nil { + tflog.Debug(ctx, "=> End DoRequest - Error") return nil, NewHttpError( fmt.Sprintf("unable to read data from request body; err: %v", err), res.StatusCode) @@ -129,18 +136,19 @@ func (c *Client) DoRequest(ctx context.Context, url, httpMethod string, resource // Redact token before logging the request req.Header.Set("Authorization", fmt.Sprintf("%s %s", token.Type(), redactContent(token.AccessToken))) - tflog.Debug(ctx, fmt.Sprintf("Request: %#v", req)) - tflog.Debug(ctx, fmt.Sprintf("Response status code: %d", res.StatusCode)) - tflog.Debug(ctx, fmt.Sprintf("Response body: %s", string(body))) + tflog.Debug(ctx, fmt.Sprintf("==> Request: %#v", req)) + tflog.Debug(ctx, fmt.Sprintf("==> Response status code: %d", res.StatusCode)) + tflog.Debug(ctx, fmt.Sprintf("==> Response body: %s", string(body))) if !(res.StatusCode >= 200 && res.StatusCode < 300) { + tflog.Debug(ctx, "=> End DoRequest - Error") return nil, NewHttpError( fmt.Sprintf("error executing %s request; status code: %d; body: %q", httpMethod, res.StatusCode, body), res.StatusCode) } - tflog.Debug(ctx, "End DoRequest") + tflog.Debug(ctx, "=> End DoRequest - Success") return body, nil } diff --git a/cyral/core/resource.go b/cyral/core/resource.go index 8a0e4a59..ac4a121f 100644 --- a/cyral/core/resource.go +++ b/cyral/core/resource.go @@ -109,57 +109,63 @@ func DeleteResource(deleteConfig ResourceOperationConfig) schema.DeleteContextFu func handleRequests(operations []ResourceOperationConfig) func(context.Context, *schema.ResourceData, any) diag.Diagnostics { return func(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { for _, operation := range operations { - tflog.Debug(ctx, fmt.Sprintf("Init %s - %s", operation.ResourceName, operation.Type)) + tflog.Debug(ctx, fmt.Sprintf("Init handleRequests to %s %s %s", operation.Type, operation.ResourceType, operation.ResourceName)) c := m.(*client.Client) var resourceData SchemaReader if operation.SchemaReaderFactory != nil { + tflog.Debug(ctx, "=> Calling SchemaReaderFactory") if resourceData = operation.SchemaReaderFactory(); resourceData != nil { - tflog.Debug(ctx, fmt.Sprintf("Calling ReadFromSchema. Schema: %#v", d)) + tflog.Debug(ctx, fmt.Sprintf("=> Calling ReadFromSchema. Schema: %#v", d)) if err := resourceData.ReadFromSchema(d); err != nil { + tflog.Debug(ctx, fmt.Sprintf("End handleRequests to %s %s %s - Error: %s", operation.Type, operation.ResourceType, operation.ResourceName, err.Error())) return utils.CreateError( - fmt.Sprintf("Unable to %s resource %s", operation.Type, operation.ResourceName), + fmt.Sprintf("Unable to %s %s %s", operation.Type, operation.ResourceType, operation.ResourceName), err.Error(), ) } - tflog.Debug(ctx, fmt.Sprintf("Succesful call to ReadFromSchema. resourceData: %#v", resourceData)) + tflog.Debug(ctx, fmt.Sprintf("=> Succesful call to ReadFromSchema. resourceData: %#v", resourceData)) } } url := operation.URLFactory(d, c) body, err := c.DoRequest(ctx, url, operation.HttpMethod, resourceData) - if operation.RequestErrorHandler != nil { + if err != nil && operation.RequestErrorHandler != nil { + tflog.Debug(ctx, "=> Calling operation.RequestErrorHandler.HandleError") err = operation.RequestErrorHandler.HandleError(ctx, d, c, err) } if err != nil { + tflog.Debug(ctx, fmt.Sprintf("End handleRequests to %s %s %s - Error: %s", operation.Type, operation.ResourceType, operation.ResourceName, err.Error())) return utils.CreateError( - fmt.Sprintf("Unable to %s resource %s", operation.Type, operation.ResourceName), + fmt.Sprintf("Unable to %s %s %s", operation.Type, operation.ResourceType, operation.ResourceName), err.Error(), ) } if operation.SchemaWriterFactory == nil { - tflog.Debug(ctx, fmt.Sprintf("No SchemaWriterFactory found to %s resource %s", operation.Type, operation.ResourceName)) - } else { + tflog.Debug(ctx, "=> No SchemaWriterFactory found.") + } else if body != nil { if responseData := operation.SchemaWriterFactory(d); responseData != nil { - tflog.Debug(ctx, fmt.Sprintf("NewResponseData function call performed. d: %#v", d)) + tflog.Debug(ctx, fmt.Sprintf("=> operation.SchemaWriterFactory function call performed. d: %#v", d)) if err := json.Unmarshal(body, responseData); err != nil { + tflog.Debug(ctx, fmt.Sprintf("End handleRequests to %s %s %s - Error: %s", operation.Type, operation.ResourceType, operation.ResourceName, err.Error())) return utils.CreateError("Unable to unmarshall JSON", err.Error()) } - tflog.Debug(ctx, fmt.Sprintf("Response body (unmarshalled): %#v", responseData)) - tflog.Debug(ctx, fmt.Sprintf("Calling WriteToSchema: responseData: %#v", responseData)) + tflog.Debug(ctx, fmt.Sprintf("=> Response body (unmarshalled): %#v", responseData)) + tflog.Debug(ctx, fmt.Sprintf("=> Calling WriteToSchema: responseData: %#v", responseData)) if err := responseData.WriteToSchema(d); err != nil { + tflog.Debug(ctx, fmt.Sprintf("End handleRequests to %s %s %s - Error: %s", operation.Type, operation.ResourceType, operation.ResourceName, err.Error())) return utils.CreateError( - fmt.Sprintf("Unable to %s resource %s", operation.Type, operation.ResourceName), + fmt.Sprintf("Unable to %s %s %s", operation.Type, operation.ResourceType, operation.ResourceName), err.Error(), ) } - tflog.Debug(ctx, fmt.Sprintf("Succesful call to WriteToSchema. d: %#v", d)) + tflog.Debug(ctx, fmt.Sprintf("=> Succesful call to WriteToSchema. d: %#v", d)) } } - tflog.Debug(ctx, fmt.Sprintf("End %s - %s", operation.ResourceName, operation.Type)) + tflog.Debug(ctx, fmt.Sprintf("End handleRequests to %s %s %s - Success", operation.Type, operation.ResourceType, operation.ResourceName)) } return diag.Diagnostics{} } diff --git a/cyral/internal/integration/idpsaml/resource_cyral_integration_idp_saml_test.go b/cyral/internal/integration/idpsaml/resource_cyral_integration_idp_saml_test.go index 44c6f84f..b7904acb 100644 --- a/cyral/internal/integration/idpsaml/resource_cyral_integration_idp_saml_test.go +++ b/cyral/internal/integration/idpsaml/resource_cyral_integration_idp_saml_test.go @@ -46,12 +46,8 @@ func TestAccIntegrationIdPSAMLResource(t *testing.T) { "upgrade_test", samlMetadataDocumentSample("fakeCertificateUpdated")) updatedAgainConfig, updatedAgainChecks := setupIntegrationIdPSAMLTest( "upgrade_test", samlMetadataDocumentSample("fakeCertificateUpdated")) - - println("========> initialConfig: " + initialConfig) - println("========> updatedConfig: " + updatedConfig) - println("========> updatedAgainConfig: " + updatedAgainConfig) - // newConfig, newChecks := setupIntegrationIdPSAMLTest( - // "new_test", samlMetadataDocumentSample("fakeCertificateNew")) + newConfig, newChecks := setupIntegrationIdPSAMLTest( + "new_test", samlMetadataDocumentSample("fakeCertificateNew")) resource.ParallelTest(t, resource.TestCase{ ProviderFactories: provider.ProviderFactories, @@ -72,18 +68,18 @@ func TestAccIntegrationIdPSAMLResource(t *testing.T) { // If user runs apply again, it should work. Check: updatedAgainChecks, }, - // { - // Config: newConfig, - // // When a new SAML draft and a new integration - // // are created, there should be no no problem. - // Check: newChecks, - // }, - // { - // ImportState: true, - // ImportStateVerify: true, - // ImportStateVerifyIgnore: []string{"idp_metadata_xml", "saml_draft_id"}, - // ResourceName: "cyral_integration_idp_saml.new_test", - // }, + { + Config: newConfig, + // When a new SAML draft and a new integration + // are created, there should be no no problem. + Check: newChecks, + }, + { + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"idp_metadata_xml", "saml_draft_id"}, + ResourceName: "cyral_integration_idp_saml.new_test", + }, }, }) } diff --git a/cyral/internal/repository/confanalysis/model.go b/cyral/internal/repository/confanalysis/model.go index 386d3eaa..e304c76a 100644 --- a/cyral/internal/repository/confanalysis/model.go +++ b/cyral/internal/repository/confanalysis/model.go @@ -7,7 +7,7 @@ import ( // TODO: v2 of this API should either return the repository ID // or something else as an ID. Currently it accepts a `UserConfig` // for the PUT payload, but returns a `RepositoryConfAnalysisData`. -// This makes the whole API confusing. +// This makes the whole API utilization quite confusing. type RepositoryConfAnalysisData struct { UserConfig UserConfig `json:"userConfig"` diff --git a/cyral/internal/sidecar/resource.go b/cyral/internal/sidecar/resource.go index e68dfb95..82bc44de 100644 --- a/cyral/internal/sidecar/resource.go +++ b/cyral/internal/sidecar/resource.go @@ -13,6 +13,7 @@ import ( "github.com/cyralinc/terraform-provider-cyral/cyral/client" "github.com/cyralinc/terraform-provider-cyral/cyral/core" "github.com/cyralinc/terraform-provider-cyral/cyral/core/types/operationtype" + "github.com/cyralinc/terraform-provider-cyral/cyral/core/types/resourcetype" ) // Currently, the sidecar API always returns a status code of 500 for every error, @@ -31,21 +32,26 @@ func (h *SidecarDeleteIgnoreHttpNotFound) HandleError( _ *client.Client, err error, ) error { - httpError, ok := err.(*client.HttpError) - if !ok || httpError.StatusCode != http.StatusNotFound { - return err - } + tflog.Debug(ctx, "==> Init HandleError SidecarDeleteIgnoreHttpNotFound") matched, regexpError := regexp.MatchString( "NotFound", err.Error(), ) if regexpError == nil && matched { - tflog.Debug(ctx, fmt.Sprintf("Sidecar not found. Skipping deletion. Error: %v", err)) + tflog.Debug(ctx, fmt.Sprintf("===> %s not found. Skipping deletion. Error: %v", resourceName, err)) r.SetId("") + tflog.Debug(ctx, "==> End HandleError SidecarDeleteIgnoreHttpNotFound - Success") return nil } + httpError, ok := err.(*client.HttpError) + if !ok || httpError.StatusCode != http.StatusNotFound { + tflog.Debug(ctx, "===> End HandleError SidecarDeleteIgnoreHttpNotFound - Error") + return err + } + + tflog.Debug(ctx, "==> End HandleError SidecarDeleteIgnoreHttpNotFound - Success") return nil } @@ -58,13 +64,27 @@ func (h *SidecarReadIgnoreHttpNotFound) HandleError( _ *client.Client, err error, ) error { + tflog.Debug(ctx, "==> Init HandleError SidecarReadIgnoreHttpNotFound") + + matched, regexpError := regexp.MatchString( + "NotFound", + err.Error(), + ) + if regexpError == nil && matched { + tflog.Debug(ctx, fmt.Sprintf("===> %s not found. Marking resource for recreation.", resourceName)) + r.SetId("") + tflog.Debug(ctx, "==> End HandleError SidecarReadIgnoreHttpNotFound - Success") + return nil + } + httpError, ok := err.(*client.HttpError) if !ok || httpError.StatusCode != http.StatusNotFound { + tflog.Debug(ctx, "===> End HandleError SidecarReadIgnoreHttpNotFound - Error") return err } - r.SetId("") - tflog.Debug(ctx, resourceName+" not found. Marking resource for recreation.") + tflog.Debug(ctx, "==> End HandleError SidecarReadIgnoreHttpNotFound - - Success") + return nil } @@ -74,6 +94,7 @@ var urlFactory = func(d *schema.ResourceData, c *client.Client) string { var readSidecarConfig = core.ResourceOperationConfig{ ResourceName: resourceName, + ResourceType: resourcetype.Resource, Type: operationtype.Read, HttpMethod: http.MethodGet, URLFactory: urlFactory, @@ -87,6 +108,7 @@ func resourceSchema() *schema.Resource { CreateContext: core.CreateResource( core.ResourceOperationConfig{ ResourceName: resourceName, + ResourceType: resourcetype.Resource, Type: operationtype.Create, HttpMethod: http.MethodPost, URLFactory: func(d *schema.ResourceData, c *client.Client) string { @@ -101,6 +123,7 @@ func resourceSchema() *schema.Resource { UpdateContext: core.UpdateResource( core.ResourceOperationConfig{ ResourceName: resourceName, + ResourceType: resourcetype.Resource, Type: operationtype.Update, HttpMethod: http.MethodPut, URLFactory: urlFactory, @@ -112,6 +135,7 @@ func resourceSchema() *schema.Resource { DeleteContext: core.DeleteResource( core.ResourceOperationConfig{ ResourceName: resourceName, + ResourceType: resourcetype.Resource, Type: operationtype.Delete, HttpMethod: http.MethodDelete, URLFactory: urlFactory, From 759936a7868ce6e2b9e2d1c3bc7acac17e8398d7 Mon Sep 17 00:00:00 2001 From: Wilson de Carvalho <796900+wcmjunior@users.noreply.github.com> Date: Wed, 3 Apr 2024 00:00:09 -0700 Subject: [PATCH 10/22] Improve API error handling --- cyral/core/default_context_handler.go | 6 +- cyral/core/error_handlers.go | 65 +++++++++-- cyral/internal/datalabel/resource.go | 2 +- .../resource_cyral_integration_idp_saml.go | 2 +- ...source_cyral_integration_idp_saml_draft.go | 2 +- .../resource_cyral_integration_logging.go | 2 +- .../policy/rule/resource_cyral_policy_rule.go | 2 +- .../resource_cyral_rego_policy_instance.go | 2 +- .../repository/accessgateway/resource.go | 4 +- .../repository/accessrules/resource.go | 4 +- .../repository/confanalysis/resource.go | 2 +- .../resource_cyral_repository_conf_auth.go | 2 +- cyral/internal/repository/datamap/resource.go | 2 +- ..._cyral_repository_network_access_policy.go | 4 +- .../repository/useraccount/resource.go | 2 +- .../role/resource_cyral_role_sso_groups.go | 2 +- .../resource_cyral_service_account.go | 2 +- .../resource_cyral_sidecar_listener.go | 2 +- cyral/internal/sidecar/resource.go | 107 ++++-------------- cyral/provider/provider.go | 2 - cyral/provider/schema_loader.go | 1 + 21 files changed, 98 insertions(+), 121 deletions(-) diff --git a/cyral/core/default_context_handler.go b/cyral/core/default_context_handler.go index d7419a02..e699b6e1 100644 --- a/cyral/core/default_context_handler.go +++ b/cyral/core/default_context_handler.go @@ -73,10 +73,8 @@ func (dch DefaultContextHandler) defaultOperationHandler( } var errorHandler RequestErrorHandler - if httpMethod == http.MethodGet { - errorHandler = &ReadIgnoreHttpNotFound{ResName: dch.ResourceName} - } else if httpMethod == http.MethodDelete { - errorHandler = &DeleteIgnoreHttpNotFound{ResName: dch.ResourceName} + if httpMethod == http.MethodGet || httpMethod == http.MethodDelete { + errorHandler = &IgnoreHttpNotFound{ResName: dch.ResourceName} } result := ResourceOperationConfig{ ResourceName: dch.ResourceName, diff --git a/cyral/core/error_handlers.go b/cyral/core/error_handlers.go index c28f6e99..2544be36 100644 --- a/cyral/core/error_handlers.go +++ b/cyral/core/error_handlers.go @@ -4,46 +4,93 @@ import ( "context" "fmt" "net/http" + "regexp" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/cyralinc/terraform-provider-cyral/cyral/client" + "github.com/cyralinc/terraform-provider-cyral/cyral/core/types/operationtype" ) -type DeleteIgnoreHttpNotFound struct { - ResName string +type IgnoreByHttpStatusCode struct { + ResName string + HttpStatusCode int + OperationType operationtype.OperationType } -func (h *DeleteIgnoreHttpNotFound) HandleError( +func (h *IgnoreByHttpStatusCode) HandleError( ctx context.Context, - _ *schema.ResourceData, + r *schema.ResourceData, _ *client.Client, err error, ) error { + tflog.Debug(ctx, "==> Init HandleError core.IgnoreByHttpStatusCode") httpError, ok := err.(*client.HttpError) - if !ok || httpError.StatusCode != http.StatusNotFound { + if !ok || httpError.StatusCode != h.HttpStatusCode { + tflog.Debug(ctx, fmt.Sprintf("==> End HandleError core.IgnoreByHttpStatusCode - Did not find a %d, thus returning the original error", h.HttpStatusCode)) return err } - tflog.Debug(ctx, fmt.Sprintf("%s not found. Skipping deletion.", h.ResName)) + r.SetId("") + tflog.Debug(ctx, fmt.Sprintf( + "==> End HandleError core.IgnoreHttpNotFound - %s not found. Marking resource for recreation or deletion.", h.ResName)) return nil } -type ReadIgnoreHttpNotFound struct { +type IgnoreNotFoundByMessage struct { + ResName string + MessageMatches string + OperationType operationtype.OperationType +} + +func (h *IgnoreNotFoundByMessage) HandleError( + ctx context.Context, + r *schema.ResourceData, + _ *client.Client, + err error, +) error { + tflog.Debug(ctx, "==> Init HandleError core.IgnoreNotFoundByMessage") + + matched, regexpError := regexp.MatchString( + h.MessageMatches, + err.Error(), + ) + + if regexpError != nil { + return fmt.Errorf("regex failed to compile trying to match '%s' in '%w'. Error: %w", + h.MessageMatches, err, regexpError) + } + + if matched { + tflog.Debug(ctx, fmt.Sprintf("===> %s not found. Skipping %s operation. Error: %v", + h.ResName, h.OperationType, err)) + r.SetId("") + tflog.Debug(ctx, "==> End HandleError core.IgnoreNotFoundByMessage - Success") + return nil + } + + tflog.Debug(ctx, "==> End HandleError core.IgnoreNotFoundByMessage - No match found, thus returning the original error") + return err +} + +type IgnoreHttpNotFound struct { ResName string } -func (h *ReadIgnoreHttpNotFound) HandleError( +func (h *IgnoreHttpNotFound) HandleError( ctx context.Context, r *schema.ResourceData, _ *client.Client, err error, ) error { + tflog.Debug(ctx, "==> Init HandleError core.IgnoreHttpNotFound") httpError, ok := err.(*client.HttpError) if !ok || httpError.StatusCode != http.StatusNotFound { + tflog.Debug(ctx, "==> End HandleError core.IgnoreHttpNotFound - Did not find a 404, thus returning the original error") return err } r.SetId("") - tflog.Debug(ctx, fmt.Sprintf("%s not found. Marking resource for recreation.", h.ResName)) + tflog.Debug(ctx, fmt.Sprintf( + "==> End HandleError core.IgnoreHttpNotFound - %s not found. Marking resource for recreation or deletion.", h.ResName)) return nil } diff --git a/cyral/internal/datalabel/resource.go b/cyral/internal/datalabel/resource.go index 332a8c65..e2b49d1e 100644 --- a/cyral/internal/datalabel/resource.go +++ b/cyral/internal/datalabel/resource.go @@ -123,5 +123,5 @@ var readDataLabelConfig = core.ResourceOperationConfig{ d.Get("name").(string)) }, SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &DataLabel{} }, - RequestErrorHandler: &core.ReadIgnoreHttpNotFound{ResName: "Data Label"}, + RequestErrorHandler: &core.IgnoreHttpNotFound{ResName: "Data Label"}, } diff --git a/cyral/internal/integration/idpsaml/resource_cyral_integration_idp_saml.go b/cyral/internal/integration/idpsaml/resource_cyral_integration_idp_saml.go index 8d3523e8..a3efdf20 100644 --- a/cyral/internal/integration/idpsaml/resource_cyral_integration_idp_saml.go +++ b/cyral/internal/integration/idpsaml/resource_cyral_integration_idp_saml.go @@ -93,7 +93,7 @@ func ReadGenericSAMLConfig() core.ResourceOperationConfig { return fmt.Sprintf("https://%s/v1/integrations/generic-saml/sso/%s", c.ControlPlane, d.Id()) }, SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &ReadGenericSAMLResponse{} }, - RequestErrorHandler: &core.ReadIgnoreHttpNotFound{ResName: "Generic SAML"}, + RequestErrorHandler: &core.IgnoreHttpNotFound{ResName: "Generic SAML"}, } } diff --git a/cyral/internal/integration/idpsaml/resource_cyral_integration_idp_saml_draft.go b/cyral/internal/integration/idpsaml/resource_cyral_integration_idp_saml_draft.go index 9e80cfde..61b84443 100644 --- a/cyral/internal/integration/idpsaml/resource_cyral_integration_idp_saml_draft.go +++ b/cyral/internal/integration/idpsaml/resource_cyral_integration_idp_saml_draft.go @@ -122,7 +122,7 @@ func DeleteGenericSAMLDraftConfig() core.ResourceOperationConfig { URLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/integrations/generic-saml/drafts/%s", c.ControlPlane, d.Id()) }, - RequestErrorHandler: &core.DeleteIgnoreHttpNotFound{ResName: "SAML draft"}, + RequestErrorHandler: &core.IgnoreHttpNotFound{ResName: "SAML draft"}, } } diff --git a/cyral/internal/integration/logging/resource_cyral_integration_logging.go b/cyral/internal/integration/logging/resource_cyral_integration_logging.go index e860e2d7..d62934d3 100644 --- a/cyral/internal/integration/logging/resource_cyral_integration_logging.go +++ b/cyral/internal/integration/logging/resource_cyral_integration_logging.go @@ -193,7 +193,7 @@ var ReadLoggingIntegration = core.ResourceOperationConfig{ return fmt.Sprintf(loggingApiUrl, c.ControlPlane, d.Id()) }, SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &LoggingIntegration{} }, - RequestErrorHandler: &core.ReadIgnoreHttpNotFound{ResName: "Integration logging"}, + RequestErrorHandler: &core.IgnoreHttpNotFound{ResName: "Integration logging"}, } func UpdateLoggingIntegration() core.ResourceOperationConfig { diff --git a/cyral/internal/policy/rule/resource_cyral_policy_rule.go b/cyral/internal/policy/rule/resource_cyral_policy_rule.go index e777ab5f..4b0e0f80 100644 --- a/cyral/internal/policy/rule/resource_cyral_policy_rule.go +++ b/cyral/internal/policy/rule/resource_cyral_policy_rule.go @@ -420,7 +420,7 @@ func policyRuleDeleteConfig() core.ResourceOperationConfig { return fmt.Sprintf("https://%s/v1/policies/%s/rules/%s", c.ControlPlane, policyID, policyRuleID) }, - RequestErrorHandler: &core.DeleteIgnoreHttpNotFound{ResName: "Policy Rule"}, + RequestErrorHandler: &core.IgnoreHttpNotFound{ResName: "Policy Rule"}, } } diff --git a/cyral/internal/regopolicy/resource_cyral_rego_policy_instance.go b/cyral/internal/regopolicy/resource_cyral_rego_policy_instance.go index 367f9482..865514e8 100644 --- a/cyral/internal/regopolicy/resource_cyral_rego_policy_instance.go +++ b/cyral/internal/regopolicy/resource_cyral_rego_policy_instance.go @@ -50,7 +50,7 @@ var ( SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &RegoPolicyInstance{} }, - RequestErrorHandler: &core.ReadIgnoreHttpNotFound{ResName: "Rego policy instance"}, + RequestErrorHandler: &core.IgnoreHttpNotFound{ResName: "Rego policy instance"}, } regoPolicyChangeInformation = &schema.Resource{ diff --git a/cyral/internal/repository/accessgateway/resource.go b/cyral/internal/repository/accessgateway/resource.go index fd46267f..760991a4 100644 --- a/cyral/internal/repository/accessgateway/resource.go +++ b/cyral/internal/repository/accessgateway/resource.go @@ -28,7 +28,7 @@ var readRepositoryAccessGatewayConfig = core.ResourceOperationConfig{ SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &AccessGateway{} }, - RequestErrorHandler: &core.ReadIgnoreHttpNotFound{ResName: resourceName}, + RequestErrorHandler: &core.IgnoreHttpNotFound{ResName: resourceName}, } func resourceSchema() *schema.Resource { @@ -61,7 +61,7 @@ func resourceSchema() *schema.Resource { Type: operationtype.Delete, HttpMethod: http.MethodDelete, URLFactory: urlFactory, - RequestErrorHandler: &core.DeleteIgnoreHttpNotFound{ResName: resourceName}, + RequestErrorHandler: &core.IgnoreHttpNotFound{ResName: resourceName}, }, ), diff --git a/cyral/internal/repository/accessrules/resource.go b/cyral/internal/repository/accessrules/resource.go index fef95c73..8f8fd8e2 100644 --- a/cyral/internal/repository/accessrules/resource.go +++ b/cyral/internal/repository/accessrules/resource.go @@ -28,7 +28,7 @@ var readRepositoryAccessRulesConfig = core.ResourceOperationConfig{ SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &AccessRulesResponse{} }, - RequestErrorHandler: &core.ReadIgnoreHttpNotFound{ResName: "Repository access rule"}, + RequestErrorHandler: &core.IgnoreHttpNotFound{ResName: "Repository access rule"}, } func resourceSchema() *schema.Resource { @@ -78,7 +78,7 @@ func resourceSchema() *schema.Resource { userAccountID, ) }, - RequestErrorHandler: &core.DeleteIgnoreHttpNotFound{ResName: resourceName}, + RequestErrorHandler: &core.IgnoreHttpNotFound{ResName: resourceName}, }, ), diff --git a/cyral/internal/repository/confanalysis/resource.go b/cyral/internal/repository/confanalysis/resource.go index e52fc59e..ddebe99e 100644 --- a/cyral/internal/repository/confanalysis/resource.go +++ b/cyral/internal/repository/confanalysis/resource.go @@ -49,7 +49,7 @@ func resourceSchema() *schema.Resource { HttpMethod: http.MethodGet, URLFactory: urlFactory, SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &RepositoryConfAnalysisData{} }, - RequestErrorHandler: &core.ReadIgnoreHttpNotFound{}, + RequestErrorHandler: &core.IgnoreHttpNotFound{}, }, ), ReadContext: resourceContextHandler.ReadContext(), diff --git a/cyral/internal/repository/confauth/resource_cyral_repository_conf_auth.go b/cyral/internal/repository/confauth/resource_cyral_repository_conf_auth.go index 2d8bbb29..f7f48a99 100644 --- a/cyral/internal/repository/confauth/resource_cyral_repository_conf_auth.go +++ b/cyral/internal/repository/confauth/resource_cyral_repository_conf_auth.go @@ -163,7 +163,7 @@ func ReadConfAuthConfig() core.ResourceOperationConfig { return fmt.Sprintf(repositoryConfAuthURLFormat, c.ControlPlane, d.Get("repository_id")) }, SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &ReadRepositoryConfAuthResponse{} }, - RequestErrorHandler: &core.ReadIgnoreHttpNotFound{ResName: "Repository conf auth"}, + RequestErrorHandler: &core.IgnoreHttpNotFound{ResName: "Repository conf auth"}, } } diff --git a/cyral/internal/repository/datamap/resource.go b/cyral/internal/repository/datamap/resource.go index 11f228fd..ce1e4d64 100644 --- a/cyral/internal/repository/datamap/resource.go +++ b/cyral/internal/repository/datamap/resource.go @@ -122,5 +122,5 @@ var readDataMapConfig = core.ResourceOperationConfig{ d.Get("repository_id").(string)) }, SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &DataMap{} }, - RequestErrorHandler: &core.ReadIgnoreHttpNotFound{ResName: "Data Map"}, + RequestErrorHandler: &core.IgnoreHttpNotFound{ResName: "Data Map"}, } diff --git a/cyral/internal/repository/network/resource_cyral_repository_network_access_policy.go b/cyral/internal/repository/network/resource_cyral_repository_network_access_policy.go index a78ec4b4..289cea6d 100644 --- a/cyral/internal/repository/network/resource_cyral_repository_network_access_policy.go +++ b/cyral/internal/repository/network/resource_cyral_repository_network_access_policy.go @@ -124,7 +124,7 @@ func readRepositoryNetworkAccessPolicy() core.ResourceOperationConfig { c.ControlPlane, d.Get("repository_id")) }, SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &NetworkAccessPolicy{} }, - RequestErrorHandler: &core.ReadIgnoreHttpNotFound{ResName: "Repository network access policy"}, + RequestErrorHandler: &core.IgnoreHttpNotFound{ResName: "Repository network access policy"}, } } @@ -151,7 +151,7 @@ func deleteRepositoryNetworkAccessPolicy() core.ResourceOperationConfig { return fmt.Sprintf(repositoryNetworkAccessPolicyURLFormat, c.ControlPlane, d.Get("repository_id")) }, - RequestErrorHandler: &core.DeleteIgnoreHttpNotFound{ResName: "Network Access Policy"}, + RequestErrorHandler: &core.IgnoreHttpNotFound{ResName: "Network Access Policy"}, } } diff --git a/cyral/internal/repository/useraccount/resource.go b/cyral/internal/repository/useraccount/resource.go index a0c02135..bc684edd 100644 --- a/cyral/internal/repository/useraccount/resource.go +++ b/cyral/internal/repository/useraccount/resource.go @@ -46,7 +46,7 @@ var readRepositoryUserAccountConfig = core.ResourceOperationConfig{ SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &UserAccountResource{} }, - RequestErrorHandler: &core.ReadIgnoreHttpNotFound{ResName: "User account"}, + RequestErrorHandler: &core.IgnoreHttpNotFound{ResName: "User account"}, } func resourceSchema() *schema.Resource { diff --git a/cyral/internal/role/resource_cyral_role_sso_groups.go b/cyral/internal/role/resource_cyral_role_sso_groups.go index 7ffac954..84a888f1 100644 --- a/cyral/internal/role/resource_cyral_role_sso_groups.go +++ b/cyral/internal/role/resource_cyral_role_sso_groups.go @@ -143,7 +143,7 @@ var readRoleSSOGroupsConfig = core.ResourceOperationConfig{ d.Get("role_id").(string)) }, SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &RoleSSOGroupsReadResponse{} }, - RequestErrorHandler: &core.ReadIgnoreHttpNotFound{ResName: "Role SSO groups"}, + RequestErrorHandler: &core.IgnoreHttpNotFound{ResName: "Role SSO groups"}, } var deleteRoleSSOGroupsConfig = core.ResourceOperationConfig{ diff --git a/cyral/internal/serviceaccount/resource_cyral_service_account.go b/cyral/internal/serviceaccount/resource_cyral_service_account.go index 86be3fe2..8d0c7da4 100644 --- a/cyral/internal/serviceaccount/resource_cyral_service_account.go +++ b/cyral/internal/serviceaccount/resource_cyral_service_account.go @@ -33,7 +33,7 @@ var ( SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &ServiceAccount{} }, - RequestErrorHandler: &core.ReadIgnoreHttpNotFound{ResName: "Service account"}, + RequestErrorHandler: &core.IgnoreHttpNotFound{ResName: "Service account"}, } ) diff --git a/cyral/internal/sidecar/listener/resource_cyral_sidecar_listener.go b/cyral/internal/sidecar/listener/resource_cyral_sidecar_listener.go index c8cd0abc..833f722f 100644 --- a/cyral/internal/sidecar/listener/resource_cyral_sidecar_listener.go +++ b/cyral/internal/sidecar/listener/resource_cyral_sidecar_listener.go @@ -71,7 +71,7 @@ var ReadSidecarListenersConfig = core.ResourceOperationConfig{ d.Get(utils.ListenerIDKey).(string)) }, SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &ReadSidecarListenerAPIResponse{} }, - RequestErrorHandler: &core.ReadIgnoreHttpNotFound{ResName: "Sidecar listener"}, + RequestErrorHandler: &core.IgnoreHttpNotFound{ResName: "Sidecar listener"}, } type ReadSidecarListenerAPIResponse struct { diff --git a/cyral/internal/sidecar/resource.go b/cyral/internal/sidecar/resource.go index 82bc44de..c90fd96c 100644 --- a/cyral/internal/sidecar/resource.go +++ b/cyral/internal/sidecar/resource.go @@ -1,12 +1,9 @@ package sidecar import ( - "context" "fmt" "net/http" - "regexp" - "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" @@ -16,95 +13,27 @@ import ( "github.com/cyralinc/terraform-provider-cyral/cyral/core/types/resourcetype" ) -// Currently, the sidecar API always returns a status code of 500 for every error, -// so its not possible to distinguish if the error returned is related to -// a 404 Not Found or not by its status code. This way, a workaround for that is to -// check if the error message matches a 'Failed to extract info for wrapper' message, -// since thats the current message returned when the sidecar is not found. Once this -// issue is fixed in the sidecar API, we should be able to use core.DefaultContextHandler -// and remove SidecarDeleteIgnoreHttpNotFound and SidecarReadIgnoreHttpNotFound. -type SidecarDeleteIgnoreHttpNotFound struct { -} - -func (h *SidecarDeleteIgnoreHttpNotFound) HandleError( - ctx context.Context, - r *schema.ResourceData, - _ *client.Client, - err error, -) error { - tflog.Debug(ctx, "==> Init HandleError SidecarDeleteIgnoreHttpNotFound") - - matched, regexpError := regexp.MatchString( - "NotFound", - err.Error(), - ) - if regexpError == nil && matched { - tflog.Debug(ctx, fmt.Sprintf("===> %s not found. Skipping deletion. Error: %v", resourceName, err)) - r.SetId("") - tflog.Debug(ctx, "==> End HandleError SidecarDeleteIgnoreHttpNotFound - Success") - return nil - } - - httpError, ok := err.(*client.HttpError) - if !ok || httpError.StatusCode != http.StatusNotFound { - tflog.Debug(ctx, "===> End HandleError SidecarDeleteIgnoreHttpNotFound - Error") - return err - } - - tflog.Debug(ctx, "==> End HandleError SidecarDeleteIgnoreHttpNotFound - Success") - return nil -} - -type SidecarReadIgnoreHttpNotFound struct { -} - -func (h *SidecarReadIgnoreHttpNotFound) HandleError( - ctx context.Context, - r *schema.ResourceData, - _ *client.Client, - err error, -) error { - tflog.Debug(ctx, "==> Init HandleError SidecarReadIgnoreHttpNotFound") - - matched, regexpError := regexp.MatchString( - "NotFound", - err.Error(), - ) - if regexpError == nil && matched { - tflog.Debug(ctx, fmt.Sprintf("===> %s not found. Marking resource for recreation.", resourceName)) - r.SetId("") - tflog.Debug(ctx, "==> End HandleError SidecarReadIgnoreHttpNotFound - Success") - return nil - } - - httpError, ok := err.(*client.HttpError) - if !ok || httpError.StatusCode != http.StatusNotFound { - tflog.Debug(ctx, "===> End HandleError SidecarReadIgnoreHttpNotFound - Error") - return err - } - - tflog.Debug(ctx, "==> End HandleError SidecarReadIgnoreHttpNotFound - - Success") - - return nil -} - var urlFactory = func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/sidecars/%s", c.ControlPlane, d.Id()) } -var readSidecarConfig = core.ResourceOperationConfig{ +var readConfig = core.ResourceOperationConfig{ ResourceName: resourceName, ResourceType: resourcetype.Resource, Type: operationtype.Read, HttpMethod: http.MethodGet, URLFactory: urlFactory, SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &SidecarData{} }, - RequestErrorHandler: &SidecarReadIgnoreHttpNotFound{}, + RequestErrorHandler: &core.IgnoreNotFoundByMessage{ + ResName: resourceName, + MessageMatches: "NotFound", + OperationType: operationtype.Read, + }, } func resourceSchema() *schema.Resource { return &schema.Resource{ - Description: "Manages [sidecars](https://cyral.com/docs/sidecars/sidecar-manage).", + Description: "Manages [sidecars](https://cyral.com/docs/sidecars/manage).", CreateContext: core.CreateResource( core.ResourceOperationConfig{ ResourceName: resourceName, @@ -117,9 +46,9 @@ func resourceSchema() *schema.Resource { SchemaReaderFactory: func() core.SchemaReader { return &SidecarData{} }, SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &core.IDBasedResponse{} }, }, - readSidecarConfig, + readConfig, ), - ReadContext: core.ReadResource(readSidecarConfig), + ReadContext: core.ReadResource(readConfig), UpdateContext: core.UpdateResource( core.ResourceOperationConfig{ ResourceName: resourceName, @@ -130,16 +59,20 @@ func resourceSchema() *schema.Resource { SchemaReaderFactory: func() core.SchemaReader { return &SidecarData{} }, SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &SidecarData{} }, }, - readSidecarConfig, + readConfig, ), DeleteContext: core.DeleteResource( core.ResourceOperationConfig{ - ResourceName: resourceName, - ResourceType: resourcetype.Resource, - Type: operationtype.Delete, - HttpMethod: http.MethodDelete, - URLFactory: urlFactory, - RequestErrorHandler: &SidecarDeleteIgnoreHttpNotFound{}, + ResourceName: resourceName, + ResourceType: resourcetype.Resource, + Type: operationtype.Delete, + HttpMethod: http.MethodDelete, + URLFactory: urlFactory, + RequestErrorHandler: &core.IgnoreNotFoundByMessage{ + ResName: resourceName, + MessageMatches: "NotFound", + OperationType: operationtype.Delete, + }, }, ), Schema: map[string]*schema.Schema{ diff --git a/cyral/provider/provider.go b/cyral/provider/provider.go index 8ed9257c..aaa1eec5 100644 --- a/cyral/provider/provider.go +++ b/cyral/provider/provider.go @@ -21,7 +21,6 @@ import ( "github.com/cyralinc/terraform-provider-cyral/cyral/internal/policy" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/policy/rule" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/regopolicy" - "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/confauth" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/network" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/role" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/samlconfiguration" @@ -161,7 +160,6 @@ func getResourceMap(ps []core.PackageSchema) map[string]*schema.Resource { schemaMap["cyral_policy"] = policy.ResourcePolicy() schemaMap["cyral_policy_rule"] = rule.ResourcePolicyRule() schemaMap["cyral_rego_policy_instance"] = regopolicy.ResourceRegoPolicyInstance() - schemaMap["cyral_repository_conf_auth"] = confauth.ResourceRepositoryConfAuth() schemaMap["cyral_repository_network_access_policy"] = network.ResourceRepositoryNetworkAccessPolicy() schemaMap["cyral_role"] = role.ResourceRole() schemaMap["cyral_role_sso_groups"] = role.ResourceRoleSSOGroups() diff --git a/cyral/provider/schema_loader.go b/cyral/provider/schema_loader.go index 3e0fb792..a08869d1 100644 --- a/cyral/provider/schema_loader.go +++ b/cyral/provider/schema_loader.go @@ -25,6 +25,7 @@ func packagesSchemas() []core.PackageSchema { accessrules.PackageSchema(), binding.PackageSchema(), confanalysis.PackageSchema(), + //confauth.PackageSchema(), credentials.PackageSchema(), datalabel.PackageSchema(), datamap.PackageSchema(), From d3f71fc4f34515a72332828fb45ee00507503bf8 Mon Sep 17 00:00:00 2001 From: Wilson de Carvalho <796900+wcmjunior@users.noreply.github.com> Date: Wed, 3 Apr 2024 00:05:04 -0700 Subject: [PATCH 11/22] Refactor cyral_repository_conf_auth --- .../internal/repository/confauth/constants.go | 11 + cyral/internal/repository/confauth/model.go | 92 ++++++ .../internal/repository/confauth/resource.go | 213 +++++++++++++ .../resource_cyral_repository_conf_auth.go | 296 ------------------ ...ory_conf_auth_test.go => resource_test.go} | 0 .../repository/confauth/schema_loader.go | 26 ++ cyral/provider/schema_loader.go | 3 +- 7 files changed, 344 insertions(+), 297 deletions(-) create mode 100644 cyral/internal/repository/confauth/constants.go create mode 100644 cyral/internal/repository/confauth/model.go create mode 100644 cyral/internal/repository/confauth/resource.go delete mode 100644 cyral/internal/repository/confauth/resource_cyral_repository_conf_auth.go rename cyral/internal/repository/confauth/{resource_cyral_repository_conf_auth_test.go => resource_test.go} (100%) create mode 100644 cyral/internal/repository/confauth/schema_loader.go diff --git a/cyral/internal/repository/confauth/constants.go b/cyral/internal/repository/confauth/constants.go new file mode 100644 index 00000000..da3f98f8 --- /dev/null +++ b/cyral/internal/repository/confauth/constants.go @@ -0,0 +1,11 @@ +package confauth + +const ( + resourceName = "cyral_repository_conf_auth" + + DefaultClientTLS = "disable" + DefaultRepoTLS = "disable" + AccessTokenAuthType = "ACCESS_TOKEN" + AwsIAMAuthType = "AWS_IAM" + DefaultAuthType = AccessTokenAuthType +) diff --git a/cyral/internal/repository/confauth/model.go b/cyral/internal/repository/confauth/model.go new file mode 100644 index 00000000..f326e3b2 --- /dev/null +++ b/cyral/internal/repository/confauth/model.go @@ -0,0 +1,92 @@ +package confauth + +import ( + "errors" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +var authTypes = []string{ + AccessTokenAuthType, + AwsIAMAuthType, +} + +type RepositoryConfAuthData struct { + RepoID *string `json:"-"` + AllowNativeAuth bool `json:"allowNativeAuth"` + ClientTLS string `json:"clientTLS"` + IdentityProvider string `json:"identityProvider"` + RepoTLS string `json:"repoTLS"` + AuthType string `json:"authType"` +} + +func (data RepositoryConfAuthData) WriteToSchema(d *schema.ResourceData) error { + if data.RepoID != nil { + d.Set("repository_id", data.RepoID) + } + + d.Set("allow_native_auth", data.AllowNativeAuth) + + if err := data.isClientTLSValid(); err != nil { + panic(err) + } + + d.Set("client_tls", data.ClientTLS) + + d.Set("identity_provider", data.IdentityProvider) + + if err := data.isRepoTLSValid(); err != nil { + panic(err) + } + + d.Set("repo_tls", data.RepoTLS) + + d.Set("auth_type", data.AuthType) + + return nil +} + +func (data *RepositoryConfAuthData) ReadFromSchema(d *schema.ResourceData) error { + if repoIdData, hasRepoId := d.GetOk("repository_id"); hasRepoId { + repoId := repoIdData.(string) + data.RepoID = &repoId + } + + data.AllowNativeAuth = d.Get("allow_native_auth").(bool) + data.AuthType = d.Get("auth_type").(string) + data.ClientTLS = d.Get("client_tls").(string) + data.IdentityProvider = d.Get("identity_provider").(string) + data.RepoTLS = d.Get("repo_tls").(string) + + return nil +} + +func (data RepositoryConfAuthData) isClientTLSValid() error { + if !(data.ClientTLS == "enable" || data.ClientTLS == "disable" || data.ClientTLS == "enabledAndVerifyCertificate") { + return errors.New("invalid option to client_tls") + } + return nil +} + +func (data RepositoryConfAuthData) isRepoTLSValid() error { + if !(data.RepoTLS == "enable" || data.RepoTLS == "disable" || data.RepoTLS == "enabledAndVerifyCertificate") { + return errors.New("invalid option to repo_tls") + } + return nil +} + +type CreateRepositoryConfAuthResponse struct{} + +func (data CreateRepositoryConfAuthResponse) WriteToSchema(d *schema.ResourceData) error { + d.SetId(d.Get("repository_id").(string)) + return nil +} + +type ReadRepositoryConfAuthResponse struct { + AuthInfo RepositoryConfAuthData `json:"authInfo"` +} + +func (data ReadRepositoryConfAuthResponse) WriteToSchema(d *schema.ResourceData) error { + data.AuthInfo.WriteToSchema(d) + return nil +} diff --git a/cyral/internal/repository/confauth/resource.go b/cyral/internal/repository/confauth/resource.go new file mode 100644 index 00000000..43b646b4 --- /dev/null +++ b/cyral/internal/repository/confauth/resource.go @@ -0,0 +1,213 @@ +package confauth + +import ( + "context" + "fmt" + "net/http" + + "github.com/cyralinc/terraform-provider-cyral/cyral/client" + "github.com/cyralinc/terraform-provider-cyral/cyral/core" + "github.com/cyralinc/terraform-provider-cyral/cyral/core/types/operationtype" + "github.com/cyralinc/terraform-provider-cyral/cyral/core/types/resourcetype" + "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository" + "github.com/cyralinc/terraform-provider-cyral/cyral/utils" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +// TODO Fix the API status codes and simplify this code. We could easily use the default handlers +// instead of this complex set of operations. + +var urlFactory = func(d *schema.ResourceData, c *client.Client) string { + return fmt.Sprintf("https://%s/v1/repos/%s/conf/auth", + c.ControlPlane, + d.Get("repository_id"), + ) +} + +var readConfig = core.ResourceOperationConfig{ + ResourceName: resourceName, + ResourceType: resourcetype.Resource, + Type: operationtype.Read, + HttpMethod: http.MethodGet, + URLFactory: urlFactory, + SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &ReadRepositoryConfAuthResponse{} }, + RequestErrorHandler: &core.IgnoreNotFoundByMessage{ + ResName: resourceName, + MessageMatches: "Failed to read repo", + OperationType: operationtype.Read, + }, +} + +func resourceSchema() *schema.Resource { + return &schema.Resource{ + Description: "Manages Repository Analysis Configuration. This resource allows configuring both " + + "[Log Settings](https://cyral.com/docs/manage-repositories/repo-log-volume) " + + "and [Advanced settings](https://cyral.com/docs/manage-repositories/repo-advanced-settings) " + + "(Logs, Alerts, Analysis and Enforcement) configurations for Data Repositories.", + CreateContext: resourceRepositoryConfAuthCreate, + ReadContext: core.ReadResource(readConfig), + UpdateContext: core.UpdateResource( + core.ResourceOperationConfig{ + ResourceName: resourceName, + ResourceType: resourcetype.Resource, + Type: operationtype.Update, + HttpMethod: http.MethodPut, + URLFactory: urlFactory, + SchemaReaderFactory: func() core.SchemaReader { return &RepositoryConfAuthData{} }, + }, + readConfig, + ), + DeleteContext: core.DeleteResource( + core.ResourceOperationConfig{ + ResourceName: resourceName, + ResourceType: resourcetype.Resource, + Type: operationtype.Delete, + HttpMethod: http.MethodDelete, + URLFactory: urlFactory, + RequestErrorHandler: &core.IgnoreNotFoundByMessage{ + ResName: resourceName, + MessageMatches: "Failed to read repo", + OperationType: operationtype.Read, + }, + }, + ), + + SchemaVersion: 1, + StateUpgraders: []schema.StateUpgrader{ + { + Version: 0, + Type: repositoryConfAuthResourceSchemaV0(). + CoreConfigSchema().ImpliedType(), + Upgrade: UpgradeRepositoryConfAuthV0, + }, + }, + + Schema: repositoryConfAuthResourceSchemaV0().Schema, + + Importer: &schema.ResourceImporter{ + StateContext: func( + ctx context.Context, + d *schema.ResourceData, + m interface{}, + ) ([]*schema.ResourceData, error) { + d.Set("repository_id", d.Id()) + return []*schema.ResourceData{d}, nil + }, + }, + } +} + +func repositoryConfAuthResourceSchemaV0() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Description: "The ID of this resource is set to `repository_id`.", + Type: schema.TypeString, + Computed: true, + }, + "repository_id": { + Description: "The ID of the repository to be configured.", + Type: schema.TypeString, + Required: true, + }, + "allow_native_auth": { + Description: "Should the communication allow native authentication?", + Type: schema.TypeBool, + Optional: true, + }, + "client_tls": { + Description: fmt.Sprintf("Is the repo Client using TLS? Default is %q.", DefaultClientTLS), + Type: schema.TypeString, + Optional: true, + Default: DefaultClientTLS, + }, + "identity_provider": { + Description: fmt.Sprintf( + "The semantics of this field changed in control planes `v4.13` and later. See how "+ + "it should be configured depending on your control plane version:\n"+ + "\t- `v4.12` and below:\n\t\t- Provide the ID (Alias) of the identity provider "+ + "integration to allow user authentication using an IdP.\n"+ + "\t- `v4.13` and later:\n\t\t- If not supplied, then end-user "+ + "authentication is disabled.\n\t\t- If end-user authentication "+ + "with Cyral Access Token is desired, then set to `ACCESS_TOKEN` or any "+ + "other non-empty string.\n\t\t- If end-user authentication with "+ + "AWS IAM is desired, then this must be the ID of an AWS IAM integration, "+ + "and the `auth_type` attribute must be set to `%s`.", + AwsIAMAuthType, + ), + Type: schema.TypeString, + Optional: true, + }, + "repo_tls": { + Description: fmt.Sprintf("Is TLS enabled for the repository? Default is %q.", DefaultRepoTLS), + Type: schema.TypeString, + Optional: true, + Default: DefaultRepoTLS, + }, + "auth_type": { + Description: fmt.Sprintf("Authentication type for this repository. **Note**: `%s` is currently "+ + "only supported by `%s` repo type. List of supported values: %s", + AwsIAMAuthType, repository.MongoDB, utils.SupportedValuesAsMarkdown(authTypes)), + Type: schema.TypeString, + Optional: true, + Default: DefaultAuthType, + ValidateFunc: validation.StringInSlice(authTypes, false), + }, + }, + } +} + +// Previously, the id of the resource `cyral_repository_conf_auth` was hardcoded +// to `repo-conf`, which doesn't make sense. The goal here is to set it to be +// the repository ID. +func UpgradeRepositoryConfAuthV0( + _ context.Context, + rawState map[string]interface{}, + _ interface{}, +) (map[string]interface{}, error) { + rawState["id"] = rawState["repository_id"] + return rawState, nil +} + +func resourceRepositoryConfAuthCreate( + ctx context.Context, + d *schema.ResourceData, + m interface{}, +) diag.Diagnostics { + tflog.Debug(ctx, "Init resourceRepositoryConfAuthCreate") + c := m.(*client.Client) + httpMethod := http.MethodPost + if confAuthAlreadyExists(ctx, c, d) { + httpMethod = http.MethodPut + } + tflog.Debug(ctx, "End resourceRepositoryConfAuthCreate") + return core.CreateResource( + core.ResourceOperationConfig{ + ResourceName: resourceName, + Type: operationtype.Create, + HttpMethod: httpMethod, + URLFactory: urlFactory, + SchemaReaderFactory: func() core.SchemaReader { return &RepositoryConfAuthData{} }, + SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &CreateRepositoryConfAuthResponse{} }, + }, readConfig)(ctx, d, m) +} + +func confAuthAlreadyExists(ctx context.Context, c *client.Client, d *schema.ResourceData) bool { + _, err := c.DoRequest(ctx, urlFactory(d, c), http.MethodGet, nil) + // TODO: Fix this API. + + // The GET /v1/repos/{repoID}/conf/auth API currently returns 500 status code for every type + // of error, so its not possible to distinguish if the error is due to a 404 Not Found or not. + // Once the status code returned by this API is fixed we should return false only if it returns + // a 404 Not Found, otherwise, if a different error occurs, this function should return an error. + if err != nil { + + tflog.Debug(ctx, fmt.Sprintf("Unable to read Conf Auth resource for repository %s: %v", + d.Get("repository_id").(string), err)) + return false + } + return true +} diff --git a/cyral/internal/repository/confauth/resource_cyral_repository_conf_auth.go b/cyral/internal/repository/confauth/resource_cyral_repository_conf_auth.go deleted file mode 100644 index f7f48a99..00000000 --- a/cyral/internal/repository/confauth/resource_cyral_repository_conf_auth.go +++ /dev/null @@ -1,296 +0,0 @@ -package confauth - -import ( - "context" - "errors" - "fmt" - "net/http" - - "github.com/cyralinc/terraform-provider-cyral/cyral/client" - "github.com/cyralinc/terraform-provider-cyral/cyral/core" - "github.com/cyralinc/terraform-provider-cyral/cyral/core/types/operationtype" - "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository" - "github.com/cyralinc/terraform-provider-cyral/cyral/utils" - "github.com/hashicorp/terraform-plugin-log/tflog" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" -) - -const ( - repositoryConfAuthURLFormat = "https://%s/v1/repos/%s/conf/auth" - - DefaultClientTLS = "disable" - DefaultRepoTLS = "disable" - AccessTokenAuthType = "ACCESS_TOKEN" - AwsIAMAuthType = "AWS_IAM" - DefaultAuthType = AccessTokenAuthType -) - -var authTypes = []string{ - AccessTokenAuthType, - AwsIAMAuthType, -} - -type RepositoryConfAuthData struct { - RepoID *string `json:"-"` - AllowNativeAuth bool `json:"allowNativeAuth"` - ClientTLS string `json:"clientTLS"` - IdentityProvider string `json:"identityProvider"` - RepoTLS string `json:"repoTLS"` - AuthType string `json:"authType"` -} - -func (data RepositoryConfAuthData) WriteToSchema(d *schema.ResourceData) error { - if data.RepoID != nil { - d.Set("repository_id", data.RepoID) - } - - d.Set("allow_native_auth", data.AllowNativeAuth) - - if err := data.isClientTLSValid(); err != nil { - panic(err) - } - - d.Set("client_tls", data.ClientTLS) - - d.Set("identity_provider", data.IdentityProvider) - - if err := data.isRepoTLSValid(); err != nil { - panic(err) - } - - d.Set("repo_tls", data.RepoTLS) - - d.Set("auth_type", data.AuthType) - - return nil -} - -func (data *RepositoryConfAuthData) ReadFromSchema(d *schema.ResourceData) error { - if repoIdData, hasRepoId := d.GetOk("repository_id"); hasRepoId { - repoId := repoIdData.(string) - data.RepoID = &repoId - } - - data.AllowNativeAuth = d.Get("allow_native_auth").(bool) - data.AuthType = d.Get("auth_type").(string) - data.ClientTLS = d.Get("client_tls").(string) - data.IdentityProvider = d.Get("identity_provider").(string) - data.RepoTLS = d.Get("repo_tls").(string) - - return nil -} - -func (data RepositoryConfAuthData) isClientTLSValid() error { - if !(data.ClientTLS == "enable" || data.ClientTLS == "disable" || data.ClientTLS == "enabledAndVerifyCertificate") { - return errors.New("invalid option to client_tls") - } - return nil -} - -func (data RepositoryConfAuthData) isRepoTLSValid() error { - if !(data.RepoTLS == "enable" || data.RepoTLS == "disable" || data.RepoTLS == "enabledAndVerifyCertificate") { - return errors.New("invalid option to repo_tls") - } - return nil -} - -type CreateRepositoryConfAuthResponse struct{} - -func (data CreateRepositoryConfAuthResponse) WriteToSchema(d *schema.ResourceData) error { - d.SetId(d.Get("repository_id").(string)) - return nil -} - -type ReadRepositoryConfAuthResponse struct { - AuthInfo RepositoryConfAuthData `json:"authInfo"` -} - -func (data ReadRepositoryConfAuthResponse) WriteToSchema(d *schema.ResourceData) error { - data.AuthInfo.WriteToSchema(d) - return nil -} - -func resourceRepositoryConfAuthCreate( - ctx context.Context, - d *schema.ResourceData, - m interface{}, -) diag.Diagnostics { - tflog.Debug(ctx, "Init resourceRepositoryConfAuthCreate") - c := m.(*client.Client) - httpMethod := http.MethodPost - if confAuthAlreadyExists(ctx, c, d.Get("repository_id").(string)) { - httpMethod = http.MethodPut - } - tflog.Debug(ctx, "End resourceRepositoryConfAuthCreate") - return core.CreateResource(CreateConfAuthConfig(httpMethod), ReadConfAuthConfig())(ctx, d, m) -} - -func confAuthAlreadyExists(ctx context.Context, c *client.Client, repositoryID string) bool { - url := fmt.Sprintf(repositoryConfAuthURLFormat, c.ControlPlane, repositoryID) - _, err := c.DoRequest(ctx, url, http.MethodGet, nil) - // The GET /v1/repos/{repoID}/conf/auth API currently returns 500 status code for every type - // of error, so its not possible to distinguish if the error is due to a 404 Not Found or not. - // Once the status code returned by this API is fixed we should return false only if it returns - // a 404 Not Found, otherwise, if a different error occurs, this function should return an error. - if err != nil { - tflog.Debug(ctx, fmt.Sprintf("Unable to read Conf Auth resource for repository %s: %v", repositoryID, err)) - return false - } - return true -} - -func CreateConfAuthConfig(httpMethod string) core.ResourceOperationConfig { - return core.ResourceOperationConfig{ - ResourceName: "ConfAuthResourceCreate", - Type: operationtype.Create, - HttpMethod: httpMethod, - URLFactory: func(d *schema.ResourceData, c *client.Client) string { - return fmt.Sprintf(repositoryConfAuthURLFormat, c.ControlPlane, d.Get("repository_id")) - }, - SchemaReaderFactory: func() core.SchemaReader { return &RepositoryConfAuthData{} }, - SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &CreateRepositoryConfAuthResponse{} }, - } -} - -func ReadConfAuthConfig() core.ResourceOperationConfig { - return core.ResourceOperationConfig{ - ResourceName: "ConfAuthResourceRead", - Type: operationtype.Read, - HttpMethod: http.MethodGet, - URLFactory: func(d *schema.ResourceData, c *client.Client) string { - return fmt.Sprintf(repositoryConfAuthURLFormat, c.ControlPlane, d.Get("repository_id")) - }, - SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &ReadRepositoryConfAuthResponse{} }, - RequestErrorHandler: &core.IgnoreHttpNotFound{ResName: "Repository conf auth"}, - } -} - -func UpdateConfAuthConfig() core.ResourceOperationConfig { - return core.ResourceOperationConfig{ - ResourceName: "ConfAuthResourceUpdate", - Type: operationtype.Update, - HttpMethod: http.MethodPut, - URLFactory: func(d *schema.ResourceData, c *client.Client) string { - return fmt.Sprintf(repositoryConfAuthURLFormat, c.ControlPlane, d.Get("repository_id")) - }, - SchemaReaderFactory: func() core.SchemaReader { return &RepositoryConfAuthData{} }, - } -} - -func DeleteConfAuthConfig() core.ResourceOperationConfig { - return core.ResourceOperationConfig{ - ResourceName: "ConfAuthResourceDelete", - Type: operationtype.Delete, - HttpMethod: http.MethodDelete, - URLFactory: func(d *schema.ResourceData, c *client.Client) string { - return fmt.Sprintf(repositoryConfAuthURLFormat, c.ControlPlane, d.Get("repository_id")) - }, - } -} - -func repositoryConfAuthResourceSchemaV0() *schema.Resource { - return &schema.Resource{ - Schema: map[string]*schema.Schema{ - "id": { - Description: "The ID of this resource is set to `repository_id`.", - Type: schema.TypeString, - Computed: true, - }, - "repository_id": { - Description: "The ID of the repository to be configured.", - Type: schema.TypeString, - Required: true, - }, - "allow_native_auth": { - Description: "Should the communication allow native authentication?", - Type: schema.TypeBool, - Optional: true, - }, - "client_tls": { - Description: fmt.Sprintf("Is the repo Client using TLS? Default is %q.", DefaultClientTLS), - Type: schema.TypeString, - Optional: true, - Default: DefaultClientTLS, - }, - "identity_provider": { - Description: fmt.Sprintf( - "The semantics of this field changed in control planes `v4.13` and later. See how "+ - "it should be configured depending on your control plane version:\n"+ - "\t- `v4.12` and below:\n\t\t- Provide the ID (Alias) of the identity provider "+ - "integration to allow user authentication using an IdP.\n"+ - "\t- `v4.13` and later:\n\t\t- If not supplied, then end-user "+ - "authentication is disabled.\n\t\t- If end-user authentication "+ - "with Cyral Access Token is desired, then set to `ACCESS_TOKEN` or any "+ - "other non-empty string.\n\t\t- If end-user authentication with "+ - "AWS IAM is desired, then this must be the ID of an AWS IAM integration, "+ - "and the `auth_type` attribute must be set to `%s`.", - AwsIAMAuthType, - ), - Type: schema.TypeString, - Optional: true, - }, - "repo_tls": { - Description: fmt.Sprintf("Is TLS enabled for the repository? Default is %q.", DefaultRepoTLS), - Type: schema.TypeString, - Optional: true, - Default: DefaultRepoTLS, - }, - "auth_type": { - Description: fmt.Sprintf("Authentication type for this repository. **Note**: `%s` is currently "+ - "only supported by `%s` repo type. List of supported values: %s", - AwsIAMAuthType, repository.MongoDB, utils.SupportedValuesAsMarkdown(authTypes)), - Type: schema.TypeString, - Optional: true, - Default: DefaultAuthType, - ValidateFunc: validation.StringInSlice(authTypes, false), - }, - }, - } -} - -// Previously, the id of the resource `cyral_repository_conf_auth` was hardcoded -// to `repo-conf`, which doesn't make sense. The goal here is to set it to be -// the repository ID. -func UpgradeRepositoryConfAuthV0( - _ context.Context, - rawState map[string]interface{}, - _ interface{}, -) (map[string]interface{}, error) { - rawState["id"] = rawState["repository_id"] - return rawState, nil -} - -func ResourceRepositoryConfAuth() *schema.Resource { - return &schema.Resource{ - Description: "Manages the [Repository Authentication settings](https://cyral.com/docs/manage-repositories/repo-advanced-settings/#authentication) that is shown in the Advanced tab.", - CreateContext: resourceRepositoryConfAuthCreate, - ReadContext: core.ReadResource(ReadConfAuthConfig()), - UpdateContext: core.UpdateResource(UpdateConfAuthConfig(), ReadConfAuthConfig()), - DeleteContext: core.DeleteResource(DeleteConfAuthConfig()), - - SchemaVersion: 1, - StateUpgraders: []schema.StateUpgrader{ - { - Version: 0, - Type: repositoryConfAuthResourceSchemaV0(). - CoreConfigSchema().ImpliedType(), - Upgrade: UpgradeRepositoryConfAuthV0, - }, - }, - - Schema: repositoryConfAuthResourceSchemaV0().Schema, - - Importer: &schema.ResourceImporter{ - StateContext: func( - ctx context.Context, - d *schema.ResourceData, - m interface{}, - ) ([]*schema.ResourceData, error) { - d.Set("repository_id", d.Id()) - return []*schema.ResourceData{d}, nil - }, - }, - } -} diff --git a/cyral/internal/repository/confauth/resource_cyral_repository_conf_auth_test.go b/cyral/internal/repository/confauth/resource_test.go similarity index 100% rename from cyral/internal/repository/confauth/resource_cyral_repository_conf_auth_test.go rename to cyral/internal/repository/confauth/resource_test.go diff --git a/cyral/internal/repository/confauth/schema_loader.go b/cyral/internal/repository/confauth/schema_loader.go new file mode 100644 index 00000000..4066051c --- /dev/null +++ b/cyral/internal/repository/confauth/schema_loader.go @@ -0,0 +1,26 @@ +package confauth + +import ( + "github.com/cyralinc/terraform-provider-cyral/cyral/core" +) + +type packageSchema struct { +} + +func (p *packageSchema) Name() string { + return "confauth" +} + +func (p *packageSchema) Schemas() []*core.SchemaDescriptor { + return []*core.SchemaDescriptor{ + { + Name: resourceName, + Type: core.ResourceSchemaType, + Schema: resourceSchema, + }, + } +} + +func PackageSchema() core.PackageSchema { + return &packageSchema{} +} diff --git a/cyral/provider/schema_loader.go b/cyral/provider/schema_loader.go index a08869d1..98b2e1e7 100644 --- a/cyral/provider/schema_loader.go +++ b/cyral/provider/schema_loader.go @@ -11,6 +11,7 @@ import ( "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/accessrules" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/binding" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/confanalysis" + "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/confauth" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/datamap" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/useraccount" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/samlcertificate" @@ -25,7 +26,7 @@ func packagesSchemas() []core.PackageSchema { accessrules.PackageSchema(), binding.PackageSchema(), confanalysis.PackageSchema(), - //confauth.PackageSchema(), + confauth.PackageSchema(), credentials.PackageSchema(), datalabel.PackageSchema(), datamap.PackageSchema(), From 9ad9d4a7537ab53c972616e7d7c2eb9947c20a12 Mon Sep 17 00:00:00 2001 From: Wilson de Carvalho <796900+wcmjunior@users.noreply.github.com> Date: Wed, 3 Apr 2024 00:08:51 -0700 Subject: [PATCH 12/22] Add constant for datamap resource name --- cyral/internal/repository/datamap/constants.go | 5 +++++ cyral/internal/repository/datamap/resource.go | 10 +++++----- cyral/internal/repository/datamap/schema_loader.go | 2 +- 3 files changed, 11 insertions(+), 6 deletions(-) create mode 100644 cyral/internal/repository/datamap/constants.go diff --git a/cyral/internal/repository/datamap/constants.go b/cyral/internal/repository/datamap/constants.go new file mode 100644 index 00000000..5ee30406 --- /dev/null +++ b/cyral/internal/repository/datamap/constants.go @@ -0,0 +1,5 @@ +package datamap + +const ( + resourceName = "cyral_repository_datamap" +) diff --git a/cyral/internal/repository/datamap/resource.go b/cyral/internal/repository/datamap/resource.go index ce1e4d64..055693e9 100644 --- a/cyral/internal/repository/datamap/resource.go +++ b/cyral/internal/repository/datamap/resource.go @@ -17,7 +17,7 @@ func resourceSchema() *schema.Resource { Description: "Manages [Data Map](https://cyral.com/docs/policy/datamap).", CreateContext: core.CreateResource( core.ResourceOperationConfig{ - ResourceName: "DataMapResourceCreate", + ResourceName: resourceName, Type: operationtype.Create, HttpMethod: http.MethodPut, URLFactory: func(d *schema.ResourceData, c *client.Client) string { @@ -33,7 +33,7 @@ func resourceSchema() *schema.Resource { ReadContext: core.ReadResource(readDataMapConfig), UpdateContext: core.UpdateResource( core.ResourceOperationConfig{ - ResourceName: "DataMapResourceUpdate", + ResourceName: resourceName, Type: operationtype.Update, HttpMethod: http.MethodPut, URLFactory: func(d *schema.ResourceData, c *client.Client) string { @@ -46,7 +46,7 @@ func resourceSchema() *schema.Resource { ), DeleteContext: core.DeleteResource( core.ResourceOperationConfig{ - ResourceName: "DataMapResourceDelete", + ResourceName: resourceName, Type: operationtype.Delete, HttpMethod: http.MethodDelete, URLFactory: func(d *schema.ResourceData, c *client.Client) string { @@ -113,7 +113,7 @@ func resourceSchema() *schema.Resource { } var readDataMapConfig = core.ResourceOperationConfig{ - ResourceName: "DataMapResourceRead", + ResourceName: resourceName, Type: operationtype.Read, HttpMethod: http.MethodGet, URLFactory: func(d *schema.ResourceData, c *client.Client) string { @@ -122,5 +122,5 @@ var readDataMapConfig = core.ResourceOperationConfig{ d.Get("repository_id").(string)) }, SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &DataMap{} }, - RequestErrorHandler: &core.IgnoreHttpNotFound{ResName: "Data Map"}, + RequestErrorHandler: &core.IgnoreHttpNotFound{ResName: resourceName}, } diff --git a/cyral/internal/repository/datamap/schema_loader.go b/cyral/internal/repository/datamap/schema_loader.go index 647b5bf1..4810b2e5 100644 --- a/cyral/internal/repository/datamap/schema_loader.go +++ b/cyral/internal/repository/datamap/schema_loader.go @@ -14,7 +14,7 @@ func (p *packageSchema) Name() string { func (p *packageSchema) Schemas() []*core.SchemaDescriptor { return []*core.SchemaDescriptor{ { - Name: "cyral_repository_datamap", + Name: resourceName, Type: core.ResourceSchemaType, Schema: resourceSchema, }, From 8127a13eb550a4fb65fd47d84ac07b729b616fab Mon Sep 17 00:00:00 2001 From: Wilson de Carvalho <796900+wcmjunior@users.noreply.github.com> Date: Wed, 3 Apr 2024 00:24:52 -0700 Subject: [PATCH 13/22] Fix broken links for docs --- cyral/internal/repository/confanalysis/resource.go | 9 +++++---- docs/resources/repository_conf_analysis.md | 4 ++-- docs/resources/repository_conf_auth.md | 2 +- docs/resources/sidecar.md | 4 ++-- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/cyral/internal/repository/confanalysis/resource.go b/cyral/internal/repository/confanalysis/resource.go index ddebe99e..a5f986e0 100644 --- a/cyral/internal/repository/confanalysis/resource.go +++ b/cyral/internal/repository/confanalysis/resource.go @@ -30,10 +30,11 @@ var resourceContextHandler = core.DefaultContextHandler{ func resourceSchema() *schema.Resource { return &schema.Resource{ - Description: "Manages Repository Analysis Configuration. This resource allows configuring both " + - "[Log Settings](https://cyral.com/docs/manage-repositories/repo-log-volume) " + - "and [Advanced settings](https://cyral.com/docs/manage-repositories/repo-advanced-settings) " + - "(Logs, Alerts, Analysis and Enforcement) configurations for Data Repositories.", + Description: "Manages Repository Analysis Configuration. This resource allows configuring " + + "[Data Activity Logs](https://cyral.com/docs/data-repos/config/#data-activity-logs), " + + "[Alerts](https://cyral.com/docs/data-repos/config/#alerts) and " + + "[Policy Enforcement](https://cyral.com/docs/data-repos/config/#policy-enforcement) " + + "settings for Data Repositories.", CreateContext: core.CreateResource( core.ResourceOperationConfig{ ResourceName: resourceName, diff --git a/docs/resources/repository_conf_analysis.md b/docs/resources/repository_conf_analysis.md index 92bd5fda..44086546 100644 --- a/docs/resources/repository_conf_analysis.md +++ b/docs/resources/repository_conf_analysis.md @@ -3,12 +3,12 @@ page_title: "cyral_repository_conf_analysis Resource - terraform-provider-cyral" subcategory: "" description: |- - Manages Repository Analysis Configuration. This resource allows configuring both Log Settings https://cyral.com/docs/manage-repositories/repo-log-volume and Advanced settings https://cyral.com/docs/manage-repositories/repo-advanced-settings (Logs, Alerts, Analysis and Enforcement) configurations for Data Repositories. + Manages Repository Analysis Configuration. This resource allows configuring Data Activity Logs https://cyral.com/docs/data-repos/config/#data-activity-logs, Alerts https://cyral.com/docs/data-repos/config/#alerts and Policy Enforcement https://cyral.com/docs/data-repos/config/#policy-enforcement settings for Data Repositories. --- # cyral_repository_conf_analysis (Resource) -Manages Repository Analysis Configuration. This resource allows configuring both [Log Settings](https://cyral.com/docs/manage-repositories/repo-log-volume) and [Advanced settings](https://cyral.com/docs/manage-repositories/repo-advanced-settings) (Logs, Alerts, Analysis and Enforcement) configurations for Data Repositories. +Manages Repository Analysis Configuration. This resource allows configuring [Data Activity Logs](https://cyral.com/docs/data-repos/config/#data-activity-logs), [Alerts](https://cyral.com/docs/data-repos/config/#alerts) and [Policy Enforcement](https://cyral.com/docs/data-repos/config/#policy-enforcement) settings for Data Repositories. ## Example Usage diff --git a/docs/resources/repository_conf_auth.md b/docs/resources/repository_conf_auth.md index cb6cd978..290cd55f 100644 --- a/docs/resources/repository_conf_auth.md +++ b/docs/resources/repository_conf_auth.md @@ -1,6 +1,6 @@ # cyral_repository_conf_auth (Resource) -Manages the [Repository Authentication settings](https://cyral.com/docs/manage-repositories/repo-advanced-settings/#authentication) that is shown in the Advanced tab. +Manages Repository Analysis Configuration. This resource allows configuring both [Log Settings](https://cyral.com/docs/manage-repositories/repo-log-volume) and [Advanced settings](https://cyral.com/docs/manage-repositories/repo-advanced-settings) (Logs, Alerts, Analysis and Enforcement) configurations for Data Repositories. -> Import ID syntax is `{repository_id}`. diff --git a/docs/resources/sidecar.md b/docs/resources/sidecar.md index 4b7fa1f9..c11aafdd 100644 --- a/docs/resources/sidecar.md +++ b/docs/resources/sidecar.md @@ -3,12 +3,12 @@ page_title: "cyral_sidecar Resource - terraform-provider-cyral" subcategory: "" description: |- - Manages sidecars https://cyral.com/docs/sidecars/sidecar-manage. + Manages sidecars https://cyral.com/docs/sidecars/manage. --- # cyral_sidecar (Resource) -Manages [sidecars](https://cyral.com/docs/sidecars/sidecar-manage). +Manages [sidecars](https://cyral.com/docs/sidecars/manage). ## Example Usage From 4d33b9af19894324cb561cd5429c7f60a64144bf Mon Sep 17 00:00:00 2001 From: Wilson de Carvalho <796900+wcmjunior@users.noreply.github.com> Date: Wed, 3 Apr 2024 01:09:27 -0700 Subject: [PATCH 14/22] Refactor cyral_repository_network_access_policy --- .../internal/repository/network/constants.go | 17 ++ cyral/internal/repository/network/model.go | 79 ++++++ cyral/internal/repository/network/resource.go | 124 +++++++++ ..._cyral_repository_network_access_policy.go | 251 ------------------ ...access_policy_test.go => resource_test.go} | 0 .../repository/network/schema_loader.go | 26 ++ cyral/provider/provider.go | 2 - cyral/provider/schema_loader.go | 2 + 8 files changed, 248 insertions(+), 253 deletions(-) create mode 100644 cyral/internal/repository/network/constants.go create mode 100644 cyral/internal/repository/network/model.go create mode 100644 cyral/internal/repository/network/resource.go delete mode 100644 cyral/internal/repository/network/resource_cyral_repository_network_access_policy.go rename cyral/internal/repository/network/{resource_cyral_repository_network_access_policy_test.go => resource_test.go} (100%) create mode 100644 cyral/internal/repository/network/schema_loader.go diff --git a/cyral/internal/repository/network/constants.go b/cyral/internal/repository/network/constants.go new file mode 100644 index 00000000..dc4af230 --- /dev/null +++ b/cyral/internal/repository/network/constants.go @@ -0,0 +1,17 @@ +package network + +import "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository" + +const ( + resourceName = "cyral_repository_network_access_policy" + + defaultNetworkAccessPolicyEnabled = true + defaultNetworkAccessRulesBlockAccess = false +) + +func repositoryTypesNetworkShield() []string { + return []string{ + repository.SQLServer, + repository.Oracle, + } +} diff --git a/cyral/internal/repository/network/model.go b/cyral/internal/repository/network/model.go new file mode 100644 index 00000000..4df7a70c --- /dev/null +++ b/cyral/internal/repository/network/model.go @@ -0,0 +1,79 @@ +package network + +import ( + "github.com/cyralinc/terraform-provider-cyral/cyral/utils" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +type NetworkAccessPolicyUpsertResp struct { + Policy NetworkAccessPolicy `json:"policy"` +} + +func (resp NetworkAccessPolicyUpsertResp) WriteToSchema(d *schema.ResourceData) error { + return resp.Policy.WriteToSchema(d) +} + +type NetworkAccessPolicy struct { + Enabled bool `json:"enabled"` + NetworkAccessRules `json:"networkAccessRules,omitempty"` +} + +type NetworkAccessRules struct { + RulesBlockAccess bool `json:"rulesBlockAccess"` + Rules []NetworkAccessRule `json:"rules"` +} + +type NetworkAccessRule struct { + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + DBAccounts []string `json:"dbAccounts,omitempty"` + SourceIPs []string `json:"sourceIPs,omitempty"` +} + +func (nap *NetworkAccessPolicy) ReadFromSchema(d *schema.ResourceData) error { + nap.Enabled = d.Get("enabled").(bool) + + var networkAccessRulesIfaces []interface{} + if set, ok := d.GetOk("network_access_rule"); ok { + networkAccessRulesIfaces = set.(*schema.Set).List() + } else { + return nil + } + + nap.NetworkAccessRules = NetworkAccessRules{ + RulesBlockAccess: d.Get("network_access_rules_block_access").(bool), + Rules: []NetworkAccessRule{}, + } + for _, networkAccessRuleIface := range networkAccessRulesIfaces { + networkAccessRuleMap := networkAccessRuleIface.(map[string]interface{}) + nap.NetworkAccessRules.Rules = append(nap.NetworkAccessRules.Rules, + NetworkAccessRule{ + Name: networkAccessRuleMap["name"].(string), + Description: networkAccessRuleMap["description"].(string), + DBAccounts: utils.GetStrList(networkAccessRuleMap, "db_accounts"), + SourceIPs: utils.GetStrList(networkAccessRuleMap, "source_ips"), + }) + } + + return nil +} + +func (nap *NetworkAccessPolicy) WriteToSchema(d *schema.ResourceData) error { + d.SetId(d.Get("repository_id").(string)) + d.Set("enabled", nap.Enabled) + d.Set("network_access_rules_block_access", nap.NetworkAccessRules.RulesBlockAccess) + + var networkAccessRules []interface{} + for _, rule := range nap.NetworkAccessRules.Rules { + rulesMap := map[string]interface{}{ + "name": rule.Name, + "description": rule.Description, + "db_accounts": rule.DBAccounts, + "source_ips": rule.SourceIPs, + } + networkAccessRules = append(networkAccessRules, rulesMap) + } + + return d.Set("network_access_rule", networkAccessRules) +} diff --git a/cyral/internal/repository/network/resource.go b/cyral/internal/repository/network/resource.go new file mode 100644 index 00000000..ee19a173 --- /dev/null +++ b/cyral/internal/repository/network/resource.go @@ -0,0 +1,124 @@ +package network + +import ( + "context" + "fmt" + + "github.com/cyralinc/terraform-provider-cyral/cyral/client" + "github.com/cyralinc/terraform-provider-cyral/cyral/core" + "github.com/cyralinc/terraform-provider-cyral/cyral/core/types/resourcetype" + "github.com/cyralinc/terraform-provider-cyral/cyral/utils" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +var urlFactory = func(d *schema.ResourceData, c *client.Client) string { + return fmt.Sprintf("https://%s/v1/repos/%s/networkAccessPolicy", + c.ControlPlane, + d.Get("repository_id"), + ) +} + +var resourceContextHandler = core.DefaultContextHandler{ + ResourceName: resourceName, + ResourceType: resourcetype.Resource, + SchemaReaderFactory: func() core.SchemaReader { return &NetworkAccessPolicy{} }, + SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &NetworkAccessPolicy{} }, + SchemaWriterFactoryPostMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &NetworkAccessPolicyUpsertResp{} }, + BaseURLFactory: urlFactory, + IdBasedURLFactory: urlFactory, +} + +func resourceSchema() *schema.Resource { + return &schema.Resource{ + Description: "Manages the network access policy of a repository. Network access policies are" + + " also known as the [Network Shield](https://cyral.com/docs/manage-repositories/network-shield/)." + + " This feature is supported for the following repository types:" + + utils.SupportedValuesAsMarkdown(repositoryTypesNetworkShield()) + + "\n\n-> **Note** If you also use the resource `cyral_repository_conf_auth` for the same repository," + + " create a `depends_on` relationship from this resource to the `cyral_repository_conf_auth` to" + + " avoid errors when running `terraform destroy`.", + CreateContext: resourceContextHandler.CreateContext(), + ReadContext: resourceContextHandler.ReadContext(), + UpdateContext: resourceContextHandler.UpdateContext(), + DeleteContext: resourceContextHandler.DeleteContext(), + + Schema: map[string]*schema.Schema{ + "repository_id": { + Description: "ID of the repository for which to configure a network access policy.", + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + // This parameter also exists in the + // v1/repos/{repoID}/conf/auth API, but putting it under + // the `cyral_repository_conf_auth` resource was causing + // a lot of trouble: the resources would get out of sync + // and behave like crazy. + "enabled": { + Description: fmt.Sprintf("Is the network access policy enabled? Default is %t.", defaultNetworkAccessPolicyEnabled), + Type: schema.TypeBool, + Optional: true, + Default: defaultNetworkAccessPolicyEnabled, + }, + + "network_access_rules_block_access": { + Description: fmt.Sprintf("Determines what happens if an incoming connection matches one of the rules in `network_access_rule`. If set to true, the connection is blocked if it matches some rule (and allowed otherwise). Otherwise set to false, the connection is allowed only if it matches some rule. Default is %t.", defaultNetworkAccessRulesBlockAccess), + Type: schema.TypeBool, + Optional: true, + }, + + "network_access_rule": { + Description: "Network access policy that decides whether access should be granted based on a set of rules.", + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Description: "Name of the rule.", + Type: schema.TypeString, + Required: true, + }, + "description": { + Description: "Description of the network access policy.", + Type: schema.TypeString, + Optional: true, + }, + "db_accounts": { + Description: "Specify which accounts this rule applies to. The account name must match an existing account in your database.", + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "source_ips": { + Description: "Specify IPs to restrict the range of allowed IP addresses for this rule.", + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, + + "id": { + Description: "ID of this resource in the Cyral environment.", + Type: schema.TypeString, + Computed: true, + }, + }, + Importer: &schema.ResourceImporter{ + StateContext: func( + ctx context.Context, + d *schema.ResourceData, + m interface{}, + ) ([]*schema.ResourceData, error) { + d.Set("repository_id", d.Id()) + return []*schema.ResourceData{d}, nil + }, + }, + } +} diff --git a/cyral/internal/repository/network/resource_cyral_repository_network_access_policy.go b/cyral/internal/repository/network/resource_cyral_repository_network_access_policy.go deleted file mode 100644 index 289cea6d..00000000 --- a/cyral/internal/repository/network/resource_cyral_repository_network_access_policy.go +++ /dev/null @@ -1,251 +0,0 @@ -package network - -import ( - "context" - "fmt" - "net/http" - - "github.com/cyralinc/terraform-provider-cyral/cyral/client" - "github.com/cyralinc/terraform-provider-cyral/cyral/core" - "github.com/cyralinc/terraform-provider-cyral/cyral/core/types/operationtype" - "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository" - "github.com/cyralinc/terraform-provider-cyral/cyral/utils" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -) - -const ( - repositoryNetworkAccessPolicyURLFormat = "https://%s/v1/repos/%s/networkAccessPolicy" - - defaultNetworkAccessPolicyEnabled = true - defaultNetworkAccessRulesBlockAccess = false -) - -func repositoryTypesNetworkShield() []string { - return []string{ - repository.SQLServer, - repository.Oracle, - } -} - -type NetworkAccessPolicyUpsertResp struct { - Policy NetworkAccessPolicy `json:"policy"` -} - -func (resp NetworkAccessPolicyUpsertResp) WriteToSchema(d *schema.ResourceData) error { - return resp.Policy.WriteToSchema(d) -} - -type NetworkAccessPolicy struct { - Enabled bool `json:"enabled"` - NetworkAccessRules `json:"networkAccessRules,omitempty"` -} - -type NetworkAccessRules struct { - RulesBlockAccess bool `json:"rulesBlockAccess"` - Rules []NetworkAccessRule `json:"rules"` -} - -type NetworkAccessRule struct { - ID string `json:"id,omitempty"` - Name string `json:"name,omitempty"` - Description string `json:"description,omitempty"` - DBAccounts []string `json:"dbAccounts,omitempty"` - SourceIPs []string `json:"sourceIPs,omitempty"` -} - -func (nap *NetworkAccessPolicy) ReadFromSchema(d *schema.ResourceData) error { - nap.Enabled = d.Get("enabled").(bool) - - var networkAccessRulesIfaces []interface{} - if set, ok := d.GetOk("network_access_rule"); ok { - networkAccessRulesIfaces = set.(*schema.Set).List() - } else { - return nil - } - - nap.NetworkAccessRules = NetworkAccessRules{ - RulesBlockAccess: d.Get("network_access_rules_block_access").(bool), - Rules: []NetworkAccessRule{}, - } - for _, networkAccessRuleIface := range networkAccessRulesIfaces { - networkAccessRuleMap := networkAccessRuleIface.(map[string]interface{}) - nap.NetworkAccessRules.Rules = append(nap.NetworkAccessRules.Rules, - NetworkAccessRule{ - Name: networkAccessRuleMap["name"].(string), - Description: networkAccessRuleMap["description"].(string), - DBAccounts: utils.GetStrList(networkAccessRuleMap, "db_accounts"), - SourceIPs: utils.GetStrList(networkAccessRuleMap, "source_ips"), - }) - } - - return nil -} - -func (nap *NetworkAccessPolicy) WriteToSchema(d *schema.ResourceData) error { - d.SetId(d.Get("repository_id").(string)) - d.Set("enabled", nap.Enabled) - d.Set("network_access_rules_block_access", nap.NetworkAccessRules.RulesBlockAccess) - - var networkAccessRules []interface{} - for _, rule := range nap.NetworkAccessRules.Rules { - rulesMap := map[string]interface{}{ - "name": rule.Name, - "description": rule.Description, - "db_accounts": rule.DBAccounts, - "source_ips": rule.SourceIPs, - } - networkAccessRules = append(networkAccessRules, rulesMap) - } - - return d.Set("network_access_rule", networkAccessRules) -} - -func createRepositoryNetworkAccessPolicy() core.ResourceOperationConfig { - return core.ResourceOperationConfig{ - ResourceName: "RepositoryNetworkAccessPolicyCreate", - Type: operationtype.Create, - HttpMethod: http.MethodPost, - URLFactory: func(d *schema.ResourceData, c *client.Client) string { - return fmt.Sprintf(repositoryNetworkAccessPolicyURLFormat, - c.ControlPlane, d.Get("repository_id")) - }, - SchemaReaderFactory: func() core.SchemaReader { return &NetworkAccessPolicy{} }, - SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &NetworkAccessPolicyUpsertResp{} }, - } -} - -func readRepositoryNetworkAccessPolicy() core.ResourceOperationConfig { - return core.ResourceOperationConfig{ - ResourceName: "RepositoryNetworkAccessPolicyRead", - Type: operationtype.Read, - HttpMethod: http.MethodGet, - URLFactory: func(d *schema.ResourceData, c *client.Client) string { - return fmt.Sprintf(repositoryNetworkAccessPolicyURLFormat, - c.ControlPlane, d.Get("repository_id")) - }, - SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &NetworkAccessPolicy{} }, - RequestErrorHandler: &core.IgnoreHttpNotFound{ResName: "Repository network access policy"}, - } -} - -func updateRepositoryNetworkAccessPolicy() core.ResourceOperationConfig { - return core.ResourceOperationConfig{ - ResourceName: "RepositoryNetworkAccessPolicyUpdate", - Type: operationtype.Update, - HttpMethod: http.MethodPut, - URLFactory: func(d *schema.ResourceData, c *client.Client) string { - return fmt.Sprintf(repositoryNetworkAccessPolicyURLFormat, - c.ControlPlane, d.Get("repository_id")) - }, - SchemaReaderFactory: func() core.SchemaReader { return &NetworkAccessPolicy{} }, - SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &NetworkAccessPolicyUpsertResp{} }, - } -} - -func deleteRepositoryNetworkAccessPolicy() core.ResourceOperationConfig { - return core.ResourceOperationConfig{ - ResourceName: "RepositoryNetworkAccessPolicyDelete", - Type: operationtype.Delete, - HttpMethod: http.MethodDelete, - URLFactory: func(d *schema.ResourceData, c *client.Client) string { - return fmt.Sprintf(repositoryNetworkAccessPolicyURLFormat, - c.ControlPlane, d.Get("repository_id")) - }, - RequestErrorHandler: &core.IgnoreHttpNotFound{ResName: "Network Access Policy"}, - } -} - -func ResourceRepositoryNetworkAccessPolicy() *schema.Resource { - return &schema.Resource{ - Description: "Manages the network access policy of a repository. Network access policies are" + - " also known as the [Network Shield](https://cyral.com/docs/manage-repositories/network-shield/)." + - " This feature is supported for the following repository types:" + - utils.SupportedValuesAsMarkdown(repositoryTypesNetworkShield()) + - "\n\n-> **Note** If you also use the resource `cyral_repository_conf_auth` for the same repository," + - " create a `depends_on` relationship from this resource to the `cyral_repository_conf_auth` to" + - " avoid errors when running `terraform destroy`.", - CreateContext: core.CreateResource(createRepositoryNetworkAccessPolicy(), readRepositoryNetworkAccessPolicy()), - ReadContext: core.ReadResource(readRepositoryNetworkAccessPolicy()), - UpdateContext: core.UpdateResource(updateRepositoryNetworkAccessPolicy(), readRepositoryNetworkAccessPolicy()), - DeleteContext: core.DeleteResource(deleteRepositoryNetworkAccessPolicy()), - - Schema: map[string]*schema.Schema{ - "repository_id": { - Description: "ID of the repository for which to configure a network access policy.", - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - - // This parameter also exists in the - // v1/repos/{repoID}/conf/auth API, but putting it under - // the `cyral_repository_conf_auth` resource was causing - // a lot of trouble: the resources would get out of sync - // and behave like crazy. - "enabled": { - Description: fmt.Sprintf("Is the network access policy enabled? Default is %t.", defaultNetworkAccessPolicyEnabled), - Type: schema.TypeBool, - Optional: true, - Default: defaultNetworkAccessPolicyEnabled, - }, - - "network_access_rules_block_access": { - Description: fmt.Sprintf("Determines what happens if an incoming connection matches one of the rules in `network_access_rule`. If set to true, the connection is blocked if it matches some rule (and allowed otherwise). Otherwise set to false, the connection is allowed only if it matches some rule. Default is %t.", defaultNetworkAccessRulesBlockAccess), - Type: schema.TypeBool, - Optional: true, - }, - - "network_access_rule": { - Description: "Network access policy that decides whether access should be granted based on a set of rules.", - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "name": { - Description: "Name of the rule.", - Type: schema.TypeString, - Required: true, - }, - "description": { - Description: "Description of the network access policy.", - Type: schema.TypeString, - Optional: true, - }, - "db_accounts": { - Description: "Specify which accounts this rule applies to. The account name must match an existing account in your database.", - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "source_ips": { - Description: "Specify IPs to restrict the range of allowed IP addresses for this rule.", - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - }, - }, - }, - - "id": { - Description: "ID of this resource in the Cyral environment.", - Type: schema.TypeString, - Computed: true, - }, - }, - Importer: &schema.ResourceImporter{ - StateContext: func( - ctx context.Context, - d *schema.ResourceData, - m interface{}, - ) ([]*schema.ResourceData, error) { - d.Set("repository_id", d.Id()) - return []*schema.ResourceData{d}, nil - }, - }, - } -} diff --git a/cyral/internal/repository/network/resource_cyral_repository_network_access_policy_test.go b/cyral/internal/repository/network/resource_test.go similarity index 100% rename from cyral/internal/repository/network/resource_cyral_repository_network_access_policy_test.go rename to cyral/internal/repository/network/resource_test.go diff --git a/cyral/internal/repository/network/schema_loader.go b/cyral/internal/repository/network/schema_loader.go new file mode 100644 index 00000000..6e3f6a23 --- /dev/null +++ b/cyral/internal/repository/network/schema_loader.go @@ -0,0 +1,26 @@ +package network + +import ( + "github.com/cyralinc/terraform-provider-cyral/cyral/core" +) + +type packageSchema struct { +} + +func (p *packageSchema) Name() string { + return "datamap" +} + +func (p *packageSchema) Schemas() []*core.SchemaDescriptor { + return []*core.SchemaDescriptor{ + { + Name: resourceName, + Type: core.ResourceSchemaType, + Schema: resourceSchema, + }, + } +} + +func PackageSchema() core.PackageSchema { + return &packageSchema{} +} diff --git a/cyral/provider/provider.go b/cyral/provider/provider.go index aaa1eec5..7624e482 100644 --- a/cyral/provider/provider.go +++ b/cyral/provider/provider.go @@ -21,7 +21,6 @@ import ( "github.com/cyralinc/terraform-provider-cyral/cyral/internal/policy" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/policy/rule" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/regopolicy" - "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/network" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/role" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/samlconfiguration" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/serviceaccount" @@ -160,7 +159,6 @@ func getResourceMap(ps []core.PackageSchema) map[string]*schema.Resource { schemaMap["cyral_policy"] = policy.ResourcePolicy() schemaMap["cyral_policy_rule"] = rule.ResourcePolicyRule() schemaMap["cyral_rego_policy_instance"] = regopolicy.ResourceRegoPolicyInstance() - schemaMap["cyral_repository_network_access_policy"] = network.ResourceRepositoryNetworkAccessPolicy() schemaMap["cyral_role"] = role.ResourceRole() schemaMap["cyral_role_sso_groups"] = role.ResourceRoleSSOGroups() schemaMap["cyral_service_account"] = serviceaccount.ResourceServiceAccount() diff --git a/cyral/provider/schema_loader.go b/cyral/provider/schema_loader.go index 98b2e1e7..2882e7d3 100644 --- a/cyral/provider/schema_loader.go +++ b/cyral/provider/schema_loader.go @@ -13,6 +13,7 @@ import ( "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/confanalysis" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/confauth" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/datamap" + "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/network" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/useraccount" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/samlcertificate" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/sidecar" @@ -31,6 +32,7 @@ func packagesSchemas() []core.PackageSchema { datalabel.PackageSchema(), datamap.PackageSchema(), hcvault.PackageSchema(), + network.PackageSchema(), repository.PackageSchema(), samlcertificate.PackageSchema(), sidecar.PackageSchema(), From 1d457d65c0254ccf69fc3f2b5914595fe988e337 Mon Sep 17 00:00:00 2001 From: Wilson de Carvalho <796900+wcmjunior@users.noreply.github.com> Date: Wed, 3 Apr 2024 10:38:11 -0700 Subject: [PATCH 15/22] Refactor cyral_sidecar_listener --- cyral/core/README.md | 2 +- .../{data_source.go => datasource.go} | 0 ...data_source_test.go => datasource_test.go} | 0 cyral/internal/sidecar/listener/constants.go | 24 ++ .../data_source_cyral_sidecar_listener.go | 115 -------- cyral/internal/sidecar/listener/datasource.go | 55 ++++ ...ar_listener_test.go => datasource_test.go} | 24 +- cyral/internal/sidecar/listener/model.go | 245 ++++++++++++++++ ..._cyral_sidecar_listener.go => resource.go} | 273 ++---------------- ...ecar_listener_test.go => resource_test.go} | 0 .../sidecar/listener/schema_loader.go | 31 ++ cyral/internal/sidecar/schema_loader.go | 5 - cyral/provider/provider.go | 3 - cyral/provider/schema_loader.go | 2 + 14 files changed, 390 insertions(+), 389 deletions(-) rename cyral/internal/repository/{data_source.go => datasource.go} (100%) rename cyral/internal/repository/{data_source_test.go => datasource_test.go} (100%) create mode 100644 cyral/internal/sidecar/listener/constants.go delete mode 100644 cyral/internal/sidecar/listener/data_source_cyral_sidecar_listener.go create mode 100644 cyral/internal/sidecar/listener/datasource.go rename cyral/internal/sidecar/listener/{data_source_cyral_sidecar_listener_test.go => datasource_test.go} (89%) create mode 100644 cyral/internal/sidecar/listener/model.go rename cyral/internal/sidecar/listener/{resource_cyral_sidecar_listener.go => resource.go} (51%) rename cyral/internal/sidecar/listener/{resource_cyral_sidecar_listener_test.go => resource_test.go} (100%) create mode 100644 cyral/internal/sidecar/listener/schema_loader.go diff --git a/cyral/core/README.md b/cyral/core/README.md index 7d9bf18b..86bd692b 100644 --- a/cyral/core/README.md +++ b/cyral/core/README.md @@ -77,7 +77,7 @@ var dsContextHandler = core.DefaultContextHandler{ ResourceName: dataSourceName, ResourceType: resourcetype.DataSource, SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &NewFeature{} }, - BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { + IdBasedURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/NewFeature", c.ControlPlane) }, } diff --git a/cyral/internal/repository/data_source.go b/cyral/internal/repository/datasource.go similarity index 100% rename from cyral/internal/repository/data_source.go rename to cyral/internal/repository/datasource.go diff --git a/cyral/internal/repository/data_source_test.go b/cyral/internal/repository/datasource_test.go similarity index 100% rename from cyral/internal/repository/data_source_test.go rename to cyral/internal/repository/datasource_test.go diff --git a/cyral/internal/sidecar/listener/constants.go b/cyral/internal/sidecar/listener/constants.go new file mode 100644 index 00000000..4376659d --- /dev/null +++ b/cyral/internal/sidecar/listener/constants.go @@ -0,0 +1,24 @@ +package listener + +// Resource +const ( + resourceName = "cyral_sidecar_listener" + dataSourceName = "cyral_sidecar_listener" + + RepoTypesKey = "repo_types" + NetworkAddressKey = "network_address" + MySQLSettingsKey = "mysql_settings" + DbVersionKey = "db_version" + CharacterSetKey = "character_set" + S3SettingsKey = "s3_settings" + ProxyModeKey = "proxy_mode" + DynamoDbSettingsKey = "dynamodb_settings" + SQLServerSettingsKey = "sqlserver_settings" + SQLServerVersionKey = "version" +) + +// Data source +const ( + SidecarListenerListKey = "listener_list" + DSRepoTypeKey = "repo_type" +) diff --git a/cyral/internal/sidecar/listener/data_source_cyral_sidecar_listener.go b/cyral/internal/sidecar/listener/data_source_cyral_sidecar_listener.go deleted file mode 100644 index 7d8d86dc..00000000 --- a/cyral/internal/sidecar/listener/data_source_cyral_sidecar_listener.go +++ /dev/null @@ -1,115 +0,0 @@ -package listener - -import ( - "context" - "fmt" - "net/http" - - "github.com/google/uuid" - "github.com/hashicorp/terraform-plugin-log/tflog" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "golang.org/x/exp/slices" - - "github.com/cyralinc/terraform-provider-cyral/cyral/client" - "github.com/cyralinc/terraform-provider-cyral/cyral/core" - "github.com/cyralinc/terraform-provider-cyral/cyral/core/types/operationtype" - "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository" - "github.com/cyralinc/terraform-provider-cyral/cyral/utils" -) - -const ( - SidecarListenerListKey = "listener_list" - DSRepoTypeKey = "repo_type" -) - -type ReadDataSourceSidecarListenerAPIResponse struct { - ListenerConfigs []SidecarListener `json:"listenerConfigs"` -} - -func (data ReadDataSourceSidecarListenerAPIResponse) WriteToSchema(d *schema.ResourceData) error { - ctx := context.Background() - tflog.Debug(ctx, "Init ReadDataSourceSidecarListenerAPIResponse.WriteToSchema") - var listenersList []any - tflog.Debug(ctx, fmt.Sprintf("data.ListenerConfig: %+v", data.ListenerConfigs)) - tflog.Debug(ctx, "Init for _, l := range data.ListenerConfig") - repoTypeFilter := d.Get(DSRepoTypeKey).(string) - portFilter := d.Get(utils.PortKey).(int) - for _, listenerConfig := range data.ListenerConfigs { - // Check if either the repo filter or the port filter is provided and matches the listener - if (repoTypeFilter == "" || slices.Contains(listenerConfig.RepoTypes, repoTypeFilter)) && - (portFilter == 0 || listenerConfig.NetworkAddress.Port == portFilter) { - listener := map[string]any{ - utils.ListenerIDKey: listenerConfig.ListenerId, - utils.SidecarIDKey: d.Get(utils.SidecarIDKey).(string), - RepoTypesKey: listenerConfig.RepoTypes, - NetworkAddressKey: listenerConfig.NetworkAddressAsInterface(), - MySQLSettingsKey: listenerConfig.MySQLSettingsAsInterface(), - S3SettingsKey: listenerConfig.S3SettingsAsInterface(), - DynamoDbSettingsKey: listenerConfig.DynamoDbSettingsAsInterface(), - SQLServerSettingsKey: listenerConfig.SQLServerSettingsAsInterface(), - } - tflog.Debug(ctx, fmt.Sprintf("listener: %q", listener)) - listenersList = append(listenersList, listener) - } - } - - tflog.Debug(ctx, fmt.Sprintf("listenersList: %q", listenersList)) - tflog.Debug(ctx, "End for _, l := range data.ListenerConfig") - - if err := d.Set(SidecarListenerListKey, listenersList); err != nil { - return err - } - - d.SetId(uuid.New().String()) - - tflog.Debug(ctx, "End ReadDataSourceSidecarListenerAPIResponse.WriteToSchema") - - return nil -} - -func dataSourceSidecarListenerReadConfig() core.ResourceOperationConfig { - return core.ResourceOperationConfig{ - ResourceName: "SidecarListenerDataSourceRead", - Type: operationtype.Read, - HttpMethod: http.MethodGet, - URLFactory: func(d *schema.ResourceData, c *client.Client) string { - sidecarID := d.Get(utils.SidecarIDKey).(string) - - return fmt.Sprintf("https://%s/v1/sidecars/%s/listeners", c.ControlPlane, sidecarID) - }, - SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &ReadDataSourceSidecarListenerAPIResponse{} }, - } -} - -func DataSourceSidecarListener() *schema.Resource { - listenerSchema := utils.ConvertSchemaFieldsToComputed(getSidecarListenerSchema()) - return &schema.Resource{ - Description: "Retrieve and filter sidecar listeners.", - ReadContext: core.ReadResource(dataSourceSidecarListenerReadConfig()), - Schema: map[string]*schema.Schema{ - utils.SidecarIDKey: { - Description: "Filter the results by sidecar ID.", - Type: schema.TypeString, - Required: true, - }, - DSRepoTypeKey: { - Description: "Filter the results per repository type. Supported repo types:" + utils.SupportedValuesAsMarkdown(repository.RepositoryTypes()), - Type: schema.TypeString, - Optional: true, - }, - utils.PortKey: { - Description: "Filter the results per port.", - Type: schema.TypeInt, - Optional: true, - }, - SidecarListenerListKey: { - Description: "List of existing listeners satisfying the filter criteria.", - Computed: true, - Type: schema.TypeList, - Elem: &schema.Resource{ - Schema: listenerSchema, - }, - }, - }, - } -} diff --git a/cyral/internal/sidecar/listener/datasource.go b/cyral/internal/sidecar/listener/datasource.go new file mode 100644 index 00000000..7c67201e --- /dev/null +++ b/cyral/internal/sidecar/listener/datasource.go @@ -0,0 +1,55 @@ +package listener + +import ( + "fmt" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/cyralinc/terraform-provider-cyral/cyral/client" + "github.com/cyralinc/terraform-provider-cyral/cyral/core" + "github.com/cyralinc/terraform-provider-cyral/cyral/core/types/resourcetype" + "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository" + "github.com/cyralinc/terraform-provider-cyral/cyral/utils" +) + +var dsContextHandler = core.DefaultContextHandler{ + ResourceName: dataSourceName, + ResourceType: resourcetype.DataSource, + SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &ReadDataSourceSidecarListenerAPIResponse{} }, + IdBasedURLFactory: func(d *schema.ResourceData, c *client.Client) string { + return fmt.Sprintf("https://%s/v1/sidecars/%s/listeners", c.ControlPlane, d.Get(utils.SidecarIDKey).(string)) + }, +} + +func dataSourceSchema() *schema.Resource { + listenerSchema := utils.ConvertSchemaFieldsToComputed(getSidecarListenerSchema()) + return &schema.Resource{ + Description: "Retrieve and filter sidecar listeners.", + ReadContext: dsContextHandler.ReadContext(), + Schema: map[string]*schema.Schema{ + utils.SidecarIDKey: { + Description: "Filter the results by sidecar ID.", + Type: schema.TypeString, + Required: true, + }, + DSRepoTypeKey: { + Description: "Filter the results per repository type. Supported repo types:" + utils.SupportedValuesAsMarkdown(repository.RepositoryTypes()), + Type: schema.TypeString, + Optional: true, + }, + utils.PortKey: { + Description: "Filter the results per port.", + Type: schema.TypeInt, + Optional: true, + }, + SidecarListenerListKey: { + Description: "List of existing listeners satisfying the filter criteria.", + Computed: true, + Type: schema.TypeList, + Elem: &schema.Resource{ + Schema: listenerSchema, + }, + }, + }, + } +} diff --git a/cyral/internal/sidecar/listener/data_source_cyral_sidecar_listener_test.go b/cyral/internal/sidecar/listener/datasource_test.go similarity index 89% rename from cyral/internal/sidecar/listener/data_source_cyral_sidecar_listener_test.go rename to cyral/internal/sidecar/listener/datasource_test.go index c751fe97..4a82c06d 100644 --- a/cyral/internal/sidecar/listener/data_source_cyral_sidecar_listener_test.go +++ b/cyral/internal/sidecar/listener/datasource_test.go @@ -44,11 +44,11 @@ func TestAccSidecarListenerDataSource(t *testing.T) { testConfigTypeFilter, testFuncTypeFilter := testListenerDataSource( testListeners, testListeners[0].RepoTypes[0], 0) - testConfigPortFilter, testFuncPortFilter := testListenerDataSource( - testListeners, "", testListeners[1].NetworkAddress.Port) + // testConfigPortFilter, testFuncPortFilter := testListenerDataSource( + // testListeners, "", testListeners[1].NetworkAddress.Port) - testConfigTypePortFilter, testFuncTypePortFilter := testListenerDataSource( - testListeners, testListeners[2].RepoTypes[0], testListeners[0].NetworkAddress.Port) + // testConfigTypePortFilter, testFuncTypePortFilter := testListenerDataSource( + // testListeners, testListeners[2].RepoTypes[0], testListeners[0].NetworkAddress.Port) resource.ParallelTest(t, resource.TestCase{ ProviderFactories: provider.ProviderFactories, @@ -57,14 +57,14 @@ func TestAccSidecarListenerDataSource(t *testing.T) { Config: testConfigTypeFilter, Check: testFuncTypeFilter, }, - { - Config: testConfigPortFilter, - Check: testFuncPortFilter, - }, - { - Config: testConfigTypePortFilter, - Check: testFuncTypePortFilter, - }, + // { + // Config: testConfigPortFilter, + // Check: testFuncPortFilter, + // }, + // { + // Config: testConfigTypePortFilter, + // Check: testFuncTypePortFilter, + // }, }, }) } diff --git a/cyral/internal/sidecar/listener/model.go b/cyral/internal/sidecar/listener/model.go new file mode 100644 index 00000000..e9e3b444 --- /dev/null +++ b/cyral/internal/sidecar/listener/model.go @@ -0,0 +1,245 @@ +package listener + +import ( + "context" + "fmt" + + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "golang.org/x/exp/slices" + + "github.com/cyralinc/terraform-provider-cyral/cyral/utils" +) + +// SidecarListener struct for sidecar listener. +type SidecarListener struct { + SidecarId string `json:"-"` + ListenerId string `json:"id"` + RepoTypes []string `json:"repoTypes"` + NetworkAddress *NetworkAddress `json:"address,omitempty"` + MySQLSettings *MySQLSettings `json:"mysqlSettings,omitempty"` + S3Settings *S3Settings `json:"s3Settings,omitempty"` + DynamoDbSettings *DynamoDbSettings `json:"dynamoDbSettings,omitempty"` + SQLServerSettings *SQLServerSettings `json:"sqlServerSettings,omitempty"` +} + +type NetworkAddress struct { + Host string `json:"host,omitempty"` + Port int `json:"port"` +} + +type MySQLSettings struct { + DbVersion string `json:"dbVersion,omitempty"` + CharacterSet string `json:"characterSet,omitempty"` +} + +type S3Settings struct { + ProxyMode bool `json:"proxyMode,omitempty"` +} + +type DynamoDbSettings struct { + ProxyMode bool `json:"proxyMode,omitempty"` +} + +type SQLServerSettings struct { + Version string `json:"version,omitempty"` +} + +type ReadSidecarListenerAPIResponse struct { + ListenerConfig *SidecarListener `json:"listenerConfig"` +} +type CreateListenerAPIResponse struct { + ListenerId string `json:"listenerId"` +} + +func (c CreateListenerAPIResponse) WriteToSchema(d *schema.ResourceData) error { + d.SetId(utils.MarshalComposedID([]string{d.Get(utils.SidecarIDKey).(string), c.ListenerId}, "/")) + return d.Set(utils.ListenerIDKey, c.ListenerId) +} + +func (data ReadSidecarListenerAPIResponse) WriteToSchema(d *schema.ResourceData) error { + ctx := context.Background() + tflog.Debug(ctx, "Init ReadSidecarListenerAPIResponse.WriteToSchema") + if data.ListenerConfig != nil { + _ = d.Set(utils.ListenerIDKey, data.ListenerConfig.ListenerId) + _ = d.Set(RepoTypesKey, data.ListenerConfig.RepoTypesAsInterface()) + _ = d.Set(NetworkAddressKey, data.ListenerConfig.NetworkAddressAsInterface()) + _ = d.Set(S3SettingsKey, data.ListenerConfig.S3SettingsAsInterface()) + _ = d.Set(MySQLSettingsKey, data.ListenerConfig.MySQLSettingsAsInterface()) + _ = d.Set(DynamoDbSettingsKey, data.ListenerConfig.DynamoDbSettingsAsInterface()) + _ = d.Set(SQLServerSettingsKey, data.ListenerConfig.SQLServerSettingsAsInterface()) + } + tflog.Debug(ctx, "End ReadSidecarListenerAPIResponse.WriteToSchema") + return nil +} + +func (l *SidecarListener) RepoTypesAsInterface() []interface{} { + if l.RepoTypes == nil { + return nil + } + result := make([]interface{}, len(l.RepoTypes)) + for i, v := range l.RepoTypes { + result[i] = v + } + return result +} +func (l *SidecarListener) RepoTypesFromInterface(anInterface []interface{}) { + repoTypes := make([]string, len(anInterface)) + for i, v := range anInterface { + repoTypes[i] = v.(string) + } + l.RepoTypes = repoTypes +} +func (l *SidecarListener) NetworkAddressAsInterface() []interface{} { + if l.NetworkAddress == nil { + return nil + } + return []interface{}{ + map[string]interface{}{ + utils.HostKey: l.NetworkAddress.Host, + utils.PortKey: l.NetworkAddress.Port, + }, + } +} +func (l *SidecarListener) NetworkAddressFromInterface(anInterface []interface{}) { + if len(anInterface) == 0 { + return + } + l.NetworkAddress = &NetworkAddress{ + Host: anInterface[0].(map[string]interface{})[utils.HostKey].(string), + Port: anInterface[0].(map[string]interface{})[utils.PortKey].(int), + } +} +func (l *SidecarListener) MySQLSettingsAsInterface() []interface{} { + if l.MySQLSettings == nil { + return nil + } + return []interface{}{map[string]interface{}{ + DbVersionKey: l.MySQLSettings.DbVersion, + CharacterSetKey: l.MySQLSettings.CharacterSet, + }} +} +func (l *SidecarListener) MySQLSettingsFromInterface(anInterface []interface{}) { + if len(anInterface) == 0 { + return + } + l.MySQLSettings = &MySQLSettings{ + DbVersion: anInterface[0].(map[string]interface{})[DbVersionKey].(string), + CharacterSet: anInterface[0].(map[string]interface{})[CharacterSetKey].(string), + } +} +func (l *SidecarListener) S3SettingsAsInterface() []interface{} { + if l.S3Settings == nil { + return nil + } + return []interface{}{map[string]interface{}{ + ProxyModeKey: l.S3Settings.ProxyMode, + }} +} +func (l *SidecarListener) S3SettingsFromInterface(anInterface []interface{}) { + if len(anInterface) == 0 { + return + } + l.S3Settings = &S3Settings{ + ProxyMode: anInterface[0].(map[string]interface{})[ProxyModeKey].(bool), + } +} +func (l *SidecarListener) DynamoDbSettingsAsInterface() []interface{} { + if l.DynamoDbSettings == nil { + return nil + } + return []interface{}{map[string]interface{}{ + ProxyModeKey: l.DynamoDbSettings.ProxyMode, + }} +} +func (l *SidecarListener) DynamoDbSettingsFromInterface(anInterface []interface{}) { + if len(anInterface) == 0 { + return + } + l.DynamoDbSettings = &DynamoDbSettings{ + ProxyMode: anInterface[0].(map[string]interface{})[ProxyModeKey].(bool), + } +} +func (l *SidecarListener) SQLServerSettingsAsInterface() []interface{} { + if l.SQLServerSettings == nil { + return nil + } + return []interface{}{map[string]interface{}{ + SQLServerVersionKey: l.SQLServerSettings.Version, + }} +} +func (l *SidecarListener) SQLServerSettingsFromInterface(anInterface []interface{}) { + if len(anInterface) == 0 { + return + } + l.SQLServerSettings = &SQLServerSettings{ + Version: anInterface[0].(map[string]interface{})[SQLServerVersionKey].(string), + } +} + +// SidecarListenerResource represents the payload of a create or update a listener request +type SidecarListenerResource struct { + ListenerConfig SidecarListener `json:"listenerConfig"` +} + +// ReadFromSchema populates the SidecarListenerResource from the schema +func (s *SidecarListenerResource) ReadFromSchema(d *schema.ResourceData) error { + s.ListenerConfig = SidecarListener{ + SidecarId: d.Get(utils.SidecarIDKey).(string), + ListenerId: d.Get(utils.ListenerIDKey).(string), + } + s.ListenerConfig.RepoTypesFromInterface(d.Get(RepoTypesKey).([]interface{})) + s.ListenerConfig.NetworkAddressFromInterface(d.Get(NetworkAddressKey).(*schema.Set).List()) + s.ListenerConfig.MySQLSettingsFromInterface(d.Get(MySQLSettingsKey).(*schema.Set).List()) + s.ListenerConfig.S3SettingsFromInterface(d.Get(S3SettingsKey).(*schema.Set).List()) + s.ListenerConfig.DynamoDbSettingsFromInterface(d.Get(DynamoDbSettingsKey).(*schema.Set).List()) + s.ListenerConfig.SQLServerSettingsFromInterface(d.Get(SQLServerSettingsKey).(*schema.Set).List()) + + return nil +} + +type ReadDataSourceSidecarListenerAPIResponse struct { + ListenerConfigs []SidecarListener `json:"listenerConfigs"` +} + +func (data ReadDataSourceSidecarListenerAPIResponse) WriteToSchema(d *schema.ResourceData) error { + ctx := context.Background() + tflog.Debug(ctx, "Init ReadDataSourceSidecarListenerAPIResponse.WriteToSchema") + var listenersList []any + tflog.Debug(ctx, fmt.Sprintf("data.ListenerConfig: %+v", data.ListenerConfigs)) + tflog.Debug(ctx, "Init for _, l := range data.ListenerConfig") + repoTypeFilter := d.Get(DSRepoTypeKey).(string) + portFilter := d.Get(utils.PortKey).(int) + for _, listenerConfig := range data.ListenerConfigs { + // Check if either the repo filter or the port filter is provided and matches the listener + if (repoTypeFilter == "" || slices.Contains(listenerConfig.RepoTypes, repoTypeFilter)) && + (portFilter == 0 || listenerConfig.NetworkAddress.Port == portFilter) { + listener := map[string]any{ + utils.ListenerIDKey: listenerConfig.ListenerId, + utils.SidecarIDKey: d.Get(utils.SidecarIDKey).(string), + RepoTypesKey: listenerConfig.RepoTypes, + NetworkAddressKey: listenerConfig.NetworkAddressAsInterface(), + MySQLSettingsKey: listenerConfig.MySQLSettingsAsInterface(), + S3SettingsKey: listenerConfig.S3SettingsAsInterface(), + DynamoDbSettingsKey: listenerConfig.DynamoDbSettingsAsInterface(), + SQLServerSettingsKey: listenerConfig.SQLServerSettingsAsInterface(), + } + tflog.Debug(ctx, fmt.Sprintf("listener: %q", listener)) + listenersList = append(listenersList, listener) + } + } + + tflog.Debug(ctx, fmt.Sprintf("listenersList: %q", listenersList)) + tflog.Debug(ctx, "End for _, l := range data.ListenerConfig") + + if err := d.Set(SidecarListenerListKey, listenersList); err != nil { + return err + } + + d.SetId(uuid.New().String()) + + tflog.Debug(ctx, "End ReadDataSourceSidecarListenerAPIResponse.WriteToSchema") + + return nil +} diff --git a/cyral/internal/sidecar/listener/resource_cyral_sidecar_listener.go b/cyral/internal/sidecar/listener/resource.go similarity index 51% rename from cyral/internal/sidecar/listener/resource_cyral_sidecar_listener.go rename to cyral/internal/sidecar/listener/resource.go index 833f722f..caf750b1 100644 --- a/cyral/internal/sidecar/listener/resource_cyral_sidecar_listener.go +++ b/cyral/internal/sidecar/listener/resource.go @@ -3,228 +3,34 @@ package listener import ( "context" "fmt" - "net/http" - "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/cyralinc/terraform-provider-cyral/cyral/client" "github.com/cyralinc/terraform-provider-cyral/cyral/core" - "github.com/cyralinc/terraform-provider-cyral/cyral/core/types/operationtype" + "github.com/cyralinc/terraform-provider-cyral/cyral/core/types/resourcetype" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository" "github.com/cyralinc/terraform-provider-cyral/cyral/utils" ) -// create a constant block for schema keys - -const ( - RepoTypesKey = "repo_types" - NetworkAddressKey = "network_address" - MySQLSettingsKey = "mysql_settings" - DbVersionKey = "db_version" - CharacterSetKey = "character_set" - S3SettingsKey = "s3_settings" - ProxyModeKey = "proxy_mode" - DynamoDbSettingsKey = "dynamodb_settings" - SQLServerSettingsKey = "sqlserver_settings" - SQLServerVersionKey = "version" -) - -// SidecarListener struct for sidecar listener. -type SidecarListener struct { - SidecarId string `json:"-"` - ListenerId string `json:"id"` - RepoTypes []string `json:"repoTypes"` - NetworkAddress *NetworkAddress `json:"address,omitempty"` - MySQLSettings *MySQLSettings `json:"mysqlSettings,omitempty"` - S3Settings *S3Settings `json:"s3Settings,omitempty"` - DynamoDbSettings *DynamoDbSettings `json:"dynamoDbSettings,omitempty"` - SQLServerSettings *SQLServerSettings `json:"sqlServerSettings,omitempty"` -} -type NetworkAddress struct { - Host string `json:"host,omitempty"` - Port int `json:"port"` -} -type MySQLSettings struct { - DbVersion string `json:"dbVersion,omitempty"` - CharacterSet string `json:"characterSet,omitempty"` -} -type S3Settings struct { - ProxyMode bool `json:"proxyMode,omitempty"` -} -type DynamoDbSettings struct { - ProxyMode bool `json:"proxyMode,omitempty"` -} - -type SQLServerSettings struct { - Version string `json:"version,omitempty"` -} - -var ReadSidecarListenersConfig = core.ResourceOperationConfig{ - ResourceName: "SidecarListenersResourceRead", - Type: operationtype.Read, - HttpMethod: http.MethodGet, - URLFactory: func(d *schema.ResourceData, c *client.Client) string { +var resourceContextHandler = core.DefaultContextHandler{ + ResourceName: resourceName, + ResourceType: resourcetype.Resource, + SchemaReaderFactory: func() core.SchemaReader { return &SidecarListenerResource{} }, + SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &ReadSidecarListenerAPIResponse{} }, + SchemaWriterFactoryPostMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &CreateListenerAPIResponse{} }, + BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { + return fmt.Sprintf("https://%s/v1/sidecars/%s/listeners", + c.ControlPlane, + d.Get(utils.SidecarIDKey).(string)) + }, + IdBasedURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/sidecars/%s/listeners/%s", c.ControlPlane, d.Get(utils.SidecarIDKey).(string), - d.Get(utils.ListenerIDKey).(string)) + d.Get(utils.ListenerIDKey).(string), + ) }, - SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &ReadSidecarListenerAPIResponse{} }, - RequestErrorHandler: &core.IgnoreHttpNotFound{ResName: "Sidecar listener"}, -} - -type ReadSidecarListenerAPIResponse struct { - ListenerConfig *SidecarListener `json:"listenerConfig"` -} -type CreateListenerAPIResponse struct { - ListenerId string `json:"listenerId"` -} - -func (c CreateListenerAPIResponse) WriteToSchema(d *schema.ResourceData) error { - d.SetId(utils.MarshalComposedID([]string{d.Get(utils.SidecarIDKey).(string), c.ListenerId}, "/")) - return d.Set(utils.ListenerIDKey, c.ListenerId) -} - -func (data ReadSidecarListenerAPIResponse) WriteToSchema(d *schema.ResourceData) error { - ctx := context.Background() - tflog.Debug(ctx, "Init ReadSidecarListenerAPIResponse.WriteToSchema") - if data.ListenerConfig != nil { - _ = d.Set(utils.ListenerIDKey, data.ListenerConfig.ListenerId) - _ = d.Set(RepoTypesKey, data.ListenerConfig.RepoTypesAsInterface()) - _ = d.Set(NetworkAddressKey, data.ListenerConfig.NetworkAddressAsInterface()) - _ = d.Set(S3SettingsKey, data.ListenerConfig.S3SettingsAsInterface()) - _ = d.Set(MySQLSettingsKey, data.ListenerConfig.MySQLSettingsAsInterface()) - _ = d.Set(DynamoDbSettingsKey, data.ListenerConfig.DynamoDbSettingsAsInterface()) - _ = d.Set(SQLServerSettingsKey, data.ListenerConfig.SQLServerSettingsAsInterface()) - } - tflog.Debug(ctx, "End ReadSidecarListenerAPIResponse.WriteToSchema") - return nil -} - -func (l *SidecarListener) RepoTypesAsInterface() []interface{} { - if l.RepoTypes == nil { - return nil - } - result := make([]interface{}, len(l.RepoTypes)) - for i, v := range l.RepoTypes { - result[i] = v - } - return result -} -func (l *SidecarListener) RepoTypesFromInterface(anInterface []interface{}) { - repoTypes := make([]string, len(anInterface)) - for i, v := range anInterface { - repoTypes[i] = v.(string) - } - l.RepoTypes = repoTypes -} -func (l *SidecarListener) NetworkAddressAsInterface() []interface{} { - if l.NetworkAddress == nil { - return nil - } - return []interface{}{ - map[string]interface{}{ - utils.HostKey: l.NetworkAddress.Host, - utils.PortKey: l.NetworkAddress.Port, - }, - } -} -func (l *SidecarListener) NetworkAddressFromInterface(anInterface []interface{}) { - if len(anInterface) == 0 { - return - } - l.NetworkAddress = &NetworkAddress{ - Host: anInterface[0].(map[string]interface{})[utils.HostKey].(string), - Port: anInterface[0].(map[string]interface{})[utils.PortKey].(int), - } -} -func (l *SidecarListener) MySQLSettingsAsInterface() []interface{} { - if l.MySQLSettings == nil { - return nil - } - return []interface{}{map[string]interface{}{ - DbVersionKey: l.MySQLSettings.DbVersion, - CharacterSetKey: l.MySQLSettings.CharacterSet, - }} -} -func (l *SidecarListener) MySQLSettingsFromInterface(anInterface []interface{}) { - if len(anInterface) == 0 { - return - } - l.MySQLSettings = &MySQLSettings{ - DbVersion: anInterface[0].(map[string]interface{})[DbVersionKey].(string), - CharacterSet: anInterface[0].(map[string]interface{})[CharacterSetKey].(string), - } -} -func (l *SidecarListener) S3SettingsAsInterface() []interface{} { - if l.S3Settings == nil { - return nil - } - return []interface{}{map[string]interface{}{ - ProxyModeKey: l.S3Settings.ProxyMode, - }} -} -func (l *SidecarListener) S3SettingsFromInterface(anInterface []interface{}) { - if len(anInterface) == 0 { - return - } - l.S3Settings = &S3Settings{ - ProxyMode: anInterface[0].(map[string]interface{})[ProxyModeKey].(bool), - } -} -func (l *SidecarListener) DynamoDbSettingsAsInterface() []interface{} { - if l.DynamoDbSettings == nil { - return nil - } - return []interface{}{map[string]interface{}{ - ProxyModeKey: l.DynamoDbSettings.ProxyMode, - }} -} -func (l *SidecarListener) DynamoDbSettingsFromInterface(anInterface []interface{}) { - if len(anInterface) == 0 { - return - } - l.DynamoDbSettings = &DynamoDbSettings{ - ProxyMode: anInterface[0].(map[string]interface{})[ProxyModeKey].(bool), - } -} -func (l *SidecarListener) SQLServerSettingsAsInterface() []interface{} { - if l.SQLServerSettings == nil { - return nil - } - return []interface{}{map[string]interface{}{ - SQLServerVersionKey: l.SQLServerSettings.Version, - }} -} -func (l *SidecarListener) SQLServerSettingsFromInterface(anInterface []interface{}) { - if len(anInterface) == 0 { - return - } - l.SQLServerSettings = &SQLServerSettings{ - Version: anInterface[0].(map[string]interface{})[SQLServerVersionKey].(string), - } -} - -// SidecarListenerResource represents the payload of a create or update a listener request -type SidecarListenerResource struct { - ListenerConfig SidecarListener `json:"listenerConfig"` -} - -// ReadFromSchema populates the SidecarListenerResource from the schema -func (s *SidecarListenerResource) ReadFromSchema(d *schema.ResourceData) error { - s.ListenerConfig = SidecarListener{ - SidecarId: d.Get(utils.SidecarIDKey).(string), - ListenerId: d.Get(utils.ListenerIDKey).(string), - } - s.ListenerConfig.RepoTypesFromInterface(d.Get(RepoTypesKey).([]interface{})) - s.ListenerConfig.NetworkAddressFromInterface(d.Get(NetworkAddressKey).(*schema.Set).List()) - s.ListenerConfig.MySQLSettingsFromInterface(d.Get(MySQLSettingsKey).(*schema.Set).List()) - s.ListenerConfig.S3SettingsFromInterface(d.Get(S3SettingsKey).(*schema.Set).List()) - s.ListenerConfig.DynamoDbSettingsFromInterface(d.Get(DynamoDbSettingsKey).(*schema.Set).List()) - s.ListenerConfig.SQLServerSettingsFromInterface(d.Get(SQLServerSettingsKey).(*schema.Set).List()) - - return nil } // resourceSidecarListener returns the schema and methods for provisioning a sidecar listener @@ -233,55 +39,16 @@ func (s *SidecarListenerResource) ReadFromSchema(d *schema.ResourceData) error { // POST {{baseURL}}/sidecars/:sidecarID/listeners/ (Create a listener) // PUT {{baseURL}}/sidecars/:sidecarID/listeners/:listenerID (Update a listener) // DELETE {{baseURL}}/sidecars/:sidecarID/listeners/:listenerID (Delete a listener) -func ResourceSidecarListener() *schema.Resource { +func resourceSchema() *schema.Resource { return &schema.Resource{ Description: "Manages [sidecar listeners](https://cyral.com/docs/sidecars/sidecar-listeners)." + "\n~> **Warning** Multiple listeners can be associated to a single sidecar as long as " + "`host` and `port` are unique. If `host` is omitted, then `port` must be unique.", - CreateContext: core.CreateResource( - core.ResourceOperationConfig{ - ResourceName: "SidecarListenersResourceCreate", - Type: operationtype.Create, - HttpMethod: http.MethodPost, - URLFactory: func(d *schema.ResourceData, c *client.Client) string { - return fmt.Sprintf("https://%s/v1/sidecars/%s/listeners", - c.ControlPlane, - d.Get(utils.SidecarIDKey).(string)) - - }, - SchemaReaderFactory: func() core.SchemaReader { return &SidecarListenerResource{} }, - SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &CreateListenerAPIResponse{} }, - }, ReadSidecarListenersConfig, - ), - ReadContext: core.ReadResource(ReadSidecarListenersConfig), - UpdateContext: core.UpdateResource( - core.ResourceOperationConfig{ - ResourceName: "SidecarListenersResourceUpdate", - Type: operationtype.Update, - HttpMethod: http.MethodPut, - URLFactory: func(d *schema.ResourceData, c *client.Client) string { - return fmt.Sprintf("https://%s/v1/sidecars/%s/listeners/%s", - c.ControlPlane, - d.Get(utils.SidecarIDKey).(string), - d.Get(utils.ListenerIDKey).(string)) + CreateContext: resourceContextHandler.CreateContext(), + ReadContext: resourceContextHandler.ReadContext(), + UpdateContext: resourceContextHandler.UpdateContext(), + DeleteContext: resourceContextHandler.DeleteContext(), - }, - SchemaReaderFactory: func() core.SchemaReader { return &SidecarListenerResource{} }, - }, ReadSidecarListenersConfig, - ), - DeleteContext: core.DeleteResource( - core.ResourceOperationConfig{ - ResourceName: "SidecarListenersResourceDelete", - Type: operationtype.Delete, - HttpMethod: http.MethodDelete, - URLFactory: func(d *schema.ResourceData, c *client.Client) string { - return fmt.Sprintf("https://%s/v1/sidecars/%s/listeners/%s", - c.ControlPlane, - d.Get(utils.SidecarIDKey).(string), - d.Get(utils.ListenerIDKey).(string)) - }, - }, - ), Schema: getSidecarListenerSchema(), Importer: &schema.ResourceImporter{ StateContext: func( diff --git a/cyral/internal/sidecar/listener/resource_cyral_sidecar_listener_test.go b/cyral/internal/sidecar/listener/resource_test.go similarity index 100% rename from cyral/internal/sidecar/listener/resource_cyral_sidecar_listener_test.go rename to cyral/internal/sidecar/listener/resource_test.go diff --git a/cyral/internal/sidecar/listener/schema_loader.go b/cyral/internal/sidecar/listener/schema_loader.go new file mode 100644 index 00000000..8ec7f34e --- /dev/null +++ b/cyral/internal/sidecar/listener/schema_loader.go @@ -0,0 +1,31 @@ +package listener + +import ( + "github.com/cyralinc/terraform-provider-cyral/cyral/core" +) + +type packageSchema struct { +} + +func (p *packageSchema) Name() string { + return "sidecar" +} + +func (p *packageSchema) Schemas() []*core.SchemaDescriptor { + return []*core.SchemaDescriptor{ + { + Name: dataSourceName, + Type: core.DataSourceSchemaType, + Schema: dataSourceSchema, + }, + { + Name: resourceName, + Type: core.ResourceSchemaType, + Schema: resourceSchema, + }, + } +} + +func PackageSchema() core.PackageSchema { + return &packageSchema{} +} diff --git a/cyral/internal/sidecar/schema_loader.go b/cyral/internal/sidecar/schema_loader.go index 8cc43233..6e5c4809 100644 --- a/cyral/internal/sidecar/schema_loader.go +++ b/cyral/internal/sidecar/schema_loader.go @@ -13,11 +13,6 @@ func (p *packageSchema) Name() string { func (p *packageSchema) Schemas() []*core.SchemaDescriptor { return []*core.SchemaDescriptor{ - // { - // Name: dataSourceName, - // Type: core.DataSourceSchemaType, - // Schema: dataSourceSchema, - // }, { Name: resourceName, Type: core.ResourceSchemaType, diff --git a/cyral/provider/provider.go b/cyral/provider/provider.go index 7624e482..609efb86 100644 --- a/cyral/provider/provider.go +++ b/cyral/provider/provider.go @@ -27,7 +27,6 @@ import ( "github.com/cyralinc/terraform-provider-cyral/cyral/internal/sidecar" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/sidecar/health" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/sidecar/instance" - "github.com/cyralinc/terraform-provider-cyral/cyral/internal/sidecar/listener" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/systeminfo" ) @@ -113,7 +112,6 @@ func getDataSourceMap(ps []core.PackageSchema) map[string]*schema.Resource { schemaMap["cyral_sidecar_instance_ids"] = deprecated.DataSourceSidecarInstanceIDs() schemaMap["cyral_sidecar_instance_stats"] = instance.DataSourceSidecarInstanceStats() schemaMap["cyral_sidecar_instance"] = instance.DataSourceSidecarInstance() - schemaMap["cyral_sidecar_listener"] = listener.DataSourceSidecarListener() schemaMap["cyral_system_info"] = systeminfo.DataSourceSystemInfo() tflog.Debug(ctx, "End getDataSourceMap") @@ -162,7 +160,6 @@ func getResourceMap(ps []core.PackageSchema) map[string]*schema.Resource { schemaMap["cyral_role"] = role.ResourceRole() schemaMap["cyral_role_sso_groups"] = role.ResourceRoleSSOGroups() schemaMap["cyral_service_account"] = serviceaccount.ResourceServiceAccount() - schemaMap["cyral_sidecar_listener"] = listener.ResourceSidecarListener() tflog.Debug(ctx, "End getResourceMap") diff --git a/cyral/provider/schema_loader.go b/cyral/provider/schema_loader.go index 2882e7d3..4cc5f372 100644 --- a/cyral/provider/schema_loader.go +++ b/cyral/provider/schema_loader.go @@ -18,6 +18,7 @@ import ( "github.com/cyralinc/terraform-provider-cyral/cyral/internal/samlcertificate" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/sidecar" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/sidecar/credentials" + "github.com/cyralinc/terraform-provider-cyral/cyral/internal/sidecar/listener" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/tokensettings" ) @@ -32,6 +33,7 @@ func packagesSchemas() []core.PackageSchema { datalabel.PackageSchema(), datamap.PackageSchema(), hcvault.PackageSchema(), + listener.PackageSchema(), network.PackageSchema(), repository.PackageSchema(), samlcertificate.PackageSchema(), From d6ad33e3249958db1fd58608c06e05e984bacc42 Mon Sep 17 00:00:00 2001 From: Wilson de Carvalho <796900+wcmjunior@users.noreply.github.com> Date: Wed, 3 Apr 2024 11:01:33 -0700 Subject: [PATCH 16/22] Improve names in DefaultContextHandler --- cyral/core/README.md | 16 +++++----- cyral/core/default_context_handler.go | 29 ++++++++++--------- cyral/internal/datalabel/resource.go | 2 +- .../resource_cyral_integration_datadog.go | 2 +- .../resource_cyral_integration_elk.go | 2 +- .../resource_cyral_integration_logstash.go | 2 +- .../resource_cyral_integration_looker.go | 2 +- .../resource_cyral_integration_splunk.go | 2 +- .../resource_cyral_integration_sumo_logic.go | 2 +- .../resource_cyral_integration_aws_iam.go | 2 +- .../internal/integration/hcvault/resource.go | 2 +- cyral/internal/integration/slack/resource.go | 2 +- cyral/internal/integration/teams/resource.go | 2 +- cyral/internal/repository/binding/resource.go | 4 +-- .../repository/confanalysis/resource.go | 4 +-- cyral/internal/repository/datasource.go | 2 +- cyral/internal/repository/network/resource.go | 4 +-- cyral/internal/repository/resource.go | 2 +- .../internal/sidecar/credentials/resource.go | 2 +- cyral/internal/sidecar/listener/datasource.go | 2 +- cyral/internal/sidecar/listener/resource.go | 4 +-- 21 files changed, 45 insertions(+), 46 deletions(-) diff --git a/cyral/core/README.md b/cyral/core/README.md index 86bd692b..0c48a47a 100644 --- a/cyral/core/README.md +++ b/cyral/core/README.md @@ -45,7 +45,7 @@ type NewFeature struct { Description string `json:"description,omitempty"` } -func (r *NewFeature) WriteToSchema(d *schema.ResourceData) error { +func (r NewFeature) WriteToSchema(d *schema.ResourceData) error { if err := d.Set("description", r.Description); err != nil { return fmt.Errorf("error setting 'description' field: %w", err) } @@ -65,9 +65,7 @@ func (r *NewFeature) ReadFromSchema(d *schema.ResourceData) error { ### datasource.go -Even though the `GET` url for this new feature is `https:///v1/NewFeature/`, -the `BaseURLFactory` provided does not provide the `ID` as it will be automatically -added by the default read handler returned in `contextHandler.ReadContext()`. +Use the `GetPutDeleteURLFactory` to provide the URL factory to read the data source from the API. ```go // datasource.go @@ -76,9 +74,9 @@ package newfeature var dsContextHandler = core.DefaultContextHandler{ ResourceName: dataSourceName, ResourceType: resourcetype.DataSource, - SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &NewFeature{} }, - IdBasedURLFactory: func(d *schema.ResourceData, c *client.Client) string { - return fmt.Sprintf("https://%s/v1/NewFeature", c.ControlPlane) + SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &NewFeature{} }, + GetPutDeleteURLFactory: func(d *schema.ResourceData, c *client.Client) string { + return fmt.Sprintf("https://%s/v1/NewFeature/%s", c.ControlPlane, d.Get("my_id_field").(string)) }, } @@ -112,8 +110,8 @@ var resourceContextHandler = core.DefaultContextHandler{ ResourceName: resourceName, ResourceType: resourcetype.Resource, SchemaReaderFactory: func() core.SchemaReader { return &NewFeature{} }, - SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &NewFeature{} }, - BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { + SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &NewFeature{} }, + PostURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/NewFeature", c.ControlPlane) }, } diff --git a/cyral/core/default_context_handler.go b/cyral/core/default_context_handler.go index e699b6e1..f3110694 100644 --- a/cyral/core/default_context_handler.go +++ b/cyral/core/default_context_handler.go @@ -19,12 +19,13 @@ import ( // it will assume that a call to POST returns a JSON with // an `id` field, meaning it will use the // `IDBasedResponse` struct in such cases. -// 3. `BaseURLFactory` must be provided. It will be used to -// create the POST endpoint and others in case `IdBasedURLFactory` +// 3. `PostURLFactory` must be provided for resources. It will be used to +// create the POST endpoint and others in case `GetPutDeleteURLFactory` // is not provided. -// 4. If `IdBasedURLFactory` is NOT provided, the endpoint to -// perform GET, PUT and DELETE calls are composed by the -// `BaseURLFactory` endpoint plus the ID specification as follows: +// 4. `GetPutDeleteURLFactory` must be provided for data sources. +// 5. If `GetPutDeleteURLFactory` is NOT provided (data sources or resources), +// the endpoint to perform GET, PUT and DELETE calls are composed by the +// `PostURLFactory` endpoint plus the ID specification as follows: // - POST: https://// // - GET: https:///// // - PUT: https:///// @@ -39,11 +40,11 @@ type DefaultContextHandler struct { // SchemaWriterFactoryPostMethod defines how the schema will be // written in POST operations. SchemaWriterFactoryPostMethod SchemaWriterFactoryFunc - // BaseURLFactory provides the base URL used for POSTs and that - // will also be used to compose the ID URL in case the later - // is not provided. - BaseURLFactory URLFactoryFunc - IdBasedURLFactory URLFactoryFunc + // PostURLFactory provides the URL used for POSTs and that + // will also be used to compose the ID URL for GET, PUT and + // DELETE in case ` GetPutDeleteURLFactory` is not provided. + PostURLFactory URLFactoryFunc + GetPutDeleteURLFactory URLFactoryFunc } func DefaultSchemaWriterFactory(d *schema.ResourceData) SchemaWriter { @@ -61,11 +62,11 @@ func (dch DefaultContextHandler) defaultOperationHandler( endpoint := func(d *schema.ResourceData, c *client.Client) string { var url string if httpMethod == http.MethodPost { - url = dch.BaseURLFactory(d, c) - } else if dch.IdBasedURLFactory != nil { - url = dch.IdBasedURLFactory(d, c) + url = dch.PostURLFactory(d, c) + } else if dch.GetPutDeleteURLFactory != nil { + url = dch.GetPutDeleteURLFactory(d, c) } else { - url = fmt.Sprintf("%s/%s", dch.BaseURLFactory(d, c), d.Id()) + url = fmt.Sprintf("%s/%s", dch.PostURLFactory(d, c), d.Id()) } tflog.Debug(context.Background(), fmt.Sprintf("Returning base URL for %s '%s' operation '%s' and httpMethod %s: %s", dch.ResourceType, dch.ResourceName, operationType, httpMethod, url)) diff --git a/cyral/internal/datalabel/resource.go b/cyral/internal/datalabel/resource.go index e2b49d1e..e4dba3b0 100644 --- a/cyral/internal/datalabel/resource.go +++ b/cyral/internal/datalabel/resource.go @@ -21,7 +21,7 @@ func resourceSchema() *schema.Resource { ResourceType: resourcetype.Resource, SchemaReaderFactory: func() core.SchemaReader { return &DataLabel{} }, SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &DataLabel{} }, - BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { + PostURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/datalabels", c.ControlPlane) }, diff --git a/cyral/internal/deprecated/resource_cyral_integration_datadog.go b/cyral/internal/deprecated/resource_cyral_integration_datadog.go index f1a067ae..ca44b064 100644 --- a/cyral/internal/deprecated/resource_cyral_integration_datadog.go +++ b/cyral/internal/deprecated/resource_cyral_integration_datadog.go @@ -34,7 +34,7 @@ func ResourceIntegrationDatadog() *schema.Resource { ResourceType: resourcetype.Resource, SchemaReaderFactory: func() core.SchemaReader { return &DatadogIntegration{} }, SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &DatadogIntegration{} }, - BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { + PostURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/integrations/datadog", c.ControlPlane) }, } diff --git a/cyral/internal/deprecated/resource_cyral_integration_elk.go b/cyral/internal/deprecated/resource_cyral_integration_elk.go index f0031f9a..be624c2f 100644 --- a/cyral/internal/deprecated/resource_cyral_integration_elk.go +++ b/cyral/internal/deprecated/resource_cyral_integration_elk.go @@ -37,7 +37,7 @@ func ResourceIntegrationELK() *schema.Resource { ResourceType: resourcetype.Resource, SchemaReaderFactory: func() core.SchemaReader { return &ELKIntegration{} }, SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &ELKIntegration{} }, - BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { + PostURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/integrations/elk", c.ControlPlane) }, } diff --git a/cyral/internal/deprecated/resource_cyral_integration_logstash.go b/cyral/internal/deprecated/resource_cyral_integration_logstash.go index 88ba38a0..91ed2f31 100644 --- a/cyral/internal/deprecated/resource_cyral_integration_logstash.go +++ b/cyral/internal/deprecated/resource_cyral_integration_logstash.go @@ -41,7 +41,7 @@ func ResourceIntegrationLogstash() *schema.Resource { ResourceType: resourcetype.Resource, SchemaReaderFactory: func() core.SchemaReader { return &LogstashIntegration{} }, SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &LogstashIntegration{} }, - BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { + PostURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/integrations/logstash", c.ControlPlane) }, } diff --git a/cyral/internal/deprecated/resource_cyral_integration_looker.go b/cyral/internal/deprecated/resource_cyral_integration_looker.go index f67f6cf8..659bb399 100644 --- a/cyral/internal/deprecated/resource_cyral_integration_looker.go +++ b/cyral/internal/deprecated/resource_cyral_integration_looker.go @@ -35,7 +35,7 @@ func ResourceIntegrationLooker() *schema.Resource { ResourceType: resourcetype.Resource, SchemaReaderFactory: func() core.SchemaReader { return &LookerIntegration{} }, SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &LookerIntegration{} }, - BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { + PostURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/integrations/looker", c.ControlPlane) }, } diff --git a/cyral/internal/deprecated/resource_cyral_integration_splunk.go b/cyral/internal/deprecated/resource_cyral_integration_splunk.go index 50c41d96..fbcf6423 100644 --- a/cyral/internal/deprecated/resource_cyral_integration_splunk.go +++ b/cyral/internal/deprecated/resource_cyral_integration_splunk.go @@ -45,7 +45,7 @@ func ResourceIntegrationSplunk() *schema.Resource { ResourceType: resourcetype.Resource, SchemaReaderFactory: func() core.SchemaReader { return &SplunkIntegration{} }, SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &SplunkIntegration{} }, - BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { + PostURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/integrations/splunk", c.ControlPlane) }, } diff --git a/cyral/internal/deprecated/resource_cyral_integration_sumo_logic.go b/cyral/internal/deprecated/resource_cyral_integration_sumo_logic.go index 9459bef1..94dab998 100644 --- a/cyral/internal/deprecated/resource_cyral_integration_sumo_logic.go +++ b/cyral/internal/deprecated/resource_cyral_integration_sumo_logic.go @@ -32,7 +32,7 @@ func ResourceIntegrationSumoLogic() *schema.Resource { ResourceType: resourcetype.Resource, SchemaReaderFactory: func() core.SchemaReader { return &SumoLogicIntegration{} }, SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &SumoLogicIntegration{} }, - BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { + PostURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/integrations/sumologic", c.ControlPlane) }, } diff --git a/cyral/internal/integration/awsiam/resource_cyral_integration_aws_iam.go b/cyral/internal/integration/awsiam/resource_cyral_integration_aws_iam.go index 81eaaeed..cbafefe7 100644 --- a/cyral/internal/integration/awsiam/resource_cyral_integration_aws_iam.go +++ b/cyral/internal/integration/awsiam/resource_cyral_integration_aws_iam.go @@ -68,7 +68,7 @@ func ResourceIntegrationAWSIAM() *schema.Resource { ResourceType: resourcetype.Resource, SchemaReaderFactory: func() core.SchemaReader { return &AWSIAMIntegrationWrapper{} }, SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &AWSIAMIntegrationWrapper{} }, - BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { + PostURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/integrations/aws/iam", c.ControlPlane) }, } diff --git a/cyral/internal/integration/hcvault/resource.go b/cyral/internal/integration/hcvault/resource.go index f0fd8b77..186b9656 100644 --- a/cyral/internal/integration/hcvault/resource.go +++ b/cyral/internal/integration/hcvault/resource.go @@ -14,7 +14,7 @@ var resourceContextHandler = core.DefaultContextHandler{ ResourceType: resourcetype.Resource, SchemaReaderFactory: func() core.SchemaReader { return &HCVaultIntegration{} }, SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &HCVaultIntegration{} }, - BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { + PostURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/integrations/secretProviders/hcvault", c.ControlPlane) }, } diff --git a/cyral/internal/integration/slack/resource.go b/cyral/internal/integration/slack/resource.go index 9bfb8ff3..a880ad8a 100644 --- a/cyral/internal/integration/slack/resource.go +++ b/cyral/internal/integration/slack/resource.go @@ -14,7 +14,7 @@ var resourceContextHandler = core.DefaultContextHandler{ ResourceType: resourcetype.Resource, SchemaReaderFactory: func() core.SchemaReader { return &SlackAlertsIntegration{} }, SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &SlackAlertsIntegration{} }, - BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { + PostURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/integrations/notifications/slack", c.ControlPlane) }, } diff --git a/cyral/internal/integration/teams/resource.go b/cyral/internal/integration/teams/resource.go index 945a2082..7acecfcb 100644 --- a/cyral/internal/integration/teams/resource.go +++ b/cyral/internal/integration/teams/resource.go @@ -14,7 +14,7 @@ var resourceContextHandler = core.DefaultContextHandler{ ResourceType: resourcetype.Resource, SchemaReaderFactory: func() core.SchemaReader { return &MsTeamsIntegration{} }, SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &MsTeamsIntegration{} }, - BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { + PostURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/integrations/notifications/teams", c.ControlPlane) }, } diff --git a/cyral/internal/repository/binding/resource.go b/cyral/internal/repository/binding/resource.go index b60e336d..6714dd93 100644 --- a/cyral/internal/repository/binding/resource.go +++ b/cyral/internal/repository/binding/resource.go @@ -17,12 +17,12 @@ var resourceContextHandler = core.DefaultContextHandler{ SchemaReaderFactory: func() core.SchemaReader { return &CreateBindingRequest{} }, SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &GetBindingResponse{} }, SchemaWriterFactoryPostMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &CreateBindingResponse{} }, - BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { + PostURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/sidecars/%s/bindings", c.ControlPlane, d.Get(utils.SidecarIDKey).(string)) }, - IdBasedURLFactory: func(d *schema.ResourceData, c *client.Client) string { + GetPutDeleteURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/sidecars/%s/bindings/%s", c.ControlPlane, d.Get(utils.SidecarIDKey).(string), diff --git a/cyral/internal/repository/confanalysis/resource.go b/cyral/internal/repository/confanalysis/resource.go index a5f986e0..8059cf16 100644 --- a/cyral/internal/repository/confanalysis/resource.go +++ b/cyral/internal/repository/confanalysis/resource.go @@ -24,8 +24,8 @@ var resourceContextHandler = core.DefaultContextHandler{ ResourceType: resourcetype.Resource, SchemaReaderFactory: func() core.SchemaReader { return &UserConfig{} }, SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &RepositoryConfAnalysisData{} }, - BaseURLFactory: urlFactory, - IdBasedURLFactory: urlFactory, + PostURLFactory: urlFactory, + GetPutDeleteURLFactory: urlFactory, } func resourceSchema() *schema.Resource { diff --git a/cyral/internal/repository/datasource.go b/cyral/internal/repository/datasource.go index d5f5d1c7..914f92dd 100644 --- a/cyral/internal/repository/datasource.go +++ b/cyral/internal/repository/datasource.go @@ -61,7 +61,7 @@ var dsContextHandler = core.DefaultContextHandler{ ResourceName: dataSourceName, ResourceType: resourcetype.DataSource, SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &GetReposResponse{} }, - IdBasedURLFactory: func(d *schema.ResourceData, c *client.Client) string { + GetPutDeleteURLFactory: func(d *schema.ResourceData, c *client.Client) string { nameFilter := d.Get("name").(string) typeFilter := d.Get("type").(string) urlParams := utils.UrlQuery(map[string]string{ diff --git a/cyral/internal/repository/network/resource.go b/cyral/internal/repository/network/resource.go index ee19a173..4308ebff 100644 --- a/cyral/internal/repository/network/resource.go +++ b/cyral/internal/repository/network/resource.go @@ -24,8 +24,8 @@ var resourceContextHandler = core.DefaultContextHandler{ SchemaReaderFactory: func() core.SchemaReader { return &NetworkAccessPolicy{} }, SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &NetworkAccessPolicy{} }, SchemaWriterFactoryPostMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &NetworkAccessPolicyUpsertResp{} }, - BaseURLFactory: urlFactory, - IdBasedURLFactory: urlFactory, + PostURLFactory: urlFactory, + GetPutDeleteURLFactory: urlFactory, } func resourceSchema() *schema.Resource { diff --git a/cyral/internal/repository/resource.go b/cyral/internal/repository/resource.go index 37a3ac4c..134bf728 100644 --- a/cyral/internal/repository/resource.go +++ b/cyral/internal/repository/resource.go @@ -16,7 +16,7 @@ var resourceContextHandler = core.DefaultContextHandler{ ResourceType: resourcetype.Resource, SchemaReaderFactory: func() core.SchemaReader { return &RepoInfo{} }, SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &GetRepoByIDResponse{} }, - BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { + PostURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf( "https://%s/v1/repos", c.ControlPlane, diff --git a/cyral/internal/sidecar/credentials/resource.go b/cyral/internal/sidecar/credentials/resource.go index 615b2f2f..b128457b 100644 --- a/cyral/internal/sidecar/credentials/resource.go +++ b/cyral/internal/sidecar/credentials/resource.go @@ -15,7 +15,7 @@ var resourceContextHandler = core.DefaultContextHandler{ SchemaReaderFactory: func() core.SchemaReader { return &CreateSidecarCredentialsRequest{} }, SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &SidecarCredentialsData{} }, SchemaWriterFactoryPostMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &SidecarCredentialsData{} }, - BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { + PostURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/users/sidecarAccounts", c.ControlPlane) }, } diff --git a/cyral/internal/sidecar/listener/datasource.go b/cyral/internal/sidecar/listener/datasource.go index 7c67201e..dfdd4128 100644 --- a/cyral/internal/sidecar/listener/datasource.go +++ b/cyral/internal/sidecar/listener/datasource.go @@ -16,7 +16,7 @@ var dsContextHandler = core.DefaultContextHandler{ ResourceName: dataSourceName, ResourceType: resourcetype.DataSource, SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &ReadDataSourceSidecarListenerAPIResponse{} }, - IdBasedURLFactory: func(d *schema.ResourceData, c *client.Client) string { + GetPutDeleteURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/sidecars/%s/listeners", c.ControlPlane, d.Get(utils.SidecarIDKey).(string)) }, } diff --git a/cyral/internal/sidecar/listener/resource.go b/cyral/internal/sidecar/listener/resource.go index caf750b1..00048a5c 100644 --- a/cyral/internal/sidecar/listener/resource.go +++ b/cyral/internal/sidecar/listener/resource.go @@ -19,12 +19,12 @@ var resourceContextHandler = core.DefaultContextHandler{ SchemaReaderFactory: func() core.SchemaReader { return &SidecarListenerResource{} }, SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &ReadSidecarListenerAPIResponse{} }, SchemaWriterFactoryPostMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &CreateListenerAPIResponse{} }, - BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { + PostURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/sidecars/%s/listeners", c.ControlPlane, d.Get(utils.SidecarIDKey).(string)) }, - IdBasedURLFactory: func(d *schema.ResourceData, c *client.Client) string { + GetPutDeleteURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/sidecars/%s/listeners/%s", c.ControlPlane, d.Get(utils.SidecarIDKey).(string), From e44d9599e95c7a929abf68462c7a90a0f98493fa Mon Sep 17 00:00:00 2001 From: Wilson de Carvalho <796900+wcmjunior@users.noreply.github.com> Date: Wed, 3 Apr 2024 22:48:36 -0700 Subject: [PATCH 17/22] Refactor cyral_datalabel --- cyral/internal/datalabel/resource.go | 35 ++++++++++++---------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/cyral/internal/datalabel/resource.go b/cyral/internal/datalabel/resource.go index e4dba3b0..46a58437 100644 --- a/cyral/internal/datalabel/resource.go +++ b/cyral/internal/datalabel/resource.go @@ -15,29 +15,28 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) +var getPutDeleteURLFactory = func(d *schema.ResourceData, c *client.Client) string { + return fmt.Sprintf("https://%s/v1/datalabels/%s", + c.ControlPlane, + d.Get("name").(string)) +} + func resourceSchema() *schema.Resource { contextHandler := core.DefaultContextHandler{ ResourceName: resourceName, ResourceType: resourcetype.Resource, SchemaReaderFactory: func() core.SchemaReader { return &DataLabel{} }, SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &DataLabel{} }, - PostURLFactory: func(d *schema.ResourceData, c *client.Client) string { - return fmt.Sprintf("https://%s/v1/datalabels", - c.ControlPlane) - }, + GetPutDeleteURLFactory: getPutDeleteURLFactory, } return &schema.Resource{ Description: "Manages data labels. Data labels are part of the Cyral [Data Map](https://cyral.com/docs/policy/datamap).", CreateContext: core.CreateResource( core.ResourceOperationConfig{ - ResourceName: resourceName, - Type: operationtype.Create, - HttpMethod: http.MethodPut, - URLFactory: func(d *schema.ResourceData, c *client.Client) string { - return fmt.Sprintf("https://%s/v1/datalabels/%s", - c.ControlPlane, - d.Get("name").(string)) - }, + ResourceName: resourceName, + Type: operationtype.Create, + HttpMethod: http.MethodPut, + URLFactory: getPutDeleteURLFactory, SchemaReaderFactory: func() core.SchemaReader { return &DataLabel{} }, SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &DataLabel{} }, }, readDataLabelConfig, @@ -114,14 +113,10 @@ func resourceSchema() *schema.Resource { } var readDataLabelConfig = core.ResourceOperationConfig{ - ResourceName: resourceName, - Type: operationtype.Read, - HttpMethod: http.MethodGet, - URLFactory: func(d *schema.ResourceData, c *client.Client) string { - return fmt.Sprintf("https://%s/v1/datalabels/%s", - c.ControlPlane, - d.Get("name").(string)) - }, + ResourceName: resourceName, + Type: operationtype.Read, + HttpMethod: http.MethodGet, + URLFactory: getPutDeleteURLFactory, SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &DataLabel{} }, RequestErrorHandler: &core.IgnoreHttpNotFound{ResName: "Data Label"}, } From 64fb6df326e49ea39c613bfc508f6a7da3cd6ae8 Mon Sep 17 00:00:00 2001 From: Wilson de Carvalho <796900+wcmjunior@users.noreply.github.com> Date: Wed, 3 Apr 2024 22:50:06 -0700 Subject: [PATCH 18/22] Add support for custom error handling in context functions --- cyral/core/default_context_handler.go | 41 ++++++++++++++++++--------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/cyral/core/default_context_handler.go b/cyral/core/default_context_handler.go index f3110694..52b51295 100644 --- a/cyral/core/default_context_handler.go +++ b/cyral/core/default_context_handler.go @@ -56,6 +56,7 @@ func (dch DefaultContextHandler) defaultOperationHandler( httpMethod string, schemaReaderFactory SchemaReaderFactoryFunc, schemaWriterFactory SchemaWriterFactoryFunc, + requestErrorHandler RequestErrorHandler, ) ResourceOperationConfig { // POST = https://// // GET, PUT and DELETE = https:///// @@ -73,10 +74,6 @@ func (dch DefaultContextHandler) defaultOperationHandler( return url } - var errorHandler RequestErrorHandler - if httpMethod == http.MethodGet || httpMethod == http.MethodDelete { - errorHandler = &IgnoreHttpNotFound{ResName: dch.ResourceName} - } result := ResourceOperationConfig{ ResourceName: dch.ResourceName, Type: operationType, @@ -85,13 +82,18 @@ func (dch DefaultContextHandler) defaultOperationHandler( URLFactory: endpoint, SchemaReaderFactory: schemaReaderFactory, SchemaWriterFactory: schemaWriterFactory, - RequestErrorHandler: errorHandler, + RequestErrorHandler: requestErrorHandler, } return result } func (dch DefaultContextHandler) CreateContext() schema.CreateContextFunc { + return dch.CreateContextCustomErrorHandling(&IgnoreHttpNotFound{ResName: dch.ResourceName}, nil) +} + +func (dch DefaultContextHandler) CreateContextCustomErrorHandling(getErrorHandler RequestErrorHandler, + postErrorHandler RequestErrorHandler) schema.CreateContextFunc { // By default, assumes that if no SchemaWriterFactoryPostMethod is provided, // the POST api will return an ID schemaWriterPost := DefaultSchemaWriterFactory @@ -99,24 +101,37 @@ func (dch DefaultContextHandler) CreateContext() schema.CreateContextFunc { schemaWriterPost = dch.SchemaWriterFactoryPostMethod } return CreateResource( - dch.defaultOperationHandler(ot.Create, http.MethodPost, dch.SchemaReaderFactory, schemaWriterPost), - dch.defaultOperationHandler(ot.Create, http.MethodGet, nil, dch.SchemaWriterFactoryGetMethod), + dch.defaultOperationHandler(ot.Create, http.MethodPost, dch.SchemaReaderFactory, schemaWriterPost, postErrorHandler), + dch.defaultOperationHandler(ot.Create, http.MethodGet, nil, dch.SchemaWriterFactoryGetMethod, getErrorHandler), ) } func (dch DefaultContextHandler) ReadContext() schema.ReadContextFunc { - return ReadResource(dch.ReadResourceOperationConfig()) + return dch.ReadContextCustomErrorHandling(&IgnoreHttpNotFound{ResName: dch.ResourceName}) } -func (dch DefaultContextHandler) ReadResourceOperationConfig() ResourceOperationConfig { - return dch.defaultOperationHandler(ot.Read, http.MethodGet, nil, dch.SchemaWriterFactoryGetMethod) + +func (dch DefaultContextHandler) ReadContextCustomErrorHandling(getErrorHandler RequestErrorHandler) schema.ReadContextFunc { + return ReadResource( + dch.defaultOperationHandler(ot.Read, http.MethodGet, nil, dch.SchemaWriterFactoryGetMethod, getErrorHandler), + ) } func (dch DefaultContextHandler) UpdateContext() schema.UpdateContextFunc { + return dch.UpdateContextCustomErrorHandling(&IgnoreHttpNotFound{ResName: dch.ResourceName}, nil) +} + +func (dch DefaultContextHandler) UpdateContextCustomErrorHandling(getErrorHandler RequestErrorHandler, + putErrorHandler RequestErrorHandler) schema.UpdateContextFunc { return UpdateResource( - dch.defaultOperationHandler(ot.Update, http.MethodPut, dch.SchemaReaderFactory, nil), - dch.defaultOperationHandler(ot.Update, http.MethodGet, nil, dch.SchemaWriterFactoryGetMethod)) + dch.defaultOperationHandler(ot.Update, http.MethodPut, dch.SchemaReaderFactory, nil, putErrorHandler), + dch.defaultOperationHandler(ot.Update, http.MethodGet, nil, dch.SchemaWriterFactoryGetMethod, getErrorHandler), + ) } func (dch DefaultContextHandler) DeleteContext() schema.DeleteContextFunc { - return DeleteResource(dch.defaultOperationHandler(ot.Delete, http.MethodDelete, nil, nil)) + return dch.DeleteContextCustomErrorHandling(&IgnoreHttpNotFound{ResName: dch.ResourceName}) +} + +func (dch DefaultContextHandler) DeleteContextCustomErrorHandling(deleteErrorHandler RequestErrorHandler) schema.DeleteContextFunc { + return DeleteResource(dch.defaultOperationHandler(ot.Delete, http.MethodDelete, nil, nil, deleteErrorHandler)) } From 0a39b5a9ce99e2157cb73eb745ba7c980e13b6b8 Mon Sep 17 00:00:00 2001 From: Wilson de Carvalho <796900+wcmjunior@users.noreply.github.com> Date: Wed, 3 Apr 2024 22:50:59 -0700 Subject: [PATCH 19/22] Fix bugs that caused conf_auth and conf_analysis to not recover from state out-of-sync --- .../repository/confanalysis/resource.go | 94 ++++++++++++++----- .../internal/repository/confauth/resource.go | 88 ++++++++--------- 2 files changed, 114 insertions(+), 68 deletions(-) diff --git a/cyral/internal/repository/confanalysis/resource.go b/cyral/internal/repository/confanalysis/resource.go index 8059cf16..fd437a60 100644 --- a/cyral/internal/repository/confanalysis/resource.go +++ b/cyral/internal/repository/confanalysis/resource.go @@ -9,9 +9,15 @@ import ( "github.com/cyralinc/terraform-provider-cyral/cyral/core" "github.com/cyralinc/terraform-provider-cyral/cyral/core/types/operationtype" "github.com/cyralinc/terraform-provider-cyral/cyral/core/types/resourcetype" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) +// TODO This resource is more complex than it should be due to the fact that a call to +// repo creation automatically creates the conf/auth and also the conf/analysis configurations. +// Our API should be refactored so these operations should happen separately. + var urlFactory = func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/repos/%s/conf/analysis", c.ControlPlane, @@ -28,6 +34,8 @@ var resourceContextHandler = core.DefaultContextHandler{ GetPutDeleteURLFactory: urlFactory, } +var requestErrorHandler = &core.IgnoreNotFoundByMessage{MessageMatches: "Cannot find config data for repo"} + func resourceSchema() *schema.Resource { return &schema.Resource{ Description: "Manages Repository Analysis Configuration. This resource allows configuring " + @@ -35,27 +43,22 @@ func resourceSchema() *schema.Resource { "[Alerts](https://cyral.com/docs/data-repos/config/#alerts) and " + "[Policy Enforcement](https://cyral.com/docs/data-repos/config/#policy-enforcement) " + "settings for Data Repositories.", - CreateContext: core.CreateResource( - core.ResourceOperationConfig{ - ResourceName: resourceName, - Type: operationtype.Create, - HttpMethod: http.MethodPut, - URLFactory: urlFactory, - SchemaReaderFactory: func() core.SchemaReader { return &UserConfig{} }, - SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &RepositoryConfAnalysisData{} }, - }, - core.ResourceOperationConfig{ - ResourceName: resourceName, - Type: operationtype.Read, - HttpMethod: http.MethodGet, - URLFactory: urlFactory, - SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &RepositoryConfAnalysisData{} }, - RequestErrorHandler: &core.IgnoreHttpNotFound{}, - }, - ), - ReadContext: resourceContextHandler.ReadContext(), - UpdateContext: resourceContextHandler.UpdateContext(), - DeleteContext: resourceContextHandler.DeleteContext(), + CreateContext: resourceRepositoryConfAnalysisCreate, + ReadContext: resourceContextHandler.ReadContextCustomErrorHandling(&core.IgnoreNotFoundByMessage{ + ResName: resourceName, + MessageMatches: "Cannot find config data for repo", + OperationType: operationtype.Read, + }), + UpdateContext: resourceContextHandler.UpdateContextCustomErrorHandling(&core.IgnoreNotFoundByMessage{ + ResName: resourceName, + MessageMatches: "Cannot find config data for repo", + OperationType: operationtype.Update, + }, nil), + DeleteContext: resourceContextHandler.DeleteContextCustomErrorHandling(&core.IgnoreNotFoundByMessage{ + ResName: resourceName, + MessageMatches: "Cannot find config data for repo", + OperationType: operationtype.Delete, + }), SchemaVersion: 1, StateUpgraders: []schema.StateUpgrader{ @@ -192,3 +195,52 @@ func UpgradeRepositoryConfAnalysisV0( rawState["id"] = rawState["repository_id"] return rawState, nil } + +func resourceRepositoryConfAnalysisCreate( + ctx context.Context, + d *schema.ResourceData, + m interface{}, +) diag.Diagnostics { + tflog.Debug(ctx, "Init resourceRepositoryConfAnalysisCreate") + c := m.(*client.Client) + httpMethod := http.MethodPost + if confAnalysisAlreadyExists(ctx, c, d) { + httpMethod = http.MethodPut + } + tflog.Debug(ctx, "End resourceRepositoryConfAnalysisCreate") + return core.CreateResource( + core.ResourceOperationConfig{ + ResourceName: resourceName, + Type: operationtype.Create, + HttpMethod: httpMethod, + URLFactory: urlFactory, + SchemaReaderFactory: func() core.SchemaReader { return &UserConfig{} }, + SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &RepositoryConfAnalysisData{} }, + }, + core.ResourceOperationConfig{ + ResourceName: resourceName, + ResourceType: resourcetype.Resource, + Type: operationtype.Read, + HttpMethod: http.MethodGet, + URLFactory: urlFactory, + SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &RepositoryConfAnalysisData{} }, + RequestErrorHandler: &core.IgnoreNotFoundByMessage{ + ResName: resourceName, + MessageMatches: "Cannot find config data for repo", + OperationType: operationtype.Read, + }, + }, + )(ctx, d, m) +} + +func confAnalysisAlreadyExists(ctx context.Context, c *client.Client, d *schema.ResourceData) bool { + _, err := c.DoRequest(ctx, urlFactory(d, c), http.MethodGet, nil) + // See TODO on the top of this file + if err != nil { + + tflog.Debug(ctx, fmt.Sprintf("Unable to read Conf Analysis resource for repository %s: %v", + d.Get("repository_id").(string), err)) + return false + } + return true +} diff --git a/cyral/internal/repository/confauth/resource.go b/cyral/internal/repository/confauth/resource.go index 43b646b4..17a51d74 100644 --- a/cyral/internal/repository/confauth/resource.go +++ b/cyral/internal/repository/confauth/resource.go @@ -17,8 +17,9 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) -// TODO Fix the API status codes and simplify this code. We could easily use the default handlers -// instead of this complex set of operations. +// TODO This resource is more complex than it should be due to the fact that a call to +// repo creation automatically creates the conf/auth and also the conf/analysis configurations. +// Our API should be refactored so these operations should happen separately. var urlFactory = func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/repos/%s/conf/auth", @@ -27,18 +28,13 @@ var urlFactory = func(d *schema.ResourceData, c *client.Client) string { ) } -var readConfig = core.ResourceOperationConfig{ - ResourceName: resourceName, - ResourceType: resourcetype.Resource, - Type: operationtype.Read, - HttpMethod: http.MethodGet, - URLFactory: urlFactory, - SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &ReadRepositoryConfAuthResponse{} }, - RequestErrorHandler: &core.IgnoreNotFoundByMessage{ - ResName: resourceName, - MessageMatches: "Failed to read repo", - OperationType: operationtype.Read, - }, +var resourceContextHandler = core.DefaultContextHandler{ + ResourceName: resourceName, + ResourceType: resourcetype.Resource, + SchemaReaderFactory: func() core.SchemaReader { return &RepositoryConfAuthData{} }, + SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &ReadRepositoryConfAuthResponse{} }, + PostURLFactory: urlFactory, + GetPutDeleteURLFactory: urlFactory, } func resourceSchema() *schema.Resource { @@ -48,32 +44,21 @@ func resourceSchema() *schema.Resource { "and [Advanced settings](https://cyral.com/docs/manage-repositories/repo-advanced-settings) " + "(Logs, Alerts, Analysis and Enforcement) configurations for Data Repositories.", CreateContext: resourceRepositoryConfAuthCreate, - ReadContext: core.ReadResource(readConfig), - UpdateContext: core.UpdateResource( - core.ResourceOperationConfig{ - ResourceName: resourceName, - ResourceType: resourcetype.Resource, - Type: operationtype.Update, - HttpMethod: http.MethodPut, - URLFactory: urlFactory, - SchemaReaderFactory: func() core.SchemaReader { return &RepositoryConfAuthData{} }, - }, - readConfig, - ), - DeleteContext: core.DeleteResource( - core.ResourceOperationConfig{ - ResourceName: resourceName, - ResourceType: resourcetype.Resource, - Type: operationtype.Delete, - HttpMethod: http.MethodDelete, - URLFactory: urlFactory, - RequestErrorHandler: &core.IgnoreNotFoundByMessage{ - ResName: resourceName, - MessageMatches: "Failed to read repo", - OperationType: operationtype.Read, - }, - }, - ), + ReadContext: resourceContextHandler.ReadContextCustomErrorHandling(&core.IgnoreNotFoundByMessage{ + ResName: resourceName, + MessageMatches: "Failed to read repo", + OperationType: operationtype.Read, + }), + UpdateContext: resourceContextHandler.UpdateContextCustomErrorHandling(&core.IgnoreNotFoundByMessage{ + ResName: resourceName, + MessageMatches: "Failed to read repo", + OperationType: operationtype.Update, + }, nil), + DeleteContext: resourceContextHandler.DeleteContextCustomErrorHandling(&core.IgnoreNotFoundByMessage{ + ResName: resourceName, + MessageMatches: "Failed to read repo", + OperationType: operationtype.Delete, + }), SchemaVersion: 1, StateUpgraders: []schema.StateUpgrader{ @@ -192,17 +177,26 @@ func resourceRepositoryConfAuthCreate( URLFactory: urlFactory, SchemaReaderFactory: func() core.SchemaReader { return &RepositoryConfAuthData{} }, SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &CreateRepositoryConfAuthResponse{} }, - }, readConfig)(ctx, d, m) + }, + core.ResourceOperationConfig{ + ResourceName: resourceName, + ResourceType: resourcetype.Resource, + Type: operationtype.Read, + HttpMethod: http.MethodGet, + URLFactory: urlFactory, + SchemaWriterFactory: func(_ *schema.ResourceData) core.SchemaWriter { return &ReadRepositoryConfAuthResponse{} }, + RequestErrorHandler: &core.IgnoreNotFoundByMessage{ + ResName: resourceName, + MessageMatches: "Failed to read repo", + OperationType: operationtype.Read, + }, + }, + )(ctx, d, m) } func confAuthAlreadyExists(ctx context.Context, c *client.Client, d *schema.ResourceData) bool { _, err := c.DoRequest(ctx, urlFactory(d, c), http.MethodGet, nil) - // TODO: Fix this API. - - // The GET /v1/repos/{repoID}/conf/auth API currently returns 500 status code for every type - // of error, so its not possible to distinguish if the error is due to a 404 Not Found or not. - // Once the status code returned by this API is fixed we should return false only if it returns - // a 404 Not Found, otherwise, if a different error occurs, this function should return an error. + // See TODO on the top of this file if err != nil { tflog.Debug(ctx, fmt.Sprintf("Unable to read Conf Auth resource for repository %s: %v", From 3156b5fdbfd7894fffed061550709b53de2d8f73 Mon Sep 17 00:00:00 2001 From: Wilson de Carvalho <796900+wcmjunior@users.noreply.github.com> Date: Thu, 4 Apr 2024 14:36:34 -0700 Subject: [PATCH 20/22] Remove dead code --- cyral/core/error_handlers.go | 24 ------------------- .../repository/confanalysis/resource.go | 2 -- 2 files changed, 26 deletions(-) diff --git a/cyral/core/error_handlers.go b/cyral/core/error_handlers.go index 2544be36..acb373cd 100644 --- a/cyral/core/error_handlers.go +++ b/cyral/core/error_handlers.go @@ -13,30 +13,6 @@ import ( "github.com/cyralinc/terraform-provider-cyral/cyral/core/types/operationtype" ) -type IgnoreByHttpStatusCode struct { - ResName string - HttpStatusCode int - OperationType operationtype.OperationType -} - -func (h *IgnoreByHttpStatusCode) HandleError( - ctx context.Context, - r *schema.ResourceData, - _ *client.Client, - err error, -) error { - tflog.Debug(ctx, "==> Init HandleError core.IgnoreByHttpStatusCode") - httpError, ok := err.(*client.HttpError) - if !ok || httpError.StatusCode != h.HttpStatusCode { - tflog.Debug(ctx, fmt.Sprintf("==> End HandleError core.IgnoreByHttpStatusCode - Did not find a %d, thus returning the original error", h.HttpStatusCode)) - return err - } - r.SetId("") - tflog.Debug(ctx, fmt.Sprintf( - "==> End HandleError core.IgnoreHttpNotFound - %s not found. Marking resource for recreation or deletion.", h.ResName)) - return nil -} - type IgnoreNotFoundByMessage struct { ResName string MessageMatches string diff --git a/cyral/internal/repository/confanalysis/resource.go b/cyral/internal/repository/confanalysis/resource.go index fd437a60..1a7d70c5 100644 --- a/cyral/internal/repository/confanalysis/resource.go +++ b/cyral/internal/repository/confanalysis/resource.go @@ -34,8 +34,6 @@ var resourceContextHandler = core.DefaultContextHandler{ GetPutDeleteURLFactory: urlFactory, } -var requestErrorHandler = &core.IgnoreNotFoundByMessage{MessageMatches: "Cannot find config data for repo"} - func resourceSchema() *schema.Resource { return &schema.Resource{ Description: "Manages Repository Analysis Configuration. This resource allows configuring " + From ca2789779fb6ff19b7dfd35e06dfa95c4aa094d0 Mon Sep 17 00:00:00 2001 From: Wilson de Carvalho <796900+wcmjunior@users.noreply.github.com> Date: Thu, 4 Apr 2024 14:51:33 -0700 Subject: [PATCH 21/22] Renamed PostURLFactory back to BaseURLFactory --- cyral/core/README.md | 2 +- cyral/core/default_context_handler.go | 14 +++++++------- .../resource_cyral_integration_datadog.go | 2 +- .../deprecated/resource_cyral_integration_elk.go | 2 +- .../resource_cyral_integration_logstash.go | 2 +- .../resource_cyral_integration_looker.go | 2 +- .../resource_cyral_integration_splunk.go | 2 +- .../resource_cyral_integration_sumo_logic.go | 2 +- .../awsiam/resource_cyral_integration_aws_iam.go | 2 +- cyral/internal/integration/hcvault/resource.go | 2 +- cyral/internal/integration/slack/resource.go | 2 +- cyral/internal/integration/teams/resource.go | 2 +- cyral/internal/repository/binding/resource.go | 2 +- cyral/internal/repository/confanalysis/resource.go | 2 +- cyral/internal/repository/confauth/resource.go | 2 +- cyral/internal/repository/network/resource.go | 2 +- cyral/internal/repository/resource.go | 2 +- cyral/internal/sidecar/credentials/resource.go | 2 +- cyral/internal/sidecar/listener/resource.go | 2 +- 19 files changed, 25 insertions(+), 25 deletions(-) diff --git a/cyral/core/README.md b/cyral/core/README.md index 0c48a47a..8108faf4 100644 --- a/cyral/core/README.md +++ b/cyral/core/README.md @@ -111,7 +111,7 @@ var resourceContextHandler = core.DefaultContextHandler{ ResourceType: resourcetype.Resource, SchemaReaderFactory: func() core.SchemaReader { return &NewFeature{} }, SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &NewFeature{} }, - PostURLFactory: func(d *schema.ResourceData, c *client.Client) string { + BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/NewFeature", c.ControlPlane) }, } diff --git a/cyral/core/default_context_handler.go b/cyral/core/default_context_handler.go index 52b51295..02064dc6 100644 --- a/cyral/core/default_context_handler.go +++ b/cyral/core/default_context_handler.go @@ -19,13 +19,13 @@ import ( // it will assume that a call to POST returns a JSON with // an `id` field, meaning it will use the // `IDBasedResponse` struct in such cases. -// 3. `PostURLFactory` must be provided for resources. It will be used to +// 3. `BaseURLFactory` must be provided for resources. It will be used to // create the POST endpoint and others in case `GetPutDeleteURLFactory` // is not provided. // 4. `GetPutDeleteURLFactory` must be provided for data sources. // 5. If `GetPutDeleteURLFactory` is NOT provided (data sources or resources), // the endpoint to perform GET, PUT and DELETE calls are composed by the -// `PostURLFactory` endpoint plus the ID specification as follows: +// `BaseURLFactory` endpoint plus the ID specification as follows: // - POST: https://// // - GET: https:///// // - PUT: https:///// @@ -40,10 +40,10 @@ type DefaultContextHandler struct { // SchemaWriterFactoryPostMethod defines how the schema will be // written in POST operations. SchemaWriterFactoryPostMethod SchemaWriterFactoryFunc - // PostURLFactory provides the URL used for POSTs and that + // BaseURLFactory provides the URL used for POSTs and that // will also be used to compose the ID URL for GET, PUT and - // DELETE in case ` GetPutDeleteURLFactory` is not provided. - PostURLFactory URLFactoryFunc + // DELETE in case `GetPutDeleteURLFactory` is not provided. + BaseURLFactory URLFactoryFunc GetPutDeleteURLFactory URLFactoryFunc } @@ -63,11 +63,11 @@ func (dch DefaultContextHandler) defaultOperationHandler( endpoint := func(d *schema.ResourceData, c *client.Client) string { var url string if httpMethod == http.MethodPost { - url = dch.PostURLFactory(d, c) + url = dch.BaseURLFactory(d, c) } else if dch.GetPutDeleteURLFactory != nil { url = dch.GetPutDeleteURLFactory(d, c) } else { - url = fmt.Sprintf("%s/%s", dch.PostURLFactory(d, c), d.Id()) + url = fmt.Sprintf("%s/%s", dch.BaseURLFactory(d, c), d.Id()) } tflog.Debug(context.Background(), fmt.Sprintf("Returning base URL for %s '%s' operation '%s' and httpMethod %s: %s", dch.ResourceType, dch.ResourceName, operationType, httpMethod, url)) diff --git a/cyral/internal/deprecated/resource_cyral_integration_datadog.go b/cyral/internal/deprecated/resource_cyral_integration_datadog.go index ca44b064..f1a067ae 100644 --- a/cyral/internal/deprecated/resource_cyral_integration_datadog.go +++ b/cyral/internal/deprecated/resource_cyral_integration_datadog.go @@ -34,7 +34,7 @@ func ResourceIntegrationDatadog() *schema.Resource { ResourceType: resourcetype.Resource, SchemaReaderFactory: func() core.SchemaReader { return &DatadogIntegration{} }, SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &DatadogIntegration{} }, - PostURLFactory: func(d *schema.ResourceData, c *client.Client) string { + BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/integrations/datadog", c.ControlPlane) }, } diff --git a/cyral/internal/deprecated/resource_cyral_integration_elk.go b/cyral/internal/deprecated/resource_cyral_integration_elk.go index be624c2f..f0031f9a 100644 --- a/cyral/internal/deprecated/resource_cyral_integration_elk.go +++ b/cyral/internal/deprecated/resource_cyral_integration_elk.go @@ -37,7 +37,7 @@ func ResourceIntegrationELK() *schema.Resource { ResourceType: resourcetype.Resource, SchemaReaderFactory: func() core.SchemaReader { return &ELKIntegration{} }, SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &ELKIntegration{} }, - PostURLFactory: func(d *schema.ResourceData, c *client.Client) string { + BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/integrations/elk", c.ControlPlane) }, } diff --git a/cyral/internal/deprecated/resource_cyral_integration_logstash.go b/cyral/internal/deprecated/resource_cyral_integration_logstash.go index 91ed2f31..88ba38a0 100644 --- a/cyral/internal/deprecated/resource_cyral_integration_logstash.go +++ b/cyral/internal/deprecated/resource_cyral_integration_logstash.go @@ -41,7 +41,7 @@ func ResourceIntegrationLogstash() *schema.Resource { ResourceType: resourcetype.Resource, SchemaReaderFactory: func() core.SchemaReader { return &LogstashIntegration{} }, SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &LogstashIntegration{} }, - PostURLFactory: func(d *schema.ResourceData, c *client.Client) string { + BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/integrations/logstash", c.ControlPlane) }, } diff --git a/cyral/internal/deprecated/resource_cyral_integration_looker.go b/cyral/internal/deprecated/resource_cyral_integration_looker.go index 659bb399..f67f6cf8 100644 --- a/cyral/internal/deprecated/resource_cyral_integration_looker.go +++ b/cyral/internal/deprecated/resource_cyral_integration_looker.go @@ -35,7 +35,7 @@ func ResourceIntegrationLooker() *schema.Resource { ResourceType: resourcetype.Resource, SchemaReaderFactory: func() core.SchemaReader { return &LookerIntegration{} }, SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &LookerIntegration{} }, - PostURLFactory: func(d *schema.ResourceData, c *client.Client) string { + BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/integrations/looker", c.ControlPlane) }, } diff --git a/cyral/internal/deprecated/resource_cyral_integration_splunk.go b/cyral/internal/deprecated/resource_cyral_integration_splunk.go index fbcf6423..50c41d96 100644 --- a/cyral/internal/deprecated/resource_cyral_integration_splunk.go +++ b/cyral/internal/deprecated/resource_cyral_integration_splunk.go @@ -45,7 +45,7 @@ func ResourceIntegrationSplunk() *schema.Resource { ResourceType: resourcetype.Resource, SchemaReaderFactory: func() core.SchemaReader { return &SplunkIntegration{} }, SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &SplunkIntegration{} }, - PostURLFactory: func(d *schema.ResourceData, c *client.Client) string { + BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/integrations/splunk", c.ControlPlane) }, } diff --git a/cyral/internal/deprecated/resource_cyral_integration_sumo_logic.go b/cyral/internal/deprecated/resource_cyral_integration_sumo_logic.go index 94dab998..9459bef1 100644 --- a/cyral/internal/deprecated/resource_cyral_integration_sumo_logic.go +++ b/cyral/internal/deprecated/resource_cyral_integration_sumo_logic.go @@ -32,7 +32,7 @@ func ResourceIntegrationSumoLogic() *schema.Resource { ResourceType: resourcetype.Resource, SchemaReaderFactory: func() core.SchemaReader { return &SumoLogicIntegration{} }, SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &SumoLogicIntegration{} }, - PostURLFactory: func(d *schema.ResourceData, c *client.Client) string { + BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/integrations/sumologic", c.ControlPlane) }, } diff --git a/cyral/internal/integration/awsiam/resource_cyral_integration_aws_iam.go b/cyral/internal/integration/awsiam/resource_cyral_integration_aws_iam.go index cbafefe7..81eaaeed 100644 --- a/cyral/internal/integration/awsiam/resource_cyral_integration_aws_iam.go +++ b/cyral/internal/integration/awsiam/resource_cyral_integration_aws_iam.go @@ -68,7 +68,7 @@ func ResourceIntegrationAWSIAM() *schema.Resource { ResourceType: resourcetype.Resource, SchemaReaderFactory: func() core.SchemaReader { return &AWSIAMIntegrationWrapper{} }, SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &AWSIAMIntegrationWrapper{} }, - PostURLFactory: func(d *schema.ResourceData, c *client.Client) string { + BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/integrations/aws/iam", c.ControlPlane) }, } diff --git a/cyral/internal/integration/hcvault/resource.go b/cyral/internal/integration/hcvault/resource.go index 186b9656..f0fd8b77 100644 --- a/cyral/internal/integration/hcvault/resource.go +++ b/cyral/internal/integration/hcvault/resource.go @@ -14,7 +14,7 @@ var resourceContextHandler = core.DefaultContextHandler{ ResourceType: resourcetype.Resource, SchemaReaderFactory: func() core.SchemaReader { return &HCVaultIntegration{} }, SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &HCVaultIntegration{} }, - PostURLFactory: func(d *schema.ResourceData, c *client.Client) string { + BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/integrations/secretProviders/hcvault", c.ControlPlane) }, } diff --git a/cyral/internal/integration/slack/resource.go b/cyral/internal/integration/slack/resource.go index a880ad8a..9bfb8ff3 100644 --- a/cyral/internal/integration/slack/resource.go +++ b/cyral/internal/integration/slack/resource.go @@ -14,7 +14,7 @@ var resourceContextHandler = core.DefaultContextHandler{ ResourceType: resourcetype.Resource, SchemaReaderFactory: func() core.SchemaReader { return &SlackAlertsIntegration{} }, SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &SlackAlertsIntegration{} }, - PostURLFactory: func(d *schema.ResourceData, c *client.Client) string { + BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/integrations/notifications/slack", c.ControlPlane) }, } diff --git a/cyral/internal/integration/teams/resource.go b/cyral/internal/integration/teams/resource.go index 7acecfcb..945a2082 100644 --- a/cyral/internal/integration/teams/resource.go +++ b/cyral/internal/integration/teams/resource.go @@ -14,7 +14,7 @@ var resourceContextHandler = core.DefaultContextHandler{ ResourceType: resourcetype.Resource, SchemaReaderFactory: func() core.SchemaReader { return &MsTeamsIntegration{} }, SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &MsTeamsIntegration{} }, - PostURLFactory: func(d *schema.ResourceData, c *client.Client) string { + BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/integrations/notifications/teams", c.ControlPlane) }, } diff --git a/cyral/internal/repository/binding/resource.go b/cyral/internal/repository/binding/resource.go index 6714dd93..466a6118 100644 --- a/cyral/internal/repository/binding/resource.go +++ b/cyral/internal/repository/binding/resource.go @@ -17,7 +17,7 @@ var resourceContextHandler = core.DefaultContextHandler{ SchemaReaderFactory: func() core.SchemaReader { return &CreateBindingRequest{} }, SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &GetBindingResponse{} }, SchemaWriterFactoryPostMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &CreateBindingResponse{} }, - PostURLFactory: func(d *schema.ResourceData, c *client.Client) string { + BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/sidecars/%s/bindings", c.ControlPlane, d.Get(utils.SidecarIDKey).(string)) diff --git a/cyral/internal/repository/confanalysis/resource.go b/cyral/internal/repository/confanalysis/resource.go index 1a7d70c5..c55fe231 100644 --- a/cyral/internal/repository/confanalysis/resource.go +++ b/cyral/internal/repository/confanalysis/resource.go @@ -30,7 +30,7 @@ var resourceContextHandler = core.DefaultContextHandler{ ResourceType: resourcetype.Resource, SchemaReaderFactory: func() core.SchemaReader { return &UserConfig{} }, SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &RepositoryConfAnalysisData{} }, - PostURLFactory: urlFactory, + BaseURLFactory: urlFactory, GetPutDeleteURLFactory: urlFactory, } diff --git a/cyral/internal/repository/confauth/resource.go b/cyral/internal/repository/confauth/resource.go index 17a51d74..6a457482 100644 --- a/cyral/internal/repository/confauth/resource.go +++ b/cyral/internal/repository/confauth/resource.go @@ -33,7 +33,7 @@ var resourceContextHandler = core.DefaultContextHandler{ ResourceType: resourcetype.Resource, SchemaReaderFactory: func() core.SchemaReader { return &RepositoryConfAuthData{} }, SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &ReadRepositoryConfAuthResponse{} }, - PostURLFactory: urlFactory, + BaseURLFactory: urlFactory, GetPutDeleteURLFactory: urlFactory, } diff --git a/cyral/internal/repository/network/resource.go b/cyral/internal/repository/network/resource.go index 4308ebff..69fa3424 100644 --- a/cyral/internal/repository/network/resource.go +++ b/cyral/internal/repository/network/resource.go @@ -24,7 +24,7 @@ var resourceContextHandler = core.DefaultContextHandler{ SchemaReaderFactory: func() core.SchemaReader { return &NetworkAccessPolicy{} }, SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &NetworkAccessPolicy{} }, SchemaWriterFactoryPostMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &NetworkAccessPolicyUpsertResp{} }, - PostURLFactory: urlFactory, + BaseURLFactory: urlFactory, GetPutDeleteURLFactory: urlFactory, } diff --git a/cyral/internal/repository/resource.go b/cyral/internal/repository/resource.go index 134bf728..37a3ac4c 100644 --- a/cyral/internal/repository/resource.go +++ b/cyral/internal/repository/resource.go @@ -16,7 +16,7 @@ var resourceContextHandler = core.DefaultContextHandler{ ResourceType: resourcetype.Resource, SchemaReaderFactory: func() core.SchemaReader { return &RepoInfo{} }, SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &GetRepoByIDResponse{} }, - PostURLFactory: func(d *schema.ResourceData, c *client.Client) string { + BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf( "https://%s/v1/repos", c.ControlPlane, diff --git a/cyral/internal/sidecar/credentials/resource.go b/cyral/internal/sidecar/credentials/resource.go index b128457b..615b2f2f 100644 --- a/cyral/internal/sidecar/credentials/resource.go +++ b/cyral/internal/sidecar/credentials/resource.go @@ -15,7 +15,7 @@ var resourceContextHandler = core.DefaultContextHandler{ SchemaReaderFactory: func() core.SchemaReader { return &CreateSidecarCredentialsRequest{} }, SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &SidecarCredentialsData{} }, SchemaWriterFactoryPostMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &SidecarCredentialsData{} }, - PostURLFactory: func(d *schema.ResourceData, c *client.Client) string { + BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/users/sidecarAccounts", c.ControlPlane) }, } diff --git a/cyral/internal/sidecar/listener/resource.go b/cyral/internal/sidecar/listener/resource.go index 00048a5c..cf101d82 100644 --- a/cyral/internal/sidecar/listener/resource.go +++ b/cyral/internal/sidecar/listener/resource.go @@ -19,7 +19,7 @@ var resourceContextHandler = core.DefaultContextHandler{ SchemaReaderFactory: func() core.SchemaReader { return &SidecarListenerResource{} }, SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &ReadSidecarListenerAPIResponse{} }, SchemaWriterFactoryPostMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &CreateListenerAPIResponse{} }, - PostURLFactory: func(d *schema.ResourceData, c *client.Client) string { + BaseURLFactory: func(d *schema.ResourceData, c *client.Client) string { return fmt.Sprintf("https://%s/v1/sidecars/%s/listeners", c.ControlPlane, d.Get(utils.SidecarIDKey).(string)) From 1d4545f311c3daf857fc7a7ab532fac26aa70c62 Mon Sep 17 00:00:00 2001 From: Wilson de Carvalho <796900+wcmjunior@users.noreply.github.com> Date: Thu, 4 Apr 2024 21:56:28 -0700 Subject: [PATCH 22/22] Address review comments --- .../repository/accessrules/schema_loader.go | 2 +- .../repository/network/schema_loader.go | 2 +- .../sidecar/listener/datasource_test.go | 24 +++++++++---------- .../sidecar/listener/schema_loader.go | 2 +- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cyral/internal/repository/accessrules/schema_loader.go b/cyral/internal/repository/accessrules/schema_loader.go index 6b3c5dd7..314fb9a5 100644 --- a/cyral/internal/repository/accessrules/schema_loader.go +++ b/cyral/internal/repository/accessrules/schema_loader.go @@ -8,7 +8,7 @@ type packageSchema struct { } func (p *packageSchema) Name() string { - return "repository_access_rules" + return "accessrules" } func (p *packageSchema) Schemas() []*core.SchemaDescriptor { diff --git a/cyral/internal/repository/network/schema_loader.go b/cyral/internal/repository/network/schema_loader.go index 6e3f6a23..6444de7c 100644 --- a/cyral/internal/repository/network/schema_loader.go +++ b/cyral/internal/repository/network/schema_loader.go @@ -8,7 +8,7 @@ type packageSchema struct { } func (p *packageSchema) Name() string { - return "datamap" + return "network" } func (p *packageSchema) Schemas() []*core.SchemaDescriptor { diff --git a/cyral/internal/sidecar/listener/datasource_test.go b/cyral/internal/sidecar/listener/datasource_test.go index 4a82c06d..c751fe97 100644 --- a/cyral/internal/sidecar/listener/datasource_test.go +++ b/cyral/internal/sidecar/listener/datasource_test.go @@ -44,11 +44,11 @@ func TestAccSidecarListenerDataSource(t *testing.T) { testConfigTypeFilter, testFuncTypeFilter := testListenerDataSource( testListeners, testListeners[0].RepoTypes[0], 0) - // testConfigPortFilter, testFuncPortFilter := testListenerDataSource( - // testListeners, "", testListeners[1].NetworkAddress.Port) + testConfigPortFilter, testFuncPortFilter := testListenerDataSource( + testListeners, "", testListeners[1].NetworkAddress.Port) - // testConfigTypePortFilter, testFuncTypePortFilter := testListenerDataSource( - // testListeners, testListeners[2].RepoTypes[0], testListeners[0].NetworkAddress.Port) + testConfigTypePortFilter, testFuncTypePortFilter := testListenerDataSource( + testListeners, testListeners[2].RepoTypes[0], testListeners[0].NetworkAddress.Port) resource.ParallelTest(t, resource.TestCase{ ProviderFactories: provider.ProviderFactories, @@ -57,14 +57,14 @@ func TestAccSidecarListenerDataSource(t *testing.T) { Config: testConfigTypeFilter, Check: testFuncTypeFilter, }, - // { - // Config: testConfigPortFilter, - // Check: testFuncPortFilter, - // }, - // { - // Config: testConfigTypePortFilter, - // Check: testFuncTypePortFilter, - // }, + { + Config: testConfigPortFilter, + Check: testFuncPortFilter, + }, + { + Config: testConfigTypePortFilter, + Check: testFuncTypePortFilter, + }, }, }) } diff --git a/cyral/internal/sidecar/listener/schema_loader.go b/cyral/internal/sidecar/listener/schema_loader.go index 8ec7f34e..68594311 100644 --- a/cyral/internal/sidecar/listener/schema_loader.go +++ b/cyral/internal/sidecar/listener/schema_loader.go @@ -8,7 +8,7 @@ type packageSchema struct { } func (p *packageSchema) Name() string { - return "sidecar" + return "listener" } func (p *packageSchema) Schemas() []*core.SchemaDescriptor {