diff --git a/README.md b/README.md index fb2b4bdb..2b1b6010 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,8 @@ make docker-compose/docs pre-commit run --show-diff-on-failure --color=always --all-files ``` +> **_Note_** that due to a [limitation of the tfplugindocs tool](https://github.com/hashicorp/terraform-plugin-docs/issues/28), some descriptions might not be automatically generated for nested fields. In this case, its necessary to generate the documentation manually by editing the template file - in the `templates` folder - corresponding to the resource/data-source. + > `pre-commit` can sometimes fail because your user is not the owner of the files in the `/docs` directory. > To solve this problem, run the following command and re-run the `pre-commit run...` tried in the previous step: diff --git a/cyral/data_source_cyral_sidecar_instance.go b/cyral/data_source_cyral_sidecar_instance.go new file mode 100644 index 00000000..918f449c --- /dev/null +++ b/cyral/data_source_cyral_sidecar_instance.go @@ -0,0 +1,196 @@ +package cyral + +import ( + "fmt" + "net/http" + + "github.com/cyralinc/terraform-provider-cyral/client" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +const ( + // Schema keys + SidecarInstanceListKey = "instance_list" + MetadataKey = "metadata" + MonitoringKey = "monitoring" + VersionKey = "version" + DynamicVersionKey = "dynamic_version" + CapabilitiesKey = "capabilities" + StartTimestampKey = "start_timestamp" + LastRegistrationKey = "last_registration" + RecyclingKey = "recycling" + RecyclableKey = "recyclable" + ServicesKey = "services" + MetricsPortKey = "metrics_port" + ComponentsKey = "components" + ErrorKey = "error" +) + +func dataSourceSidecarInstance() *schema.Resource { + return &schema.Resource{ + Description: "Retrieve sidecar instances.", + ReadContext: ReadResource(ResourceOperationConfig{ + Name: "SidecarInstanceDataSourceRead", + HttpMethod: http.MethodGet, + CreateURL: func(d *schema.ResourceData, c *client.Client) string { + return fmt.Sprintf( + "https://%s/v2/sidecars/%s/instances", + c.ControlPlane, d.Get(SidecarIDKey), + ) + }, + NewResponseData: func(_ *schema.ResourceData) ResponseData { + return &SidecarInstances{} + }, + }), + Schema: map[string]*schema.Schema{ + SidecarIDKey: { + Description: "Sidecar identifier.", + Type: schema.TypeString, + Required: true, + }, + IDKey: { + Description: "Data source identifier.", + Type: schema.TypeString, + Computed: true, + }, + SidecarInstanceListKey: { + Description: "List of existing sidecar instances.", + Computed: true, + Type: schema.TypeList, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + IDKey: { + Description: "Instance identifier. Varies according to the computing platform that " + + "the sidecar is deployed to.", + Type: schema.TypeString, + Computed: true, + }, + MetadataKey: { + Description: "Instance metadata.", + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + VersionKey: { + Description: "Sidecar version that the instance is using.", + Type: schema.TypeString, + Computed: true, + }, + DynamicVersionKey: { + Description: "If true, indicates that the instance has dynamic versioning, " + + "that means that the version is not fixed at template level and it can be " + + "automatically upgraded.", + Type: schema.TypeBool, + Computed: true, + }, + CapabilitiesKey: { + Description: "Set of capabilities that can be enabled or disabled. **Note**: This " + + "field is per-instance, not per-sidecar, because not all sidecar instances might be " + + "in sync at some point in time.", + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + RecyclableKey: { + Description: "Indicates if sidecar instance will be recycled (e.g., by an ASG) " + + "if it reports itself as unhealthy.", + Type: schema.TypeBool, + Computed: true, + }, + }, + }, + }, + StartTimestampKey: { + Description: "The time when the instance started.", + Type: schema.TypeString, + Computed: true, + }, + LastRegistrationKey: { + Description: "The last time the instance reported to the Control Plane.", + Type: schema.TypeString, + Computed: true, + }, + RecyclingKey: { + Description: "Indicates whether the Control Plane has asked the instance to mark " + + "itself unhealthy so that it is recycled by the infrastructure.", + Type: schema.TypeBool, + Computed: true, + }, + }, + }, + }, + MonitoringKey: { + Description: "Instance monitoring information, such as its overall health.", + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + StatusKey: { + Description: "Aggregated status of all the sidecar services.", + Type: schema.TypeString, + Computed: true, + }, + ServicesKey: { + Description: "Sidecar instance services monitoring information.", + Type: schema.TypeMap, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeSet, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + StatusKey: { + Description: "Aggregated status of sidecar service.", + Type: schema.TypeString, + Computed: true, + }, + MetricsPortKey: { + Description: "Metrics port for service monitoring.", + Type: schema.TypeInt, + Computed: true, + }, + ComponentsKey: { + Description: "Map of name to monitoring component. A component is a " + + "monitored check on the service that has its own status.", + Type: schema.TypeMap, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeSet, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + StatusKey: { + Description: "Component status.", + Type: schema.TypeString, + Computed: true, + }, + DescriptionKey: { + Description: "Describes what the type of check the component represents.", + Type: schema.TypeString, + Computed: true, + }, + ErrorKey: { + Description: "Error that describes what caused the current status.", + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + HostKey: { + Description: "Service host on the deployment.", + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } +} diff --git a/cyral/data_source_cyral_sidecar_instance_ids.go b/cyral/data_source_cyral_sidecar_instance_ids.go index 965511d5..81d69b7d 100644 --- a/cyral/data_source_cyral_sidecar_instance_ids.go +++ b/cyral/data_source_cyral_sidecar_instance_ids.go @@ -15,15 +15,17 @@ import ( ) type SidecarDetails struct { - Instances []SidecarInstance `json:"instances,omitempty"` + Instances []DeprecatedSidecarInstances `json:"instances,omitempty"` } -type SidecarInstance struct { +type DeprecatedSidecarInstances struct { ASGInstanceID string `json:"asg_instance,omitempty"` } func dataSourceSidecarInstanceIDs() *schema.Resource { return &schema.Resource{ + DeprecationMessage: "This data source was deprecated. It will be removed in the next major version of " + + "the provider. Use the data source `cyral_sidecar_instance` instead", Description: "Retrieves the IDs of all the current instances of a given sidecar.", ReadContext: dataSourceSidecarInstanceIDsRead, Schema: map[string]*schema.Schema{ diff --git a/cyral/data_source_cyral_sidecar_instance_test.go b/cyral/data_source_cyral_sidecar_instance_test.go new file mode 100644 index 00000000..e91dca4f --- /dev/null +++ b/cyral/data_source_cyral_sidecar_instance_test.go @@ -0,0 +1,88 @@ +package cyral + +import ( + "fmt" + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +const ( + sidecarInstanceDataSourceFullNameFmt = "data.cyral_sidecar_instance.%s" +) + +func TestAccSidecarInstanceDataSource(t *testing.T) { + dataSourceName := "instances" + testSteps := []resource.TestStep{ + accTestStepSidecarInstanceDataSource_EmptySidecarID(dataSourceName), + accTestStepSidecarInstanceDataSource_NoSidecarFoundForGivenID(dataSourceName), + accTestStepSidecarInstanceDataSource_NoSidecarInstances(dataSourceName), + } + resource.ParallelTest( + t, resource.TestCase{ + ProviderFactories: providerFactories, + Steps: testSteps, + }, + ) +} + +func accTestStepSidecarInstanceDataSource_EmptySidecarID(dataSourceName string) resource.TestStep { + config := fmt.Sprintf(` + data "cyral_sidecar_instance" "%s" { + } + `, dataSourceName) + return resource.TestStep{ + Config: config, + ExpectError: regexp.MustCompile(fmt.Sprintf(`The argument "%s" is required`, SidecarIDKey)), + } +} + +func accTestStepSidecarInstanceDataSource_NoSidecarFoundForGivenID(dataSourceName string) resource.TestStep { + nonExistentSidecarID := "some-non-existent-sidecar-id" + config := fmt.Sprintf(` + data "cyral_sidecar_instance" "%s" { + sidecar_id = %q + } + `, dataSourceName, nonExistentSidecarID) + return resource.TestStep{ + Config: config, + ExpectError: regexp.MustCompile(fmt.Sprintf("sidecar with id '%s' does not exist", nonExistentSidecarID)), + } +} + +func accTestStepSidecarInstanceDataSource_NoSidecarInstances(dataSourceName string) resource.TestStep { + // Creates a sidecar that doesn't have any instances, since it was not + // deployed. + config := formatBasicSidecarIntoConfig( + basicSidecarResName, + accTestName("data-sidecar-instance", "sidecar"), + "cft-ec2", + "", + ) + config += fmt.Sprintf(` + data "cyral_sidecar_instance" "%s" { + sidecar_id = %s + } + `, dataSourceName, basicSidecarID) + dataSourceFullName := fmt.Sprintf(sidecarInstanceDataSourceFullNameFmt, dataSourceName) + check := resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet( + dataSourceFullName, + SidecarIDKey, + ), + resource.TestCheckResourceAttrSet( + dataSourceFullName, + IDKey, + ), + resource.TestCheckResourceAttr( + dataSourceFullName, + fmt.Sprintf("%s.#", SidecarInstanceListKey), + "0", + ), + ) + return resource.TestStep{ + Config: config, + Check: check, + } +} diff --git a/cyral/model_sidecar_instance.go b/cyral/model_sidecar_instance.go new file mode 100644 index 00000000..c7597860 --- /dev/null +++ b/cyral/model_sidecar_instance.go @@ -0,0 +1,133 @@ +package cyral + +import ( + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +type SidecarInstances struct { + Instances []SidecarInstance `json:"instances"` +} + +func (wrapper *SidecarInstances) WriteToSchema(d *schema.ResourceData) error { + d.SetId(uuid.New().String()) + d.Set(SidecarInstanceListKey, wrapper.InstancesToInterfaceList()) + return nil +} + +func (wrapper *SidecarInstances) InstancesToInterfaceList() []any { + instancesInterfaceList := make([]any, len(wrapper.Instances)) + for index, instance := range wrapper.Instances { + instancesInterfaceList[index] = instance.ToMap() + } + return instancesInterfaceList +} + +type SidecarInstance struct { + ID string `json:"id"` + Metadata SidecarInstanceMetadata `json:"metadata"` + Monitoring SidecarInstanceMonitoring `json:"monitoring"` +} + +func (instance *SidecarInstance) ToMap() map[string]any { + return map[string]any{ + IDKey: instance.ID, + MetadataKey: instance.Metadata.ToInterfaceList(), + MonitoringKey: instance.Monitoring.ToInterfaceList(), + } +} + +type SidecarInstanceMetadata struct { + Version string `json:"version"` + IsDynamicVersion bool `json:"isDynamicVersion"` + SidecarCapabilities *SidecarCapabilities `json:"capabilities"` + StartTimestamp string `json:"startTimestamp"` + LastRegistration string `json:"lastRegistration"` + IsRecycling bool `json:"isRecycling"` +} + +func (metadata *SidecarInstanceMetadata) ToInterfaceList() []any { + return []any{ + map[string]any{ + VersionKey: metadata.Version, + DynamicVersionKey: metadata.IsDynamicVersion, + CapabilitiesKey: metadata.SidecarCapabilities.ToInterfaceList(), + StartTimestampKey: metadata.StartTimestamp, + LastRegistrationKey: metadata.LastRegistration, + RecyclingKey: metadata.IsRecycling, + }, + } +} + +type SidecarCapabilities struct { + Recyclable bool `json:"recyclable"` +} + +func (capabilities *SidecarCapabilities) ToInterfaceList() []any { + if capabilities == nil { + return nil + } + return []any{ + map[string]any{ + RecyclableKey: capabilities.Recyclable, + }, + } +} + +type SidecarInstanceMonitoring struct { + Status string `json:"status"` + Services map[string]SidecarService `json:"services"` +} + +func (monitoring *SidecarInstanceMonitoring) ToInterfaceList() []any { + var services map[string]any + if monitoring.Services != nil { + services = make(map[string]any, len(monitoring.Services)) + } + for serviceKey, service := range monitoring.Services { + services[serviceKey] = service.ToMap() + } + return []any{ + map[string]any{ + StatusKey: monitoring.Status, + ServicesKey: services, + }, + } +} + +type SidecarService struct { + Status string `json:"status"` + MetricsPort uint32 `json:"metricsPort"` + Components map[string]SidecarServiceComponent `json:"components"` + Host string `json:"host"` +} + +func (service *SidecarService) ToMap() map[string]any { + var components map[string]any + if service.Components != nil { + components = make(map[string]any, len(service.Components)) + } + for componentKey, component := range service.Components { + components[componentKey] = component.ToMap() + } + return map[string]any{ + StatusKey: service.Status, + MetricsPortKey: service.MetricsPort, + ComponentsKey: components, + HostKey: service.Host, + } +} + +type SidecarServiceComponent struct { + Status string `json:"status"` + Description string `json:"description"` + Error string `json:"error"` +} + +func (component *SidecarServiceComponent) ToMap() map[string]any { + return map[string]any{ + StatusKey: component.Status, + DescriptionKey: component.Description, + ErrorKey: component.Error, + } +} diff --git a/cyral/provider.go b/cyral/provider.go index 60050fc6..2b97b3b8 100644 --- a/cyral/provider.go +++ b/cyral/provider.go @@ -85,6 +85,7 @@ func Provider() *schema.Provider { "cyral_sidecar_health": dataSourceSidecarHealth(), "cyral_sidecar_id": dataSourceSidecarID(), "cyral_sidecar_instance_ids": dataSourceSidecarInstanceIDs(), + "cyral_sidecar_instance": dataSourceSidecarInstance(), "cyral_sidecar_listener": dataSourceSidecarListener(), "cyral_system_info": dataSourceSystemInfo(), }, diff --git a/docs/data-sources/sidecar_instance.md b/docs/data-sources/sidecar_instance.md new file mode 100644 index 00000000..b803d877 --- /dev/null +++ b/docs/data-sources/sidecar_instance.md @@ -0,0 +1,75 @@ +# cyral_sidecar_instance (Data Source) + +Retrieve sidecar instances. + +## Schema + +### Required + +- `sidecar_id` (String) Sidecar identifier. + +### Read-Only + +- `id` (String) Data source identifier. +- `instance_list` (List of Object) List of existing sidecar instances. (see [below for nested schema](#nestedatt--instance_list)) + + + +### Nested Schema for `instance_list` + +Read-Only: + +- `id` (String) Instance identifier. Varies according to the computing platform that the sidecar is deployed to. +- `metadata` (Set of Object) Instance metadata. (see [below for nested schema](#nestedatt--instance_list--metadata)) +- `monitoring` (Set of Object) Instance monitoring information, such as its overall health. (see [below for nested schema](#nestedatt--instance_list--monitoring)) + + + +### Nested Schema for `instance_list.metadata` + +Read-Only: + +- `capabilities` (Set of Object) Set of capabilities that can be enabled or disabled. **Note**: This field is per-instance, not per-sidecar, because not all sidecar instances might be in sync at some point in time. (see [below for nested schema](#nestedatt--instance_list--metadata--capabilities)) +- `dynamic_version` (Boolean) If true, indicates that the instance has dynamic versioning, that means that the version is not fixed at template level and it can be automatically upgraded. +- `last_registration` (String) The last time the instance reported to the Control Plane. +- `recycling` (Boolean) Indicates whether the Control Plane has asked the instance to mark itself unhealthy so that it is recycled by the infrastructure. +- `start_timestamp` (String) The time when the instance started. +- `version` (String) Sidecar version that the instance is using. + + + +### Nested Schema for `instance_list.metadata.capabilities` + +Read-Only: + +- `recyclable` (Boolean) Indicates if sidecar instance will be recycled (e.g., by an ASG) if it reports itself as unhealthy. + + + +### Nested Schema for `instance_list.monitoring` + +Read-Only: + +- `services` (Map of Set of Object) Sidecar instance services monitoring information. (see [below for nested schema](#nestedatt--instance_list--monitoring--services)) +- `status` (String) Aggregated status of all the sidecar services. + + + +### Nested Schema for `instance_list.monitoring.services` + +Read-Only: + +- `status` (String) Aggregated status of sidecar service. +- `metrics_port` (Number) Metrics port for service monitoring. +- `components` (Map of Set of Object) Map of name to monitoring component. A component is a monitored check on the service that has its own status. (see [below for nested schema](#nestedatt--instance_list--monitoring--services--components)) +- `host` (String) Service host on the deployment. + + + +### Nested Schema for `instance_list.monitoring.services.components` + +Read-Only: + +- `status` (String) Component status. +- `description` (String) Describes what the type of check the component represents. +- `error` (String) Error that describes what caused the current status. diff --git a/docs/data-sources/sidecar_instance_ids.md b/docs/data-sources/sidecar_instance_ids.md index ab94d334..4e831322 100644 --- a/docs/data-sources/sidecar_instance_ids.md +++ b/docs/data-sources/sidecar_instance_ids.md @@ -3,12 +3,12 @@ page_title: "cyral_sidecar_instance_ids Data Source - terraform-provider-cyral" subcategory: "" description: |- - Retrieves the IDs of all the current instances of a given sidecar. + ~> DEPRECATED This data source was deprecated. It will be removed in the next major version of the provider. Use the data source cyral_sidecar_instance instead --- # cyral_sidecar_instance_ids (Data Source) -Retrieves the IDs of all the current instances of a given sidecar. +~> **DEPRECATED** This data source was deprecated. It will be removed in the next major version of the provider. Use the data source `cyral_sidecar_instance` instead ## Example Usage diff --git a/templates/data-sources/sidecar_instance.md.tmpl b/templates/data-sources/sidecar_instance.md.tmpl new file mode 100644 index 00000000..9d788962 --- /dev/null +++ b/templates/data-sources/sidecar_instance.md.tmpl @@ -0,0 +1,75 @@ +# {{ .Name | trimspace }} ({{ .Type | trimspace }}) + +{{ .Description | trimspace }} + +## Schema + +### Required + +- `sidecar_id` (String) Sidecar identifier. + +### Read-Only + +- `id` (String) Data source identifier. +- `instance_list` (List of Object) List of existing sidecar instances. (see [below for nested schema](#nestedatt--instance_list)) + + + +### Nested Schema for `instance_list` + +Read-Only: + +- `id` (String) Instance identifier. Varies according to the computing platform that the sidecar is deployed to. +- `metadata` (Set of Object) Instance metadata. (see [below for nested schema](#nestedatt--instance_list--metadata)) +- `monitoring` (Set of Object) Instance monitoring information, such as its overall health. (see [below for nested schema](#nestedatt--instance_list--monitoring)) + + + +### Nested Schema for `instance_list.metadata` + +Read-Only: + +- `capabilities` (Set of Object) Set of capabilities that can be enabled or disabled. **Note**: This field is per-instance, not per-sidecar, because not all sidecar instances might be in sync at some point in time. (see [below for nested schema](#nestedatt--instance_list--metadata--capabilities)) +- `dynamic_version` (Boolean) If true, indicates that the instance has dynamic versioning, that means that the version is not fixed at template level and it can be automatically upgraded. +- `last_registration` (String) The last time the instance reported to the Control Plane. +- `recycling` (Boolean) Indicates whether the Control Plane has asked the instance to mark itself unhealthy so that it is recycled by the infrastructure. +- `start_timestamp` (String) The time when the instance started. +- `version` (String) Sidecar version that the instance is using. + + + +### Nested Schema for `instance_list.metadata.capabilities` + +Read-Only: + +- `recyclable` (Boolean) Indicates if sidecar instance will be recycled (e.g., by an ASG) if it reports itself as unhealthy. + + + +### Nested Schema for `instance_list.monitoring` + +Read-Only: + +- `services` (Map of Set of Object) Sidecar instance services monitoring information. (see [below for nested schema](#nestedatt--instance_list--monitoring--services)) +- `status` (String) Aggregated status of all the sidecar services. + + + +### Nested Schema for `instance_list.monitoring.services` + +Read-Only: + +- `status` (String) Aggregated status of sidecar service. +- `metrics_port` (Number) Metrics port for service monitoring. +- `components` (Map of Set of Object) Map of name to monitoring component. A component is a monitored check on the service that has its own status. (see [below for nested schema](#nestedatt--instance_list--monitoring--services--components)) +- `host` (String) Service host on the deployment. + + + +### Nested Schema for `instance_list.monitoring.services.components` + +Read-Only: + +- `status` (String) Component status. +- `description` (String) Describes what the type of check the component represents. +- `error` (String) Error that describes what caused the current status.