Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/openstatusHQ/cli. OpenStatus CLI www.openstatus.dev
Something went wrong. Try again.
Go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229package monitors
import ( "context" "errors" "fmt" "os"
"github.com/google/go-cmp/cmp" "github.com/urfave/cli/v3" "sigs.k8s.io/yaml"
"github.com/openstatusHQ/cli/internal/api" "github.com/openstatusHQ/cli/internal/auth" output "github.com/openstatusHQ/cli/internal/cli" "github.com/openstatusHQ/cli/internal/config")
// countChanges computes the number of creates, updates, and deletes without making API calls.func countChanges(lock config.MonitorsLock, configData config.Monitors) (created, updated, deleted int) { for v, configValue := range configData { value, exist := lock[v] if !exist { created++ } else if !cmp.Equal(configValue, value.Monitor) { updated++ } } for v := range lock { if _, exist := configData[v]; !exist { deleted++ } } return}
// ApplyChanges applies the changes between the lock file and the config data, making API calls.// It works on a copy of the lock map so that partial failures leave the caller's map untouched.func ApplyChanges(ctx context.Context, apiKey string, lock config.MonitorsLock, configData config.Monitors) (config.MonitorsLock, error) { working := make(config.MonitorsLock, len(lock)) for k, v := range lock { working[k] = v }
var created, updated, deleted int
for v, configValue := range configData { value, exist := working[v] if !exist { result, err := CreateMonitor(ctx, api.DefaultHTTPClient, apiKey, configValue) if err != nil { return nil, err } working[v] = config.Lock{ ID: result.ID, Monitor: configValue, } created++ continue } if !cmp.Equal(configValue, value.Monitor) { result, err := UpdateMonitor(ctx, api.DefaultHTTPClient, apiKey, value.ID, configValue) if err != nil { return nil, err } working[v] = config.Lock{ ID: result.ID, Monitor: configValue, } updated++ } }
var toDelete []string for v := range working { if _, exist := configData[v]; !exist { toDelete = append(toDelete, v) } } for _, v := range toDelete { err := DeleteMonitorWithHTTPClient(ctx, api.DefaultHTTPClient, apiKey, fmt.Sprintf("%d", working[v].ID)) if err != nil { return nil, fmt.Errorf("failed to delete monitor %d: %w", working[v].ID, err) } delete(working, v) deleted++ }
if created == 0 && updated == 0 && deleted == 0 { return nil, nil }
fmt.Println("Changes applied successfully") if created > 0 { fmt.Println(" Created:", created) } if updated > 0 { fmt.Println(" Updated:", updated) } if deleted > 0 { fmt.Println(" Deleted:", deleted) } return working, nil}
func GetMonitorsApplyCmd() *cli.Command { monitorsApplyCmd := cli.Command{ Name: "apply", Usage: "Create or update monitors", Description: `Creates or updates monitors according to the OpenStatus configuration file.Compares your openstatus.yaml with the current state and applies changes.`, UsageText: `openstatus monitors apply openstatus monitors apply --config custom.yaml -y openstatus monitors apply --dry-run`, Flags: []cli.Flag{ &cli.StringFlag{ Name: "config", Usage: "The configuration file containing monitor information", Aliases: []string{"c"}, DefaultText: "openstatus.yaml", Value: "openstatus.yaml", }, &cli.StringFlag{ Name: "access-token", Usage: "OpenStatus API Access Token", Aliases: []string{"t"}, Sources: cli.EnvVars("OPENSTATUS_API_TOKEN"), }, &cli.BoolFlag{ Name: "auto-accept", Usage: "Automatically accept the prompt", Aliases: []string{"y"}, Required: false, }, &cli.BoolFlag{ Name: "dry-run", Usage: "Show what would be changed without applying", Aliases: []string{"n"}, }, }, Action: func(ctx context.Context, cmd *cli.Command) error { apiKey, err := auth.ResolveAccessToken(cmd) if err != nil { return cli.Exit(err.Error(), 1) }
path := cmd.String("config")
if path != "" { if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) { return cli.Exit("Config does not exist", 1) } }
monitors, err := config.ReadOpenStatus(path) if err != nil { return cli.Exit("Unable to read config file", 1) }
lock, err := config.ReadLockFile("openstatus.lock") if err != nil { return cli.Exit("Unable to read lock file", 1) }
created, updated, deleted := countChanges(lock, monitors) if created == 0 && updated == 0 && deleted == 0 { fmt.Println("No changes found") return nil }
fmt.Println("This will apply the following changes:") if created > 0 { fmt.Println(" Create:", created) } if updated > 0 { fmt.Println(" Update:", updated) } if deleted > 0 { fmt.Println(" Delete:", deleted) }
if cmd.Bool("dry-run") { return nil }
if !cmd.Bool("auto-accept") { confirmed, err := output.AskForConfirmation("Do you want to continue?") if err != nil { return cli.Exit(fmt.Sprintf("Failed to read input: %v", err), 1) } if !confirmed { return nil } }
s := output.StartSpinner("Applying changes...") newLock, err := ApplyChanges(ctx, apiKey, lock, monitors) output.StopSpinner(s) if err != nil { return cli.Exit(fmt.Sprintf("Failed to apply changes: %v", err), 1) } if newLock == nil { fmt.Println("No changes found") return nil } y, err := yaml.Marshal(&newLock) if err != nil { return cli.Exit("Failed to marshal lock file", 1) } file, err := os.OpenFile("openstatus.lock", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600) if err != nil { return cli.Exit("Failed to open lock file", 1) } defer file.Close()
_, err = file.Write(y) if err != nil { return cli.Exit("Failed to write lock file", 1) } if err := file.Sync(); err != nil { return cli.Exit("Failed to sync lock file", 1) } fmt.Println("\nRun 'openstatus monitors list' to see your monitors") return nil }, } return &monitorsApplyCmd}