Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
9f1eee8
feat: add cleanup command to bootstrap
OliverTrautvetter Feb 3, 2026
f1a09cb
feat: update cleanup command
OliverTrautvetter Feb 10, 2026
539a590
Merge branch 'main' into cleanup_bootstrapped_infra
OliverTrautvetter Feb 10, 2026
382e896
ref: mockery
OliverTrautvetter Feb 10, 2026
9cfbd1e
chore(docs): Auto-update docs and licenses
OliverTrautvetter Feb 10, 2026
f20991b
test: add tests for new cleanup
OliverTrautvetter Feb 10, 2026
5b7220f
Merge branch 'cleanup_bootstrapped_infra' of https://github.com/codes…
OliverTrautvetter Feb 10, 2026
5508d73
ref: enhance cleanup command with dependency injection and error hand…
OliverTrautvetter Feb 10, 2026
4f32c3f
ref: add utility functions for OMS-managed label checks and DNS recor…
OliverTrautvetter Feb 10, 2026
ec12723
ref: enhance GCP cleanup command with project ID handling
OliverTrautvetter Feb 10, 2026
bb57d56
chore(docs): Auto-update docs and licenses
OliverTrautvetter Feb 10, 2026
3e923af
ref: remove redundant file existence check in cleanup command
OliverTrautvetter Feb 10, 2026
f362c2a
fix: error handling in EnableAPIs method for GCPClient
OliverTrautvetter Feb 10, 2026
8f23960
ref: streamline GCP cleanup process with improved infra file handling…
OliverTrautvetter Feb 16, 2026
22df9dd
ref: improve error messages for GCP infra file handling
OliverTrautvetter Feb 16, 2026
e2e1117
Merge branch 'main' into cleanup_bootstrapped_infra
OliverTrautvetter Feb 16, 2026
424cb1e
chore(docs): Auto-update docs and licenses
OliverTrautvetter Feb 16, 2026
af14d9c
fix: improve cleanup to revoke set permissions
OliverTrautvetter Feb 18, 2026
84adde4
feat: enhance GCP cleanup command with DNS settings and error handlin…
OliverTrautvetter Feb 20, 2026
6c12790
Merge remote-tracking branch 'origin/main' into cleanup_bootstrapped_…
OliverTrautvetter Feb 23, 2026
bd05188
fix: merge error
OliverTrautvetter Feb 23, 2026
deef455
chore(docs): Auto-update docs and licenses
OliverTrautvetter Feb 23, 2026
ca4b2c3
ref: consolidate infrastructure file loading into a single function
OliverTrautvetter Feb 23, 2026
9d89e67
Merge branch 'cleanup_bootstrapped_infra' of https://github.com/codes…
OliverTrautvetter Feb 23, 2026
8c95a6c
fix: add DNSProjectID option for GCP cleanup command
OliverTrautvetter Feb 24, 2026
285b82a
chore(docs): Auto-update docs and licenses
OliverTrautvetter Feb 24, 2026
11efdea
Merge branch 'main' into cleanup_bootstrapped_infra
OliverTrautvetter Feb 24, 2026
328063a
fix: typo
OliverTrautvetter Feb 26, 2026
92a9b24
chore(docs): Auto-update docs and licenses
OliverTrautvetter Feb 26, 2026
ae0cca0
Update cli/cmd/bootstrap_gcp_cleanup.go
OliverTrautvetter Feb 26, 2026
94a8952
Update cli/cmd/bootstrap_gcp_cleanup.go
OliverTrautvetter Feb 26, 2026
91018a9
Update internal/bootstrap/gcp/gcp_client_cleanup_test.go
OliverTrautvetter Feb 26, 2026
37b4390
Update cli/cmd/bootstrap_gcp_cleanup_test.go
OliverTrautvetter Feb 26, 2026
8dd311b
fix: cleanup
OliverTrautvetter Feb 26, 2026
a528af5
chore(docs): Auto-update docs and licenses
OliverTrautvetter Feb 26, 2026
d8eff57
ref: simplify code
OliverTrautvetter Mar 4, 2026
f2e7e1b
chore(docs): Auto-update docs and licenses
OliverTrautvetter Mar 4, 2026
06569c1
fix: clean
OliverTrautvetter Mar 4, 2026
c9bccc2
Merge branch 'main' into cleanup_bootstrapped_infra
OliverTrautvetter Mar 4, 2026
e5c59a0
chore(docs): Auto-update docs and licenses
OliverTrautvetter Mar 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cli/cmd/bootstrap_gcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ func AddBootstrapGcpCmd(parent *cobra.Command, opts *GlobalOptions) {

parent.AddCommand(bootstrapGcpCmd.cmd)
AddBootstrapGcpPostconfigCmd(bootstrapGcpCmd.cmd, opts)
AddBootstrapGcpCleanupCmd(bootstrapGcpCmd.cmd, opts)
}

func (c *BootstrapGcpCmd) BootstrapGcp() error {
Expand Down
208 changes: 208 additions & 0 deletions cli/cmd/bootstrap_gcp_cleanup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
// Copyright (c) Codesphere Inc.
// SPDX-License-Identifier: Apache-2.0

package cmd

import (
"bufio"
"encoding/json"
"fmt"
"io"
"log"
"os"
"strings"

csio "github.com/codesphere-cloud/cs-go/pkg/io"
"github.com/codesphere-cloud/oms/internal/bootstrap"
"github.com/codesphere-cloud/oms/internal/bootstrap/gcp"
"github.com/codesphere-cloud/oms/internal/util"
"github.com/spf13/cobra"
)

type BootstrapGcpCleanupCmd struct {
cmd *cobra.Command
Opts *BootstrapGcpCleanupOpts
}

type BootstrapGcpCleanupOpts struct {
*GlobalOptions
ProjectID string
Force bool
SkipDNSCleanup bool
}

type CleanupDeps struct {
GCPClient gcp.GCPClientManager
FileIO util.FileIO
StepLogger *bootstrap.StepLogger
ConfirmReader io.Reader
InfraFilePath string
}

func (c *BootstrapGcpCleanupCmd) RunE(_ *cobra.Command, args []string) error {
ctx := c.cmd.Context()
stlog := bootstrap.NewStepLogger(false)
gcpClient := gcp.NewGCPClient(ctx, stlog, os.Getenv("GOOGLE_APPLICATION_CREDENTIALS"))
fw := util.NewFilesystemWriter()

deps := &CleanupDeps{
GCPClient: gcpClient,
FileIO: fw,
StepLogger: stlog,
ConfirmReader: os.Stdin,
InfraFilePath: gcp.GetInfraFilePath(),
}

return c.ExecuteCleanup(deps)
}

// ExecuteCleanup performs the cleanup operation with the provided dependencies.
func (c *BootstrapGcpCleanupCmd) ExecuteCleanup(deps *CleanupDeps) error {
projectID := c.Opts.ProjectID
var codesphereEnv gcp.CodesphereEnvironment
projectIDFromInfraFile := false

// If no project ID provided, try to load from infra file
if projectID == "" {
if deps.FileIO.Exists(deps.InfraFilePath) {
envFileContent, err := deps.FileIO.ReadFile(deps.InfraFilePath)
if err != nil {
return fmt.Errorf("failed to read gcp infra file: %w", err)
}

err = json.Unmarshal(envFileContent, &codesphereEnv)
if err != nil {
return fmt.Errorf("failed to unmarshal gcp infra file: %w", err)
}
projectID = codesphereEnv.ProjectID
if projectID == "" {
return fmt.Errorf("infra file at %s contains empty project ID", deps.InfraFilePath)
}
projectIDFromInfraFile = true
log.Printf("Using project ID from infra file: %s", projectID)
} else {
return fmt.Errorf("no project ID provided and no infra file found at %s", deps.InfraFilePath)
}
} else if deps.FileIO.Exists(deps.InfraFilePath) {
// Load infra file to check if it matches the provided project ID
envFileContent, err := deps.FileIO.ReadFile(deps.InfraFilePath)
if err != nil {
log.Printf("Warning: failed to read infra file: %v", err)
} else {
err = json.Unmarshal(envFileContent, &codesphereEnv)
if err != nil {
log.Printf("Warning: failed to parse infra file: %v", err)
} else if codesphereEnv.ProjectID != projectID {
log.Printf("Warning: infra file contains project ID '%s' but deleting '%s'; ignoring infra file for DNS cleanup", codesphereEnv.ProjectID, projectID)
codesphereEnv = gcp.CodesphereEnvironment{}
} else {
projectIDFromInfraFile = true
}
}
}

// Verify the project was bootstrapped by OMS (skip if --force is used)
if !c.Opts.Force {
isOMSManaged, err := deps.GCPClient.IsOMSManagedProject(projectID)
if err != nil {
return fmt.Errorf("failed to verify project: %w", err)
}

if !isOMSManaged {
return fmt.Errorf("project %s was not bootstrapped by OMS (missing 'oms-managed' label). Use --force to override this check", projectID)
}
} else {
log.Printf("Skipping OMS-managed verification (--force flag used)")
}

// Confirm deletion unless force flag is set
if !c.Opts.Force {
log.Printf("WARNING: This will permanently delete the GCP project '%s' and all its resources.", projectID)
log.Printf("This action cannot be undone.\n")

log.Println("Type the project ID to confirm deletion: ")
reader := bufio.NewReader(deps.ConfirmReader)
confirmation, err := reader.ReadString('\n')
if err != nil {
return fmt.Errorf("failed to read confirmation: %w", err)
}

confirmation = strings.TrimSpace(confirmation)
if confirmation != projectID {
return fmt.Errorf("confirmation did not match project ID, aborting cleanup")
}
}

// Clean up DNS records if infra file has the necessary information
if !c.Opts.SkipDNSCleanup && codesphereEnv.BaseDomain != "" && codesphereEnv.DNSZoneName != "" {
dnsProjectID := codesphereEnv.DNSProjectID
if dnsProjectID == "" {
dnsProjectID = projectID
}

err := deps.StepLogger.Step("Cleaning up DNS records", func() error {
return deps.GCPClient.DeleteDNSRecordSets(dnsProjectID, codesphereEnv.DNSZoneName, codesphereEnv.BaseDomain)
})
if err != nil {
log.Printf("Warning: failed to clean up DNS records: %v", err)
log.Printf("You may need to manually delete DNS records for %s in project %s", codesphereEnv.BaseDomain, dnsProjectID)
}
} else if !c.Opts.SkipDNSCleanup && codesphereEnv.BaseDomain == "" {
log.Printf("Skipping DNS cleanup: no infrastructure information available (provide infra file or use --skip-dns-cleanup)")
}

// Delete the project
err := deps.StepLogger.Step("Deleting GCP project", func() error {
return deps.GCPClient.DeleteProject(projectID)
})
if err != nil {
return fmt.Errorf("failed to delete project: %w", err)
}

if projectIDFromInfraFile && deps.FileIO.Exists(deps.InfraFilePath) {
err = os.Remove(deps.InfraFilePath)
if err != nil {
log.Printf("Warning: failed to remove local infra file: %v", err)
} else {
log.Printf("Removed local infra file: %s", deps.InfraFilePath)
}
}

log.Println("\nGCP project cleanup completed successfully!")
log.Printf("Project '%s' has been scheduled for deletion.", projectID)
log.Printf("Note: GCP projects are retained for 30 days before permanent deletion. You can restore the project within this period from the GCP Console.")

return nil
}

func AddBootstrapGcpCleanupCmd(bootstrapGcp *cobra.Command, opts *GlobalOptions) {
cleanup := BootstrapGcpCleanupCmd{
cmd: &cobra.Command{
Use: "cleanup",
Short: "Clean up GCP infrastructure created by bootstrap-gcp",
Long: csio.Long(`Deletes a GCP project that was previously created using the bootstrap-gcp command.`),
Example: ` # Clean up using project ID from the local infra file
oms-cli beta bootstrap-gcp cleanup

# Clean up a specific project
oms-cli beta bootstrap-gcp cleanup --project-id my-project-abc123

# Force cleanup without confirmation (skips OMS-managed check)
oms-cli beta bootstrap-gcp cleanup --project-id my-project-abc123 --force

# Skip DNS record cleanup
oms-cli beta bootstrap-gcp cleanup --skip-dns-cleanup`,
},
Opts: &BootstrapGcpCleanupOpts{
GlobalOptions: opts,
},
}

flags := cleanup.cmd.Flags()
flags.StringVar(&cleanup.Opts.ProjectID, "project-id", "", "GCP Project ID to delete (optional, will use infra file if not provided)")
flags.BoolVar(&cleanup.Opts.Force, "force", false, "Skip confirmation prompt and OMS-managed check")
flags.BoolVar(&cleanup.Opts.SkipDNSCleanup, "skip-dns-cleanup", false, "Skip cleaning up DNS records")

cleanup.cmd.RunE = cleanup.RunE
bootstrapGcp.AddCommand(cleanup.cmd)
}
Loading
Loading