Merge pull request #135 from Gmaker689/feat-cloud-dm-delete-instance-sync

Feat cloud dm delete instance sync
This commit is contained in:
2026-07-30 18:30:23 +08:00
committed by GitHub
4 changed files with 120 additions and 0 deletions
+1
View File
@@ -67,6 +67,7 @@ DELIVERY_MYSQL_INSPECT_TIMEOUT_SECONDS=90
DELIVERY_ROLLBACK_TEMPLATE_ID=0 DELIVERY_ROLLBACK_TEMPLATE_ID=0
DELIVERY_SERVICE_TOKEN= DELIVERY_SERVICE_TOKEN=
CLOUDDM_REGISTER_URL= CLOUDDM_REGISTER_URL=
CLOUDDM_DELETE_URL=
CLOUDDM_API_TOKEN= CLOUDDM_API_TOKEN=
JWT_SECRET=change-this-secret JWT_SECRET=change-this-secret
+2
View File
@@ -62,6 +62,7 @@ type Config struct {
CloudDMAdminUsername string CloudDMAdminUsername string
CloudDMAdminPassword string CloudDMAdminPassword string
CloudDMRegisterURL string CloudDMRegisterURL string
CloudDMDeleteURL string
CloudDMAPIToken string CloudDMAPIToken string
AWXBaseURL string AWXBaseURL string
AWXToken string AWXToken string
@@ -147,6 +148,7 @@ func Load() Config {
CloudDMAdminUsername: env("CLOUDDM_ADMIN_USERNAME", ""), CloudDMAdminUsername: env("CLOUDDM_ADMIN_USERNAME", ""),
CloudDMAdminPassword: env("CLOUDDM_ADMIN_PASSWORD", ""), CloudDMAdminPassword: env("CLOUDDM_ADMIN_PASSWORD", ""),
CloudDMRegisterURL: trimURL(env("CLOUDDM_REGISTER_URL", "")), CloudDMRegisterURL: trimURL(env("CLOUDDM_REGISTER_URL", "")),
CloudDMDeleteURL: trimURL(env("CLOUDDM_DELETE_URL", "")),
CloudDMAPIToken: env("CLOUDDM_API_TOKEN", ""), CloudDMAPIToken: env("CLOUDDM_API_TOKEN", ""),
AWXBaseURL: trimURL(env("AWX_BASE_URL", "")), AWXBaseURL: trimURL(env("AWX_BASE_URL", "")),
AWXToken: env("AWX_TOKEN", ""), AWXToken: env("AWX_TOKEN", ""),
+86
View File
@@ -1215,6 +1215,25 @@ func (s *DeliveryService) applyMySQLInspectResults(ctx context.Context, results
Updates(map[string]any{"status": "released", "released_at": now, "updated_at": now}).Error; err != nil { Updates(map[string]any{"status": "released", "released_at": now, "updated_at": now}).Error; err != nil {
return err return err
} }
dataSourceID, deleteStatus := cloudDMDeleteInfo(meta)
if dataSourceID > 0 && deleteStatus != "deleted" {
if err := s.deleteCloudDMDataSource(ctx, current); err != nil {
meta = updateCloudDMDeleteMetadata(meta, "failed", err.Error(), "")
if updateErr := s.db.WithContext(ctx).Model(&model.DeploymentResult{}).
Where("id = ?", result.ID).
Update("metadata", string(mustJSON(meta))).Error; updateErr != nil {
return updateErr
}
return err
}
meta = updateCloudDMDeleteMetadata(meta, "deleted", "", now.Format(time.RFC3339))
if err := s.db.WithContext(ctx).Model(&model.DeploymentResult{}).
Where("id = ?", result.ID).
Update("metadata", string(mustJSON(meta))).Error; err != nil {
return err
}
}
} }
} }
return nil return nil
@@ -2384,6 +2403,12 @@ type cloudDMRegisterRequest struct {
DataSource cloudDMDataSource `json:"dataSource"` DataSource cloudDMDataSource `json:"dataSource"`
} }
type cloudDMDeleteRequest struct {
SourceSystem string `json:"sourceSystem"`
ResourceType string `json:"resourceType"`
ExternalResourceID string `json:"externalResourceId"`
}
type cloudDMDataSource struct { type cloudDMDataSource struct {
InstanceName string `json:"instanceName"` InstanceName string `json:"instanceName"`
InstanceDesc string `json:"instanceDesc"` InstanceDesc string `json:"instanceDesc"`
@@ -2463,6 +2488,36 @@ func cloudDMMetadata(existing map[string]any, instance model.DeploymentResult, d
return meta return meta
} }
func buildCloudDMDeleteRequest(instance model.DeploymentResult) cloudDMDeleteRequest {
return cloudDMDeleteRequest{
SourceSystem: "xinfra",
ResourceType: "MYSQL_INSTANCE",
ExternalResourceID: cloudDMExternalResourceID(instance.ID),
}
}
func cloudDMDeleteInfo(meta map[string]any) (uint64, string) {
clouddm, ok := meta["clouddm"].(map[string]any)
if !ok {
return 0, ""
}
dataSourceID, _ := cloudDMDataSourceIDFromValue(clouddm["data_source_id"])
deleteStatus := strings.ToLower(strings.TrimSpace(stringValue(clouddm["delete_status"])))
return dataSourceID, deleteStatus
}
func updateCloudDMDeleteMetadata(meta map[string]any, deleteStatus, deleteError, deletedAt string) map[string]any {
clouddm, ok := meta["clouddm"].(map[string]any)
if !ok {
clouddm = map[string]any{}
}
clouddm["delete_status"] = deleteStatus
clouddm["delete_error"] = deleteError
clouddm["deleted_at"] = deletedAt
meta["clouddm"] = clouddm
return meta
}
func buildCloudDMRegisterRequest(instance model.DeploymentResult, payload deliveryPayload, password string) cloudDMRegisterRequest { func buildCloudDMRegisterRequest(instance model.DeploymentResult, payload deliveryPayload, password string) cloudDMRegisterRequest {
description := strings.TrimSpace(payload.InstanceDesc) description := strings.TrimSpace(payload.InstanceDesc)
if description == "" { if description == "" {
@@ -2498,6 +2553,37 @@ func buildCloudDMRegisterRequest(instance model.DeploymentResult, payload delive
} }
} }
func (s *DeliveryService) deleteCloudDMDataSource(ctx context.Context, instance model.DeploymentResult) error {
if strings.TrimSpace(s.cfg.CloudDMDeleteURL) == "" {
return fmt.Errorf("CloudDM deletion requires CLOUDDM_DELETE_URL to be configured")
}
if strings.TrimSpace(s.cfg.CloudDMAPIToken) == "" {
return fmt.Errorf("CloudDM deletion requires CLOUDDM_API_TOKEN to be configured")
}
body := buildCloudDMDeleteRequest(instance)
raw, err := json.Marshal(body)
if err != nil {
return fmt.Errorf("encode CloudDM deletion request: %w", err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, s.cfg.CloudDMDeleteURL, bytes.NewReader(raw))
if err != nil {
return err
}
req.Header.Set("Authorization", "Bearer "+strings.TrimSpace(s.cfg.CloudDMAPIToken))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Request-ID", randomUUID())
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
respBody, _ := io.ReadAll(resp.Body)
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return fmt.Errorf("CloudDM deletion returned %s: %s", resp.Status, strings.TrimSpace(string(respBody)))
}
return nil
}
func (s *DeliveryService) RegisterCloudDM(ctx context.Context, taskID string) error { func (s *DeliveryService) RegisterCloudDM(ctx context.Context, taskID string) error {
if strings.TrimSpace(s.cfg.CloudDMRegisterURL) == "" { if strings.TrimSpace(s.cfg.CloudDMRegisterURL) == "" {
return nil return nil
+31
View File
@@ -189,6 +189,37 @@ func TestBuildCloudDMRegisterRequest(t *testing.T) {
} }
} }
func TestBuildCloudDMDeleteRequest(t *testing.T) {
req := buildCloudDMDeleteRequest(model.DeploymentResult{ID: 42})
if req.SourceSystem != "xinfra" || req.ResourceType != "MYSQL_INSTANCE" {
t.Fatalf("unexpected request metadata: %#v", req)
}
if req.ExternalResourceID != "mysql-instance:42" {
t.Fatalf("unexpected externalResourceId: %q", req.ExternalResourceID)
}
}
func TestCloudDMDeleteInfoAndMetadata(t *testing.T) {
meta := map[string]any{
"clouddm": map[string]any{
"data_source_id": 123.0,
"delete_status": "Failed",
"delete_error": "boom",
"deleted_at": "",
"external_resource_id": "mysql-instance:42",
},
}
dataSourceID, deleteStatus := cloudDMDeleteInfo(meta)
if dataSourceID != 123 || deleteStatus != "failed" {
t.Fatalf("unexpected delete info: %d, %q", dataSourceID, deleteStatus)
}
updated := updateCloudDMDeleteMetadata(meta, "deleted", "", "2026-07-30T12:00:00Z")
clouddm := updated["clouddm"].(map[string]any)
if clouddm["delete_status"] != "deleted" || clouddm["delete_error"] != "" || clouddm["deleted_at"] != "2026-07-30T12:00:00Z" {
t.Fatalf("unexpected updated metadata: %#v", clouddm)
}
}
func TestCloudDMDataSourceIDFromResponse(t *testing.T) { func TestCloudDMDataSourceIDFromResponse(t *testing.T) {
tests := []struct { tests := []struct {
name string name string