mirror of
https://github.com/Dvorinka/Containr.git
synced 2026-10-29 00:01:22 +00:00
feat(cli): v2 — full API coverage, profiles, --json/--yes, containr up
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]>
This commit is contained in:
1 parent
17e429b86b
commit
e51b20b2d4
26 files changed
+2798
-350
No files matched your search
@@ -0,0 +1,361 @@
|
||||
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)
|
||||
}
|
||||
Reference in new issue
Block a user