This is an automated email from the ASF dual-hosted git repository. kiranchavala pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/cloudstack-terraform-provider.git
The following commit(s) were added to refs/heads/main by this push: new 5105978 Add support for snapshot policies (#223) 5105978 is described below commit 5105978d386edd76a89a53e74c291ba664490953 Author: Pearl Dsilva <pearl1...@gmail.com> AuthorDate: Mon Sep 22 13:28:45 2025 -0400 Add support for snapshot policies (#223) * Add support for snapshot policies * fix test * update doc * fix test * fix test * update test * update test to use cardinals as opposed to frequency of interval type * add forceNew if the policy is modified to recreate the resource + fix test * update test * update test * test * handle tags --- cloudstack/provider.go | 1 + cloudstack/resource_cloudstack_snapshot_policy.go | 246 +++++++++++ .../resource_cloudstack_snapshot_policy_test.go | 458 +++++++++++++++++++++ website/docs/r/snapshot_policy.html.markdown | 148 +++++++ 4 files changed, 853 insertions(+) diff --git a/cloudstack/provider.go b/cloudstack/provider.go index 3b8c441..91c0f5c 100644 --- a/cloudstack/provider.go +++ b/cloudstack/provider.go @@ -149,6 +149,7 @@ func Provider() *schema.Provider { "cloudstack_network_service_provider": resourceCloudStackNetworkServiceProvider(), "cloudstack_role": resourceCloudStackRole(), "cloudstack_limits": resourceCloudStackLimits(), + "cloudstack_snapshot_policy": resourceCloudStackSnapshotPolicy(), }, ConfigureFunc: providerConfigure, diff --git a/cloudstack/resource_cloudstack_snapshot_policy.go b/cloudstack/resource_cloudstack_snapshot_policy.go new file mode 100644 index 0000000..8340b14 --- /dev/null +++ b/cloudstack/resource_cloudstack_snapshot_policy.go @@ -0,0 +1,246 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package cloudstack + +import ( + "fmt" + "log" + + "github.com/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func intervalTypeToString(intervalType int) string { + switch intervalType { + case 0: + return "HOURLY" + case 1: + return "DAILY" + case 2: + return "WEEKLY" + case 3: + return "MONTHLY" + default: + return fmt.Sprintf("%d", intervalType) + } +} + +func resourceCloudStackSnapshotPolicy() *schema.Resource { + return &schema.Resource{ + Create: resourceCloudstackSnapshotPolicyCreate, + Read: resourceCloudstackSnapshotPolicyRead, + Update: resourceCloudstackSnapshotPolicyUpdate, + Delete: resourceCloudstackSnapshotPolicyDelete, + + Schema: map[string]*schema.Schema{ + "volume_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "interval_type": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "max_snaps": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + "schedule": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "timezone": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "zone_ids": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "custom_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "tags": tagsSchema(), + }, + } +} + +func resourceCloudstackSnapshotPolicyCreate(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + p := cs.Snapshot.NewCreateSnapshotPolicyParams( + d.Get("interval_type").(string), + d.Get("max_snaps").(int), + d.Get("schedule").(string), + d.Get("timezone").(string), + d.Get("volume_id").(string), + ) + + if v, ok := d.GetOk("zone_ids"); ok && v != nil { + zoneIDs := []string{} + for _, id := range v.([]interface{}) { + zoneIDs = append(zoneIDs, id.(string)) + } + p.SetZoneids(zoneIDs) + } + + snapshotPolicy, err := cs.Snapshot.CreateSnapshotPolicy(p) + if err != nil { + return fmt.Errorf("Error creating snapshot policy: %s", err) + } + + log.Printf("[DEBUG] CreateSnapshotPolicy response: %+v", snapshotPolicy) + + if snapshotPolicy.Id == "" { + log.Printf("[DEBUG] CloudStack returned empty ID, trying to find created policy by volume ID") + + listParams := cs.Snapshot.NewListSnapshotPoliciesParams() + listParams.SetVolumeid(d.Get("volume_id").(string)) + + resp, listErr := cs.Snapshot.ListSnapshotPolicies(listParams) + if listErr != nil { + return fmt.Errorf("Error listing snapshot policies to find created policy: %s", listErr) + } + + if resp.Count == 0 { + return fmt.Errorf("No snapshot policies found for volume after creation") + } + + foundPolicy := resp.SnapshotPolicies[resp.Count-1] + log.Printf("[DEBUG] Found policy with ID: %s", foundPolicy.Id) + d.SetId(foundPolicy.Id) + } else { + d.SetId(snapshotPolicy.Id) + } + + log.Printf("[DEBUG] Snapshot policy created with ID: %s", d.Id()) + + // Set tags if provided + if err := setTags(cs, d, "SnapshotPolicy"); err != nil { + return fmt.Errorf("Error setting tags on snapshot policy %s: %s", d.Id(), err) + } + + return resourceCloudstackSnapshotPolicyRead(d, meta) +} + +func resourceCloudstackSnapshotPolicyRead(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + if d.Id() == "" { + log.Printf("[DEBUG] Snapshot policy ID is empty") + return fmt.Errorf("Snapshot policy ID is empty") + } + + p := cs.Snapshot.NewListSnapshotPoliciesParams() + p.SetId(d.Id()) + + resp, err := cs.Snapshot.ListSnapshotPolicies(p) + if err != nil { + return fmt.Errorf("Failed to list snapshot policies: %s", err) + } + + if resp.Count == 0 { + log.Printf("[DEBUG] Snapshot policy %s not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + snapshotPolicy := resp.SnapshotPolicies[0] + + d.Set("volume_id", snapshotPolicy.Volumeid) + d.Set("interval_type", intervalTypeToString(snapshotPolicy.Intervaltype)) + d.Set("max_snaps", snapshotPolicy.Maxsnaps) + d.Set("schedule", snapshotPolicy.Schedule) + d.Set("timezone", snapshotPolicy.Timezone) + + if snapshotPolicy.Zone != nil { + zoneIDs := []string{} + for _, zone := range snapshotPolicy.Zone { + if zoneMap, ok := zone.(map[string]interface{}); ok { + if id, ok := zoneMap["id"].(string); ok { + zoneIDs = append(zoneIDs, id) + } + } + } + d.Set("zone_ids", zoneIDs) + } else { + d.Set("zone_ids", nil) + } + + // Handle tags + tags := make(map[string]interface{}) + for _, tag := range snapshotPolicy.Tags { + tags[tag.Key] = tag.Value + } + d.Set("tags", tags) + + return nil +} + +func resourceCloudstackSnapshotPolicyUpdate(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + p := cs.Snapshot.NewUpdateSnapshotPolicyParams() + p.SetId(d.Id()) + if v, ok := d.GetOk("custom_id"); ok { + p.SetCustomid(v.(string)) + } + + _, err := cs.Snapshot.UpdateSnapshotPolicy(p) + if err != nil { + return err + } + + // Handle tags + if d.HasChange("tags") { + if err := updateTags(cs, d, "SnapshotPolicy"); err != nil { + return fmt.Errorf("Error updating tags on snapshot policy %s: %s", d.Id(), err) + } + } + + return resourceCloudstackSnapshotPolicyRead(d, meta) +} + +func resourceCloudstackSnapshotPolicyDelete(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + p := cs.Snapshot.NewDeleteSnapshotPoliciesParams() + p.SetId(d.Id()) + + _, err := cs.Snapshot.DeleteSnapshotPolicies(p) + if err != nil { + return fmt.Errorf("Failed to delete snapshot policy %s: %s", d.Id(), err) + } + + d.SetId("") + + return nil +} diff --git a/cloudstack/resource_cloudstack_snapshot_policy_test.go b/cloudstack/resource_cloudstack_snapshot_policy_test.go new file mode 100644 index 0000000..acb2801 --- /dev/null +++ b/cloudstack/resource_cloudstack_snapshot_policy_test.go @@ -0,0 +1,458 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package cloudstack + +import ( + "fmt" + "testing" + + "github.com/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +func TestAccCloudStackSnapshotPolicy_basic(t *testing.T) { + var snapshotPolicy cloudstack.SnapshotPolicy + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackSnapshotPolicyDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackSnapshotPolicy_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackSnapshotPolicyExists("cloudstack_snapshot_policy.foo", &snapshotPolicy), + resource.TestCheckResourceAttr("cloudstack_snapshot_policy.foo", "interval_type", "DAILY"), + resource.TestCheckResourceAttr("cloudstack_snapshot_policy.foo", "max_snaps", "7"), + resource.TestCheckResourceAttr("cloudstack_snapshot_policy.foo", "schedule", "02:30"), + resource.TestCheckResourceAttr("cloudstack_snapshot_policy.foo", "timezone", "UTC"), + resource.TestCheckResourceAttr("cloudstack_snapshot_policy.foo", "tags.%", "2"), + resource.TestCheckResourceAttr("cloudstack_snapshot_policy.foo", "tags.Environment", "test"), + resource.TestCheckResourceAttr("cloudstack_snapshot_policy.foo", "tags.Purpose", "backup"), + ), + }, + }, + }) +} + +func TestAccCloudStackSnapshotPolicy_hourly(t *testing.T) { + var snapshotPolicy cloudstack.SnapshotPolicy + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackSnapshotPolicyDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackSnapshotPolicy_hourly, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackSnapshotPolicyExists("cloudstack_snapshot_policy.hourly", &snapshotPolicy), + resource.TestCheckResourceAttr("cloudstack_snapshot_policy.hourly", "interval_type", "HOURLY"), + resource.TestCheckResourceAttr("cloudstack_snapshot_policy.hourly", "max_snaps", "6"), + resource.TestCheckResourceAttr("cloudstack_snapshot_policy.hourly", "schedule", "0"), + resource.TestCheckResourceAttr("cloudstack_snapshot_policy.hourly", "timezone", "UTC"), + resource.TestCheckResourceAttr("cloudstack_snapshot_policy.hourly", "custom_id", "test-hourly"), + resource.TestCheckResourceAttr("cloudstack_snapshot_policy.hourly", "tags.%", "0"), + ), + }, + }, + }) +} + +func TestAccCloudStackSnapshotPolicy_update(t *testing.T) { + var snapshotPolicy cloudstack.SnapshotPolicy + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackSnapshotPolicyDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackSnapshotPolicy_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackSnapshotPolicyExists("cloudstack_snapshot_policy.foo", &snapshotPolicy), + resource.TestCheckResourceAttr("cloudstack_snapshot_policy.foo", "max_snaps", "7"), + resource.TestCheckResourceAttr("cloudstack_snapshot_policy.foo", "tags.Environment", "test"), + ), + }, + { + Config: testAccCloudStackSnapshotPolicy_update, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackSnapshotPolicyExists("cloudstack_snapshot_policy.foo", &snapshotPolicy), + resource.TestCheckResourceAttr("cloudstack_snapshot_policy.foo", "max_snaps", "8"), + resource.TestCheckResourceAttr("cloudstack_snapshot_policy.foo", "tags.Environment", "production"), + resource.TestCheckResourceAttr("cloudstack_snapshot_policy.foo", "tags.Updated", "true"), + ), + }, + }, + }) +} + +func TestAccCloudStackSnapshotPolicy_weekly(t *testing.T) { + var snapshotPolicy cloudstack.SnapshotPolicy + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackSnapshotPolicyDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackSnapshotPolicy_weekly, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackSnapshotPolicyExists("cloudstack_snapshot_policy.weekly", &snapshotPolicy), + resource.TestCheckResourceAttr("cloudstack_snapshot_policy.weekly", "interval_type", "WEEKLY"), + resource.TestCheckResourceAttr("cloudstack_snapshot_policy.weekly", "schedule", "1:03:00"), + resource.TestCheckResourceAttr("cloudstack_snapshot_policy.weekly", "tags.%", "0"), + ), + }, + }, + }) +} + +func TestAccCloudStackSnapshotPolicy_monthly(t *testing.T) { + var snapshotPolicy cloudstack.SnapshotPolicy + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackSnapshotPolicyDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackSnapshotPolicy_monthly, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackSnapshotPolicyExists("cloudstack_snapshot_policy.monthly", &snapshotPolicy), + resource.TestCheckResourceAttr("cloudstack_snapshot_policy.monthly", "interval_type", "MONTHLY"), + resource.TestCheckResourceAttr("cloudstack_snapshot_policy.monthly", "schedule", "15:01:00"), + resource.TestCheckResourceAttr("cloudstack_snapshot_policy.monthly", "tags.%", "0"), + ), + }, + }, + }) +} + +func testAccCheckCloudStackSnapshotPolicyExists(n string, snapshotPolicy *cloudstack.SnapshotPolicy) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No snapshot policy ID is set") + } + + cs := testAccProvider.Meta().(*cloudstack.CloudStackClient) + sp, _, err := cs.Snapshot.GetSnapshotPolicyByID(rs.Primary.ID) + if err != nil { + return err + } + + if sp.Id != rs.Primary.ID { + return fmt.Errorf("Snapshot policy not found") + } + + *snapshotPolicy = *sp + return nil + } +} + +func testAccCheckCloudStackSnapshotPolicyDestroy(s *terraform.State) error { + cs := testAccProvider.Meta().(*cloudstack.CloudStackClient) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "cloudstack_snapshot_policy" { + continue + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No snapshot policy ID is set") + } + + _, _, err := cs.Snapshot.GetSnapshotPolicyByID(rs.Primary.ID) + if err == nil { + return fmt.Errorf("Snapshot policy %s still exists", rs.Primary.ID) + } + } + + return nil +} + +const testAccCloudStackSnapshotPolicy_basic = ` +data "cloudstack_zone" "zone" { + filter { + name = "name" + value = "Sandbox-simulator" + } +} + +resource "cloudstack_network" "foo" { + name = "terraform-network" + display_text = "terraform-network" + cidr = "10.1.1.0/24" + network_offering = "DefaultIsolatedNetworkOfferingWithSourceNatService" + zone = data.cloudstack_zone.zone.name +} + +resource "cloudstack_instance" "foobar" { + name = "terraform-test" + display_name = "terraform" + service_offering = "Small Instance" + network_id = cloudstack_network.foo.id + template = "CentOS 5.6 (64-bit) no GUI (Simulator)" + zone = data.cloudstack_zone.zone.name + expunge = true +} + +resource "cloudstack_disk_offering" "foo" { + name = "terraform-disk-offering" + display_text = "terraform-disk-offering" + disk_size = 10 +} + +resource "cloudstack_disk" "foo" { + name = "terraform-disk" + attach = true + disk_offering = cloudstack_disk_offering.foo.name + virtual_machine_id = cloudstack_instance.foobar.id + zone = data.cloudstack_zone.zone.name +} + +resource "cloudstack_snapshot_policy" "foo" { + volume_id = cloudstack_disk.foo.id + interval_type = "DAILY" + max_snaps = 7 + schedule = "02:30" + timezone = "UTC" + zone_ids = [data.cloudstack_zone.zone.id] + + tags = { + Environment = "test" + Purpose = "backup" + } +} +` + +const testAccCloudStackSnapshotPolicy_update = ` +data "cloudstack_zone" "zone" { + filter { + name = "name" + value = "Sandbox-simulator" + } +} + +resource "cloudstack_network" "foo" { + name = "terraform-network" + display_text = "terraform-network" + cidr = "10.1.1.0/24" + network_offering = "DefaultIsolatedNetworkOfferingWithSourceNatService" + zone = data.cloudstack_zone.zone.name +} + +resource "cloudstack_instance" "foobar" { + name = "terraform-test" + display_name = "terraform" + service_offering = "Small Instance" + network_id = cloudstack_network.foo.id + template = "CentOS 5.6 (64-bit) no GUI (Simulator)" + zone = data.cloudstack_zone.zone.name + expunge = true +} + +resource "cloudstack_disk_offering" "foo" { + name = "terraform-disk-offering" + display_text = "terraform-disk-offering" + disk_size = 10 +} + +resource "cloudstack_disk" "foo" { + name = "terraform-disk" + attach = true + disk_offering = cloudstack_disk_offering.foo.name + virtual_machine_id = cloudstack_instance.foobar.id + zone = data.cloudstack_zone.zone.name +} + +resource "cloudstack_snapshot_policy" "foo" { + volume_id = cloudstack_disk.foo.id + interval_type = "DAILY" + max_snaps = 8 + schedule = "02:30" + timezone = "UTC" + zone_ids = [data.cloudstack_zone.zone.id] + + tags = { + Environment = "production" + Purpose = "backup" + Updated = "true" + } +} +` + +const testAccCloudStackSnapshotPolicy_hourly = ` +data "cloudstack_zone" "zone" { + filter { + name = "name" + value = "Sandbox-simulator" + } +} + +resource "cloudstack_network" "foo" { + name = "terraform-network-hourly" + display_text = "terraform-network-hourly" + cidr = "10.1.1.0/24" + network_offering = "DefaultIsolatedNetworkOfferingWithSourceNatService" + zone = data.cloudstack_zone.zone.name +} + +resource "cloudstack_instance" "foobar" { + name = "terraform-test-hourly" + display_name = "terraform-hourly" + service_offering = "Small Instance" + network_id = cloudstack_network.foo.id + template = "CentOS 5.6 (64-bit) no GUI (Simulator)" + zone = data.cloudstack_zone.zone.name + expunge = true +} + +resource "cloudstack_disk_offering" "foo" { + name = "terraform-disk-offering-hourly" + display_text = "terraform-disk-offering-hourly" + disk_size = 10 +} + +resource "cloudstack_disk" "foo" { + name = "terraform-disk-hourly" + attach = true + disk_offering = cloudstack_disk_offering.foo.name + virtual_machine_id = cloudstack_instance.foobar.id + zone = data.cloudstack_zone.zone.name +} + +resource "cloudstack_snapshot_policy" "hourly" { + volume_id = cloudstack_disk.foo.id + interval_type = "HOURLY" + max_snaps = 6 + schedule = "0" + timezone = "UTC" + zone_ids = [data.cloudstack_zone.zone.id] + custom_id = "test-hourly" +} +` + +const testAccCloudStackSnapshotPolicy_weekly = ` +data "cloudstack_zone" "zone" { + filter { + name = "name" + value = "Sandbox-simulator" + } +} + +resource "cloudstack_network" "foo" { + name = "terraform-network-weekly" + display_text = "terraform-network-weekly" + cidr = "10.1.1.0/24" + network_offering = "DefaultIsolatedNetworkOfferingWithSourceNatService" + zone = data.cloudstack_zone.zone.name +} + +resource "cloudstack_instance" "foobar" { + name = "terraform-test-weekly" + display_name = "terraform-weekly" + service_offering = "Small Instance" + network_id = cloudstack_network.foo.id + template = "CentOS 5.6 (64-bit) no GUI (Simulator)" + zone = data.cloudstack_zone.zone.name + expunge = true +} + +resource "cloudstack_disk_offering" "foo" { + name = "terraform-disk-offering-weekly" + display_text = "terraform-disk-offering-weekly" + disk_size = 10 +} + +resource "cloudstack_disk" "foo" { + name = "terraform-disk-weekly" + attach = true + disk_offering = cloudstack_disk_offering.foo.name + virtual_machine_id = cloudstack_instance.foobar.id + zone = data.cloudstack_zone.zone.name +} + +resource "cloudstack_snapshot_policy" "weekly" { + volume_id = cloudstack_disk.foo.id + interval_type = "WEEKLY" + max_snaps = 4 + schedule = "1:03:00" # Monday at 3:00 AM + timezone = "UTC" + zone_ids = [data.cloudstack_zone.zone.id] +} +` + +const testAccCloudStackSnapshotPolicy_monthly = ` +data "cloudstack_zone" "zone" { + filter { + name = "name" + value = "Sandbox-simulator" + } +} + +resource "cloudstack_network" "foo" { + name = "terraform-network-monthly" + display_text = "terraform-network-monthly" + cidr = "10.1.1.0/24" + network_offering = "DefaultIsolatedNetworkOfferingWithSourceNatService" + zone = data.cloudstack_zone.zone.name +} + +resource "cloudstack_instance" "foobar" { + name = "terraform-test-monthly" + display_name = "terraform-monthly" + service_offering = "Small Instance" + network_id = cloudstack_network.foo.id + template = "CentOS 5.6 (64-bit) no GUI (Simulator)" + zone = data.cloudstack_zone.zone.name + expunge = true +} + +resource "cloudstack_disk_offering" "foo" { + name = "terraform-disk-offering-monthly" + display_text = "terraform-disk-offering-monthly" + disk_size = 10 +} + +resource "cloudstack_disk" "foo" { + name = "terraform-disk-monthly" + attach = true + disk_offering = cloudstack_disk_offering.foo.name + virtual_machine_id = cloudstack_instance.foobar.id + zone = data.cloudstack_zone.zone.name +} + +resource "cloudstack_snapshot_policy" "monthly" { + volume_id = cloudstack_disk.foo.id + interval_type = "MONTHLY" + max_snaps = 8 + schedule = "15:01:00" # 15th day at 1:00 AM + timezone = "UTC" + zone_ids = [data.cloudstack_zone.zone.id] +} +` diff --git a/website/docs/r/snapshot_policy.html.markdown b/website/docs/r/snapshot_policy.html.markdown new file mode 100644 index 0000000..6e7a389 --- /dev/null +++ b/website/docs/r/snapshot_policy.html.markdown @@ -0,0 +1,148 @@ +--- +subcategory: "Snapshot" +layout: "cloudstack" +page_title: "CloudStack: cloudstack_snapshot_policy" +sidebar_current: "docs-cloudstack-resource-snapshot-policy" +description: |- + Creates and manages snapshot policies for volumes. +--- + +# cloudstack_snapshot_policy + +Provides a CloudStack snapshot policy resource. This can be used to create, modify, and delete snapshot policies for volumes. + +## Example Usage + +### Basic Snapshot Policy + +```hcl +resource "cloudstack_snapshot_policy" "daily" { + volume_id = cloudstack_disk.data.id + interval_type = "DAILY" + max_snaps = 7 + schedule = "02:30" + timezone = "UTC" + zone_ids = [data.cloudstack_zone.zone1.id] + + tags = { + Environment = "production" + Purpose = "backup" + } +} +``` + +### Hourly Snapshot Policy + +```hcl +resource "cloudstack_snapshot_policy" "hourly" { + volume_id = cloudstack_disk.database.id + interval_type = "HOURLY" + max_snaps = 6 + schedule = "0" # Top of every hour + timezone = "America/New_York" + zone_ids = [data.cloudstack_zone.zone1.id] + + custom_id = "hourly-db-backup" +} +``` + +### Multiple Zone Snapshot Policy + +```hcl +resource "cloudstack_snapshot_policy" "multi_zone" { + volume_id = cloudstack_disk.shared.id + interval_type = "WEEKLY" + max_snaps = 4 + schedule = "1:03:00" # Monday at 3:00 AM + timezone = "UTC" + zone_ids = [ + data.cloudstack_zone.zone1.id, + data.cloudstack_zone.zone2.id + ] +} +``` + +### Monthly Archive Policy + +```hcl +resource "cloudstack_snapshot_policy" "monthly_archive" { + volume_id = cloudstack_disk.archive.id + interval_type = "MONTHLY" + max_snaps = 12 + schedule = "1:01:00" # 1st day of month at 1:00 AM + timezone = "UTC" + zone_ids = [data.cloudstack_zone.zone1.id] + + tags = { + Type = "archive" + Retention = "1-year" + Environment = "production" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `volume_id` - (Required) The ID of the volume for which the snapshot policy is being created. + +* `interval_type` - (Required) The interval type for the snapshot policy. Valid values are: + * `HOURLY` - Take snapshots every hour + * `DAILY` - Take snapshots daily + * `WEEKLY` - Take snapshots weekly + * `MONTHLY` - Take snapshots monthly + +* `max_snaps` - (Required) Maximum number of snapshots to retain. When this limit is reached, older snapshots are automatically deleted. + +* `schedule` - (Required) The schedule for taking snapshots. The format depends on the interval type: + * **HOURLY**: Minute (0-59), e.g., `"30"` for 30 minutes past every hour + * **DAILY**: Time in HH:MM format, e.g., `"02:30"` for 2:30 AM daily + * **WEEKLY**: Day and time in D:HH:MM format, e.g., `"1:02:30"` for Monday at 2:30 AM (1=Monday, 7=Sunday) + * **MONTHLY**: Day and time in DD:HH:MM format, e.g., `"15:02:30"` for 15th day at 2:30 AM + +* `timezone` - (Required) The timezone for the schedule. Use standard timezone names like `UTC`, `America/New_York`, `Europe/London`, etc. + +* `zone_ids` - (Optional) List of zone IDs where the snapshot policy should be applied. If not specified, the policy applies to all zones. + +* `custom_id` - (Optional) A custom ID for the snapshot policy. This is useful for identification purposes and cannot be changed after creation. + +* `tags` - (Optional) A mapping of tags to assign to the resource. + +## Attribute Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The ID of the snapshot policy. + +## Schedule Format Examples + +### Hourly Schedules +```hcl +schedule = "0" # Top of every hour (XX:00) +schedule = "15" # 15 minutes past every hour (XX:15) +schedule = "30" # 30 minutes past every hour (XX:30) +schedule = "45" # 45 minutes past every hour (XX:45) +``` + +### Daily Schedules +```hcl +schedule = "01:00" # 1:00 AM daily +schedule = "02:30" # 2:30 AM daily +schedule = "14:00" # 2:00 PM daily +schedule = "23:59" # 11:59 PM daily +``` + +### Weekly Schedules +```hcl +schedule = "1:02:00" # Monday at 2:00 AM +schedule = "2:03:30" # Tuesday at 3:30 AM +schedule = "7:01:00" # Sunday at 1:00 AM +``` + +### Monthly Schedules +```hcl +schedule = "1:02:00" # 1st day of month at 2:00 AM +schedule = "15:03:30" # 15th day of month at 3:30 AM +schedule = "28:01:00" # 28th day of month at 1:00 AM +```