Skip to content

Commit 3b8e48b

Browse files
committed
Adding the wait_for_sync support for basic and ecr
1 parent 6dd8512 commit 3b8e48b

File tree

1 file changed

+186
-47
lines changed

1 file changed

+186
-47
lines changed

spectrocloud/resource_registry_oci_ecr.go

Lines changed: 186 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,14 @@ func resourceRegistryEcrCreate(ctx context.Context, d *schema.ResourceData, m in
221221
return diag.FromErr(err)
222222
}
223223
d.SetId(uid)
224+
225+
// Wait for sync if requested and provider_type is helm (ECR supports helm only for wait_for_sync)
226+
if providerType == "helm" && d.Get("wait_for_sync") != nil && d.Get("wait_for_sync").(bool) {
227+
diags, isError := waitForOCIRegistrySyncAndSetStatus(ctx, d, uid, diags, c, schema.TimeoutCreate, "ecr")
228+
if isError {
229+
return diags
230+
}
231+
}
224232
case "basic":
225233
registry := toRegistryBasic(d)
226234
if err := validateRegistryCred(c, registryType, providerType, isSync, registry.Spec, nil); err != nil {
@@ -235,29 +243,12 @@ func resourceRegistryEcrCreate(ctx context.Context, d *schema.ResourceData, m in
235243

236244
// Wait for sync if requested and provider_type is zarf or helm
237245
if (providerType == "zarf" || providerType == "helm") && d.Get("wait_for_sync") != nil && d.Get("wait_for_sync").(bool) {
238-
diagnostics, isError := waitForOciRegistrySync(ctx, d, uid, diags, c, schema.TimeoutCreate)
239-
if len(diagnostics) > 0 {
240-
diags = append(diags, diagnostics...)
241-
}
242-
// Fetch final sync status and set wait_for_status_message
243-
syncStatus, statusErr := c.GetOciBasicRegistrySyncStatus(uid)
244-
if statusErr == nil && syncStatus != nil {
245-
statusMessage := ""
246-
if syncStatus.Message != "" {
247-
statusMessage = syncStatus.Message
248-
} else if syncStatus.Status != "" {
249-
statusMessage = fmt.Sprintf("Status: %s", syncStatus.Status)
250-
}
251-
if err := d.Set("wait_for_status_message", statusMessage); err != nil {
252-
diags = append(diags, diag.FromErr(err)...)
253-
}
254-
}
246+
diags, isError := waitForOCIRegistrySyncAndSetStatus(ctx, d, uid, diags, c, schema.TimeoutCreate, "basic")
255247
if isError {
256-
return diagnostics
248+
return diags
257249
}
258250
}
259251
}
260-
261252
return diags
262253
}
263254

@@ -298,9 +289,6 @@ func resourceRegistryEcrRead(ctx context.Context, d *schema.ResourceData, m inte
298289
return diag.FromErr(err)
299290
}
300291

301-
if err := d.Set("wait_for_sync", false); err != nil {
302-
return diag.FromErr(err)
303-
}
304292
credentials := make([]interface{}, 0, 1)
305293
acc := make(map[string]interface{})
306294
switch *registry.Spec.Credentials.CredentialType {
@@ -311,17 +299,29 @@ func resourceRegistryEcrRead(ctx context.Context, d *schema.ResourceData, m inte
311299
case models.V1AwsCloudAccountCredentialTypeSecret:
312300
acc["access_key"] = registry.Spec.Credentials.AccessKey
313301
acc["credential_type"] = models.V1AwsCloudAccountCredentialTypeSecret
302+
// Preserve secret_key from state to avoid drift when API does not return it
303+
if currentCredsRaw := d.Get("credentials"); currentCredsRaw != nil {
304+
if currentCredsList, ok := currentCredsRaw.([]interface{}); ok && len(currentCredsList) > 0 {
305+
if currentCredMap, ok := currentCredsList[0].(map[string]interface{}); ok {
306+
if secretKey, exists := currentCredMap["secret_key"]; exists && secretKey != nil {
307+
acc["secret_key"] = secretKey
308+
}
309+
}
310+
}
311+
}
314312
default:
315313
errMsg := fmt.Sprintf("Registry type %s not implemented.", *registry.Spec.Credentials.CredentialType)
316314
err = errors.New(errMsg)
317315
return diag.FromErr(err)
318316
}
319317
// tls configuration handling
320318
tlsConfig := make([]interface{}, 0, 1)
321-
tls := make(map[string]interface{})
322-
tls["certificate"] = registry.Spec.TLS.Certificate
323-
tls["insecure_skip_verify"] = registry.Spec.TLS.InsecureSkipVerify
324-
tlsConfig = append(tlsConfig, tls)
319+
if registry.Spec.TLS != nil {
320+
tls := make(map[string]interface{})
321+
tls["certificate"] = registry.Spec.TLS.Certificate
322+
tls["insecure_skip_verify"] = registry.Spec.TLS.InsecureSkipVerify
323+
tlsConfig = append(tlsConfig, tls)
324+
}
325325
acc["tls_config"] = tlsConfig
326326
credentials = append(credentials, acc)
327327

@@ -367,9 +367,6 @@ func resourceRegistryEcrRead(ctx context.Context, d *schema.ResourceData, m inte
367367
return diag.FromErr(err)
368368
}
369369

370-
if err := d.Set("wait_for_sync", false); err != nil {
371-
return diag.FromErr(err)
372-
}
373370
credentials := make([]interface{}, 0, 1)
374371
acc := make(map[string]interface{})
375372
// Read the actual auth type from the API response
@@ -445,6 +442,14 @@ func resourceRegistryEcrUpdate(ctx context.Context, d *schema.ResourceData, m in
445442
if err != nil {
446443
return diag.FromErr(err)
447444
}
445+
446+
// Wait for sync if requested and provider_type is helm
447+
if providerType == "helm" && d.Get("wait_for_sync") != nil && d.Get("wait_for_sync").(bool) {
448+
diags, isError := waitForOCIRegistrySyncAndSetStatus(ctx, d, d.Id(), diags, c, schema.TimeoutUpdate, "ecr")
449+
if isError {
450+
return diags
451+
}
452+
}
448453
case "basic":
449454
registry := toRegistryBasic(d)
450455
if err := validateRegistryCred(c, registryType, providerType, isSync, registry.Spec, nil); err != nil {
@@ -457,25 +462,9 @@ func resourceRegistryEcrUpdate(ctx context.Context, d *schema.ResourceData, m in
457462

458463
// Wait for sync if requested and provider_type is zarf or helm
459464
if (providerType == "zarf" || providerType == "helm") && d.Get("wait_for_sync") != nil && d.Get("wait_for_sync").(bool) {
460-
diagnostics, isError := waitForOciRegistrySync(ctx, d, d.Id(), diags, c, schema.TimeoutUpdate)
461-
if len(diagnostics) > 0 {
462-
diags = append(diags, diagnostics...)
463-
}
464-
// Fetch final sync status and set wait_for_status_message
465-
syncStatus, statusErr := c.GetOciBasicRegistrySyncStatus(d.Id())
466-
if statusErr == nil && syncStatus != nil {
467-
statusMessage := ""
468-
if syncStatus.Message != "" {
469-
statusMessage = syncStatus.Message
470-
} else if syncStatus.Status != "" {
471-
statusMessage = fmt.Sprintf("Status: %s", syncStatus.Status)
472-
}
473-
if err := d.Set("wait_for_status_message", statusMessage); err != nil {
474-
diags = append(diags, diag.FromErr(err)...)
475-
}
476-
}
465+
diags, isError := waitForOCIRegistrySyncAndSetStatus(ctx, d, d.Id(), diags, c, schema.TimeoutUpdate, "basic")
477466
if isError {
478-
return diagnostics
467+
return diags
479468
}
480469
}
481470
}
@@ -607,6 +596,51 @@ func toRegistryAwsAccountCredential(regCred map[string]interface{}) *models.V1Aw
607596
return account
608597
}
609598

599+
// waitForOCIRegistrySyncAndSetStatus runs the appropriate wait-for-sync for the given registry type,
600+
// then sets wait_for_status_message from the API. Returns (combined diagnostics, true if caller should return).
601+
func waitForOCIRegistrySyncAndSetStatus(ctx context.Context, d *schema.ResourceData, uid string, diags diag.Diagnostics, c *client.V1Client, timeoutType string, registryType string) (diag.Diagnostics, bool) {
602+
switch registryType {
603+
case "ecr":
604+
diagnostics, isError := waitForOciEcrRegistrySync(ctx, d, uid, diags, c, timeoutType)
605+
if len(diagnostics) > 0 {
606+
diags = append(diags, diagnostics...)
607+
}
608+
registry, statusErr := c.GetOciEcrRegistry(uid)
609+
if statusErr == nil && registry != nil && registry.Status != nil && registry.Status.SyncStatus != nil {
610+
statusMessage := ""
611+
if registry.Status.SyncStatus.Message != "" {
612+
statusMessage = registry.Status.SyncStatus.Message
613+
} else if registry.Status.SyncStatus.Status != "" {
614+
statusMessage = fmt.Sprintf("Status: %s", registry.Status.SyncStatus.Status)
615+
}
616+
if err := d.Set("wait_for_status_message", statusMessage); err != nil {
617+
diags = append(diags, diag.FromErr(err)...)
618+
}
619+
}
620+
return diags, isError
621+
case "basic":
622+
diagnostics, isError := waitForOciRegistrySync(ctx, d, uid, diags, c, timeoutType)
623+
if len(diagnostics) > 0 {
624+
diags = append(diags, diagnostics...)
625+
}
626+
syncStatus, statusErr := c.GetOciBasicRegistrySyncStatus(uid)
627+
if statusErr == nil && syncStatus != nil {
628+
statusMessage := ""
629+
if syncStatus.Message != "" {
630+
statusMessage = syncStatus.Message
631+
} else if syncStatus.Status != "" {
632+
statusMessage = fmt.Sprintf("Status: %s", syncStatus.Status)
633+
}
634+
if err := d.Set("wait_for_status_message", statusMessage); err != nil {
635+
diags = append(diags, diag.FromErr(err)...)
636+
}
637+
}
638+
return diags, isError
639+
default:
640+
return diags, false
641+
}
642+
}
643+
610644
// waitForOciRegistrySync waits for an OCI registry to complete its synchronization
611645
func waitForOciRegistrySync(ctx context.Context, d *schema.ResourceData, uid string, diags diag.Diagnostics, c *client.V1Client, timeoutType string) (diag.Diagnostics, bool) {
612646
stateConf := &retry.StateChangeConf{
@@ -727,3 +761,108 @@ func resourceOciRegistrySyncRefreshFunc(c *client.V1Client, uid string) retry.St
727761
}
728762
}
729763
}
764+
765+
// waitForOciEcrRegistrySync waits for an OCI ECR registry to complete its synchronization by polling GetOciEcrRegistry.
766+
func waitForOciEcrRegistrySync(ctx context.Context, d *schema.ResourceData, uid string, diags diag.Diagnostics, c *client.V1Client, timeoutType string) (diag.Diagnostics, bool) {
767+
stateConf := &retry.StateChangeConf{
768+
Pending: []string{
769+
"InProgress",
770+
"Pending",
771+
"Unknown",
772+
"",
773+
},
774+
Target: []string{
775+
"Success",
776+
"Completed",
777+
},
778+
Refresh: resourceOciEcrRegistrySyncRefreshFunc(c, uid),
779+
Timeout: d.Timeout(timeoutType) - 1*time.Minute,
780+
MinTimeout: 10 * time.Second,
781+
Delay: 30 * time.Second,
782+
}
783+
784+
_, err := stateConf.WaitForStateContext(ctx)
785+
if err != nil {
786+
var timeoutErr *retry.TimeoutError
787+
if errors.As(err, &timeoutErr) {
788+
currentStatus := timeoutErr.LastState
789+
statusMessage := ""
790+
registry, statusErr := c.GetOciEcrRegistry(uid)
791+
if statusErr == nil && registry != nil && registry.Status != nil && registry.Status.SyncStatus != nil {
792+
if registry.Status.SyncStatus.Status != "" {
793+
currentStatus = registry.Status.SyncStatus.Status
794+
}
795+
if registry.Status.SyncStatus.Message != "" {
796+
statusMessage = fmt.Sprintf(" Message: %s", registry.Status.SyncStatus.Message)
797+
}
798+
}
799+
if currentStatus == "" {
800+
currentStatus = "Unknown"
801+
}
802+
diags = append(diags, diag.Diagnostic{
803+
Severity: diag.Warning,
804+
Summary: "OCI ECR registry sync timeout",
805+
Detail: fmt.Sprintf(
806+
"OCI ECR registry synchronization timed out after waiting for %v. Current sync status is '%s'.%s "+
807+
"The registry sync may still be in progress. You may need to increase the timeout or wait for the sync to complete manually.",
808+
d.Timeout(timeoutType)-1*time.Minute, currentStatus, statusMessage),
809+
})
810+
return diags, false
811+
}
812+
813+
registry, statusErr := c.GetOciEcrRegistry(uid)
814+
if statusErr == nil && registry != nil && registry.Status != nil && registry.Status.SyncStatus != nil {
815+
status := registry.Status.SyncStatus.Status
816+
if status == "Failed" || status == "Error" || status == "failed" || status == "error" {
817+
errorDetail := fmt.Sprintf("OCI ECR registry synchronization failed with status '%s'.", status)
818+
if registry.Status.SyncStatus.Message != "" {
819+
errorDetail += fmt.Sprintf("\n\nError details: %s", registry.Status.SyncStatus.Message)
820+
}
821+
errorDetail += "\n\nPlease check the registry configuration (endpoint, credentials) and try again."
822+
diags = append(diags, diag.Diagnostic{
823+
Severity: diag.Warning,
824+
Summary: "OCI ECR registry sync failed",
825+
Detail: errorDetail,
826+
})
827+
return diags, false
828+
}
829+
}
830+
831+
return diag.FromErr(err), true
832+
}
833+
return nil, false
834+
}
835+
836+
// resourceOciEcrRegistrySyncRefreshFunc returns a retry.StateRefreshFunc that checks the sync status of an OCI ECR registry via GetOciEcrRegistry.
837+
func resourceOciEcrRegistrySyncRefreshFunc(c *client.V1Client, uid string) retry.StateRefreshFunc {
838+
return func() (interface{}, string, error) {
839+
registry, err := c.GetOciEcrRegistry(uid)
840+
if err != nil {
841+
return nil, "", err
842+
}
843+
if registry == nil || registry.Status == nil || registry.Status.SyncStatus == nil {
844+
return nil, "", nil
845+
}
846+
syncStatus := registry.Status.SyncStatus
847+
if !syncStatus.IsSyncSupported {
848+
return syncStatus, "Success", nil
849+
}
850+
status := syncStatus.Status
851+
if status == "" {
852+
return syncStatus, "", nil
853+
}
854+
switch status {
855+
case "Success", "Completed", "success", "completed":
856+
return syncStatus, "Success", nil
857+
case "Failed", "Error", "failed", "error":
858+
if syncStatus.Message != "" {
859+
return syncStatus, status, fmt.Errorf("registry sync failed: %s", syncStatus.Message)
860+
}
861+
return syncStatus, status, fmt.Errorf("registry sync failed")
862+
case "InProgress", "Running", "Syncing", "inprogress", "running", "syncing":
863+
return syncStatus, "InProgress", nil
864+
default:
865+
return syncStatus, status, nil
866+
}
867+
}
868+
}

0 commit comments

Comments
 (0)