diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7825c5ca..0739ed3e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,12 +1,12 @@ # This GitHub action can publish assets for release when a tag is created. # Currently its setup to run on any tag that matches the pattern "v*" (ie. v0.1.0). # -# This uses an action (paultyng/ghaction-import-gpg) that assumes you set your +# This uses an action (paultyng/ghaction-import-gpg) that assumes you set your # private key in the `GPG_PRIVATE_KEY` secret and passphrase in the `PASSPHRASE` # secret. If you would rather own your own GPG handling, please fork this action # or use an alternative one for key handling. # -# You will need to pass the `--batch` flag to `gpg` in your signing step +# You will need to pass the `--batch` flag to `gpg` in your signing step # in `goreleaser` to indicate this is being used in a non-interactive mode. # name: release @@ -18,27 +18,22 @@ jobs: goreleaser: runs-on: ubuntu-latest steps: - - - name: Checkout + - name: Checkout uses: actions/checkout@v2 - - - name: Unshallow + - name: Unshallow run: git fetch --prune --unshallow - - - name: Set up Go + - name: Set up Go uses: actions/setup-go@v2 with: go-version: 1.17 - - - name: Import GPG key + - name: Import GPG key id: import_gpg - uses: paultyng/ghaction-import-gpg@v2.1.0 - env: - GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} - PASSPHRASE: ${{ secrets.PASSPHRASE }} - - - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v2 + uses: crazy-max/ghaction-import-gpg@v6 + with: + gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} + passphrase: ${{ secrets.PASSPHRASE }} + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v5 with: version: 1.26.2 args: release --rm-dist diff --git a/GNUmakefile b/GNUmakefile index dbfb0d47..9c66b58f 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -23,7 +23,7 @@ install: fmtcheck install-dev: fmtcheck mkdir -vp $(PLUGIN_DIR) ifeq ($(UNAME), arm64) - go build ${DEBUG_FLAGS} -o $(PLUGIN_DIR)/sumologic.com/dev/sumologic/1.0.0/darwin_arm64/terraform-provider-sumologic + go build ${DEBUG_FLAGS} -o $(PLUGIN_DIR)/sumologic.com/dev/sumologic/1.0.0/darwin_arm64/terraform-provider-sumologic else go build ${DEBUG_FLAGS} -o $(PLUGIN_DIR)/sumologic.com/dev/sumologic/1.0.0/darwin_amd64/terraform-provider-sumologic endif @@ -49,7 +49,7 @@ fmtcheck: lint: @echo "==> Checking source code against linters..." golangci-lint run ./... - + test: fmtcheck go test -i $(TEST) || exit 1 echo $(TEST) | \ diff --git a/sumologic/data_source_sumologic_partition.go b/sumologic/data_source_sumologic_partition.go new file mode 100644 index 00000000..b23030d6 --- /dev/null +++ b/sumologic/data_source_sumologic_partition.go @@ -0,0 +1,95 @@ +package sumologic + +import ( + "fmt" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +func dataSourcePartitionSchema() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Required: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + "routing_expression": { + Type: schema.TypeString, + Computed: true, + }, + "analytics_tier": { + Type: schema.TypeString, + Computed: true, + }, + "retention_period": { + Type: schema.TypeInt, + Computed: true, + }, + "is_compliant": { + Type: schema.TypeBool, + Computed: true, + }, + "total_bytes": { + Type: schema.TypeInt, + Computed: true, + }, + "data_forwarding_id": { + Type: schema.TypeString, + Computed: true, + }, + "is_active": { + Type: schema.TypeBool, + Computed: true, + }, + "index_type": { + Type: schema.TypeString, + Computed: true, + }, + "reduce_retention_period_immediately": { + Type: schema.TypeBool, + Optional: true, + }, + } +} + +func dataSourceSumologicPartition() *schema.Resource { + return &schema.Resource{ + Read: dataSourceSumologicPartitionRead, + Schema: dataSourcePartitionSchema(), + } +} + +func dataSourceSumologicPartitionRead(d *schema.ResourceData, meta interface{}) error { + c := meta.(*Client) + + var err error + var spartition *Partition + + if rid, ok := d.GetOk("id"); ok { + id := rid.(string) + spartition, err = c.GetPartition(id) + if err != nil { + return fmt.Errorf("partition with id %v not found: %v", id, err) + } + if spartition == nil { + return fmt.Errorf("partition with id %v not found", id) + } + } + + d.SetId(spartition.ID) + d.Set("routing_expression", spartition.RoutingExpression) + d.Set("name", spartition.Name) + d.Set("analytics_tier", spartition.AnalyticsTier) + d.Set("retention_period", spartition.RetentionPeriod) + d.Set("is_compliant", spartition.IsCompliant) + d.Set("data_forwarding_id", spartition.DataForwardingID) + d.Set("is_active", spartition.IsActive) + d.Set("total_bytes", spartition.TotalBytes) + d.Set("index_type", spartition.IndexType) + d.Set("reduce_retention_period_immediately", spartition.ReduceRetentionPeriodImmediately) + + return nil +} diff --git a/sumologic/data_source_sumologic_partitions.go b/sumologic/data_source_sumologic_partitions.go new file mode 100644 index 00000000..33ca3881 --- /dev/null +++ b/sumologic/data_source_sumologic_partitions.go @@ -0,0 +1,58 @@ +package sumologic + +import ( + "fmt" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +func dataSourceSumologicPartitions() *schema.Resource { + return &schema.Resource{ + Read: dataSourceSumologicPartitionsRead, + + Schema: map[string]*schema.Schema{ + "partitions": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: dataSourcePartitionSchema(), + Read: dataSourceSumologicPartitionRead, + }, + }, + }, + } +} + +func dataSourceSumologicPartitionsRead(d *schema.ResourceData, meta interface{}) error { + c := meta.(*Client) + + spartitions, err := c.ListPartitions() + if err != nil { + return fmt.Errorf("error retrieving partitions: %v", err) + } + + partitions := make([]map[string]interface{}, 0, len(spartitions)) + + for _, spartition := range spartitions { + partition := map[string]interface{}{ + "id": spartition.ID, + "name": spartition.Name, + "routing_expression": spartition.RoutingExpression, + "analytics_tier": spartition.AnalyticsTier, + "retention_period": spartition.RetentionPeriod, + "is_compliant": spartition.IsCompliant, + "total_bytes": spartition.TotalBytes, + "data_forwarding_id": spartition.DataForwardingID, + "is_active": spartition.IsActive, + "index_type": spartition.IndexType, + "reduce_retention_period_immediately": spartition.ReduceRetentionPeriodImmediately, + } + + partitions = append(partitions, partition) + } + + d.Set("partitions", partitions) + d.SetId(c.BaseURL.Host) + + return nil +} diff --git a/sumologic/provider.go b/sumologic/provider.go index 23cc26ec..ab59c6e9 100644 --- a/sumologic/provider.go +++ b/sumologic/provider.go @@ -75,6 +75,8 @@ func Provider() terraform.ResourceProvider { "sumologic_s3_source": resourceSumologicGenericPollingSource(), "sumologic_s3_audit_source": resourceSumologicGenericPollingSource(), "sumologic_s3_archive_source": resourceSumologicGenericPollingSource(), + "sumologic_s3_data_forwarding_destination": resourceSumologicS3DataForwardingDestination(), + "sumologic_s3_data_forwarding_rule": resourceSumologicS3DataForwardingRule(), "sumologic_cloudwatch_source": resourceSumologicGenericPollingSource(), "sumologic_aws_inventory_source": resourceSumologicGenericPollingSource(), "sumologic_aws_xray_source": resourceSumologicGenericPollingSource(), @@ -132,6 +134,8 @@ func Provider() terraform.ResourceProvider { "sumologic_role": dataSourceSumologicRole(), "sumologic_role_v2": dataSourceSumologicRoleV2(), "sumologic_user": dataSourceSumologicUser(), + "sumologic_partitions": dataSourceSumologicPartitions(), + "sumologic_partition": dataSourceSumologicPartition(), }, ConfigureFunc: providerConfigure, } diff --git a/sumologic/resource_sumologic_partition.go b/sumologic/resource_sumologic_partition.go index 54145cb7..3d8795f5 100644 --- a/sumologic/resource_sumologic_partition.go +++ b/sumologic/resource_sumologic_partition.go @@ -109,7 +109,7 @@ func resourceSumologicPartitionRead(d *schema.ResourceData, meta interface{}) er d.Set("analytics_tier", spartition.AnalyticsTier) d.Set("retention_period", spartition.RetentionPeriod) d.Set("is_compliant", spartition.IsCompliant) - d.Set("data_forwarding_id", spartition.DataForwardingId) + d.Set("data_forwarding_id", spartition.DataForwardingID) d.Set("is_active", spartition.IsActive) d.Set("total_bytes", spartition.TotalBytes) d.Set("index_type", spartition.IndexType) @@ -141,7 +141,7 @@ func resourceToPartition(d *schema.ResourceData) Partition { AnalyticsTier: d.Get("analytics_tier").(string), RetentionPeriod: d.Get("retention_period").(int), IsCompliant: d.Get("is_compliant").(bool), - DataForwardingId: d.Get("data_forwarding_id").(string), + DataForwardingID: d.Get("data_forwarding_id").(string), IsActive: d.Get("is_active").(bool), TotalBytes: d.Get("total_bytes").(int), IndexType: d.Get("index_type").(string), diff --git a/sumologic/resource_sumologic_s3_data_forwarding_destination.go b/sumologic/resource_sumologic_s3_data_forwarding_destination.go new file mode 100644 index 00000000..5d015350 --- /dev/null +++ b/sumologic/resource_sumologic_s3_data_forwarding_destination.go @@ -0,0 +1,146 @@ +package sumologic + +import ( + "fmt" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" +) + +func resourceSumologicS3DataForwardingDestination() *schema.Resource { + return &schema.Resource{ + Create: resourceSumologicS3DataForwardingDestinationCreate, + Read: resourceSumologicS3DataForwardingDestinationRead, + Update: resourceSumologicS3DataForwardingDestinationUpdate, + Delete: resourceSumologicS3DataForwardingDestinationDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, + }, + "authentication_mode": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"AccessKey", "RoleBased"}, false), + }, + "access_key_id": { + Type: schema.TypeString, + Sensitive: true, + Optional: true, + }, + "secret_access_key": { + Type: schema.TypeString, + Sensitive: true, + Optional: true, + }, + "role_arn": { + Type: schema.TypeString, + Optional: true, + }, + "region": { + Type: schema.TypeString, + Optional: true, + }, + "encrypted": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + "enabled": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + "bucket_name": { + Type: schema.TypeString, + Required: true, + }, + }, + } +} + +func resourceSumologicS3DataForwardingDestinationCreate(d *schema.ResourceData, meta interface{}) error { + c := meta.(*Client) + + if d.Id() == "" { + dfd := resourceToS3DataForwardingDestination(d) + createdDfd, err := c.CreateS3DataForwardingDestination(dfd) + + if err != nil { + return err + } + + d.SetId(createdDfd.ID) + } + + return resourceSumologicS3DataForwardingDestinationRead(d, meta) +} + +func resourceSumologicS3DataForwardingDestinationRead(d *schema.ResourceData, meta interface{}) error { + c := meta.(*Client) + id := d.Id() + dfd, err := c.GetS3DataForwardingDestination(id) + + if err != nil { + return err + } + + if dfd == nil { + d.SetId("") + return fmt.Errorf("S3DataForwardingDestination for id %s not found", id) + } + + d.SetId(dfd.ID) + d.Set("name", dfd.Name) + d.Set("description", dfd.Description) + d.Set("authentication_mode", dfd.AuthenticationMode) + d.Set("access_key_id", dfd.AccessKeyID) + d.Set("secret_access_key", dfd.SecretAccessKey) + d.Set("role_arn", dfd.RoleARN) + d.Set("region", dfd.Region) + d.Set("encrypted", dfd.Encrypted) + d.Set("enabled", dfd.Enabled) + d.Set("bucket_name", dfd.BucketName) + + return nil +} + +func resourceSumologicS3DataForwardingDestinationUpdate(d *schema.ResourceData, meta interface{}) error { + c := meta.(*Client) + dfd := resourceToS3DataForwardingDestination(d) + err := c.UpdateS3DataForwardingDestination(dfd) + + if err != nil { + return err + } + + return resourceSumologicS3DataForwardingDestinationRead(d, meta) +} + +func resourceSumologicS3DataForwardingDestinationDelete(d *schema.ResourceData, meta interface{}) error { + c := meta.(*Client) + return c.DeleteS3DataForwardingDestination(d.Id()) +} + +func resourceToS3DataForwardingDestination(d *schema.ResourceData) S3DataForwardingDestination { + return S3DataForwardingDestination{ + ID: d.Id(), + Name: d.Get("name").(string), + Description: d.Get("description").(string), + AuthenticationMode: d.Get("authentication_mode").(string), + AccessKeyID: d.Get("access_key_id").(string), + SecretAccessKey: d.Get("secret_access_key").(string), + RoleARN: d.Get("role_arn").(string), + Region: d.Get("region").(string), + Encrypted: d.Get("encrypted").(bool), + Enabled: d.Get("enabled").(bool), + BucketName: d.Get("bucket_name").(string), + } +} diff --git a/sumologic/resource_sumologic_s3_data_forwarding_rule.go b/sumologic/resource_sumologic_s3_data_forwarding_rule.go new file mode 100644 index 00000000..7acca73b --- /dev/null +++ b/sumologic/resource_sumologic_s3_data_forwarding_rule.go @@ -0,0 +1,112 @@ +package sumologic + +import ( + "fmt" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +func resourceSumologicS3DataForwardingRule() *schema.Resource { + return &schema.Resource{ + Create: resourceSumologicS3DataForwardingRuleCreate, + Read: resourceSumologicS3DataForwardingRuleRead, + Update: resourceSumologicS3DataForwardingRuleUpdate, + Delete: resourceSumologicS3DataForwardingRuleDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + Schema: map[string]*schema.Schema{ + "index_id": { + Type: schema.TypeString, + Required: true, + }, + "destination_id": { + Type: schema.TypeString, + Required: true, + }, + "enabled": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + "file_format": { + Type: schema.TypeString, + Optional: true, + Default: "{index}_{day}_{hour}_{minute}_{second}", + }, + "format": { + Type: schema.TypeString, + Optional: true, + Default: "csv", + }, + }, + } +} + +func resourceSumologicS3DataForwardingRuleCreate(d *schema.ResourceData, meta interface{}) error { + c := meta.(*Client) + + if d.Id() == "" { + dfr := resourceToS3DataForwardingRule(d) + createdDfd, err := c.CreateS3DataForwardingRule(dfr) + + if err != nil { + return err + } + + d.SetId(createdDfd.IndexID) + } + + return resourceSumologicS3DataForwardingRuleRead(d, meta) +} + +func resourceSumologicS3DataForwardingRuleRead(d *schema.ResourceData, meta interface{}) error { + c := meta.(*Client) + id := d.Id() + dfr, err := c.GetS3DataForwardingRule(id) + + if err != nil { + return err + } + + if dfr == nil { + d.SetId("") + return fmt.Errorf("S3DataForwardingRule for id %s not found", id) + } + + d.SetId(dfr.IndexID) + d.Set("index_id", dfr.IndexID) + d.Set("destination_id", dfr.DestinationID) + d.Set("enabled", dfr.Enabled) + d.Set("file_format", dfr.FileFormat) + d.Set("format", dfr.Format) + + return nil +} + +func resourceSumologicS3DataForwardingRuleUpdate(d *schema.ResourceData, meta interface{}) error { + c := meta.(*Client) + dfr := resourceToS3DataForwardingRule(d) + err := c.UpdateS3DataForwardingRule(dfr) + + if err != nil { + return err + } + + return resourceSumologicS3DataForwardingRuleRead(d, meta) +} + +func resourceSumologicS3DataForwardingRuleDelete(d *schema.ResourceData, meta interface{}) error { + c := meta.(*Client) + return c.DeleteS3DataForwardingRule(d.Id()) +} + +func resourceToS3DataForwardingRule(d *schema.ResourceData) S3DataForwardingRule { + return S3DataForwardingRule{ + IndexID: d.Get("index_id").(string), + DestinationID: d.Get("destination_id").(string), + Enabled: d.Get("enabled").(bool), + FileFormat: d.Get("file_format").(string), + Format: d.Get("format").(string), + } +} diff --git a/sumologic/sumologic_partition.go b/sumologic/sumologic_partition.go index f28015e9..076b1497 100644 --- a/sumologic/sumologic_partition.go +++ b/sumologic/sumologic_partition.go @@ -6,6 +6,57 @@ import ( "strings" ) +type ListPartitionResp struct { + Data []Partition `json:"data"` + Next string `json:"next"` +} + +func (s *ListPartitionResp) Reset() { + s.Data = nil + s.Next = "" +} + +func (s *Client) ListPartitions() ([]Partition, error) { + var listPartitionResp ListPartitionResp + + data, _, err := s.Get("v1/partitions?limit=1000") + if err != nil { + return nil, err + } + + err = json.Unmarshal(data, &listPartitionResp) + if err != nil { + return nil, err + } + + spartitions := listPartitionResp.Data + + for listPartitionResp.Next != "" { + data, _, err = s.Get("v1/partitions?token=" + listPartitionResp.Next) + if err != nil { + return nil, err + } + + listPartitionResp.Reset() + + err = json.Unmarshal(data, &listPartitionResp) + if err != nil { + return nil, err + } + + spartitions = append(spartitions, listPartitionResp.Data...) + } + + var activePartitions []Partition + for _, partition := range spartitions { + if partition.IsActive { + activePartitions = append(activePartitions, partition) + } + } + + return activePartitions, nil +} + func (s *Client) GetPartition(id string) (*Partition, error) { data, _, err := s.Get(fmt.Sprintf("v1/partitions/%s", id)) if err != nil { @@ -26,7 +77,7 @@ func (s *Client) GetPartition(id string) (*Partition, error) { err = json.Unmarshal(data, &spartition) if err != nil { return nil, err - } else if spartition.IsActive == false { + } else if !spartition.IsActive { return nil, nil } @@ -70,7 +121,7 @@ type Partition struct { AnalyticsTier string `json:"analyticsTier"` RetentionPeriod int `json:"retentionPeriod"` IsCompliant bool `json:"isCompliant"` - DataForwardingId string `json:"dataForwardingId"` + DataForwardingID string `json:"dataForwardingId"` IsActive bool `json:"isActive"` TotalBytes int `json:"totalBytes"` IndexType string `json:"indexType"` diff --git a/sumologic/sumologic_s3_data_forwarding_destination.go b/sumologic/sumologic_s3_data_forwarding_destination.go new file mode 100644 index 00000000..00cfecab --- /dev/null +++ b/sumologic/sumologic_s3_data_forwarding_destination.go @@ -0,0 +1,74 @@ +package sumologic + +import ( + "encoding/json" + "fmt" + "strings" +) + +type S3DataForwardingDestination struct { + ID string `json:"id,omitempty"` + Name string `json:"destinationName,omitempty"` + Description string `json:"description,omitempty"` + AuthenticationMode string `json:"authenticationMode"` + AccessKeyID string `json:"accessKeyId,omitempty"` + SecretAccessKey string `json:"secretAccessKey,omitempty"` + RoleARN string `json:"roleArn,omitempty"` + Region string `json:"region,omitempty"` + Encrypted bool `json:"encrypted"` + Enabled bool `json:"enabled"` + BucketName string `json:"bucketName,omitempty"` +} + +func (s *Client) GetS3DataForwardingDestination(id string) (*S3DataForwardingDestination, error) { + data, _, err := s.Get(fmt.Sprintf("v1/logsDataForwarding/destinations/%s", id)) + + if err != nil { + if strings.Contains(err.Error(), "gn:destination_name_not_exists") { + return nil, nil + } + return nil, err + } + + if data == nil { + return nil, nil + } + + var dfd S3DataForwardingDestination + err = json.Unmarshal(data, &dfd) + if err != nil { + return nil, err + } + + return &dfd, nil +} + +func (s *Client) CreateS3DataForwardingDestination(dfd S3DataForwardingDestination) (*S3DataForwardingDestination, error) { + var createdDfd S3DataForwardingDestination + + responseBody, err := s.Post("v1/logsDataForwarding/destinations", dfd) + if err != nil { + return nil, err + } + + err = json.Unmarshal(responseBody, &createdDfd) + + if err != nil { + return nil, err + } + + return &createdDfd, nil +} + +func (s *Client) DeleteS3DataForwardingDestination(id string) error { + _, err := s.Delete(fmt.Sprintf("v1/logsDataForwarding/destinations/%s", id)) + + return err +} + +func (s *Client) UpdateS3DataForwardingDestination(dfd S3DataForwardingDestination) error { + url := fmt.Sprintf("v1/logsDataForwarding/destinations/%s", dfd.ID) + _, err := s.Put(url, dfd) + + return err +} diff --git a/sumologic/sumologic_s3_data_forwarding_rule.go b/sumologic/sumologic_s3_data_forwarding_rule.go new file mode 100644 index 00000000..a7092817 --- /dev/null +++ b/sumologic/sumologic_s3_data_forwarding_rule.go @@ -0,0 +1,69 @@ +package sumologic + +import ( + "encoding/json" + "fmt" + "strings" +) + +type S3DataForwardingRule struct { + ID string `json:"id,omitempty"` + IndexID string `json:"indexId,omitempty"` + DestinationID string `json:"destinationId,omitempty"` + Enabled bool `json:"enabled"` + FileFormat string `json:"fileFormat,omitempty"` + Format string `json:"format,omitempty"` +} + +func (s *Client) GetS3DataForwardingRule(indexId string) (*S3DataForwardingRule, error) { + data, _, err := s.Get(fmt.Sprintf("v1/logsDataForwarding/rules/%s", indexId)) + + if err != nil { + if strings.Contains(err.Error(), "partition:partition_not_found") || strings.Contains(err.Error(), "gn:data_forwarding_rule_not_found") { + return nil, nil + } + return nil, err + } + + if data == nil { + return nil, nil + } + + var dfr S3DataForwardingRule + err = json.Unmarshal(data, &dfr) + if err != nil { + return nil, err + } + + return &dfr, nil +} + +func (s *Client) CreateS3DataForwardingRule(dfr S3DataForwardingRule) (*S3DataForwardingRule, error) { + var createdDfr S3DataForwardingRule + + responseBody, err := s.Post("v1/logsDataForwarding/rules", dfr) + if err != nil { + return nil, err + } + + err = json.Unmarshal(responseBody, &createdDfr) + + if err != nil { + return nil, err + } + + return &createdDfr, nil +} + +func (s *Client) DeleteS3DataForwardingRule(indexId string) error { + _, err := s.Delete(fmt.Sprintf("v1/logsDataForwarding/rules/%s", indexId)) + + return err +} + +func (s *Client) UpdateS3DataForwardingRule(dfr S3DataForwardingRule) error { + url := fmt.Sprintf("v1/logsDataForwarding/rules/%s", dfr.IndexID) + _, err := s.Put(url, dfr) + + return err +}