|
| 1 | +--- |
| 2 | +phase: 15-replication-resources |
| 3 | +plan: 01 |
| 4 | +type: execute |
| 5 | +wave: 1 |
| 6 | +depends_on: [] |
| 7 | +files_modified: |
| 8 | + - internal/client/models_storage.go |
| 9 | + - internal/client/remote_credentials.go |
| 10 | + - internal/client/bucket_replica_links.go |
| 11 | +autonomous: true |
| 12 | +requirements: [RCR-01, RCR-02, BRL-01, BRL-02, BRL-03] |
| 13 | + |
| 14 | +must_haves: |
| 15 | + truths: |
| 16 | + - "Client can GET/POST/PATCH/DELETE remote credentials by name" |
| 17 | + - "Client can GET/POST/PATCH/DELETE bucket replica links by local+remote bucket names" |
| 18 | + - "Model structs serialize to correct JSON for FlashBlade API" |
| 19 | + artifacts: |
| 20 | + - path: "internal/client/models_storage.go" |
| 21 | + provides: "ObjectStoreRemoteCredentials, ObjectStoreRemoteCredentialsPost, ObjectStoreRemoteCredentialsPatch, BucketReplicaLink, BucketReplicaLinkPost, BucketReplicaLinkPatch model structs" |
| 22 | + contains: "ObjectStoreRemoteCredentials" |
| 23 | + - path: "internal/client/remote_credentials.go" |
| 24 | + provides: "CRUD client methods for /object-store-remote-credentials" |
| 25 | + exports: ["GetRemoteCredentials", "PostRemoteCredentials", "PatchRemoteCredentials", "DeleteRemoteCredentials"] |
| 26 | + - path: "internal/client/bucket_replica_links.go" |
| 27 | + provides: "CRUD client methods for /bucket-replica-links" |
| 28 | + exports: ["GetBucketReplicaLink", "PostBucketReplicaLink", "PatchBucketReplicaLink", "DeleteBucketReplicaLink"] |
| 29 | + key_links: |
| 30 | + - from: "internal/client/remote_credentials.go" |
| 31 | + to: "internal/client/models_storage.go" |
| 32 | + via: "uses ObjectStoreRemoteCredentials types" |
| 33 | + pattern: "ObjectStoreRemoteCredentials" |
| 34 | + - from: "internal/client/bucket_replica_links.go" |
| 35 | + to: "internal/client/models_storage.go" |
| 36 | + via: "uses BucketReplicaLink types" |
| 37 | + pattern: "BucketReplicaLink" |
| 38 | +--- |
| 39 | + |
| 40 | +<objective> |
| 41 | +Add model structs and client CRUD methods for both remote credentials and bucket replica link resources. |
| 42 | + |
| 43 | +Purpose: Foundation layer for Phase 15 -- downstream resource implementations (plans 02 and 03) depend on these models and client methods. |
| 44 | +Output: Model structs in models_storage.go, client methods in two new files. |
| 45 | +</objective> |
| 46 | + |
| 47 | +<execution_context> |
| 48 | +@/home/gule/.claude/get-shit-done/workflows/execute-plan.md |
| 49 | +@/home/gule/.claude/get-shit-done/templates/summary.md |
| 50 | +</execution_context> |
| 51 | + |
| 52 | +<context> |
| 53 | +@.planning/PROJECT.md |
| 54 | +@.planning/ROADMAP.md |
| 55 | +@.planning/STATE.md |
| 56 | + |
| 57 | +@internal/client/models_storage.go |
| 58 | +@internal/client/models_common.go |
| 59 | +@internal/client/object_store_accounts.go |
| 60 | +@internal/client/buckets.go |
| 61 | +@internal/client/array_connections.go |
| 62 | + |
| 63 | +<interfaces> |
| 64 | +<!-- Existing types the executor needs --> |
| 65 | + |
| 66 | +From internal/client/models_common.go: |
| 67 | +```go |
| 68 | +type NamedReference struct { |
| 69 | + Name string `json:"name,omitempty"` |
| 70 | + ID string `json:"id,omitempty"` |
| 71 | +} |
| 72 | + |
| 73 | +type ListResponse[T any] struct { |
| 74 | + Items []T `json:"items"` |
| 75 | + ContinuationToken string `json:"continuation_token,omitempty"` |
| 76 | + TotalItemCount int `json:"total_item_count,omitempty"` |
| 77 | +} |
| 78 | +``` |
| 79 | + |
| 80 | +From internal/client/client.go (methods available): |
| 81 | +```go |
| 82 | +func (c *FlashBladeClient) get(ctx context.Context, path string, out interface{}) error |
| 83 | +func (c *FlashBladeClient) post(ctx context.Context, path string, body interface{}, out interface{}) error |
| 84 | +func (c *FlashBladeClient) patch(ctx context.Context, path string, body interface{}, out interface{}) error |
| 85 | +func (c *FlashBladeClient) delete(ctx context.Context, path string) error |
| 86 | +``` |
| 87 | +</interfaces> |
| 88 | +</context> |
| 89 | + |
| 90 | +<tasks> |
| 91 | + |
| 92 | +<task type="auto"> |
| 93 | + <name>Task 1: Add model structs for remote credentials and bucket replica link</name> |
| 94 | + <files>internal/client/models_storage.go</files> |
| 95 | + <action> |
| 96 | +Append the following model structs to the end of models_storage.go: |
| 97 | + |
| 98 | +**ObjectStoreRemoteCredentials** (GET response model): |
| 99 | +- `ID` string `json:"id"` |
| 100 | +- `Name` string `json:"name"` |
| 101 | +- `AccessKeyID` string `json:"access_key_id"` |
| 102 | +- `SecretAccessKey` string `json:"secret_access_key,omitempty"` (only returned on POST, empty on GET) |
| 103 | +- `Remote` NamedReference `json:"remote"` |
| 104 | +- `Realms` []NamedReference `json:"realms,omitempty"` (computed, read-only) |
| 105 | + |
| 106 | +**ObjectStoreRemoteCredentialsPost** (POST body): |
| 107 | +- `AccessKeyID` string `json:"access_key_id"` |
| 108 | +- `SecretAccessKey` string `json:"secret_access_key"` |
| 109 | +- Name is passed via `?names=` query param, NOT in body. |
| 110 | +- Remote is passed via `?remote_names=` query param (reference to array connection remote), NOT in body. |
| 111 | + |
| 112 | +**ObjectStoreRemoteCredentialsPatch** (PATCH body — pointer fields for partial update): |
| 113 | +- `AccessKeyID` *string `json:"access_key_id,omitempty"` |
| 114 | +- `SecretAccessKey` *string `json:"secret_access_key,omitempty"` |
| 115 | + |
| 116 | +**BucketReplicaLink** (GET response model): |
| 117 | +- `ID` string `json:"id"` |
| 118 | +- `LocalBucket` NamedReference `json:"local_bucket"` |
| 119 | +- `RemoteBucket` NamedReference `json:"remote_bucket"` |
| 120 | +- `Remote` NamedReference `json:"remote"` |
| 121 | +- `RemoteCredentials` *NamedReference `json:"remote_credentials,omitempty"` (pointer — nil for FB-to-FB links) |
| 122 | +- `Paused` bool `json:"paused"` |
| 123 | +- `CascadingEnabled` bool `json:"cascading_enabled"` |
| 124 | +- `Direction` string `json:"direction,omitempty"` (read-only: "inbound" or "outbound") |
| 125 | +- `Status` string `json:"status,omitempty"` (read-only) |
| 126 | +- `StatusDetails` string `json:"status_details,omitempty"` (read-only) |
| 127 | +- `Lag` int64 `json:"lag,omitempty"` (read-only, milliseconds) |
| 128 | +- `RecoveryPoint` int64 `json:"recovery_point,omitempty"` (read-only, epoch ms) |
| 129 | +- `ObjectBacklog` *ObjectBacklog `json:"object_backlog,omitempty"` (read-only) |
| 130 | + |
| 131 | +**ObjectBacklog** (nested struct for replica link): |
| 132 | +- `Count` int64 `json:"count,omitempty"` |
| 133 | +- `TotalSize` int64 `json:"total_size,omitempty"` |
| 134 | + |
| 135 | +**BucketReplicaLinkPost** (POST body): |
| 136 | +- `Paused` bool `json:"paused,omitempty"` |
| 137 | +- `CascadingEnabled` bool `json:"cascading_enabled,omitempty"` |
| 138 | +- Local bucket, remote bucket, and remote credentials are all query params, NOT body fields. |
| 139 | + |
| 140 | +**BucketReplicaLinkPatch** (PATCH body — pointer fields): |
| 141 | +- `Paused` *bool `json:"paused,omitempty"` |
| 142 | + </action> |
| 143 | + <verify> |
| 144 | + <automated>cd /home/gule/Workspace/team-infrastructure/terraform-provider-flashblade && go build ./internal/client/...</automated> |
| 145 | + </verify> |
| 146 | + <done>All 6 new structs compile. ObjectStoreRemoteCredentials, ObjectStoreRemoteCredentialsPost, ObjectStoreRemoteCredentialsPatch, BucketReplicaLink, BucketReplicaLinkPost, BucketReplicaLinkPatch, ObjectBacklog exist in models_storage.go.</done> |
| 147 | +</task> |
| 148 | + |
| 149 | +<task type="auto"> |
| 150 | + <name>Task 2: Client CRUD methods for remote credentials and bucket replica links</name> |
| 151 | + <files>internal/client/remote_credentials.go, internal/client/bucket_replica_links.go</files> |
| 152 | + <action> |
| 153 | +**Create internal/client/remote_credentials.go** following the pattern from object_store_accounts.go: |
| 154 | + |
| 155 | +- `GetRemoteCredentials(ctx, name string) (*ObjectStoreRemoteCredentials, error)` — GET `/object-store-remote-credentials?names={name}`, return first item or 404. |
| 156 | +- `ListRemoteCredentials(ctx) ([]ObjectStoreRemoteCredentials, error)` — GET with pagination (continuation_token loop). |
| 157 | +- `PostRemoteCredentials(ctx, name string, remoteName string, body ObjectStoreRemoteCredentialsPost) (*ObjectStoreRemoteCredentials, error)` — POST `/object-store-remote-credentials?names={name}&remote_names={remoteName}`, body contains access_key_id + secret_access_key. |
| 158 | +- `PatchRemoteCredentials(ctx, name string, body ObjectStoreRemoteCredentialsPatch) (*ObjectStoreRemoteCredentials, error)` — PATCH `/object-store-remote-credentials?names={name}`. |
| 159 | +- `DeleteRemoteCredentials(ctx, name string) error` — DELETE `/object-store-remote-credentials?names={name}`. |
| 160 | +
|
| 161 | +**Create internal/client/bucket_replica_links.go** — this resource uses MULTIPLE query params for identification (not just names=): |
| 162 | +
|
| 163 | +- `GetBucketReplicaLink(ctx, localBucketName string, remoteBucketName string) (*BucketReplicaLink, error)` — GET `/bucket-replica-links?local_bucket_names={local}&remote_bucket_names={remote}`, return first item or 404. |
| 164 | +- `ListBucketReplicaLinks(ctx) ([]BucketReplicaLink, error)` — GET with pagination. |
| 165 | +- `PostBucketReplicaLink(ctx, localBucketName string, remoteBucketName string, remoteCredentialsName string, body BucketReplicaLinkPost) (*BucketReplicaLink, error)` — POST `/bucket-replica-links?local_bucket_names={local}&remote_bucket_names={remote}&remote_credentials_names={cred}`. If remoteCredentialsName is empty, omit that param (FB-to-FB case uses remote_names= derived from array connection, but for now only remote_credentials_names is supported). |
| 166 | +- `PatchBucketReplicaLink(ctx, id string, body BucketReplicaLinkPatch) (*BucketReplicaLink, error)` — PATCH `/bucket-replica-links?ids={id}`. Use ID for PATCH stability (same pattern as PatchBucket). |
| 167 | +- `DeleteBucketReplicaLink(ctx, localBucketName string, remoteBucketName string) error` — DELETE `/bucket-replica-links?local_bucket_names={local}&remote_bucket_names={remote}`. |
| 168 | +
|
| 169 | +All methods use `url.QueryEscape` for parameter values. All POST/PATCH return the first item from the ListResponse wrapper. |
| 170 | + </action> |
| 171 | + <verify> |
| 172 | + <automated>cd /home/gule/Workspace/team-infrastructure/terraform-provider-flashblade && go build ./internal/client/...</automated> |
| 173 | + </verify> |
| 174 | + <done>Both client files compile. GetRemoteCredentials, PostRemoteCredentials, PatchRemoteCredentials, DeleteRemoteCredentials, GetBucketReplicaLink, PostBucketReplicaLink, PatchBucketReplicaLink, DeleteBucketReplicaLink are all callable from the client package.</done> |
| 175 | +</task> |
| 176 | +
|
| 177 | +</tasks> |
| 178 | +
|
| 179 | +<verification> |
| 180 | +```bash |
| 181 | +cd /home/gule/Workspace/team-infrastructure/terraform-provider-flashblade && go build ./... && go vet ./internal/client/... |
| 182 | +``` |
| 183 | +</verification> |
| 184 | + |
| 185 | +<success_criteria> |
| 186 | +- models_storage.go contains 7 new structs (ObjectStoreRemoteCredentials, Post, Patch, BucketReplicaLink, Post, Patch, ObjectBacklog) |
| 187 | +- remote_credentials.go has Get/List/Post/Patch/Delete methods |
| 188 | +- bucket_replica_links.go has Get/List/Post/Patch/Delete methods |
| 189 | +- `go build ./...` passes with no errors |
| 190 | +</success_criteria> |
| 191 | + |
| 192 | +<output> |
| 193 | +After completion, create `.planning/phases/15-replication-resources/15-01-SUMMARY.md` |
| 194 | +</output> |
0 commit comments