Skip to content

Commit 7526be6

Browse files
authored
Integrate Snapshot Replication (#141)
* integrate snapshot replication
1 parent fa802fe commit 7526be6

14 files changed

+142
-40
lines changed

.github/workflows/release.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
with:
2222
# Allow goreleaser to access older tag information.
2323
fetch-depth: 0
24-
- uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # v5.1.0
24+
- uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
2525
with:
2626
go-version-file: "go.mod"
2727
cache: true

.github/workflows/test.yml

+3-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ jobs:
2323
timeout-minutes: 5
2424
steps:
2525
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
26-
- uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # v5.1.0
26+
- uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
2727
with:
2828
go-version-file: "go.mod"
2929
cache: true
@@ -38,10 +38,11 @@ jobs:
3838
runs-on: ubuntu-latest
3939
steps:
4040
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
41-
- uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # v5.1.0
41+
- uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
4242
with:
4343
go-version-file: "go.mod"
4444
cache: true
45+
- uses: hashicorp/setup-terraform@v3
4546
- run: go generate ./...
4647
- name: git diff
4748
run: |

docs/data-sources/images.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,12 @@ data "genesiscloud_images" "preconfigured-images" {
5555
Required:
5656

5757
- `type` (String) Filter by the kind of image.
58-
- The value must be one of: ["base-os" "cloud-image" "preconfigured" "snapshot"].
58+
- The value must be one of: ["cloud-image"].
5959

6060
Optional:
6161

6262
- `region` (String) Filter by the region identifier.
63-
- The value must be one of: ["ARC-IS-HAF-1" "EUC-DE-MUC-1" "NORD-NO-KRS-1"].
63+
- The value must be one of: ["EUC-DE-MUC-1" "NORD-NO-KRS-1"].
6464

6565

6666
<a id="nestedatt--timeouts"></a>

docs/resources/filesystem.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ resource "genesiscloud_filesystem" "example" {
3030
- `name` (String) The human-readable name for the filesystem.
3131
- `region` (String) The identifier for the region this filesystem exists in.
3232
- If the value of this attribute changes, the resource will be replaced.
33-
- The value must be one of: ["ARC-IS-HAF-1" "EUC-DE-MUC-1" "NORD-NO-KRS-1"].
33+
- The value must be one of: ["EUC-DE-MUC-1" "NORD-NO-KRS-1"].
3434
- `size` (Number) The storage size of this filesystem given in GiB.
3535
- The value must be at least 1.
3636
- `type` (String) The storage type of the filesystem.

docs/resources/floating_ip.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ resource "genesiscloud_floating_ip" "floating_ip" {
2828
- `name` (String) The human-readable name for the floating IP.
2929
- `region` (String) The region identifier.
3030
- If the value of this attribute changes, the resource will be replaced.
31-
- The value must be one of: ["ARC-IS-HAF-1" "EUC-DE-MUC-1" "NORD-NO-KRS-1"].
31+
- The value must be one of: ["EUC-DE-MUC-1" "NORD-NO-KRS-1"].
3232
- `version` (String) The version of the floating IP.
3333
- If the value of this attribute changes, the resource will be replaced.
3434
- The value must be one of: ["ipv4"].

docs/resources/instance.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ resource "genesiscloud_instance" "example" {
3636
- `name` (String) The human-readable name for the instance.
3737
- `region` (String) The region identifier.
3838
- If the value of this attribute changes, the resource will be replaced.
39-
- The value must be one of: ["ARC-IS-HAF-1" "EUC-DE-MUC-1" "NORD-NO-KRS-1"].
39+
- The value must be one of: ["EUC-DE-MUC-1" "NORD-NO-KRS-1"].
4040
- `type` (String) The instance type identifier. Learn more about instance types [here](https://developers.genesiscloud.com/instances#instance-types).
4141
- If the value of this attribute changes, the resource will be replaced.
4242

docs/resources/security_group.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ resource "genesiscloud_security_group" "allow-https" {
3535
- `name` (String) The human-readable name for the security group.
3636
- `region` (String) The region identifier.
3737
- If the value of this attribute changes, the resource will be replaced.
38-
- The value must be one of: ["ARC-IS-HAF-1" "EUC-DE-MUC-1" "NORD-NO-KRS-1"].
38+
- The value must be one of: ["EUC-DE-MUC-1" "NORD-NO-KRS-1"].
3939
- `rules` (Attributes List) (see [below for nested schema](#nestedatt--rules))
4040

4141
### Optional

docs/resources/snapshot.md

+6-3
Original file line numberDiff line numberDiff line change
@@ -30,21 +30,24 @@ resource "genesiscloud_snapshot" "example" {
3030

3131
### Required
3232

33-
- `instance_id` (String) The id of the instance to snapshot.
34-
- If the value of this attribute changes, the resource will be replaced.
3533
- `name` (String) The human-readable name for the snapshot.
3634

3735
### Optional
3836

37+
- `region` (String) The region identifier. Should only be explicity specified when using the 'source_snapshot_id'.
38+
- `replicated_region` (String) Target region for snapshot replication. When specified, also creates a copy of the snapshot in the given region. If omitted, the snapshot exists only in the current region.
3939
- `retain_on_delete` (Boolean) Flag to retain the snapshot when the resource is deleted.
4040
- Sets the default value "false" if the attribute is not set.
41+
- `source_instance_id` (String) The id of the source instance from which this snapshot was derived.
42+
- If the value of this attribute changes, the resource will be replaced.
43+
- `source_snapshot_id` (String) The id of the source snapshot from which this snapsot was derived.
44+
- If the value of this attribute changes, the resource will be replaced.
4145
- `timeouts` (Attributes) (see [below for nested schema](#nestedatt--timeouts))
4246

4347
### Read-Only
4448

4549
- `created_at` (String) The timestamp when this snapshot was created in RFC 3339.
4650
- `id` (String) The unique ID of the snapshot.
47-
- `region` (String) The region identifier.
4851
- `size` (Number) The storage size of this snapshot given in GiB.
4952
- `status` (String) The snapshot status.
5053

docs/resources/volume.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ resource "genesiscloud_volume" "example" {
2929
- `name` (String) The human-readable name for the volume.
3030
- `region` (String) The region identifier.
3131
- If the value of this attribute changes, the resource will be replaced.
32-
- The value must be one of: ["ARC-IS-HAF-1" "EUC-DE-MUC-1" "NORD-NO-KRS-1"].
32+
- The value must be one of: ["EUC-DE-MUC-1" "NORD-NO-KRS-1"].
3333
- `size` (Number) The storage size of this volume given in GiB.
3434
- The value must be at least 1.
3535
- `type` (String) The storage type of the volume.

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ go 1.22.0
55
toolchain go1.23.1
66

77
require (
8-
github.com/genesiscloud/genesiscloud-go v1.0.11
8+
github.com/genesiscloud/genesiscloud-go v1.0.14
99
github.com/hashicorp/go-retryablehttp v0.7.7
1010
github.com/hashicorp/terraform-plugin-docs v0.19.4
1111
github.com/hashicorp/terraform-plugin-framework v1.13.0

go.sum

+2-2
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
4545
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
4646
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
4747
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
48-
github.com/genesiscloud/genesiscloud-go v1.0.11 h1:SjhBrZ2YXvWspQbfl48lUR+otqXS30QF97fO4TMJXjI=
49-
github.com/genesiscloud/genesiscloud-go v1.0.11/go.mod h1:t2m8sfDGOa8pFio3oitkkjW9YcajyxkiWEu+BHOLyes=
48+
github.com/genesiscloud/genesiscloud-go v1.0.14 h1:ydDXnjBaKj80UmUMz5YCbPNjVTR/pq4hiVHDGDJ0Mh4=
49+
github.com/genesiscloud/genesiscloud-go v1.0.14/go.mod h1:t2m8sfDGOa8pFio3oitkkjW9YcajyxkiWEu+BHOLyes=
5050
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
5151
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
5252
github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=

internal/provider/images_data_source.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ func (d *ImagesDataSource) Read(ctx context.Context, req datasource.ReadRequest,
131131
}
132132

133133
for page := 1; ; page++ {
134-
response, err := d.client.ListImagesWithResponse(ctx, &genesiscloud.ListImagesParams{
134+
response, err := d.client.ListImagesPaginatedWithResponse(ctx, &genesiscloud.ListImagesPaginatedParams{
135135
Page: pointer(page),
136136
PerPage: pointer(100),
137137
Type: filterType,

internal/provider/snapshot_resource.go

+106-20
Original file line numberDiff line numberDiff line change
@@ -62,16 +62,29 @@ func (r *SnapshotResource) Schema(ctx context.Context, req resource.SchemaReques
6262
MarkdownDescription: "The human-readable name for the snapshot.",
6363
Required: true,
6464
}),
65+
"replicated_region": resourceenhancer.Attribute(ctx, schema.StringAttribute{
66+
MarkdownDescription: "Target region for snapshot replication. When specified, also creates a copy of the snapshot in the given region. If omitted, the snapshot exists only in the current region.",
67+
Required: false,
68+
Optional: true,
69+
}),
6570
"region": resourceenhancer.Attribute(ctx, schema.StringAttribute{
66-
MarkdownDescription: "The region identifier.",
71+
MarkdownDescription: "The region identifier. Should only be explicity specified when using the 'source_snapshot_id'.",
72+
Optional: true,
6773
Computed: true,
6874
PlanModifiers: []planmodifier.String{
69-
stringplanmodifier.UseStateForUnknown(), // immutable
75+
stringplanmodifier.UseStateForUnknown(),
7076
},
7177
}),
72-
"instance_id": resourceenhancer.Attribute(ctx, schema.StringAttribute{
73-
MarkdownDescription: "The id of the instance to snapshot.",
74-
Required: true,
78+
"source_instance_id": resourceenhancer.Attribute(ctx, schema.StringAttribute{
79+
MarkdownDescription: "The id of the source instance from which this snapshot was derived.",
80+
Optional: true,
81+
PlanModifiers: []planmodifier.String{
82+
stringplanmodifier.RequiresReplace(),
83+
},
84+
}),
85+
"source_snapshot_id": resourceenhancer.Attribute(ctx, schema.StringAttribute{
86+
MarkdownDescription: "The id of the source snapshot from which this snapsot was derived.",
87+
Optional: true,
7588
PlanModifiers: []planmodifier.String{
7689
stringplanmodifier.RequiresReplace(),
7790
},
@@ -122,27 +135,100 @@ func (r *SnapshotResource) Create(ctx context.Context, req resource.CreateReques
122135
}
123136
defer cancel()
124137

125-
body := genesiscloud.CreateInstanceSnapshotJSONRequestBody{}
126-
body.Name = data.Name.ValueString()
127-
128-
instanceId := data.InstanceId.ValueString()
129-
130-
response, err := r.client.CreateInstanceSnapshotWithResponse(ctx, instanceId, body)
131-
if err != nil {
132-
resp.Diagnostics.AddError("Client Error", generateErrorMessage("create snapshot", err))
138+
if data.SourceInstanceId.IsNull() && data.SourceSnapshotId.IsNull() {
139+
resp.Diagnostics.AddError(
140+
"Invalid Configuration",
141+
"Either 'source_instance_id' or 'source_snapshot_id' must be specified.",
142+
)
133143
return
134144
}
135145

136-
snapshotResponse := response.JSON201
137-
if snapshotResponse == nil {
138-
resp.Diagnostics.AddError("Client Error", generateClientErrorMessage("create snapshot", ErrorResponse{
139-
Body: response.Body,
140-
HTTPResponse: response.HTTPResponse,
141-
Error: response.JSONDefault,
142-
}))
146+
if !data.SourceInstanceId.IsNull() && !data.SourceSnapshotId.IsNull() {
147+
resp.Diagnostics.AddError(
148+
"Invalid Configuration",
149+
"Can only specify either 'source_instance_id' or 'source_snapshot_id', not both at the same time.",
150+
)
143151
return
144152
}
145153

154+
var snapshotResponse *genesiscloud.SingleSnapshotResponse
155+
156+
if !data.SourceInstanceId.IsNull() {
157+
if data.Region.ValueString() != "" {
158+
resp.Diagnostics.AddError(
159+
"Invalid Configuration",
160+
"When specifying a source instance, the 'region' specification won't take effect",
161+
)
162+
return
163+
164+
}
165+
body := genesiscloud.CreateInstanceSnapshotJSONRequestBody{}
166+
167+
body.Name = data.Name.ValueString()
168+
169+
instanceId := data.SourceInstanceId.ValueString()
170+
171+
if !data.ReplicatedRegion.IsNull() {
172+
body.ReplicatedRegion = pointer(genesiscloud.Region(data.ReplicatedRegion.ValueString()))
173+
}
174+
175+
response, err := r.client.CreateInstanceSnapshotWithResponse(ctx, instanceId, body)
176+
if err != nil {
177+
resp.Diagnostics.AddError("Client Error", generateErrorMessage("create snapshot", err))
178+
return
179+
}
180+
181+
snapshotResponse = response.JSON201
182+
if snapshotResponse == nil {
183+
resp.Diagnostics.AddError("Client Error", generateClientErrorMessage("create snapshot", ErrorResponse{
184+
Body: response.Body,
185+
HTTPResponse: response.HTTPResponse,
186+
Error: response.JSONDefault,
187+
}))
188+
return
189+
}
190+
} else if !data.SourceSnapshotId.IsNull() {
191+
if data.Region.IsNull() {
192+
resp.Diagnostics.AddError(
193+
"Invalid Configuration",
194+
"When specifying `source_snapshot_id` as the snapshot source, you must be specified the `region`.",
195+
)
196+
return
197+
}
198+
199+
if !data.ReplicatedRegion.IsNull() {
200+
resp.Diagnostics.AddError(
201+
"Invalid Configuration",
202+
"When specifying `source_snapshot_id` as the snapshot source, you cannot specify the `replicated_region`.",
203+
)
204+
return
205+
}
206+
207+
body := genesiscloud.CloneSnapshotJSONRequestBody{}
208+
209+
body.Name = data.Name.ValueString()
210+
211+
body.Region = genesiscloud.Region(data.Region.ValueString())
212+
213+
snapshotId := data.SourceSnapshotId.ValueString()
214+
215+
response, err := r.client.CloneSnapshotWithResponse(ctx, snapshotId, body)
216+
if err != nil {
217+
resp.Diagnostics.AddError("Client Error", generateErrorMessage("clone snapshot", err))
218+
return
219+
}
220+
221+
snapshotResponse = response.JSON201
222+
if snapshotResponse == nil {
223+
resp.Diagnostics.AddError("Client Error", generateClientErrorMessage("clone snapshot", ErrorResponse{
224+
Body: response.Body,
225+
HTTPResponse: response.HTTPResponse,
226+
Error: response.JSONDefault,
227+
}))
228+
return
229+
}
230+
}
231+
146232
resp.Diagnostics.Append(data.PopulateFromClientResponse(ctx, &snapshotResponse.Snapshot)...)
147233
if resp.Diagnostics.HasError() {
148234
return

internal/provider/snapshot_types.go

+15-3
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,14 @@ type SnapshotResourceModel struct {
2222
// Region The region identifier.
2323
Region types.String `tfsdk:"region"`
2424

25-
// InstanceId The id of the instance that was snapshotted.
26-
InstanceId types.String `tfsdk:"instance_id"`
25+
// SourceInstanceId The id of the source instance from which this snapshot was derived.
26+
SourceInstanceId types.String `tfsdk:"source_instance_id"`
27+
28+
// SourceSnapshotId The id of the source snapshot from which this snapsot was derived.
29+
SourceSnapshotId types.String `tfsdk:"source_snapshot_id"`
30+
31+
// ReplicatedRegion The region identifier when the snapshot should be replicated.
32+
ReplicatedRegion types.String `tfsdk:"replicated_region"`
2733

2834
// Size The storage size of this snapshot given in GiB.
2935
Size types.Int64 `tfsdk:"size"`
@@ -45,9 +51,15 @@ func (data *SnapshotResourceModel) PopulateFromClientResponse(ctx context.Contex
4551
data.Id = types.StringValue(snapshot.Id)
4652
data.Name = types.StringValue(snapshot.Name)
4753
data.Region = types.StringValue(string(snapshot.Region))
48-
data.InstanceId = types.StringValue(string(snapshot.ResourceId))
4954
data.Size = types.Int64Value(int64(snapshot.Size))
5055
data.Status = types.StringValue(string(snapshot.Status))
5156

57+
if snapshot.SourceInstanceId != nil {
58+
data.SourceInstanceId = types.StringValue(*snapshot.SourceInstanceId)
59+
}
60+
if snapshot.SourceSnapshotId != nil {
61+
data.SourceSnapshotId = types.StringValue(*snapshot.SourceSnapshotId)
62+
}
63+
5264
return
5365
}

0 commit comments

Comments
 (0)