Merge pull request #135 from Gmaker689/feat-cloud-dm-delete-instance-sync
Feat cloud dm delete instance sync
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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", ""),
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user