mirror of
https://github.com/Dvorinka/Containr.git
synced 2026-10-28 07:41:27 +00:00
Rewrite the CLI as a thin client over the authenticated API so agents and scripts reach every resource the UI does (ROADMAP2 phase A2). - shared Client with bearer auth, error-envelope decoding, and stable exit codes: 0 ok / 1 error / 2 auth / 3 not-found / 4 validation / 5 unreachable - named profiles in ~/.containr.yaml (current_profile + profiles.*); resolution order --flag > env > profile > localhost default; CONTAINR_TOKEN / CONTAINR_API_URL / CONTAINR_PROFILE env support - --json prints raw API responses; --yes skips confirmations; destructive commands refuse non-interactively without it - auth login verifies the token against /user/profile before saving, interactive prompt via term.ReadPassword - commands for every resource: tokens, projects, services, deploy, deployments (logs/rollback), logs --follow (SSE), exec, variables (set/unset merge with secret masking preserved), databases (backup/restore/download), cron, templates, nodes + agent tokens, scaling, ha, security, gateway, admin, notifications - containr up: git remote + branch → create-or-reuse service → deploy Verified live: login/status/tokens/projects/services, up creating a service + deployment, non-TTY confirmation refusal, scope-capped admin rejection (exit 2), revoke. Authored By: TDvorak <[email protected]>
362 lines
8.4 KiB
Go
362 lines
8.4 KiB
Go
package commands
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
// AdminCmd groups platform-admin operations.
|
|
var AdminCmd = &cobra.Command{
|
|
Use: "admin",
|
|
Short: "Platform administration (admin scope required)",
|
|
}
|
|
|
|
var adminOverviewCmd = &cobra.Command{
|
|
Use: "overview",
|
|
Short: "Platform overview",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
c, err := client()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
data, err := c.Do("GET", "/admin/overview", nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
PrintRaw(data)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var adminUsersCmd = &cobra.Command{
|
|
Use: "users",
|
|
Short: "List all users",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
c, err := client()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
data, err := c.Do("GET", "/admin/users", nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
items, err := unwrapList(data, "users")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
rows := make([][]string, 0, len(items))
|
|
for _, u := range items {
|
|
rows = append(rows, []string{
|
|
str(u, "id"), str(u, "email"), str(u, "name"), str(u, "is_admin"), relTime(u, "created_at"),
|
|
})
|
|
}
|
|
printRows(data, []string{"ID", "EMAIL", "NAME", "ADMIN", "CREATED"}, rows)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var adminSettingsCmd = &cobra.Command{
|
|
Use: "settings",
|
|
Short: "Show platform settings",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
c, err := client()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
data, err := c.Do("GET", "/settings", nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
PrintRaw(data)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var adminAuditCmd = &cobra.Command{
|
|
Use: "audit-logs",
|
|
Short: "List audit log entries",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
c, err := client()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
data, err := c.Do("GET", "/audit-logs", nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
PrintRaw(data)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
// GatewayCmd manages the API gateway (admin).
|
|
var GatewayCmd = &cobra.Command{
|
|
Use: "gateway",
|
|
Short: "Manage the API gateway (admin)",
|
|
}
|
|
|
|
var gatewayServicesCmd = &cobra.Command{
|
|
Use: "services",
|
|
Short: "List gateway services",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
c, err := client()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
data, err := c.Do("GET", "/gateway/services", nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
PrintRaw(data)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var gatewayKeysCmd = &cobra.Command{
|
|
Use: "keys",
|
|
Short: "List gateway API keys",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
c, err := client()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
data, err := c.Do("GET", "/gateway/keys", nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
PrintRaw(data)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var gatewayAnalyticsCmd = &cobra.Command{
|
|
Use: "analytics",
|
|
Short: "Gateway traffic analytics",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
c, err := client()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
kind, _ := cmd.Flags().GetString("type")
|
|
data, err := c.Do("GET", "/gateway/analytics/"+kind, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
PrintRaw(data)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
// HACmd manages high availability.
|
|
var HACmd = &cobra.Command{
|
|
Use: "ha",
|
|
Short: "High availability controls",
|
|
}
|
|
|
|
var haStatusCmd = &cobra.Command{
|
|
Use: "status",
|
|
Short: "Show HA status",
|
|
RunE: simpleGet("/ha/status"),
|
|
}
|
|
|
|
var haAlertsCmd = &cobra.Command{
|
|
Use: "alerts",
|
|
Short: "List active HA alerts",
|
|
RunE: simpleGet("/ha/alerts/active"),
|
|
}
|
|
|
|
var haHealthCmd = &cobra.Command{
|
|
Use: "health",
|
|
Short: "List health check results",
|
|
RunE: simpleGet("/ha/health/results"),
|
|
}
|
|
|
|
var haPoliciesCmd = &cobra.Command{
|
|
Use: "policies",
|
|
Short: "List failover policies",
|
|
RunE: simpleGet("/ha/failover/policies"),
|
|
}
|
|
|
|
var haEnableCmd = &cobra.Command{
|
|
Use: "enable",
|
|
Short: "Enable the HA manager",
|
|
RunE: simplePost("/ha/enable"),
|
|
}
|
|
|
|
var haDisableCmd = &cobra.Command{
|
|
Use: "disable",
|
|
Short: "Disable the HA manager",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if !Confirm("Disable HA? Services will no longer be failed over.") {
|
|
return &APIError{Message: "aborted (pass --yes to skip confirmation)", ExitCode: ExitError}
|
|
}
|
|
return simplePost("/ha/disable")(cmd, args)
|
|
},
|
|
}
|
|
|
|
var haFailoverCmd = &cobra.Command{
|
|
Use: "failover",
|
|
Short: "Trigger a manual failover",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if !Confirm("Trigger a platform failover now? Services may be rescheduled.") {
|
|
return &APIError{Message: "aborted (pass --yes to skip confirmation)", ExitCode: ExitError}
|
|
}
|
|
return simplePost("/ha/failover")(cmd, args)
|
|
},
|
|
}
|
|
|
|
// ScalingCmd manages autoscaling.
|
|
var ScalingCmd = &cobra.Command{
|
|
Use: "scaling",
|
|
Short: "Autoscaling controls",
|
|
}
|
|
|
|
var scalingStatusCmd = &cobra.Command{
|
|
Use: "status",
|
|
Short: "Show autoscaler status",
|
|
RunE: simpleGet("/scaling/status"),
|
|
}
|
|
|
|
var scalingPoliciesCmd = &cobra.Command{
|
|
Use: "policies",
|
|
Short: "List scaling policies",
|
|
RunE: simpleGet("/scaling/policies"),
|
|
}
|
|
|
|
var scalingServicesCmd = &cobra.Command{
|
|
Use: "services",
|
|
Short: "Show per-service scaling state",
|
|
RunE: simpleGet("/scaling/services"),
|
|
}
|
|
|
|
var scalingScaleCmd = &cobra.Command{
|
|
Use: "scale <service-id> <replicas>",
|
|
Short: "Manually scale a service",
|
|
Args: cobra.ExactArgs(2),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
c, err := client()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var replicas int
|
|
fmt.Sscanf(args[1], "%d", &replicas)
|
|
data, err := c.Do("POST", "/scaling/services/"+args[0]+"/scale", map[string]int{"replicas": replicas})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
PrintRaw(data)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
// SecurityCmd manages security scans and findings.
|
|
var SecurityCmd = &cobra.Command{
|
|
Use: "security",
|
|
Short: "Security scans and vulnerabilities",
|
|
}
|
|
|
|
var securityScanCmd = &cobra.Command{
|
|
Use: "scan <project-id>",
|
|
Short: "Run a security scan",
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
c, err := client()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
scanType, _ := cmd.Flags().GetString("type")
|
|
serviceID, _ := cmd.Flags().GetString("service")
|
|
body := map[string]interface{}{"project_id": args[0], "scan_type": scanType}
|
|
if serviceID != "" {
|
|
body["service_id"] = serviceID
|
|
}
|
|
data, err := c.Do("POST", "/security/scans", body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
PrintRaw(data)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var securityVulnsCmd = &cobra.Command{
|
|
Use: "vulnerabilities <project-id>",
|
|
Short: "List a project's vulnerabilities",
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
c, err := client()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
data, err := c.Do("GET", "/projects/"+args[0]+"/vulnerabilities", nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
PrintRaw(data)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var securityMetricsCmd = &cobra.Command{
|
|
Use: "metrics <project-id>",
|
|
Short: "Show a project's security metrics",
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
c, err := client()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
data, err := c.Do("GET", "/projects/"+args[0]+"/security/metrics", nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
PrintRaw(data)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
// simpleGet/simplePost cover the many read/trigger endpoints whose full
|
|
// JSON response is the useful output.
|
|
func simpleGet(path string) func(*cobra.Command, []string) error {
|
|
return func(cmd *cobra.Command, args []string) error {
|
|
c, err := client()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
data, err := c.Do("GET", path, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
PrintRaw(data)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func simplePost(path string) func(*cobra.Command, []string) error {
|
|
return func(cmd *cobra.Command, args []string) error {
|
|
c, err := client()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
data, err := c.Do("POST", path, map[string]interface{}{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
PrintRaw(data)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func init() {
|
|
gatewayAnalyticsCmd.Flags().String("type", "traffic", "traffic|ops")
|
|
securityScanCmd.Flags().String("type", "configuration", "dependency|configuration|comprehensive")
|
|
securityScanCmd.Flags().String("service", "", "limit scan to one service")
|
|
|
|
AdminCmd.AddCommand(adminOverviewCmd, adminUsersCmd, adminSettingsCmd, adminAuditCmd)
|
|
GatewayCmd.AddCommand(gatewayServicesCmd, gatewayKeysCmd, gatewayAnalyticsCmd)
|
|
HACmd.AddCommand(haStatusCmd, haAlertsCmd, haHealthCmd, haPoliciesCmd, haEnableCmd, haDisableCmd, haFailoverCmd)
|
|
ScalingCmd.AddCommand(scalingStatusCmd, scalingPoliciesCmd, scalingServicesCmd, scalingScaleCmd)
|
|
SecurityCmd.AddCommand(securityScanCmd, securityVulnsCmd, securityMetricsCmd)
|
|
}
|