diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index fa1bff49d6..4480cba12b 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -2,6 +2,7 @@ blank_issues_enabled: false
contact_links:
- name: Track the Most Upvoted Features
url: https://upvotes.getarcane.app/
+ about: Track the Most Upvoted Features
- name: 💬 Discord
url: https://discord.gg/WyXYpdyV3Z
about: For help and chatting with the community
diff --git a/backend/cli/generate/api_key_test.go b/backend/cli/generate/api_key_test.go
index 85473c8903..62897f7d3e 100644
--- a/backend/cli/generate/api_key_test.go
+++ b/backend/cli/generate/api_key_test.go
@@ -6,7 +6,7 @@ import (
)
func TestAPIKeyCommandAvailableInBackendCLI(t *testing.T) {
- cmd := GenerateCmd
+ cmd := Cmd
cmd.SetArgs([]string{"api-key"})
out, err := captureOutput(func() error {
diff --git a/backend/cli/generate/generate.go b/backend/cli/generate/generate.go
index 95371b9c72..b59d244465 100644
--- a/backend/cli/generate/generate.go
+++ b/backend/cli/generate/generate.go
@@ -5,4 +5,4 @@ import (
"github.com/spf13/cobra"
)
-var GenerateCmd *cobra.Command = cligenerate.GenerateCmd
+var Cmd *cobra.Command = cligenerate.Cmd
diff --git a/backend/cli/generate/secret_test.go b/backend/cli/generate/secret_test.go
index a00e57d768..31f0f576bf 100644
--- a/backend/cli/generate/secret_test.go
+++ b/backend/cli/generate/secret_test.go
@@ -34,7 +34,7 @@ func captureOutput(fn func() error) (string, error) {
}
func TestSecretDefaultBase64(t *testing.T) {
- cmd := GenerateCmd
+ cmd := Cmd
cmd.SetArgs([]string{"secret"})
out, err := captureOutput(func() error {
@@ -81,7 +81,7 @@ func TestSecretDefaultBase64(t *testing.T) {
}
func TestSecretAllFormatContainsSections(t *testing.T) {
- cmd := GenerateCmd
+ cmd := Cmd
cmd.SetArgs([]string{"secret", "-f", "all"})
out, err := captureOutput(func() error {
diff --git a/backend/cli/root.go b/backend/cli/root.go
index ba7690ceb6..59204e6e0c 100644
--- a/backend/cli/root.go
+++ b/backend/cli/root.go
@@ -39,7 +39,7 @@ func Execute() {
func init() {
rootCmd.AddCommand(upgrade.UpgradeCmd)
- rootCmd.AddCommand(generate.GenerateCmd)
+ rootCmd.AddCommand(generate.Cmd)
}
func getVersion() string {
diff --git a/backend/internal/api/diagnostics_handler.go b/backend/internal/api/diagnostics_handler.go
index a2348b71be..aaa829460f 100644
--- a/backend/internal/api/diagnostics_handler.go
+++ b/backend/internal/api/diagnostics_handler.go
@@ -6,7 +6,7 @@ import (
"time"
"github.com/getarcaneapp/arcane/backend/internal/middleware"
- ws "github.com/getarcaneapp/arcane/backend/pkg/libarcane/ws"
+ "github.com/getarcaneapp/arcane/backend/pkg/libarcane/ws"
"github.com/gin-gonic/gin"
)
diff --git a/backend/internal/bootstrap/jobs_bootstrap.go b/backend/internal/bootstrap/jobs_bootstrap.go
index e76d544e65..67adac3c17 100644
--- a/backend/internal/bootstrap/jobs_bootstrap.go
+++ b/backend/internal/bootstrap/jobs_bootstrap.go
@@ -8,47 +8,47 @@ import (
"github.com/getarcaneapp/arcane/backend/internal/config"
"github.com/getarcaneapp/arcane/backend/pkg/libarcane"
- pkg_scheduler "github.com/getarcaneapp/arcane/backend/pkg/scheduler"
+ "github.com/getarcaneapp/arcane/backend/pkg/scheduler"
)
-func registerJobs(appCtx context.Context, newScheduler *pkg_scheduler.JobScheduler, appServices *Services, appConfig *config.Config) {
- autoUpdateJob := pkg_scheduler.NewAutoUpdateJob(appServices.Updater, appServices.Settings)
+func registerJobs(appCtx context.Context, newScheduler *scheduler.JobScheduler, appServices *Services, appConfig *config.Config) {
+ autoUpdateJob := scheduler.NewAutoUpdateJob(appServices.Updater, appServices.Settings)
newScheduler.RegisterJob(autoUpdateJob)
- imagePollingJob := pkg_scheduler.NewImagePollingJob(appServices.ImageUpdate, appServices.Settings, appServices.Environment)
+ imagePollingJob := scheduler.NewImagePollingJob(appServices.ImageUpdate, appServices.Settings, appServices.Environment)
newScheduler.RegisterJob(imagePollingJob)
- environmentHealthJob := pkg_scheduler.NewEnvironmentHealthJob(appServices.Environment, appServices.Settings)
+ environmentHealthJob := scheduler.NewEnvironmentHealthJob(appServices.Environment, appServices.Settings)
if !appConfig.AgentMode {
newScheduler.RegisterJob(environmentHealthJob)
}
- dockerClientRefreshJob := pkg_scheduler.NewDockerClientRefreshJob(appServices.Docker, appServices.Settings)
+ dockerClientRefreshJob := scheduler.NewDockerClientRefreshJob(appServices.Docker, appServices.Settings)
newScheduler.RegisterJob(dockerClientRefreshJob)
- analyticsJob := pkg_scheduler.NewAnalyticsJob(appServices.Settings, appServices.KV, nil, appConfig)
+ analyticsJob := scheduler.NewAnalyticsJob(appServices.Settings, appServices.KV, nil, appConfig)
newScheduler.RegisterJob(analyticsJob)
// Send initial heartbeat on startup without blocking bootstrap.
go analyticsJob.Run(appCtx)
- eventCleanupJob := pkg_scheduler.NewEventCleanupJob(appServices.Event, appServices.Settings)
+ eventCleanupJob := scheduler.NewEventCleanupJob(appServices.Event, appServices.Settings)
newScheduler.RegisterJob(eventCleanupJob)
- scheduledPruneJob := pkg_scheduler.NewScheduledPruneJob(appServices.System, appServices.Settings, appServices.Notification)
+ scheduledPruneJob := scheduler.NewScheduledPruneJob(appServices.System, appServices.Settings, appServices.Notification)
newScheduler.RegisterJob(scheduledPruneJob)
- fsWatcherJob, err := pkg_scheduler.RegisterFilesystemWatcherJob(appCtx, appServices.Project, appServices.Template, appServices.Settings, appConfig.ProjectScanMaxDepth)
+ fsWatcherJob, err := scheduler.RegisterFilesystemWatcherJob(appCtx, appServices.Project, appServices.Template, appServices.Settings, appConfig.ProjectScanMaxDepth)
if err != nil {
slog.ErrorContext(appCtx, "Failed to register filesystem watcher job", "error", err)
}
- gitOpsSyncJob := pkg_scheduler.NewGitOpsSyncJob(appServices.GitOpsSync, appServices.Settings)
+ gitOpsSyncJob := scheduler.NewGitOpsSyncJob(appServices.GitOpsSync, appServices.Settings)
newScheduler.RegisterJob(gitOpsSyncJob)
- vulnerabilityScanJob := pkg_scheduler.NewVulnerabilityScanJob(appServices.Vulnerability, appServices.Settings)
+ vulnerabilityScanJob := scheduler.NewVulnerabilityScanJob(appServices.Vulnerability, appServices.Settings)
newScheduler.RegisterJob(vulnerabilityScanJob)
- autoHealJob := pkg_scheduler.NewAutoHealJob(appServices.Docker, appServices.Settings, appServices.Event, appServices.Notification)
+ autoHealJob := scheduler.NewAutoHealJob(appServices.Docker, appServices.Settings, appServices.Event, appServices.Notification)
newScheduler.RegisterJob(autoHealJob)
setupJobScheduleCallbacks(
@@ -73,16 +73,16 @@ func setupJobScheduleCallbacks(
lifecycleCtx context.Context,
appServices *Services,
appConfig *config.Config,
- newScheduler *pkg_scheduler.JobScheduler,
- imagePollingJob *pkg_scheduler.ImagePollingJob,
- autoUpdateJob *pkg_scheduler.AutoUpdateJob,
- dockerClientRefreshJob *pkg_scheduler.DockerClientRefreshJob,
- environmentHealthJob *pkg_scheduler.EnvironmentHealthJob,
- eventCleanupJob *pkg_scheduler.EventCleanupJob,
- scheduledPruneJob *pkg_scheduler.ScheduledPruneJob,
- gitOpsSyncJob *pkg_scheduler.GitOpsSyncJob,
- vulnerabilityScanJob *pkg_scheduler.VulnerabilityScanJob,
- autoHealJob *pkg_scheduler.AutoHealJob,
+ newScheduler *scheduler.JobScheduler,
+ imagePollingJob *scheduler.ImagePollingJob,
+ autoUpdateJob *scheduler.AutoUpdateJob,
+ dockerClientRefreshJob *scheduler.DockerClientRefreshJob,
+ environmentHealthJob *scheduler.EnvironmentHealthJob,
+ eventCleanupJob *scheduler.EventCleanupJob,
+ scheduledPruneJob *scheduler.ScheduledPruneJob,
+ gitOpsSyncJob *scheduler.GitOpsSyncJob,
+ vulnerabilityScanJob *scheduler.VulnerabilityScanJob,
+ autoHealJob *scheduler.AutoHealJob,
) {
if appServices.JobSchedule == nil {
return
@@ -114,16 +114,16 @@ func handleJobScheduleChangeInternal(
ctx context.Context,
key string,
appConfig *config.Config,
- newScheduler *pkg_scheduler.JobScheduler,
- imagePollingJob *pkg_scheduler.ImagePollingJob,
- autoUpdateJob *pkg_scheduler.AutoUpdateJob,
- dockerClientRefreshJob *pkg_scheduler.DockerClientRefreshJob,
- environmentHealthJob *pkg_scheduler.EnvironmentHealthJob,
- eventCleanupJob *pkg_scheduler.EventCleanupJob,
- scheduledPruneJob *pkg_scheduler.ScheduledPruneJob,
- gitOpsSyncJob *pkg_scheduler.GitOpsSyncJob,
- vulnerabilityScanJob *pkg_scheduler.VulnerabilityScanJob,
- autoHealJob *pkg_scheduler.AutoHealJob,
+ newScheduler *scheduler.JobScheduler,
+ imagePollingJob *scheduler.ImagePollingJob,
+ autoUpdateJob *scheduler.AutoUpdateJob,
+ dockerClientRefreshJob *scheduler.DockerClientRefreshJob,
+ environmentHealthJob *scheduler.EnvironmentHealthJob,
+ eventCleanupJob *scheduler.EventCleanupJob,
+ scheduledPruneJob *scheduler.ScheduledPruneJob,
+ gitOpsSyncJob *scheduler.GitOpsSyncJob,
+ vulnerabilityScanJob *scheduler.VulnerabilityScanJob,
+ autoHealJob *scheduler.AutoHealJob,
) {
switch key {
case "pollingInterval":
@@ -168,7 +168,7 @@ func handleJobScheduleChangeInternal(
}
}
-func setupSettingsCallbacks(lifecycleCtx context.Context, appServices *Services, appConfig *config.Config, newScheduler *pkg_scheduler.JobScheduler, imagePollingJob *pkg_scheduler.ImagePollingJob, autoUpdateJob *pkg_scheduler.AutoUpdateJob, environmentHealthJob *pkg_scheduler.EnvironmentHealthJob, fsWatcherJob *pkg_scheduler.FilesystemWatcherJob, scheduledPruneJob *pkg_scheduler.ScheduledPruneJob, vulnerabilityScanJob *pkg_scheduler.VulnerabilityScanJob, autoHealJob *pkg_scheduler.AutoHealJob) {
+func setupSettingsCallbacks(lifecycleCtx context.Context, appServices *Services, appConfig *config.Config, newScheduler *scheduler.JobScheduler, imagePollingJob *scheduler.ImagePollingJob, autoUpdateJob *scheduler.AutoUpdateJob, environmentHealthJob *scheduler.EnvironmentHealthJob, fsWatcherJob *scheduler.FilesystemWatcherJob, scheduledPruneJob *scheduler.ScheduledPruneJob, vulnerabilityScanJob *scheduler.VulnerabilityScanJob, autoHealJob *scheduler.AutoHealJob) {
appServices.Settings.OnImagePollingSettingsChanged = func(_ context.Context) {
if err := newScheduler.RescheduleJob(lifecycleCtx, imagePollingJob); err != nil {
slog.WarnContext(lifecycleCtx, "Failed to reschedule image-polling job", "error", err)
diff --git a/backend/internal/bootstrap/router_bootstrap.go b/backend/internal/bootstrap/router_bootstrap.go
index 3635d94e7c..2bca5e0666 100644
--- a/backend/internal/bootstrap/router_bootstrap.go
+++ b/backend/internal/bootstrap/router_bootstrap.go
@@ -141,7 +141,7 @@ func setupRouter(ctx context.Context, cfg *config.Config, appServices *Services)
handlers.RegisterWebhookTrigger(apiGroup, appServices.Webhook) //nolint:contextcheck
envProxyMiddleware := middleware.NewEnvProxyMiddlewareWithParam(
- types.LOCAL_DOCKER_ENVIRONMENT_ID,
+ types.LocalDockerEnvironmentId,
"id",
envResolver,
createAuthValidator(appServices),
diff --git a/backend/internal/common/errors.go b/backend/internal/common/errors.go
index b57941d724..fedf090e57 100644
--- a/backend/internal/common/errors.go
+++ b/backend/internal/common/errors.go
@@ -1211,6 +1211,24 @@ func (e *ApiKeyNotFoundError) Error() string {
return "API key not found"
}
+type ApiKeyExpiredError struct{}
+
+func (e *ApiKeyExpiredError) Error() string {
+ return "API key has expired"
+}
+
+type ApiKeyInvalidError struct{}
+
+func (e *ApiKeyInvalidError) Error() string {
+ return "invalid API key"
+}
+
+type ApiKeyProtectedError struct{}
+
+func (e *ApiKeyProtectedError) Error() string {
+ return "API key is protected"
+}
+
type ApiKeyUpdateError struct {
Err error
}
diff --git a/backend/internal/config/version.go b/backend/internal/config/version.go
index 57f629cc94..4072c8fb3b 100644
--- a/backend/internal/config/version.go
+++ b/backend/internal/config/version.go
@@ -9,6 +9,8 @@ var (
)
// ShortRevision returns the first 8 characters of the revision hash
+//
+//goland:noinspection GoBoolExpressions
func ShortRevision() string {
if len(Revision) > 8 {
return Revision[:8]
diff --git a/backend/internal/database/database.go b/backend/internal/database/database.go
index 739c69a3ba..6298a41328 100644
--- a/backend/internal/database/database.go
+++ b/backend/internal/database/database.go
@@ -83,26 +83,18 @@ func Initialize(ctx context.Context, databaseURL string, options MigrationOption
return nil, fmt.Errorf("failed to get sql.DB: %w", err)
}
- // Determine database provider for migrations
+ // Determine database provider and choose the correct migration driver.
var dbProvider string
+ var driver database.Driver
switch {
case strings.HasPrefix(databaseURL, "file:"):
dbProvider = "sqlite"
+ driver, err = sqliteMigrate.WithInstance(sqlDB, &sqliteMigrate.Config{})
case strings.HasPrefix(databaseURL, "postgres"):
dbProvider = "postgres"
- default:
- return nil, fmt.Errorf("unsupported database type in URL: %s", databaseURL)
- }
-
- // Choose the correct driver for migrations
- var driver database.Driver
- switch dbProvider {
- case "sqlite":
- driver, err = sqliteMigrate.WithInstance(sqlDB, &sqliteMigrate.Config{})
- case "postgres":
driver, err = postgresMigrate.WithInstance(sqlDB, &postgresMigrate.Config{})
default:
- return nil, fmt.Errorf("unsupported database provider: %s", dbProvider)
+ return nil, fmt.Errorf("unsupported database type in URL: %s", databaseURL)
}
if err != nil {
return nil, fmt.Errorf("failed to create migration driver: %w", err)
diff --git a/backend/internal/huma/handlers/apikeys.go b/backend/internal/huma/handlers/apikeys.go
index a4c8d23727..b120de33c2 100644
--- a/backend/internal/huma/handlers/apikeys.go
+++ b/backend/internal/huma/handlers/apikeys.go
@@ -256,10 +256,10 @@ func (h *ApiKeyHandler) UpdateApiKey(ctx context.Context, input *UpdateApiKeyInp
apiKey, err := h.apiKeyService.UpdateApiKey(ctx, input.ID, input.Body)
if err != nil {
- if errors.Is(err, services.ErrApiKeyNotFound) {
+ if _, ok := errors.AsType[*common.ApiKeyNotFoundError](err); ok {
return nil, huma.Error404NotFound((&common.ApiKeyNotFoundError{}).Error())
}
- if errors.Is(err, services.ErrApiKeyProtected) {
+ if _, ok := errors.AsType[*common.ApiKeyProtectedError](err); ok {
return nil, huma.Error403Forbidden("static API keys cannot be updated")
}
return nil, huma.Error500InternalServerError((&common.ApiKeyUpdateError{Err: err}).Error())
@@ -285,10 +285,10 @@ func (h *ApiKeyHandler) DeleteApiKey(ctx context.Context, input *DeleteApiKeyInp
}
if err := h.apiKeyService.DeleteApiKey(ctx, input.ID); err != nil {
- if errors.Is(err, services.ErrApiKeyNotFound) {
+ if _, ok := errors.AsType[*common.ApiKeyNotFoundError](err); ok {
return nil, huma.Error404NotFound((&common.ApiKeyNotFoundError{}).Error())
}
- if errors.Is(err, services.ErrApiKeyProtected) {
+ if _, ok := errors.AsType[*common.ApiKeyProtectedError](err); ok {
return nil, huma.Error403Forbidden("static API keys cannot be deleted")
}
return nil, huma.Error500InternalServerError((&common.ApiKeyDeletionError{Err: err}).Error())
diff --git a/backend/internal/huma/handlers/containers.go b/backend/internal/huma/handlers/containers.go
index bf4e789b40..cdc2994c2c 100644
--- a/backend/internal/huma/handlers/containers.go
+++ b/backend/internal/huma/handlers/containers.go
@@ -25,7 +25,7 @@ type ContainerHandler struct {
settingsService *services.SettingsService
}
-// Paginated response
+// ContainerPaginatedResponse response
type ContainerPaginatedResponse struct {
Success bool `json:"success"`
Data []containertypes.Summary `json:"data"`
@@ -122,7 +122,7 @@ type DeleteContainerOutput struct {
Body ContainerActionResponse
}
-// RegisterContainers registers container endpoints.
+// SetAutoUpdateInput sets the auto update value for a container.
type SetAutoUpdateInput struct {
EnvironmentID string `path:"id" doc:"Environment ID"`
ContainerID string `path:"containerId" doc:"Container ID"`
diff --git a/backend/internal/huma/handlers/customize.go b/backend/internal/huma/handlers/customize.go
index f60d86ec26..49e1d73883 100644
--- a/backend/internal/huma/handlers/customize.go
+++ b/backend/internal/huma/handlers/customize.go
@@ -80,7 +80,7 @@ func (h *CustomizeHandler) Search(ctx context.Context, input *SearchCustomizeInp
results := h.customizeSearchService.Search(input.Body.Query)
if !humamw.IsAdminFromContext(ctx) {
- filtered := []category.Category{}
+ var filtered []category.Category
for _, cat := range results.Results {
if cat.ID != "registries" && cat.ID != "variables" {
filtered = append(filtered, cat)
@@ -104,7 +104,7 @@ func (h *CustomizeHandler) GetCategories(ctx context.Context, input *GetCustomiz
categories := h.customizeSearchService.GetCustomizeCategories()
if !humamw.IsAdminFromContext(ctx) {
- filtered := []category.Category{}
+ var filtered []category.Category
for _, cat := range categories {
if cat.ID != "registries" && cat.ID != "variables" {
filtered = append(filtered, cat)
diff --git a/backend/internal/huma/handlers/environments.go b/backend/internal/huma/handlers/environments.go
index 3f4a127e56..fc0c990c11 100644
--- a/backend/internal/huma/handlers/environments.go
+++ b/backend/internal/huma/handlers/environments.go
@@ -536,7 +536,7 @@ func (h *EnvironmentHandler) createEnvironmentWithApiKey(ctx context.Context, en
func (h *EnvironmentHandler) createEnvironmentLegacy(ctx context.Context, env *models.Environment, user *models.User, body environment.Create) (*CreateEnvironmentOutput, error) {
// Legacy pairing flows
if (body.AccessToken == nil || *body.AccessToken == "") && body.BootstrapToken != nil && *body.BootstrapToken != "" {
- token, err := h.environmentService.PairAgentWithBootstrap(ctx, body.ApiUrl, *body.BootstrapToken)
+ token, err := h.environmentService.PairAgentWithBootstrap(ctx, body.ApiUrl, *body.BootstrapToken) //nolint:staticcheck
if err != nil {
slog.ErrorContext(ctx, "Failed to pair with agent", "apiUrl", body.ApiUrl, "error", err.Error())
return nil, huma.Error502BadGateway((&common.AgentPairingError{Err: err}).Error())
@@ -878,7 +878,7 @@ func (h *EnvironmentHandler) handleEnvironmentPairing(ctx context.Context, envir
pairingSucceeded := false
if isLocalEnv {
- return pairingSucceeded, nil
+ return false, nil
}
if req.AccessToken == nil && req.BootstrapToken != nil && *req.BootstrapToken != "" {
diff --git a/backend/internal/huma/handlers/projects.go b/backend/internal/huma/handlers/projects.go
index 09be87b6fe..667eb4dc04 100644
--- a/backend/internal/huma/handlers/projects.go
+++ b/backend/internal/huma/handlers/projects.go
@@ -13,7 +13,7 @@ import (
humamw "github.com/getarcaneapp/arcane/backend/internal/huma/middleware"
"github.com/getarcaneapp/arcane/backend/internal/services"
"github.com/getarcaneapp/arcane/backend/pkg/pagination"
- projects "github.com/getarcaneapp/arcane/backend/pkg/projects"
+ "github.com/getarcaneapp/arcane/backend/pkg/projects"
"github.com/getarcaneapp/arcane/backend/pkg/utils"
"github.com/getarcaneapp/arcane/backend/pkg/utils/mapper"
"github.com/getarcaneapp/arcane/types/base"
diff --git a/backend/internal/huma/handlers/swarm.go b/backend/internal/huma/handlers/swarm.go
index 60c996db78..81a9e2e43c 100644
--- a/backend/internal/huma/handlers/swarm.go
+++ b/backend/internal/huma/handlers/swarm.go
@@ -1209,7 +1209,7 @@ func (h *SwarmHandler) GetStack(ctx context.Context, input *GetSwarmStackInput)
stack, err := h.swarmService.GetStack(ctx, input.EnvironmentID, input.Name)
if err != nil {
if errdefs.IsNotFound(err) {
- return nil, huma.Error404NotFound(("Swarm stack not found"))
+ return nil, huma.Error404NotFound("Swarm stack not found")
}
return nil, mapSwarmServiceError(err, "Failed to inspect swarm stack")
}
@@ -1387,7 +1387,7 @@ func (h *SwarmHandler) RenderStackConfig(ctx context.Context, input *RenderSwarm
return &RenderSwarmStackConfigOutput{Body: base.ApiResponse[swarmtypes.StackRenderConfigResponse]{Success: true, Data: *resp}}, nil
}
-// GetSwarmInfo returns the current swarm cluster metadata for an environment.
+// GetSwarmStatus GetSwarmInfo returns the current swarm cluster metadata for an environment.
//
// It delegates to the swarm service to inspect the local swarm state and maps
// service-layer failures to the API's HTTP error model.
diff --git a/backend/internal/models/event.go b/backend/internal/models/event.go
index a00f840835..4ceaa592d3 100644
--- a/backend/internal/models/event.go
+++ b/backend/internal/models/event.go
@@ -10,7 +10,6 @@ type (
)
const (
- // Event types
EventTypeContainerStart EventType = "container.start"
EventTypeContainerStop EventType = "container.stop"
EventTypeContainerRestart EventType = "container.restart"
@@ -88,7 +87,6 @@ const (
EventTypeWebhookDelete EventType = "webhook.delete"
EventTypeWebhookTrigger EventType = "webhook.trigger"
- // Event severities
EventSeverityInfo EventSeverity = "info"
EventSeverityWarning EventSeverity = "warning"
EventSeverityError EventSeverity = "error"
diff --git a/backend/internal/models/vulnerability_ignore.go b/backend/internal/models/vulnerability_ignore.go
index 59f43297cd..2ebed5d90e 100644
--- a/backend/internal/models/vulnerability_ignore.go
+++ b/backend/internal/models/vulnerability_ignore.go
@@ -52,7 +52,7 @@ func (v *VulnerabilityIgnore) BeforeCreate(db *gorm.DB) error {
return nil
}
-// VulnerabilityIgnoreCompositeKey generates a composite key for deduplication
+// CompositeKey generates a composite key for deduplication
// This helps prevent duplicate ignore records for the same vulnerability
func (v *VulnerabilityIgnore) CompositeKey() string {
return v.EnvironmentID + ":" + v.ImageID + ":" + v.VulnerabilityID + ":" + v.PkgName + ":" + v.InstalledVersion
diff --git a/backend/internal/services/api_key_service.go b/backend/internal/services/api_key_service.go
index 6f655c26a5..1272428fbc 100644
--- a/backend/internal/services/api_key_service.go
+++ b/backend/internal/services/api_key_service.go
@@ -10,6 +10,7 @@ import (
"strings"
"time"
+ "github.com/getarcaneapp/arcane/backend/internal/common"
"github.com/getarcaneapp/arcane/backend/internal/database"
"github.com/getarcaneapp/arcane/backend/internal/models"
"github.com/getarcaneapp/arcane/backend/pkg/pagination"
@@ -17,13 +18,6 @@ import (
"gorm.io/gorm"
)
-var (
- ErrApiKeyNotFound = errors.New("API key not found")
- ErrApiKeyExpired = errors.New("API key has expired")
- ErrApiKeyInvalid = errors.New("invalid API key")
- ErrApiKeyProtected = errors.New("API key is protected")
-)
-
const (
apiKeyPrefix = "arc_"
apiKeyLength = 32
@@ -76,12 +70,12 @@ func normalizeAPIKeyInputInternal(rawKey string) string {
func parseAPIKeyPrefixInternal(rawKey string) (string, error) {
rawKey = normalizeAPIKeyInputInternal(rawKey)
if !strings.HasPrefix(rawKey, apiKeyPrefix) {
- return "", ErrApiKeyInvalid
+ return "", &common.ApiKeyInvalidError{}
}
prefixLen := len(apiKeyPrefix) + apiKeyPrefixLen
if len(rawKey) < prefixLen {
- return "", ErrApiKeyInvalid
+ return "", &common.ApiKeyInvalidError{}
}
return rawKey[:prefixLen], nil
@@ -326,7 +320,7 @@ func (s *ApiKeyService) GetApiKey(ctx context.Context, id string) (*apikey.ApiKe
var ak models.ApiKey
if err := s.db.WithContext(ctx).Where("id = ?", id).First(&ak).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
- return nil, ErrApiKeyNotFound
+ return nil, &common.ApiKeyNotFoundError{}
}
return nil, fmt.Errorf("failed to get API key: %w", err)
}
@@ -374,12 +368,12 @@ func (s *ApiKeyService) UpdateApiKey(ctx context.Context, id string, req apikey.
var ak models.ApiKey
if err := s.db.WithContext(ctx).Where("id = ?", id).First(&ak).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
- return nil, ErrApiKeyNotFound
+ return nil, &common.ApiKeyNotFoundError{}
}
return nil, fmt.Errorf("failed to get API key: %w", err)
}
if isStaticAPIKeyInternal(ak) {
- return nil, ErrApiKeyProtected
+ return nil, &common.ApiKeyProtectedError{}
}
if req.Name != nil {
@@ -414,12 +408,12 @@ func (s *ApiKeyService) DeleteApiKey(ctx context.Context, id string) error {
var apiKey models.ApiKey
if err := s.db.WithContext(ctx).Where("id = ?", id).First(&apiKey).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
- return ErrApiKeyNotFound
+ return &common.ApiKeyNotFoundError{}
}
return fmt.Errorf("failed to load API key: %w", err)
}
if isStaticAPIKeyInternal(apiKey) {
- return ErrApiKeyProtected
+ return &common.ApiKeyProtectedError{}
}
result := s.db.WithContext(ctx).Delete(&models.ApiKey{}, "id = ?", id)
@@ -427,7 +421,7 @@ func (s *ApiKeyService) DeleteApiKey(ctx context.Context, id string) error {
return fmt.Errorf("failed to delete API key: %w", result.Error)
}
if result.RowsAffected == 0 {
- return ErrApiKeyNotFound
+ return &common.ApiKeyNotFoundError{}
}
return nil
}
@@ -447,11 +441,11 @@ func (s *ApiKeyService) ValidateApiKey(ctx context.Context, rawKey string) (*mod
for _, apiKey := range apiKeys {
if err := s.validateApiKeyHash(apiKey.KeyHash, rawKey); err == nil {
if apiKey.ExpiresAt != nil && apiKey.ExpiresAt.Before(time.Now()) {
- return nil, ErrApiKeyExpired
+ return nil, &common.ApiKeyExpiredError{}
}
if apiKey.UserID == nil {
- return nil, ErrApiKeyInvalid
+ return nil, &common.ApiKeyInvalidError{}
}
s.markApiKeyUsedAsync(ctx, apiKey.ID)
@@ -465,7 +459,7 @@ func (s *ApiKeyService) ValidateApiKey(ctx context.Context, rawKey string) (*mod
}
}
- return nil, ErrApiKeyInvalid
+ return nil, &common.ApiKeyInvalidError{}
}
func (s *ApiKeyService) GetEnvironmentByApiKey(ctx context.Context, rawKey string) (*string, error) {
@@ -483,7 +477,7 @@ func (s *ApiKeyService) GetEnvironmentByApiKey(ctx context.Context, rawKey strin
for _, apiKey := range apiKeys {
if err := s.validateApiKeyHash(apiKey.KeyHash, rawKey); err == nil {
if apiKey.ExpiresAt != nil && apiKey.ExpiresAt.Before(time.Now()) {
- return nil, ErrApiKeyExpired
+ return nil, &common.ApiKeyExpiredError{}
}
s.markApiKeyUsedAsync(ctx, apiKey.ID)
@@ -492,5 +486,5 @@ func (s *ApiKeyService) GetEnvironmentByApiKey(ctx context.Context, rawKey strin
}
}
- return nil, ErrApiKeyInvalid
+ return nil, &common.ApiKeyInvalidError{}
}
diff --git a/backend/internal/services/api_key_service_test.go b/backend/internal/services/api_key_service_test.go
index f7b6cbf715..e73441fe05 100644
--- a/backend/internal/services/api_key_service_test.go
+++ b/backend/internal/services/api_key_service_test.go
@@ -7,6 +7,7 @@ import (
"testing"
"time"
+ "github.com/getarcaneapp/arcane/backend/internal/common"
glsqlite "github.com/glebarez/sqlite"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -176,7 +177,8 @@ func TestDeleteApiKeyRejectsStaticKey(t *testing.T) {
require.NoError(t, err)
err = service.DeleteApiKey(ctx, created.ApiKey.ID)
- require.ErrorIs(t, err, ErrApiKeyProtected)
+ var protectedErr *common.ApiKeyProtectedError
+ require.ErrorAs(t, err, &protectedErr)
apiKeys := listAPIKeysForUser(t, db, adminUser.ID)
require.Len(t, apiKeys, 1)
@@ -196,7 +198,8 @@ func TestUpdateApiKeyRejectsStaticKey(t *testing.T) {
Description: new("updated description"),
})
require.Nil(t, updated)
- require.ErrorIs(t, err, ErrApiKeyProtected)
+ var protectedErr *common.ApiKeyProtectedError
+ require.ErrorAs(t, err, &protectedErr)
apiKeys := listAPIKeysForUser(t, db, adminUser.ID)
require.Len(t, apiKeys, 1)
@@ -238,7 +241,8 @@ func TestReconcileDefaultAdminAPIKeyReplacesManagedKeyOnRotation(t *testing.T) {
require.NotEqual(t, first[0].ID, second[0].ID)
_, err := service.ValidateApiKey(ctx, oldKey)
- require.ErrorIs(t, err, ErrApiKeyInvalid)
+ var invalidErr *common.ApiKeyInvalidError
+ require.ErrorAs(t, err, &invalidErr)
validatedUser, err := service.ValidateApiKey(ctx, newKey)
require.NoError(t, err)
@@ -324,7 +328,8 @@ func TestReconcileDefaultAdminAPIKeyRejectsInvalidKey(t *testing.T) {
adminUser := createDefaultAdminUser(t, ctx, userService)
err := service.ReconcileDefaultAdminAPIKey(ctx, "invalid-key")
- require.ErrorIs(t, err, ErrApiKeyInvalid)
+ var invalidErr *common.ApiKeyInvalidError
+ require.ErrorAs(t, err, &invalidErr)
require.Empty(t, listAPIKeysForUser(t, db, adminUser.ID))
}
@@ -372,7 +377,8 @@ func TestValidateAPIKeyInvalidDoesNotUpdateLastUsedAt(t *testing.T) {
require.NoError(t, err)
_, err = service.ValidateApiKey(ctx, invalidateAPIKey(created.Key))
- require.ErrorIs(t, err, ErrApiKeyInvalid)
+ var invalidErr *common.ApiKeyInvalidError
+ require.ErrorAs(t, err, &invalidErr)
assertAPIKeyLastUsedStable(t, db, created.ApiKey.ID, nil, 500*time.Millisecond)
apiKey := fetchAPIKey(t, db, created.ApiKey.ID)
@@ -384,7 +390,8 @@ func TestValidateAPIKeyRejectsShortPrefixedInput(t *testing.T) {
service, _, _ := setupAPIKeyService(t)
_, err := service.ValidateApiKey(ctx, "arc_123")
- require.ErrorIs(t, err, ErrApiKeyInvalid)
+ var invalidErr *common.ApiKeyInvalidError
+ require.ErrorAs(t, err, &invalidErr)
}
func TestGetEnvironmentByAPIKeyExpiredDoesNotUpdateLastUsedAt(t *testing.T) {
@@ -400,7 +407,8 @@ func TestGetEnvironmentByAPIKeyExpiredDoesNotUpdateLastUsedAt(t *testing.T) {
require.NoError(t, err)
_, err = service.GetEnvironmentByApiKey(ctx, created.Key)
- require.ErrorIs(t, err, ErrApiKeyExpired)
+ var expiredErr *common.ApiKeyExpiredError
+ require.ErrorAs(t, err, &expiredErr)
assertAPIKeyLastUsedStable(t, db, created.ApiKey.ID, nil, 500*time.Millisecond)
apiKey := fetchAPIKey(t, db, created.ApiKey.ID)
diff --git a/backend/internal/services/auth_service_test.go b/backend/internal/services/auth_service_test.go
index cb7161839d..57606fae79 100644
--- a/backend/internal/services/auth_service_test.go
+++ b/backend/internal/services/auth_service_test.go
@@ -208,10 +208,10 @@ func TestPersistOidcTokens_SetsFields(t *testing.T) {
t.Errorf("expiresAt nil")
}
// Check approx expiry within [start+7s, start+12s] to allow CI slop
- min := start.Add(7 * time.Second)
- max := start.Add(12 * time.Second)
- if user.OidcAccessTokenExpiresAt.Before(min) || user.OidcAccessTokenExpiresAt.After(max) {
- t.Errorf("expiresAt %v not in [%v,%v]", user.OidcAccessTokenExpiresAt, min, max)
+ authMin := start.Add(7 * time.Second)
+ authMax := start.Add(12 * time.Second)
+ if user.OidcAccessTokenExpiresAt.Before(authMin) || user.OidcAccessTokenExpiresAt.After(authMax) {
+ t.Errorf("expiresAt %v not in [%v,%v]", user.OidcAccessTokenExpiresAt, authMin, authMax)
}
}
diff --git a/backend/internal/services/build_service.go b/backend/internal/services/build_service.go
index 5c86e4213b..8988c8c77f 100644
--- a/backend/internal/services/build_service.go
+++ b/backend/internal/services/build_service.go
@@ -16,7 +16,7 @@ import (
"github.com/getarcaneapp/arcane/backend/internal/database"
"github.com/getarcaneapp/arcane/backend/internal/models"
buildgit "github.com/getarcaneapp/arcane/backend/pkg/gitutil"
- libbuild "github.com/getarcaneapp/arcane/backend/pkg/libarcane/libbuild"
+ "github.com/getarcaneapp/arcane/backend/pkg/libarcane/libbuild"
"github.com/getarcaneapp/arcane/backend/pkg/pagination"
buildtypes "github.com/getarcaneapp/arcane/types/builds"
imagetypes "github.com/getarcaneapp/arcane/types/image"
@@ -553,21 +553,21 @@ func buildToRecord(build models.ImageBuild, includeOutput bool) imagetypes.Build
ContextDir: build.ContextDir,
Dockerfile: build.Dockerfile,
Target: build.Target,
- Tags: []string(build.Tags),
- Platforms: []string(build.Platforms),
+ Tags: build.Tags,
+ Platforms: build.Platforms,
BuildArgs: buildArgs,
Labels: labels,
- CacheFrom: []string(build.CacheFrom),
- CacheTo: []string(build.CacheTo),
+ CacheFrom: build.CacheFrom,
+ CacheTo: build.CacheTo,
NoCache: build.NoCache,
Pull: build.Pull,
Network: build.BuildNetwork,
Isolation: build.Isolation,
ShmSize: build.ShmSize,
Ulimits: ulimits,
- Entitlements: []string(build.Entitlements),
+ Entitlements: build.Entitlements,
Privileged: build.Privileged,
- ExtraHosts: []string(build.ExtraHosts),
+ ExtraHosts: build.ExtraHosts,
Push: build.Push,
Load: build.Load,
Digest: build.Digest,
diff --git a/backend/internal/services/container_registry_service.go b/backend/internal/services/container_registry_service.go
index b5d691ed41..fd21f2879e 100644
--- a/backend/internal/services/container_registry_service.go
+++ b/backend/internal/services/container_registry_service.go
@@ -402,7 +402,7 @@ func (s *ContainerRegistryService) GetAllRegistryAuthConfigs(ctx context.Context
Password: token,
ServerAddress: serverAddress,
}
- for _, key := range utilsregistry.RegistryAuthLookupKeys(normalizedHost) {
+ for _, key := range utilsregistry.AuthLookupKeys(normalizedHost) {
authConfigs[key] = authConfig
}
}
diff --git a/backend/internal/services/container_registry_service_test.go b/backend/internal/services/container_registry_service_test.go
index d901dfb432..a48b0ce3fc 100644
--- a/backend/internal/services/container_registry_service_test.go
+++ b/backend/internal/services/container_registry_service_test.go
@@ -595,7 +595,7 @@ func TestContainerRegistryService_InspectImageDigest_FallsBackWhenDistributionNo
calls++
assert.Equal(t, serverURL.Host+"/team/app:1.2.3", imageRef)
assert.Empty(t, options.EncodedRegistryAuth)
- return client.DistributionInspectResult{}, errors.New("Error response from daemon: Not Found")
+ return client.DistributionInspectResult{}, errors.New("error response from daemon: Not Found")
},
}, nil
}, nil)
@@ -632,7 +632,7 @@ func TestContainerRegistryService_InspectImageDigest_FallsBackWhenDistributionFo
calls++
assert.Equal(t, serverURL.Host+"/team/app:1.2.3", imageRef)
assert.Empty(t, options.EncodedRegistryAuth)
- return client.DistributionInspectResult{}, errors.New("Error response from daemon:
403 Forbidden
Request forbidden by administrative rules. ")
+ return client.DistributionInspectResult{}, errors.New("error response from daemon: 403 Forbidden
Request forbidden by administrative rules. ")
},
}, nil
}, nil)
@@ -698,7 +698,7 @@ func TestContainerRegistryService_InspectImageDigest_RetriesStoredCredentialsAft
svc := NewContainerRegistryService(db, func(context.Context) (RegistryDaemonClient, error) {
return &fakeRegistryDaemonClient{
distributionInspectFn: func(ctx context.Context, imageRef string, options client.DistributionInspectOptions) (client.DistributionInspectResult, error) {
- return client.DistributionInspectResult{}, errors.New("Error response from daemon: Not Found")
+ return client.DistributionInspectResult{}, errors.New("error response from daemon: Not Found")
},
}, nil
}, nil)
@@ -737,7 +737,7 @@ func TestContainerRegistryService_InspectImageDigest_DoesNotFallbackOnTLSFailure
}
func TestContainerRegistryService_InspectImageDigest_PreservesDaemonAndFallbackErrors(t *testing.T) {
- daemonErr := errors.New("Error response from daemon: Not Found")
+ daemonErr := errors.New("error response from daemon: Not Found")
fallbackErr := errors.New("dial tcp: i/o timeout")
svc := NewContainerRegistryService(nil, func(context.Context) (RegistryDaemonClient, error) {
@@ -789,7 +789,7 @@ func TestContainerRegistryService_InspectImageDigest_PreservesAnonymousUnauthori
svc := NewContainerRegistryService(db, func(context.Context) (RegistryDaemonClient, error) {
return &fakeRegistryDaemonClient{
distributionInspectFn: func(ctx context.Context, imageRef string, options client.DistributionInspectOptions) (client.DistributionInspectResult, error) {
- return client.DistributionInspectResult{}, errors.New("Error response from daemon: Not Found")
+ return client.DistributionInspectResult{}, errors.New("error response from daemon: Not Found")
},
}, nil
}, nil)
diff --git a/backend/internal/services/container_service.go b/backend/internal/services/container_service.go
index d0e5cd0cee..7378b24edd 100644
--- a/backend/internal/services/container_service.go
+++ b/backend/internal/services/container_service.go
@@ -1007,7 +1007,7 @@ func paginateContainerProjectGroupsInternal(
requestedPage = totalPages
}
- pageGroups := []containertypes.SummaryGroup{}
+ var pageGroups []containertypes.SummaryGroup
if len(pages) > 0 {
pageGroups = pages[requestedPage-1]
}
diff --git a/backend/internal/services/customize_search_service.go b/backend/internal/services/customize_search_service.go
index be39784cf7..d517ed2a76 100644
--- a/backend/internal/services/customize_search_service.go
+++ b/backend/internal/services/customize_search_service.go
@@ -41,7 +41,7 @@ func (s *CustomizeSearchService) buildCategoriesFromModel() []category.Category
// map category id -> list of customizations
categories := map[string][]meta.Metadata{}
- categoryOrder := []string{} // Track order from first appearance in struct
+ var categoryOrder []string // Track order from first appearance in struct
rt := reflect.TypeFor[models.CustomizeItem]()
for field := range rt.Fields() {
@@ -84,7 +84,7 @@ func (s *CustomizeSearchService) buildCategoriesFromModel() []category.Category
}
// Build final category list in struct order
- result := []category.Category{}
+ var result []category.Category
for _, catID := range categoryOrder {
catMeta := catMetaMap[catID]
if catMeta == nil {
@@ -123,7 +123,7 @@ func (s *CustomizeSearchService) Search(query string) search.Response {
}
categories := s.GetCustomizeCategories()
- results := []category.Category{}
+ var results []category.Category
for _, cat := range categories {
// Check if category matches
@@ -132,7 +132,7 @@ func (s *CustomizeSearchService) Search(query string) search.Response {
containsKeyword(cat.Keywords, query)
// Check individual settings
- matchingSettings := []meta.Metadata{}
+ var matchingSettings []meta.Metadata
for _, setting := range cat.Settings {
if matchesSetting(setting, query) {
matchingSettings = append(matchingSettings, setting)
diff --git a/backend/internal/services/dashboard_service.go b/backend/internal/services/dashboard_service.go
index c5e514a2a1..942b2af19d 100644
--- a/backend/internal/services/dashboard_service.go
+++ b/backend/internal/services/dashboard_service.go
@@ -469,9 +469,8 @@ func (s *DashboardService) buildEnvironmentOverviewInternal(
}
if snapshotErr != nil {
- message := snapshotErr.Error()
overview.SnapshotState = dashboardtypes.EnvironmentSnapshotStateError
- overview.SnapshotError = &message
+ overview.SnapshotError = new(snapshotErr.Error())
return overview
}
diff --git a/backend/internal/services/environment_service.go b/backend/internal/services/environment_service.go
index 7bfbca3d7a..19593226ab 100644
--- a/backend/internal/services/environment_service.go
+++ b/backend/internal/services/environment_service.go
@@ -786,7 +786,9 @@ func (s *EnvironmentService) RegenerateEnvironmentApiKey(ctx context.Context, en
return nil
}
-// Deprecated - Use the Api Key flow
+// PairAgentWithBootstrap Uses a predefined bootstrap token to register with agents.
+//
+// Deprecated: PairAgentWithBootstrap will be removed in a future release, Use the API Key flow instead.
func (s *EnvironmentService) PairAgentWithBootstrap(ctx context.Context, apiUrl, bootstrapToken string) (string, error) {
reqCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
diff --git a/backend/internal/services/git_repository_service.go b/backend/internal/services/git_repository_service.go
index e17d693c71..f505d14181 100644
--- a/backend/internal/services/git_repository_service.go
+++ b/backend/internal/services/git_repository_service.go
@@ -10,7 +10,7 @@ import (
"github.com/getarcaneapp/arcane/backend/internal/models"
git "github.com/getarcaneapp/arcane/backend/pkg/gitutil"
"github.com/getarcaneapp/arcane/backend/pkg/libarcane/crypto"
- libbuild "github.com/getarcaneapp/arcane/backend/pkg/libarcane/libbuild"
+ "github.com/getarcaneapp/arcane/backend/pkg/libarcane/libbuild"
"github.com/getarcaneapp/arcane/backend/pkg/libarcane/timeouts"
"github.com/getarcaneapp/arcane/backend/pkg/pagination"
"github.com/getarcaneapp/arcane/backend/pkg/utils"
diff --git a/backend/internal/services/gitops_sync_service.go b/backend/internal/services/gitops_sync_service.go
index e2af072706..fd7033b9d4 100644
--- a/backend/internal/services/gitops_sync_service.go
+++ b/backend/internal/services/gitops_sync_service.go
@@ -1318,14 +1318,12 @@ func (s *GitOpsSyncService) findUniqueProjectDirectoryCandidateInternal(ctx cont
}
func (s *GitOpsSyncService) createRecoveredProjectFromDirectoryInternal(ctx context.Context, sync *models.GitOpsSync, projectPath string) (*models.Project, error) {
- dirName := filepath.Base(projectPath)
- reason := "Project recovered from existing GitOps-managed directory"
project := &models.Project{
Name: sync.ProjectName,
- DirName: &dirName,
+ DirName: new(filepath.Base(projectPath)),
Path: projectPath,
Status: models.ProjectStatusUnknown,
- StatusReason: &reason,
+ StatusReason: new("Project recovered from existing GitOps-managed directory"),
ServiceCount: 0,
RunningCount: 0,
GitOpsManagedBy: &sync.ID,
diff --git a/backend/internal/services/image_service_test.go b/backend/internal/services/image_service_test.go
index ef5ca0c44e..d14c119bea 100644
--- a/backend/internal/services/image_service_test.go
+++ b/backend/internal/services/image_service_test.go
@@ -179,13 +179,13 @@ func TestGetPullOptionsWithAuth_DBRegistryUsesValidCredentials(t *testing.T) {
}
func TestShouldRetryAnonymousPullInternal_UnauthorizedWithAuth(t *testing.T) {
- err := errors.New(`Error response from daemon: Head "registry-1.docker.io/v2/library/nginx/manifests/latest": unauthorized: incorrect username or password`)
+ err := errors.New(`error response from daemon: Head "registry-1.docker.io/v2/library/nginx/manifests/latest": unauthorized: incorrect username or password`)
assert.True(t, shouldRetryAnonymousPullInternal(client.ImagePullOptions{RegistryAuth: "encoded-auth"}, err))
}
func TestShouldRetryAnonymousPullInternal_SkipsRetryWithoutUnauthorizedOrAuth(t *testing.T) {
- nonAuthErr := errors.New("Error response from daemon: i/o timeout")
+ nonAuthErr := errors.New("error response from daemon: i/o timeout")
unauthorizedErr := errors.New("unauthorized: authentication required")
assert.False(t, shouldRetryAnonymousPullInternal(client.ImagePullOptions{RegistryAuth: "encoded-auth"}, nonAuthErr))
diff --git a/backend/internal/services/image_update_service.go b/backend/internal/services/image_update_service.go
index 984d6c7c1f..4972ab78d2 100644
--- a/backend/internal/services/image_update_service.go
+++ b/backend/internal/services/image_update_service.go
@@ -13,7 +13,7 @@ import (
"github.com/getarcaneapp/arcane/backend/internal/models"
"github.com/getarcaneapp/arcane/backend/pkg/libarcane/crypto"
"github.com/getarcaneapp/arcane/backend/pkg/utils/imagedigest"
- registry "github.com/getarcaneapp/arcane/backend/pkg/utils/registry"
+ "github.com/getarcaneapp/arcane/backend/pkg/utils/registry"
"github.com/getarcaneapp/arcane/types/containerregistry"
"github.com/getarcaneapp/arcane/types/imageupdate"
"github.com/moby/moby/api/types/image"
diff --git a/backend/internal/services/image_update_service_test.go b/backend/internal/services/image_update_service_test.go
index 9bd57e4b26..9936ecfae0 100644
--- a/backend/internal/services/image_update_service_test.go
+++ b/backend/internal/services/image_update_service_test.go
@@ -19,7 +19,7 @@ import (
dockertypescontainer "github.com/moby/moby/api/types/container"
dockertypesimage "github.com/moby/moby/api/types/image"
"github.com/moby/moby/client"
- digest "github.com/opencontainers/go-digest"
+ "github.com/opencontainers/go-digest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
ref "go.podman.io/image/v5/docker/reference"
@@ -513,7 +513,7 @@ func TestImageUpdateService_CheckImageUpdate_UsesRegistryFallback(t *testing.T)
registryService := NewContainerRegistryService(db, func(context.Context) (RegistryDaemonClient, error) {
return &fakeRegistryDaemonClient{
distributionInspectFn: func(ctx context.Context, imageRef string, options client.DistributionInspectOptions) (client.DistributionInspectResult, error) {
- return client.DistributionInspectResult{}, errors.New("Error response from daemon: Not Found")
+ return client.DistributionInspectResult{}, errors.New("error response from daemon: Not Found")
},
}, nil
}, nil)
@@ -553,7 +553,7 @@ func TestImageUpdateService_CheckMultipleImages_UsesRegistryFallback(t *testing.
registryService := NewContainerRegistryService(db, func(context.Context) (RegistryDaemonClient, error) {
return &fakeRegistryDaemonClient{
distributionInspectFn: func(ctx context.Context, imageRef string, options client.DistributionInspectOptions) (client.DistributionInspectResult, error) {
- return client.DistributionInspectResult{}, errors.New("Error response from daemon: 403 Forbidden
Request forbidden by administrative rules. ")
+ return client.DistributionInspectResult{}, errors.New("error response from daemon: 403 Forbidden
Request forbidden by administrative rules. ")
},
}, nil
}, nil)
@@ -594,7 +594,7 @@ func TestImageUpdateService_CheckMultipleImages_PersistsRefScopedErrorsWhenLocal
registryService := NewContainerRegistryService(db, func(context.Context) (RegistryDaemonClient, error) {
return &fakeRegistryDaemonClient{
distributionInspectFn: func(ctx context.Context, imageRef string, options client.DistributionInspectOptions) (client.DistributionInspectResult, error) {
- return client.DistributionInspectResult{}, errors.New("Error response from daemon: Not Found")
+ return client.DistributionInspectResult{}, errors.New("error response from daemon: Not Found")
},
}, nil
}, nil)
diff --git a/backend/internal/services/project_service.go b/backend/internal/services/project_service.go
index 2f31238318..2e6044003b 100644
--- a/backend/internal/services/project_service.go
+++ b/backend/internal/services/project_service.go
@@ -23,7 +23,7 @@ import (
"github.com/getarcaneapp/arcane/backend/internal/database"
"github.com/getarcaneapp/arcane/backend/internal/models"
dockerutil "github.com/getarcaneapp/arcane/backend/pkg/dockerutil"
- libbuild "github.com/getarcaneapp/arcane/backend/pkg/libarcane/libbuild"
+ "github.com/getarcaneapp/arcane/backend/pkg/libarcane/libbuild"
"github.com/getarcaneapp/arcane/backend/pkg/libarcane/timeouts"
libupdater "github.com/getarcaneapp/arcane/backend/pkg/libarcane/updater"
"github.com/getarcaneapp/arcane/backend/pkg/pagination"
@@ -1126,13 +1126,11 @@ func buildProjectUpdateInfoSummaryInternal(
if strings.TrimSpace(info.Error) != "" {
summary.ErrorCount++
if summary.ErrorMessage == nil {
- errMsg := strings.TrimSpace(info.Error)
- summary.ErrorMessage = &errMsg
+ summary.ErrorMessage = new(strings.TrimSpace(info.Error))
}
}
if !info.CheckTime.IsZero() && (latestCheckTime == nil || info.CheckTime.After(*latestCheckTime)) {
- checkTime := info.CheckTime
- latestCheckTime = &checkTime
+ latestCheckTime = new(info.CheckTime)
}
}
@@ -1968,7 +1966,7 @@ func (s *ProjectService) buildServiceImageForDeploy(
project.Services[serviceName] = updatedSvc
}
- if _, err := s.buildService.BuildImage(ctx, types.LOCAL_DOCKER_ENVIRONMENT_ID, buildReq, progressWriter, serviceName, user); err != nil {
+ if _, err := s.buildService.BuildImage(ctx, types.LocalDockerEnvironmentId, buildReq, progressWriter, serviceName, user); err != nil {
return err
}
@@ -2338,7 +2336,7 @@ func (s *ProjectService) buildProjectServicesInternal(ctx context.Context, proje
}
buildCount++
- if _, err := s.buildService.BuildImage(ctx, types.LOCAL_DOCKER_ENVIRONMENT_ID, buildReq, progressWriter, name, user); err != nil {
+ if _, err := s.buildService.BuildImage(ctx, types.LocalDockerEnvironmentId, buildReq, progressWriter, name, user); err != nil {
return err
}
}
diff --git a/backend/internal/services/settings_search_service.go b/backend/internal/services/settings_search_service.go
index feb14850d1..ed74d5dc10 100644
--- a/backend/internal/services/settings_search_service.go
+++ b/backend/internal/services/settings_search_service.go
@@ -41,7 +41,7 @@ func (s *SettingsSearchService) buildCategoriesFromModel() []category.Category {
// map category id -> list of settings
categories := map[string][]meta.Metadata{}
- categoryOrder := []string{} // Track order from first appearance in struct
+ var categoryOrder []string // Track order from first appearance in struct
rt := reflect.TypeFor[models.Settings]()
for field := range rt.Fields() {
@@ -89,7 +89,7 @@ func (s *SettingsSearchService) buildCategoriesFromModel() []category.Category {
}
// Build final category list in struct order
- results := []category.Category{}
+ var results []category.Category
for _, catID := range categoryOrder {
catMeta := catMetaMap[catID]
if catMeta == nil {
diff --git a/backend/internal/services/settings_service_test.go b/backend/internal/services/settings_service_test.go
index 93aa46be18..2c5fab9c5d 100644
--- a/backend/internal/services/settings_service_test.go
+++ b/backend/internal/services/settings_service_test.go
@@ -449,11 +449,9 @@ func TestSettingsService_UpdateSettings_PruneModesDoNotTriggerScheduledPruneCall
callbackCalls++
}
- imageMode := "all"
- containerUntil := "24h"
_, err = svc.UpdateSettings(ctx, settings.Update{
- PruneImageMode: &imageMode,
- PruneContainerUntil: &containerUntil,
+ PruneImageMode: new("all"),
+ PruneContainerUntil: new("24h"),
})
require.NoError(t, err)
require.Equal(t, 0, callbackCalls)
@@ -470,9 +468,8 @@ func TestSettingsService_UpdateSettings_ScheduledPruneScheduleTriggersCallback(t
callbackCalls++
}
- enabled := "true"
_, err = svc.UpdateSettings(ctx, settings.Update{
- ScheduledPruneEnabled: &enabled,
+ ScheduledPruneEnabled: new("true"),
})
require.NoError(t, err)
require.Equal(t, 1, callbackCalls)
diff --git a/backend/internal/services/swarm_service.go b/backend/internal/services/swarm_service.go
index 30319eeeb3..6fc4712238 100644
--- a/backend/internal/services/swarm_service.go
+++ b/backend/internal/services/swarm_service.go
@@ -2107,7 +2107,7 @@ func (s *SwarmService) deleteStackSourceInternal(ctx context.Context, environmen
environmentDir := filepath.Dir(stackSourceDir)
if environmentDir != rootDir {
if err := os.Remove(environmentDir); err != nil && !errors.Is(err, os.ErrNotExist) {
- if errno, ok := errors.AsType[syscall.Errno](err); ok && (errno == syscall.ENOTEMPTY || errno == syscall.EACCES) {
+ if errno, ok := errors.AsType[syscall.Errno](err); ok && (errors.Is(errno, syscall.ENOTEMPTY) || errors.Is(errno, syscall.EACCES)) {
slog.DebugContext(ctx, "swarm stack source environment directory cleanup skipped", "dir", environmentDir, "error", err)
return nil
}
diff --git a/backend/internal/services/template_service.go b/backend/internal/services/template_service.go
index 77ddc5a4bf..047a2b77f5 100644
--- a/backend/internal/services/template_service.go
+++ b/backend/internal/services/template_service.go
@@ -325,15 +325,15 @@ func (s *TemplateService) DeleteTemplate(ctx context.Context, id string) error {
baseDir, err := projects.GetTemplatesDirectory(ctx)
if err != nil {
return fmt.Errorf("failed to get templates directory: %w", err)
- } else {
- templatePath := filepath.Join(baseDir, existing.Name)
-
- if stat, err := os.Stat(templatePath); err == nil && stat.IsDir() {
- composeFile := filepath.Join(templatePath, "compose.yaml")
- if _, err := os.Stat(composeFile); err == nil {
- if err := os.RemoveAll(templatePath); err != nil {
- return fmt.Errorf("failed to delete template directory: %w", err)
- }
+ }
+
+ templatePath := filepath.Join(baseDir, existing.Name)
+
+ if stat, err := os.Stat(templatePath); err == nil && stat.IsDir() {
+ composeFile := filepath.Join(templatePath, "compose.yaml")
+ if _, err := os.Stat(composeFile); err == nil {
+ if err := os.RemoveAll(templatePath); err != nil {
+ return fmt.Errorf("failed to delete template directory: %w", err)
}
}
}
@@ -1061,7 +1061,7 @@ func (s *TemplateService) GetGlobalVariables(ctx context.Context) ([]env.Variabl
}
defer func() { _ = file.Close() }()
- vars := []env.Variable{}
+ var vars []env.Variable
scanner := bufio.NewScanner(file)
lineNum := 0
diff --git a/backend/internal/services/vulnerability_service.go b/backend/internal/services/vulnerability_service.go
index d6ea69c79a..ec8aa72e36 100644
--- a/backend/internal/services/vulnerability_service.go
+++ b/backend/internal/services/vulnerability_service.go
@@ -2355,9 +2355,9 @@ func logTrivyContainerStartupRequestInternal(ctx context.Context, scope string,
resources := containertypes.Resources{}
autoRemove := false
- mounts := []string{}
+ var mounts []string
networkMode := ""
- securityOpts := []string{}
+ var securityOpts []string
privileged := false
if hostConfig != nil {
resources = hostConfig.Resources
diff --git a/backend/pkg/libarcane/edge/http_client.go b/backend/pkg/libarcane/edge/http_client.go
index 96804e4205..d297386d8b 100644
--- a/backend/pkg/libarcane/edge/http_client.go
+++ b/backend/pkg/libarcane/edge/http_client.go
@@ -12,19 +12,19 @@ import (
// EdgeAwareClient is an HTTP client that automatically routes requests
// to edge environments through the active edge tunnel instead of direct HTTP.
// Works with both gRPC and WebSocket tunnel transports.
-type EdgeAwareClient struct {
+type AwareClient struct {
httpClient *http.Client
}
// NewEdgeAwareClient creates a new edge-aware HTTP client
-func NewEdgeAwareClient(timeout time.Duration) *EdgeAwareClient {
- return &EdgeAwareClient{
+func NewEdgeAwareClient(timeout time.Duration) *AwareClient {
+ return &AwareClient{
httpClient: &http.Client{Timeout: timeout},
}
}
// EdgeResponse wraps the response from either direct HTTP or tunnel request
-type EdgeResponse struct {
+type Response struct {
StatusCode int
Body []byte
Headers map[string]string
@@ -43,7 +43,7 @@ type EdgeResponse struct {
// - body: request body (can be nil)
//
// Returns EdgeResponse with status code, body bytes, and headers
-func (c *EdgeAwareClient) DoForEnvironment(
+func (c *AwareClient) DoForEnvironment(
ctx context.Context,
envID string,
isEdge bool,
@@ -52,7 +52,7 @@ func (c *EdgeAwareClient) DoForEnvironment(
path string,
headers map[string]string,
body []byte,
-) (*EdgeResponse, error) {
+) (*Response, error) {
// For edge environments with active tunnels, route through tunnel
if isEdge && HasActiveTunnel(envID) {
return c.doViaTunnel(ctx, envID, method, path, headers, body)
@@ -70,14 +70,14 @@ func (c *EdgeAwareClient) DoForEnvironment(
}
// doViaTunnel routes the request through the edge tunnel
-func (c *EdgeAwareClient) doViaTunnel(
+func (c *AwareClient) doViaTunnel(
ctx context.Context,
envID string,
method string,
path string,
headers map[string]string,
body []byte,
-) (*EdgeResponse, error) {
+) (*Response, error) {
tunnel, ok := GetRegistry().Get(envID)
if !ok {
return nil, fmt.Errorf("no active tunnel for environment %s", envID)
@@ -92,7 +92,7 @@ func (c *EdgeAwareClient) doViaTunnel(
return nil, fmt.Errorf("tunnel request failed: %w", err)
}
- return &EdgeResponse{
+ return &Response{
StatusCode: statusCode,
Body: respBody,
Headers: respHeaders,
@@ -100,13 +100,13 @@ func (c *EdgeAwareClient) doViaTunnel(
}
// doDirectHTTP makes a direct HTTP request
-func (c *EdgeAwareClient) doDirectHTTP(
+func (c *AwareClient) doDirectHTTP(
ctx context.Context,
method string,
url string,
headers map[string]string,
body []byte,
-) (*EdgeResponse, error) {
+) (*Response, error) {
var bodyReader io.Reader
if body != nil {
bodyReader = bytes.NewReader(body)
@@ -141,7 +141,7 @@ func (c *EdgeAwareClient) doDirectHTTP(
}
}
- return &EdgeResponse{
+ return &Response{
StatusCode: resp.StatusCode,
Body: respBody,
Headers: respHeaders,
@@ -151,8 +151,7 @@ func (c *EdgeAwareClient) doDirectHTTP(
// DefaultEdgeAwareClient is a singleton client with reasonable defaults
var DefaultEdgeAwareClient = NewEdgeAwareClient(30 * time.Second)
-// DoRequest is a convenience function for making edge-aware requests
-// using the default client
+// DoEdgeAwareRequest is a convenience function for making edge-aware requests using the default client
func DoEdgeAwareRequest(
ctx context.Context,
envID string,
@@ -162,6 +161,6 @@ func DoEdgeAwareRequest(
path string,
headers map[string]string,
body []byte,
-) (*EdgeResponse, error) {
+) (*Response, error) {
return DefaultEdgeAwareClient.DoForEnvironment(ctx, envID, isEdge, method, url, path, headers, body)
}
diff --git a/backend/pkg/libarcane/libbuild/builder_buildkit.go b/backend/pkg/libarcane/libbuild/builder_buildkit.go
index a2cce4ffd3..f7dd14c0a4 100644
--- a/backend/pkg/libarcane/libbuild/builder_buildkit.go
+++ b/backend/pkg/libarcane/libbuild/builder_buildkit.go
@@ -11,6 +11,7 @@ import (
dockerutils "github.com/getarcaneapp/arcane/backend/pkg/dockerutil"
imagetypes "github.com/getarcaneapp/arcane/types/image"
buildkit "github.com/moby/buildkit/client"
+ "github.com/tonistiigi/fsutil"
)
func parseBuildkitCacheEntriesInternal(values []string) []buildkit.CacheOptionsEntry {
@@ -99,6 +100,39 @@ func normalizeEntitlementsInternal(entitlements []string, privileged bool) []str
return out
}
+func resolveBuildkitDockerfileMountInternal(contextDir, dockerfilePath string) (string, string) {
+ dockerfileRelDir := filepath.Dir(filepath.FromSlash(dockerfilePath))
+ if dockerfileRelDir == "." {
+ return contextDir, dockerfilePath
+ }
+
+ return filepath.Join(contextDir, dockerfileRelDir), filepath.Base(dockerfilePath)
+}
+
+func createBuildkitLocalMountsInternal(contextDir, dockerfileDir string) (map[string]fsutil.FS, error) {
+ contextMount, err := fsutil.NewFS(contextDir)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create context mount: %w", err)
+ }
+
+ if dockerfileDir == contextDir {
+ return map[string]fsutil.FS{
+ "context": contextMount,
+ "dockerfile": contextMount,
+ }, nil
+ }
+
+ dockerfileMount, err := fsutil.NewFS(dockerfileDir)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create dockerfile mount: %w", err)
+ }
+
+ return map[string]fsutil.FS{
+ "context": contextMount,
+ "dockerfile": dockerfileMount,
+ }, nil
+}
+
func (b *builder) buildSolveOptInternal(ctx context.Context, req imagetypes.BuildRequest, providerName string) (buildkit.SolveOpt, <-chan error, func(), error) {
fsInput, err := prepareBuildFilesystemInputInternal(req)
if err != nil {
@@ -110,11 +144,11 @@ func (b *builder) buildSolveOptInternal(ctx context.Context, req imagetypes.Buil
return buildkit.SolveOpt{}, nil, nil, err
}
- dockerfileDir := contextDir
- dockerfileFilename := dockerfilePath
- if dockerfileRelDir := filepath.Dir(filepath.FromSlash(dockerfilePath)); dockerfileRelDir != "." {
- dockerfileDir = filepath.Join(contextDir, dockerfileRelDir)
- dockerfileFilename = filepath.Base(dockerfilePath)
+ dockerfileDir, dockerfileFilename := resolveBuildkitDockerfileMountInternal(contextDir, dockerfilePath)
+ localMounts, err := createBuildkitLocalMountsInternal(contextDir, dockerfileDir)
+ if err != nil {
+ cleanup()
+ return buildkit.SolveOpt{}, nil, nil, err
}
frontendAttrs := map[string]string{
@@ -144,12 +178,9 @@ func (b *builder) buildSolveOptInternal(ctx context.Context, req imagetypes.Buil
}
solveOpt := buildkit.SolveOpt{
- Frontend: "dockerfile.v0",
- FrontendAttrs: frontendAttrs,
- LocalDirs: map[string]string{
- "context": contextDir,
- "dockerfile": dockerfileDir,
- },
+ Frontend: "dockerfile.v0",
+ FrontendAttrs: frontendAttrs,
+ LocalMounts: localMounts,
CacheImports: parseBuildkitCacheEntriesInternal(req.CacheFrom),
CacheExports: parseBuildkitCacheEntriesInternal(req.CacheTo),
AllowedEntitlements: normalizeEntitlementsInternal(req.Entitlements, req.Privileged),
diff --git a/backend/pkg/libarcane/libbuild/builder_buildkit_test.go b/backend/pkg/libarcane/libbuild/builder_buildkit_test.go
index 19fb164fbf..a9000a1e2d 100644
--- a/backend/pkg/libarcane/libbuild/builder_buildkit_test.go
+++ b/backend/pkg/libarcane/libbuild/builder_buildkit_test.go
@@ -34,16 +34,24 @@ func TestBuildSolveOptInternal_StagesInlineDockerfile(t *testing.T) {
assert.Nil(t, loadErrCh)
assert.Equal(t, ".arcane.inline.Dockerfile", solveOpt.FrontendAttrs["filename"])
- contextPath := solveOpt.LocalDirs["context"]
- dockerfileDir := solveOpt.LocalDirs["dockerfile"]
- assert.NotEmpty(t, contextPath)
- assert.Equal(t, contextPath, dockerfileDir)
+ contextMount := solveOpt.LocalMounts["context"]
+ dockerfileMount := solveOpt.LocalMounts["dockerfile"]
+ require.NotNil(t, contextMount)
+ require.NotNil(t, dockerfileMount)
- contents, err := os.ReadFile(filepath.Join(dockerfileDir, solveOpt.FrontendAttrs["filename"]))
+ dockerfileReader, err := dockerfileMount.Open(solveOpt.FrontendAttrs["filename"])
+ require.NoError(t, err)
+ defer dockerfileReader.Close()
+
+ contents, err := io.ReadAll(dockerfileReader)
require.NoError(t, err)
assert.Equal(t, "FROM alpine:3.20\nCOPY app.txt /app.txt\n", string(contents))
- appContents, err := os.ReadFile(filepath.Join(contextPath, "app.txt"))
+ appReader, err := contextMount.Open("app.txt")
+ require.NoError(t, err)
+ defer appReader.Close()
+
+ appContents, err := io.ReadAll(appReader)
require.NoError(t, err)
assert.Equal(t, "hello\n", string(appContents))
}
diff --git a/backend/pkg/libarcane/libbuild/builder_docker.go b/backend/pkg/libarcane/libbuild/builder_docker.go
index f2855f659f..e67adec980 100644
--- a/backend/pkg/libarcane/libbuild/builder_docker.go
+++ b/backend/pkg/libarcane/libbuild/builder_docker.go
@@ -584,7 +584,7 @@ func readDockerignoreInternal(contextDir string) []string {
}
defer file.Close()
- patterns := []string{}
+ var patterns []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
diff --git a/backend/pkg/libarcane/startup/startup.go b/backend/pkg/libarcane/startup/startup.go
index f554f7f338..63b2a4ff3d 100644
--- a/backend/pkg/libarcane/startup/startup.go
+++ b/backend/pkg/libarcane/startup/startup.go
@@ -386,28 +386,28 @@ func cronToMinutes(fields []string) (int, bool, string) {
return 0, false, ""
}
- sec, min, hour, day, month, weekday := fields[0], fields[1], fields[2], fields[3], fields[4], fields[5]
+ sec, minute, hour, day, month, weekday := fields[0], fields[1], fields[2], fields[3], fields[4], fields[5]
- if mins, ok, warn := tryConvertSecondStep(sec, min, hour, day, month, weekday); ok {
+ if mins, ok, warn := tryConvertSecondStep(sec, minute, hour, day, month, weekday); ok {
return mins, ok, warn
}
- if mins, ok, warn := tryConvertMinuteOrHour(sec, min, hour, day, month, weekday); ok {
+ if mins, ok, warn := tryConvertMinuteOrHour(sec, minute, hour, day, month, weekday); ok {
return mins, ok, warn
}
- if mins, ok, warn := tryConvertDayStep(sec, min, hour, day, month, weekday); ok {
+ if mins, ok, warn := tryConvertDayStep(sec, minute, hour, day, month, weekday); ok {
return mins, ok, warn
}
return 0, false, ""
}
-func tryConvertSecondStep(sec, min, hour, day, month, weekday string) (int, bool, string) {
+func tryConvertSecondStep(sec, minute, hour, day, month, weekday string) (int, bool, string) {
step, ok := parseCronStep(sec)
if !ok {
return 0, false, ""
}
- if min == "*" && hour == "*" && day == "*" && month == "*" && weekday == "*" {
+ if minute == "*" && hour == "*" && day == "*" && month == "*" && weekday == "*" {
minutes := max((step+59)/60, 1)
warn := ""
if step%60 != 0 {
@@ -418,18 +418,18 @@ func tryConvertSecondStep(sec, min, hour, day, month, weekday string) (int, bool
return 0, false, ""
}
-func tryConvertMinuteOrHour(sec, min, hour, day, month, weekday string) (int, bool, string) {
+func tryConvertMinuteOrHour(sec, minute, hour, day, month, weekday string) (int, bool, string) {
if (sec != "0" && sec != "*") || day != "*" || month != "*" || weekday != "*" {
return 0, false, ""
}
- if step, ok := parseCronStep(min); ok && hour == "*" {
+ if step, ok := parseCronStep(minute); ok && hour == "*" {
return step, true, ""
}
- if min == "*" && hour == "*" {
+ if minute == "*" && hour == "*" {
return 1, true, ""
}
- if min == "0" {
+ if minute == "0" {
if step, ok := parseCronStep(hour); ok && day == "*" {
return step * 60, true, ""
}
@@ -440,8 +440,8 @@ func tryConvertMinuteOrHour(sec, min, hour, day, month, weekday string) (int, bo
return 0, false, ""
}
-func tryConvertDayStep(sec, min, hour, day, month, weekday string) (int, bool, string) {
- if (sec == "0" || sec == "*") && min == "0" && hour == "0" && month == "*" && weekday == "*" {
+func tryConvertDayStep(sec, minute, hour, day, month, weekday string) (int, bool, string) {
+ if (sec == "0" || sec == "*") && minute == "0" && hour == "0" && month == "*" && weekday == "*" {
if step, ok := parseCronStep(day); ok {
return step * 1440, true, ""
}
diff --git a/backend/pkg/libarcane/updater/digest_test.go b/backend/pkg/libarcane/updater/digest_test.go
index 64c67be6aa..e672d111aa 100644
--- a/backend/pkg/libarcane/updater/digest_test.go
+++ b/backend/pkg/libarcane/updater/digest_test.go
@@ -3,7 +3,7 @@ package updater
import (
"testing"
- digest "github.com/opencontainers/go-digest"
+ "github.com/opencontainers/go-digest"
"github.com/stretchr/testify/assert"
)
diff --git a/backend/pkg/libarcane/updater/labels.go b/backend/pkg/libarcane/updater/labels.go
index e7453cde87..6476edd414 100644
--- a/backend/pkg/libarcane/updater/labels.go
+++ b/backend/pkg/libarcane/updater/labels.go
@@ -3,14 +3,17 @@ package updater
import "strings"
const (
- // Core labels
- LabelArcane = "com.getarcaneapp.arcane" // Identifies the Arcane container itself
- LabelArcaneAgent = "com.getarcaneapp.arcane.agent" // Identifies an Arcane agent container
- LabelUpdater = "com.getarcaneapp.arcane.updater" // Enable/disable updates (true/false)
-
- // Dependency labels
- LabelDependsOn = "com.getarcaneapp.arcane.depends-on" // Comma-separated list of container names this depends on
- LabelStopSignal = "com.getarcaneapp.arcane.stop-signal" // Custom stop signal (e.g., SIGINT)
+ // LabelArcane Identifies the Arcane container itself
+ LabelArcane = "com.getarcaneapp.arcane"
+ // LabelArcaneAgent Identifies an Arcane agent container
+ LabelArcaneAgent = "com.getarcaneapp.arcane.agent"
+ // LabelUpdater Enable/disable updates (true/false)
+ LabelUpdater = "com.getarcaneapp.arcane.updater"
+
+ // LabelDependsOn Comma-separated list of container names the selected container depends on
+ LabelDependsOn = "com.getarcaneapp.arcane.depends-on"
+ // LabelStopSignal Custom stop signal (e.g., SIGINT)
+ LabelStopSignal = "com.getarcaneapp.arcane.stop-signal"
)
// IsArcaneContainer checks if the container is the Arcane application itself
diff --git a/backend/pkg/libarcane/ws/hub_test.go b/backend/pkg/libarcane/ws/hub_test.go
index 7be11cc56c..2ee325a488 100644
--- a/backend/pkg/libarcane/ws/hub_test.go
+++ b/backend/pkg/libarcane/ws/hub_test.go
@@ -337,7 +337,7 @@ func TestHub_ConcurrentOperations(t *testing.T) {
}, goroutines)
for i := range goroutines {
_, pairs[i].sc, pairs[i].cleanup = newTestWSPair(t)
- defer pairs[i].cleanup()
+ t.Cleanup(pairs[i].cleanup)
}
for i := range goroutines {
diff --git a/backend/pkg/pagination/filters.go b/backend/pkg/pagination/filters.go
index 399667bf6f..b29687b1b7 100644
--- a/backend/pkg/pagination/filters.go
+++ b/backend/pkg/pagination/filters.go
@@ -41,7 +41,7 @@ func filterFn[T any](items []T, filters map[string]string, accessors []FilterAcc
return items
}
- results := []T{}
+ var results []T
for _, item := range items {
if itemMatches(item, filters, accessors) {
results = append(results, item)
diff --git a/backend/pkg/pagination/search.go b/backend/pkg/pagination/search.go
index 3b9ba0fa2c..59b3d4d524 100644
--- a/backend/pkg/pagination/search.go
+++ b/backend/pkg/pagination/search.go
@@ -4,7 +4,7 @@ import (
"strings"
)
-// Return any error to skip the field (for when matching an unknown state on an enum)
+// SearchAccessor Will Return any error to skip the field (for when matching an unknown state on an enum)
//
// Note: returning ("", nil) will match!
type SearchAccessor[T any] = func(T) (string, error)
@@ -20,7 +20,7 @@ func searchFn[T any](items []T, params SearchQuery, accessors []SearchAccessor[T
return items
}
- results := []T{}
+ var results []T
for iIdx := range items {
for aIdx := range accessors {
diff --git a/backend/pkg/projects/includes.go b/backend/pkg/projects/includes.go
index 967cf14b8d..578510b48f 100644
--- a/backend/pkg/projects/includes.go
+++ b/backend/pkg/projects/includes.go
@@ -167,11 +167,14 @@ func ValidateIncludePathForWrite(projectDir, includePath string) (string, error)
// Resolve symlinks in the include path to prevent symlink-based path traversal attacks
evalPath := absFullPath
- if evalFullPath, err := filepath.EvalSymlinks(absFullPath); err == nil {
+ evalFullPath, err := filepath.EvalSymlinks(absFullPath)
+ if err == nil {
evalPath = evalFullPath
} else if !errors.Is(err, os.ErrNotExist) {
return "", fmt.Errorf("failed to resolve include path: %w", err)
- } else {
+ }
+
+ if errors.Is(err, os.ErrNotExist) {
// File doesn't exist yet - evaluate parent directory symlinks
dir := filepath.Dir(absFullPath)
if evalDir, err := filepath.EvalSymlinks(dir); err == nil {
diff --git a/backend/pkg/scheduler/analytics_job.go b/backend/pkg/scheduler/analytics_job.go
index 12b0a2f60b..ac0e4f5b60 100644
--- a/backend/pkg/scheduler/analytics_job.go
+++ b/backend/pkg/scheduler/analytics_job.go
@@ -12,13 +12,13 @@ import (
"sync"
"time"
- backoff "github.com/cenkalti/backoff/v5"
+ "github.com/cenkalti/backoff/v5"
"github.com/getarcaneapp/arcane/backend/internal/config"
"github.com/getarcaneapp/arcane/backend/internal/services"
)
const (
- AnalyticsJobName = "analytics-heartbeat"
+ analyticsJobName = "analytics-heartbeat"
defaultHeartbeatEndpoint = "https://checkin.getarcane.app/heartbeat"
devHeartbeatEndpoint = "http://localhost:8080/heartbeat"
analyticsHeartbeatLastAttemptKey = "analytics.heartbeat.last_attempt_at"
@@ -60,7 +60,7 @@ func NewAnalyticsJob(
}
func (j *AnalyticsJob) Name() string {
- return AnalyticsJobName
+ return analyticsJobName
}
func (j *AnalyticsJob) Schedule(_ context.Context) string {
@@ -108,7 +108,7 @@ func (j *AnalyticsJob) Run(ctx context.Context) {
ctx,
"sending analytics heartbeat",
"jobName",
- AnalyticsJobName,
+ analyticsJobName,
"version",
payload.Version,
"instanceID",
@@ -172,7 +172,7 @@ func (j *AnalyticsJob) Run(ctx context.Context) {
ctx,
"analytics heartbeat sent successfully",
"jobName",
- AnalyticsJobName,
+ analyticsJobName,
"version",
payload.Version,
"instanceID",
@@ -225,7 +225,7 @@ func (j *AnalyticsJob) claimHeartbeatAttemptWindowInternal(ctx context.Context)
ctx,
"skipping analytics heartbeat; already attempted within dedupe window",
"jobName",
- AnalyticsJobName,
+ analyticsJobName,
"lastAttemptAt",
lastAttemptAt,
"nextEligibleAt",
diff --git a/backend/pkg/scheduler/vulnerability_scan_job.go b/backend/pkg/scheduler/vulnerability_scan_job.go
index 126c59d342..6f17433cf9 100644
--- a/backend/pkg/scheduler/vulnerability_scan_job.go
+++ b/backend/pkg/scheduler/vulnerability_scan_job.go
@@ -91,7 +91,7 @@ func (j *VulnerabilityScanJob) Run(ctx context.Context) {
slog.InfoContext(ctx, "cleaned up orphaned vulnerability scan records", "deleted", deleted)
}
- scanned, failed, err := j.vulnerabilityService.ScanAllImages(ctx, types.LOCAL_DOCKER_ENVIRONMENT_ID, vulnerabilityScanSystemUser)
+ scanned, failed, err := j.vulnerabilityService.ScanAllImages(ctx, types.LocalDockerEnvironmentId, vulnerabilityScanSystemUser)
if err != nil {
slog.ErrorContext(ctx, "scheduled vulnerability scan failed", "error", err)
return
diff --git a/backend/pkg/utils/distribution/distribution_test.go b/backend/pkg/utils/distribution/distribution_test.go
index 5d201c381b..3f2d85d3a0 100644
--- a/backend/pkg/utils/distribution/distribution_test.go
+++ b/backend/pkg/utils/distribution/distribution_test.go
@@ -7,7 +7,7 @@ import (
"net/http/httptest"
"testing"
- digest "github.com/opencontainers/go-digest"
+ "github.com/opencontainers/go-digest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
diff --git a/backend/pkg/utils/imagedigest/digest.go b/backend/pkg/utils/imagedigest/digest.go
index d6e6987911..d63f1ef908 100644
--- a/backend/pkg/utils/imagedigest/digest.go
+++ b/backend/pkg/utils/imagedigest/digest.go
@@ -4,7 +4,7 @@ import (
"fmt"
"strings"
- digest "github.com/opencontainers/go-digest"
+ "github.com/opencontainers/go-digest"
)
func Normalize(value string) (string, error) {
diff --git a/backend/pkg/utils/imagedigest/digest_test.go b/backend/pkg/utils/imagedigest/digest_test.go
index 6015b9b3ef..3b6a023ed9 100644
--- a/backend/pkg/utils/imagedigest/digest_test.go
+++ b/backend/pkg/utils/imagedigest/digest_test.go
@@ -3,7 +3,7 @@ package imagedigest
import (
"testing"
- digest "github.com/opencontainers/go-digest"
+ "github.com/opencontainers/go-digest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
diff --git a/backend/pkg/utils/jwtclaims/jwt.go b/backend/pkg/utils/jwtclaims/jwt.go
index 9b0bb992f9..f5c88e136e 100644
--- a/backend/pkg/utils/jwtclaims/jwt.go
+++ b/backend/pkg/utils/jwtclaims/jwt.go
@@ -89,12 +89,13 @@ func CheckOrGenerateJwtSecret(jwtSecret string) []byte {
if jwtSecret != "" {
secretBytes = []byte(jwtSecret)
return secretBytes
- } else {
- secretBytes = make([]byte, 32)
- if _, err := rand.Read(secretBytes); err != nil {
- panic(fmt.Errorf("failed to generate random JWT secret: %w", err))
- }
}
+
+ secretBytes = make([]byte, 32)
+ if _, err := rand.Read(secretBytes); err != nil {
+ panic(fmt.Errorf("failed to generate random JWT secret: %w", err))
+ }
+
return secretBytes
}
diff --git a/backend/pkg/utils/reflection.go b/backend/pkg/utils/reflection.go
index e937ac88c4..931ca8de7a 100644
--- a/backend/pkg/utils/reflection.go
+++ b/backend/pkg/utils/reflection.go
@@ -28,7 +28,7 @@ func ParseMetaTag(tag string) map[string]string {
// ParseKeywords parses a comma-separated keywords string into a slice
// Returns an empty slice if the input is empty or contains only whitespace
func ParseKeywords(keywordsStr string) []string {
- keywords := []string{}
+ var keywords []string
if k := strings.TrimSpace(keywordsStr); k != "" {
for kk := range strings.SplitSeq(k, ",") {
if t := strings.TrimSpace(kk); t != "" {
diff --git a/backend/pkg/utils/registry/helpers.go b/backend/pkg/utils/registry/helpers.go
index 3043955b22..d385e4e898 100644
--- a/backend/pkg/utils/registry/helpers.go
+++ b/backend/pkg/utils/registry/helpers.go
@@ -95,7 +95,7 @@ func DecodeAuthHeader(authEncoded string) (dockerregistry.AuthConfig, error) {
return *cfg, nil
}
-func RegistryAuthLookupKeys(url string) []string {
+func AuthLookupKeys(url string) []string {
normalizedHost := NormalizeRegistryForComparison(url)
if normalizedHost == "" {
return nil
diff --git a/backend/pkg/utils/registry/helpers_test.go b/backend/pkg/utils/registry/helpers_test.go
index c68cd7a9bd..bb7d78c800 100644
--- a/backend/pkg/utils/registry/helpers_test.go
+++ b/backend/pkg/utils/registry/helpers_test.go
@@ -81,7 +81,7 @@ func TestDecodeAuthHeader_InvalidInput(t *testing.T) {
}
func TestRegistryAuthLookupKeys(t *testing.T) {
- assert.Equal(t, []string{"ghcr.io"}, RegistryAuthLookupKeys("https://GHCR.IO/"))
- assert.Equal(t, []string{"docker.io", "index.docker.io", "registry-1.docker.io"}, RegistryAuthLookupKeys("https://index.docker.io/v1/"))
- assert.Nil(t, RegistryAuthLookupKeys(" "))
+ assert.Equal(t, []string{"ghcr.io"}, AuthLookupKeys("https://GHCR.IO/"))
+ assert.Equal(t, []string{"docker.io", "index.docker.io", "registry-1.docker.io"}, AuthLookupKeys("https://index.docker.io/v1/"))
+ assert.Nil(t, AuthLookupKeys(" "))
}
diff --git a/cli/go.mod b/cli/go.mod
index 8c733d4a4e..baef921d95 100644
--- a/cli/go.mod
+++ b/cli/go.mod
@@ -24,7 +24,7 @@ require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/charmbracelet/colorprofile v0.4.3 // indirect
github.com/charmbracelet/harmonica v0.2.0 // indirect
- github.com/charmbracelet/ultraviolet v0.0.0-20260416155717-489999b90468 // indirect
+ github.com/charmbracelet/ultraviolet v0.0.0-20260416161146-9c68a866306c // indirect
github.com/charmbracelet/x/ansi v0.11.7 // indirect
github.com/charmbracelet/x/termios v0.1.1 // indirect
github.com/charmbracelet/x/windows v0.2.2 // indirect
@@ -43,8 +43,8 @@ require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.4.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
- github.com/mattn/go-isatty v0.0.20 // indirect
- github.com/mattn/go-shellwords v1.0.12 // indirect
+ github.com/mattn/go-isatty v0.0.21 // indirect
+ github.com/mattn/go-shellwords v1.0.13 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/moby/api v1.54.2 // indirect
github.com/moby/moby/client v0.4.1 // indirect
@@ -61,7 +61,7 @@ require (
github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
- go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect
go.opentelemetry.io/otel v1.43.0 // indirect
go.opentelemetry.io/otel/metric v1.43.0 // indirect
go.opentelemetry.io/otel/trace v1.43.0 // indirect
diff --git a/cli/go.sum b/cli/go.sum
index 4ede7ef899..9511f8c1f2 100644
--- a/cli/go.sum
+++ b/cli/go.sum
@@ -16,8 +16,8 @@ github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex
github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q=
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
-github.com/charmbracelet/ultraviolet v0.0.0-20260416155717-489999b90468 h1:Q9fO0y1Zo5KB/5Vu8JZoLGm1N3RzF9bNj3Ao3xoR+Ac=
-github.com/charmbracelet/ultraviolet v0.0.0-20260416155717-489999b90468/go.mod h1:bAAz7dh/FTYfC+oiHavL4mX1tOIBZ0ZwYjSi3qE6ivM=
+github.com/charmbracelet/ultraviolet v0.0.0-20260416161146-9c68a866306c h1:a+Q3cOt8vEb6ETG/st32Qjm8R5fdI9wSKb3tqPISnoY=
+github.com/charmbracelet/ultraviolet v0.0.0-20260416161146-9c68a866306c/go.mod h1:bAAz7dh/FTYfC+oiHavL4mX1tOIBZ0ZwYjSi3qE6ivM=
github.com/charmbracelet/x/ansi v0.11.7 h1:kzv1kJvjg2S3r9KHo8hDdHFQLEqn4RBCb39dAYC84jI=
github.com/charmbracelet/x/ansi v0.11.7/go.mod h1:9qGpnAVYz+8ACONkZBUWPtL7lulP9No6p1epAihUZwQ=
github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f h1:pk6gmGpCE7F3FcjaOEKYriCvpmIN4+6OS/RD0vm4uIA=
@@ -44,6 +44,7 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8Yc
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c=
+github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
@@ -78,12 +79,12 @@ github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW
github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
-github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
-github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mattn/go-isatty v0.0.21 h1:xYae+lCNBP7QuW4PUnNG61ffM4hVIfm+zUzDuSzYLGs=
+github.com/mattn/go-isatty v0.0.21/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw=
github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
-github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
-github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
+github.com/mattn/go-shellwords v1.0.13 h1:DC0OMEpGjm6LfNFU4ckYcvbQKyp2vE8atyFGXNtDcf4=
+github.com/mattn/go-shellwords v1.0.13/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/moby/api v1.54.2 h1:wiat9QAhnDQjA7wk1kh/TqHz2I1uUA7M7t9SAl/JNXg=
@@ -130,8 +131,8 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
-go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18=
-go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo=
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
@@ -152,7 +153,6 @@ golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8t
golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
-golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
diff --git a/cli/internal/config/config.go b/cli/internal/config/config.go
index 6d2cd3b76d..de8c84bd0c 100644
--- a/cli/internal/config/config.go
+++ b/cli/internal/config/config.go
@@ -124,7 +124,7 @@ func DefaultConfig() *types.Config {
// ConfigPath returns the absolute path to the configuration file.
// The config file is located at ~/.config/arcanecli.yml.
// Returns an error if the user's home directory cannot be determined.
-func ConfigPath() (string, error) {
+func Path() (string, error) {
if customConfigPath != "" {
return customConfigPath, nil
}
@@ -170,7 +170,7 @@ func SetConfigPath(path string) error {
// If the config file does not exist, default values are returned.
// Returns an error if the file exists but cannot be read or parsed.
func Load() (*types.Config, error) {
- path, err := ConfigPath()
+ path, err := Path()
if err != nil {
return nil, err
}
@@ -230,7 +230,7 @@ func Load() (*types.Config, error) {
// The config directory is created if it does not exist.
// The file is created with 0600 permissions for security.
func Save(c *types.Config) error {
- path, err := ConfigPath()
+ path, err := Path()
if err != nil {
return err
}
@@ -311,7 +311,7 @@ func Save(c *types.Config) error {
// not already exist. It returns true when a file is created, or false when an
// existing file is left unchanged.
func InitDefaultFile() (bool, error) {
- path, err := ConfigPath()
+ path, err := Path()
if err != nil {
return false, err
}
@@ -370,7 +370,7 @@ func InitDefaultFile() (bool, error) {
// original file from its previous location. If no config file exists, it
// returns moved=false and no error.
func BackupFile() (backupPath string, moved bool, err error) {
- path, err := ConfigPath()
+ path, err := Path()
if err != nil {
return "", false, err
}
diff --git a/cli/pkg/admin/apikeys/cmd.go b/cli/pkg/admin/apikeys/cmd.go
index dab8200e6c..e706f945b0 100644
--- a/cli/pkg/admin/apikeys/cmd.go
+++ b/cli/pkg/admin/apikeys/cmd.go
@@ -27,8 +27,7 @@ var (
apikeyUpdateExpiresAt string
)
-// ApiKeysCmd is the parent command for API key operations
-var ApiKeysCmd = &cobra.Command{
+var Cmd = &cobra.Command{
Use: "api-keys",
Aliases: []string{"apikey", "keys", "key"},
Short: "Manage API keys",
@@ -63,12 +62,7 @@ var listCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result)
}
headers := []string{"ID", "NAME", "DESCRIPTION", "CREATED", "LAST USED"}
@@ -144,12 +138,7 @@ var createCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Success("API key created successfully")
@@ -200,12 +189,7 @@ var deleteCmd = &cobra.Command{
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return fmt.Errorf("failed to parse response: %w", err)
}
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Success("API key deleted successfully")
@@ -236,12 +220,7 @@ var getCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Header("API Key Details")
@@ -299,12 +278,10 @@ var updateCmd = &cobra.Command{
if jsonOutput {
var result base.ApiResponse[any]
- if err := json.NewDecoder(resp.Body).Decode(&result); err == nil {
- if resultBytes, err := json.MarshalIndent(result.Data, "", " "); err == nil {
- fmt.Println(string(resultBytes))
- }
+ if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
+ return fmt.Errorf("failed to parse response: %w", err)
}
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Success("API key updated successfully")
@@ -313,11 +290,11 @@ var updateCmd = &cobra.Command{
}
func init() {
- ApiKeysCmd.AddCommand(listCmd)
- ApiKeysCmd.AddCommand(createCmd)
- ApiKeysCmd.AddCommand(getCmd)
- ApiKeysCmd.AddCommand(updateCmd)
- ApiKeysCmd.AddCommand(deleteCmd)
+ Cmd.AddCommand(listCmd)
+ Cmd.AddCommand(createCmd)
+ Cmd.AddCommand(getCmd)
+ Cmd.AddCommand(updateCmd)
+ Cmd.AddCommand(deleteCmd)
// List command flags
listCmd.Flags().IntVarP(&limitFlag, "limit", "n", 20, "Number of API keys to show")
diff --git a/cli/pkg/admin/cmd.go b/cli/pkg/admin/cmd.go
index c671a04518..6d1e4c7910 100644
--- a/cli/pkg/admin/cmd.go
+++ b/cli/pkg/admin/cmd.go
@@ -8,16 +8,15 @@ import (
"github.com/spf13/cobra"
)
-// AdminCmd is the parent command for administrative operations.
-var AdminCmd = &cobra.Command{
+var Cmd = &cobra.Command{
Use: "admin",
Aliases: []string{"adm"},
Short: "Administration & platform management",
}
func init() {
- AdminCmd.AddCommand(users.UsersCmd)
- AdminCmd.AddCommand(apikeys.ApiKeysCmd)
- AdminCmd.AddCommand(events.EventsCmd)
- AdminCmd.AddCommand(notifications.NotificationsCmd)
+ Cmd.AddCommand(users.Cmd)
+ Cmd.AddCommand(apikeys.Cmd)
+ Cmd.AddCommand(events.Cmd)
+ Cmd.AddCommand(notifications.Cmd)
}
diff --git a/cli/pkg/admin/events/cmd.go b/cli/pkg/admin/events/cmd.go
index 42bab6f229..0d4f98da82 100644
--- a/cli/pkg/admin/events/cmd.go
+++ b/cli/pkg/admin/events/cmd.go
@@ -20,8 +20,7 @@ var (
jsonOutput bool
)
-// EventsCmd is the parent command for event operations
-var EventsCmd = &cobra.Command{
+var Cmd = &cobra.Command{
Use: "events",
Aliases: []string{"event", "evt"},
Short: "Manage events",
@@ -56,12 +55,7 @@ var listCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result)
}
headers := []string{"ID", "TYPE", "RESOURCE", "USER", "TIMESTAMP"}
@@ -118,12 +112,7 @@ var listEnvCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result)
}
headers := []string{"ID", "TYPE", "RESOURCE", "USER", "TIMESTAMP"}
@@ -190,9 +179,9 @@ var deleteCmd = &cobra.Command{
}
func init() {
- EventsCmd.AddCommand(listCmd)
- EventsCmd.AddCommand(listEnvCmd)
- EventsCmd.AddCommand(deleteCmd)
+ Cmd.AddCommand(listCmd)
+ Cmd.AddCommand(listEnvCmd)
+ Cmd.AddCommand(deleteCmd)
listCmd.Flags().IntVarP(&limitFlag, "limit", "n", 20, "Number of events to show")
listCmd.Flags().IntVar(&startFlag, "start", 0, "Offset for pagination")
diff --git a/cli/pkg/admin/notifications/cmd.go b/cli/pkg/admin/notifications/cmd.go
index 78f8df9c5b..f40f2facf7 100644
--- a/cli/pkg/admin/notifications/cmd.go
+++ b/cli/pkg/admin/notifications/cmd.go
@@ -18,8 +18,7 @@ var (
notifForceFlag bool
)
-// NotificationsCmd is the parent command for notification operations
-var NotificationsCmd = &cobra.Command{
+var Cmd = &cobra.Command{
Use: "notifications",
Aliases: []string{"notif", "notify"},
Short: "Manage notifications",
@@ -57,12 +56,7 @@ var appriseGetCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Header("Apprise Configuration")
@@ -97,12 +91,7 @@ var settingsGetCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result)
}
headers := []string{"ID", "PROVIDER", "ENABLED"}
@@ -142,12 +131,10 @@ var appriseTestCmd = &cobra.Command{
if jsonOutput {
var result base.ApiResponse[any]
- if err := json.NewDecoder(resp.Body).Decode(&result); err == nil {
- if resultBytes, err := json.MarshalIndent(result.Data, "", " "); err == nil {
- fmt.Println(string(resultBytes))
- }
+ if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
+ return fmt.Errorf("failed to parse response: %w", err)
}
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Success("Apprise notification test successful")
@@ -213,12 +200,10 @@ var testProviderCmd = &cobra.Command{
if jsonOutput {
var result base.ApiResponse[any]
- if err := json.NewDecoder(resp.Body).Decode(&result); err == nil {
- if resultBytes, err := json.MarshalIndent(result.Data, "", " "); err == nil {
- fmt.Println(string(resultBytes))
- }
+ if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
+ return fmt.Errorf("failed to parse response: %w", err)
}
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Success("Notification test for %s successful", args[0])
@@ -227,9 +212,9 @@ var testProviderCmd = &cobra.Command{
}
func init() {
- NotificationsCmd.AddCommand(appriseCmd)
- NotificationsCmd.AddCommand(settingsCmd)
- NotificationsCmd.AddCommand(testProviderCmd)
+ Cmd.AddCommand(appriseCmd)
+ Cmd.AddCommand(settingsCmd)
+ Cmd.AddCommand(testProviderCmd)
appriseCmd.AddCommand(appriseGetCmd)
appriseCmd.AddCommand(appriseTestCmd)
diff --git a/cli/pkg/admin/users/cmd.go b/cli/pkg/admin/users/cmd.go
index b88709d4bd..ea29b98e76 100644
--- a/cli/pkg/admin/users/cmd.go
+++ b/cli/pkg/admin/users/cmd.go
@@ -38,8 +38,7 @@ var (
userUpdateRoles []string
)
-// UsersCmd is the parent command for user operations
-var UsersCmd = &cobra.Command{
+var Cmd = &cobra.Command{
Use: "users",
Aliases: []string{"user", "usr"},
Short: "Manage users",
@@ -74,12 +73,7 @@ var listCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result)
}
headers := []string{"ID", "USERNAME", "DISPLAY NAME", "EMAIL", "ROLES"}
@@ -158,12 +152,7 @@ var createCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Success("User %s created successfully", result.Data.Username)
@@ -197,12 +186,7 @@ var getCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Header("User Details")
@@ -256,12 +240,10 @@ var updateCmd = &cobra.Command{
if jsonOutput {
var result base.ApiResponse[any]
- if err := json.NewDecoder(resp.Body).Decode(&result); err == nil {
- if resultBytes, err := json.MarshalIndent(result.Data, "", " "); err == nil {
- fmt.Println(string(resultBytes))
- }
+ if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
+ return fmt.Errorf("failed to parse response: %w", err)
}
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Success("User updated successfully")
@@ -307,11 +289,11 @@ var deleteCmd = &cobra.Command{
}
func init() {
- UsersCmd.AddCommand(listCmd)
- UsersCmd.AddCommand(createCmd)
- UsersCmd.AddCommand(getCmd)
- UsersCmd.AddCommand(updateCmd)
- UsersCmd.AddCommand(deleteCmd)
+ Cmd.AddCommand(listCmd)
+ Cmd.AddCommand(createCmd)
+ Cmd.AddCommand(getCmd)
+ Cmd.AddCommand(updateCmd)
+ Cmd.AddCommand(deleteCmd)
listCmd.Flags().IntVarP(&limitFlag, "limit", "n", 20, "Number of users to show")
listCmd.Flags().IntVar(&startFlag, "start", 0, "Offset for pagination")
diff --git a/cli/pkg/alerts/cmd.go b/cli/pkg/alerts/cmd.go
index f76876be9f..ba4f7f516b 100644
--- a/cli/pkg/alerts/cmd.go
+++ b/cli/pkg/alerts/cmd.go
@@ -15,8 +15,7 @@ import (
var debugAllGood bool
-// AlertsCmd shows dashboard alerts/action items.
-var AlertsCmd = &cobra.Command{
+var Cmd = &cobra.Command{
Use: "alerts",
Aliases: []string{"alert", "actionitems"},
Short: "Show dashboard alerts",
@@ -96,6 +95,6 @@ func formatActionItemKind(kind dashboardtypes.ActionItemKind) string {
}
func init() {
- AlertsCmd.Flags().Bool("json", false, "Output in JSON format")
- AlertsCmd.Flags().BoolVar(&debugAllGood, "debug-all-good", false, "Force no alerts (debug mode)")
+ Cmd.Flags().Bool("json", false, "Output in JSON format")
+ Cmd.Flags().BoolVar(&debugAllGood, "debug-all-good", false, "Force no alerts (debug mode)")
}
diff --git a/cli/pkg/auth/cmd.go b/cli/pkg/auth/cmd.go
index c9ffda6a61..35f8a73371 100644
--- a/cli/pkg/auth/cmd.go
+++ b/cli/pkg/auth/cmd.go
@@ -20,8 +20,8 @@ import (
var jsonOutput bool
-// AuthCmd is the parent command for authentication operations
-var AuthCmd = &cobra.Command{
+// Cmd is the parent command for authentication operations
+var Cmd = &cobra.Command{
Use: "auth",
Aliases: []string{"authentication"},
Short: "Authentication operations",
@@ -133,18 +133,13 @@ var loginCmd = &cobra.Command{
}
if cmdutil.JSONOutputEnabled(cmd) || jsonOutput {
- resultBytes, err := json.MarshalIndent(map[string]any{
+ return cmdutil.PrintJSON(map[string]any{
"success": tokenResult.Success,
"token": tokenResult.Token,
"refreshToken": tokenResult.RefreshToken,
"expiresAt": tokenResult.ExpiresAt,
"user": tokenResult.User,
- }, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ })
}
cfg, err := config.Load()
@@ -159,7 +154,7 @@ var loginCmd = &cobra.Command{
}
output.Success("Login successful")
- path, _ := config.ConfigPath()
+ path, _ := config.Path()
output.KeyValue("JWT token saved to config", path)
return nil
}
@@ -198,16 +193,14 @@ var logoutCmd = &cobra.Command{
if cmdutil.JSONOutputEnabled(cmd) || jsonOutput {
var result base.ApiResponse[any]
- if err := json.NewDecoder(resp.Body).Decode(&result); err == nil {
- if resultBytes, err := json.MarshalIndent(result.Data, "", " "); err == nil {
- fmt.Println(string(resultBytes))
- }
+ if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
+ return fmt.Errorf("failed to parse response: %w", err)
}
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Success("Logout successful")
- path, _ := config.ConfigPath()
+ path, _ := config.Path()
output.KeyValue("JWT token cleared from config", path)
return nil
},
@@ -238,12 +231,7 @@ var meCmd = &cobra.Command{
}
if cmdutil.JSONOutputEnabled(cmd) || jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Header("Current User")
@@ -310,12 +298,10 @@ var passwordCmd = &cobra.Command{
if cmdutil.JSONOutputEnabled(cmd) || jsonOutput {
var result base.ApiResponse[any]
- if err := json.NewDecoder(resp.Body).Decode(&result); err == nil {
- if resultBytes, err := json.MarshalIndent(result.Data, "", " "); err == nil {
- fmt.Println(string(resultBytes))
- }
+ if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
+ return fmt.Errorf("failed to parse response: %w", err)
}
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Success("Password changed successfully")
@@ -370,16 +356,11 @@ var refreshCmd = &cobra.Command{
}
if cmdutil.JSONOutputEnabled(cmd) || jsonOutput {
- resultBytes, err := json.MarshalIndent(map[string]any{
+ return cmdutil.PrintJSON(map[string]any{
"token": result.Data.Token,
"refreshToken": result.Data.RefreshToken,
"expiresAt": result.Data.ExpiresAt,
- }, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ })
}
// Save new JWT token to config
@@ -393,7 +374,7 @@ var refreshCmd = &cobra.Command{
}
output.Success("Token refreshed successfully")
- path, _ := config.ConfigPath()
+ path, _ := config.Path()
output.KeyValue("New JWT token saved to config", path)
return nil
},
@@ -428,12 +409,7 @@ var oidcStatusCmd = &cobra.Command{
}
if cmdutil.JSONOutputEnabled(cmd) || jsonOutput {
- resultBytes, err := json.MarshalIndent(result, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result)
}
output.Header("OIDC Status")
@@ -445,12 +421,12 @@ var oidcStatusCmd = &cobra.Command{
}
func init() {
- AuthCmd.AddCommand(loginCmd)
- AuthCmd.AddCommand(logoutCmd)
- AuthCmd.AddCommand(meCmd)
- AuthCmd.AddCommand(passwordCmd)
- AuthCmd.AddCommand(refreshCmd)
- AuthCmd.AddCommand(oidcStatusCmd)
+ Cmd.AddCommand(loginCmd)
+ Cmd.AddCommand(logoutCmd)
+ Cmd.AddCommand(meCmd)
+ Cmd.AddCommand(passwordCmd)
+ Cmd.AddCommand(refreshCmd)
+ Cmd.AddCommand(oidcStatusCmd)
// Login command flags
loginCmd.Flags().BoolVar(&jsonOutput, "json", false, "Output in JSON format")
diff --git a/cli/pkg/config/cmd.go b/cli/pkg/config/cmd.go
index 7a26bd7ff3..2d755af624 100644
--- a/cli/pkg/config/cmd.go
+++ b/cli/pkg/config/cmd.go
@@ -25,8 +25,7 @@ var (
setResourceLimit []string
)
-// ConfigCmd is the command for managing API configuration
-var ConfigCmd = &cobra.Command{
+var Cmd = &cobra.Command{
Use: "config",
Short: "Manage Arcane CLI's Configuration",
}
@@ -40,7 +39,7 @@ var configShowCmd = &cobra.Command{
return fmt.Errorf("failed to load config: %w", err)
}
- path, _ := config.ConfigPath()
+ path, _ := config.Path()
fmt.Printf("Config file: %s\n\n", path)
fmt.Printf("Server URL: %s\n", maskIfEmpty(cfg.ServerURL, "(not set)"))
fmt.Printf("API Key: %s\n", maskAPIKey(cfg.APIKey))
@@ -142,7 +141,7 @@ Legacy flag syntax (flags shown below) is still supported:
return fmt.Errorf("failed to save config: %w", err)
}
- path, _ := config.ConfigPath()
+ path, _ := config.Path()
fmt.Printf("\nConfiguration saved to %s\n", path)
return nil
@@ -153,7 +152,7 @@ var configPathCmd = &cobra.Command{
Use: "path",
Short: "Print the config file path",
RunE: func(cmd *cobra.Command, args []string) error {
- path, err := config.ConfigPath()
+ path, err := config.Path()
if err != nil {
return err
}
@@ -215,7 +214,7 @@ var configInitCmd = &cobra.Command{
If the config file already exists, this command is a no-op and does not overwrite it.`,
RunE: func(cmd *cobra.Command, args []string) error {
- path, err := config.ConfigPath()
+ path, err := config.Path()
if err != nil {
return err
}
@@ -244,7 +243,7 @@ var configBackupCmd = &cobra.Command{
This removes the original config file from its previous path.`,
RunE: func(cmd *cobra.Command, args []string) error {
- path, err := config.ConfigPath()
+ path, err := config.Path()
if err != nil {
return err
}
@@ -265,12 +264,12 @@ This removes the original config file from its previous path.`,
}
func init() {
- ConfigCmd.AddCommand(configShowCmd)
- ConfigCmd.AddCommand(configSetCmd)
- ConfigCmd.AddCommand(configInitCmd)
- ConfigCmd.AddCommand(configBackupCmd)
- ConfigCmd.AddCommand(configPathCmd)
- ConfigCmd.AddCommand(configTestCmd)
+ Cmd.AddCommand(configShowCmd)
+ Cmd.AddCommand(configSetCmd)
+ Cmd.AddCommand(configInitCmd)
+ Cmd.AddCommand(configBackupCmd)
+ Cmd.AddCommand(configPathCmd)
+ Cmd.AddCommand(configTestCmd)
configSetCmd.Flags().StringVar(&setServerURL, "server-url", "", "Arcane server URL (e.g., http://localhost:3553)")
configSetCmd.Flags().StringVar(&setAPIKey, "api-key", "", "API key for authentication")
diff --git a/cli/pkg/containers/cmd.go b/cli/pkg/containers/cmd.go
index 40b1ffa849..ea7935e598 100644
--- a/cli/pkg/containers/cmd.go
+++ b/cli/pkg/containers/cmd.go
@@ -50,8 +50,7 @@ var (
const maxPromptOptions = 20
-// ContainersCmd is the parent command for container operations
-var ContainersCmd = &cobra.Command{
+var Cmd = &cobra.Command{
Use: "containers",
Aliases: []string{"container", "c"},
Short: "Manage containers",
@@ -98,12 +97,7 @@ func runContainersList(cmd *cobra.Command, forceHasUpdateFilter bool) error {
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result)
}
effectiveUpdatesFilter := strings.TrimSpace(containersUpdatesFilter)
@@ -240,12 +234,7 @@ var containersGetCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(resolved, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(resolved)
}
output.Header("Container Details")
@@ -287,12 +276,7 @@ var containersStartCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Success("Container %s started successfully", containerDisplayName(resolved))
@@ -329,12 +313,7 @@ var containersStopCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Success("Container %s stopped successfully", containerDisplayName(resolved))
@@ -371,12 +350,7 @@ var containersRestartCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Success("Container %s restarted successfully", containerDisplayName(resolved))
@@ -416,12 +390,7 @@ var containersUpdateCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Success("Container %s updated successfully", containerDisplayName(resolved))
@@ -460,12 +429,7 @@ var containersRedeployCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Success("Container %s redeployed successfully", containerDisplayName(resolved))
@@ -524,12 +488,7 @@ var containersDeleteCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Success("Container %s deleted successfully", displayName)
@@ -560,12 +519,7 @@ var containersCountsCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Header("Container Status Counts")
@@ -688,12 +642,7 @@ var containersCreateCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Success("Container %s created successfully", result.Data.Name)
@@ -706,17 +655,17 @@ var containersCreateCmd = &cobra.Command{
}
func init() {
- ContainersCmd.AddCommand(containersListCmd)
- ContainersCmd.AddCommand(containersUpdatesCmd)
- ContainersCmd.AddCommand(containersGetCmd)
- ContainersCmd.AddCommand(containersStartCmd)
- ContainersCmd.AddCommand(containersStopCmd)
- ContainersCmd.AddCommand(containersRestartCmd)
- ContainersCmd.AddCommand(containersUpdateCmd)
- ContainersCmd.AddCommand(containersRedeployCmd)
- ContainersCmd.AddCommand(containersDeleteCmd)
- ContainersCmd.AddCommand(containersCountsCmd)
- ContainersCmd.AddCommand(containersCreateCmd)
+ Cmd.AddCommand(containersListCmd)
+ Cmd.AddCommand(containersUpdatesCmd)
+ Cmd.AddCommand(containersGetCmd)
+ Cmd.AddCommand(containersStartCmd)
+ Cmd.AddCommand(containersStopCmd)
+ Cmd.AddCommand(containersRestartCmd)
+ Cmd.AddCommand(containersUpdateCmd)
+ Cmd.AddCommand(containersRedeployCmd)
+ Cmd.AddCommand(containersDeleteCmd)
+ Cmd.AddCommand(containersCountsCmd)
+ Cmd.AddCommand(containersCreateCmd)
// Create command flags
containersCreateCmd.Flags().StringVarP(&containerCreateFile, "file", "f", "", "JSON config file for container creation")
diff --git a/cli/pkg/doctor/cmd.go b/cli/pkg/doctor/cmd.go
index 529ba78fdb..68f4a084d6 100644
--- a/cli/pkg/doctor/cmd.go
+++ b/cli/pkg/doctor/cmd.go
@@ -1,10 +1,10 @@
package doctor
import (
- "encoding/json"
"fmt"
"strings"
+ "github.com/getarcaneapp/arcane/cli/internal/cmdutil"
"github.com/getarcaneapp/arcane/cli/internal/config"
"github.com/getarcaneapp/arcane/cli/internal/output"
runtimectx "github.com/getarcaneapp/arcane/cli/internal/runtime"
@@ -24,8 +24,7 @@ type report struct {
var jsonOutput bool
-// DoctorCmd runs environment and connection diagnostics.
-var DoctorCmd = &cobra.Command{
+var Cmd = &cobra.Command{
Use: "doctor",
Short: "Run CLI diagnostics",
SilenceUsage: true,
@@ -45,7 +44,7 @@ var DoctorCmd = &cobra.Command{
}
rep := report{Healthy: true}
- path, _ := config.ConfigPath()
+ path, _ := config.Path()
rep.Checks = append(rep.Checks, checkResult{
Name: "config_path",
Status: "ok",
@@ -103,23 +102,19 @@ var DoctorCmd = &cobra.Command{
}
if jsonOutput || app.IsJSON() {
- b, err := json.MarshalIndent(rep, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal doctor output: %w", err)
- }
- fmt.Println(string(b))
+ return cmdutil.PrintJSON(rep)
+ }
+
+ output.Header("Arcane CLI Diagnostics")
+ rows := make([][]string, 0, len(rep.Checks))
+ for _, item := range rep.Checks {
+ rows = append(rows, []string{item.Name, item.Status, item.Details})
+ }
+ output.Table([]string{"CHECK", "STATUS", "DETAILS"}, rows)
+ if rep.Healthy {
+ output.Success("Doctor checks passed")
} else {
- output.Header("Arcane CLI Diagnostics")
- rows := make([][]string, 0, len(rep.Checks))
- for _, item := range rep.Checks {
- rows = append(rows, []string{item.Name, item.Status, item.Details})
- }
- output.Table([]string{"CHECK", "STATUS", "DETAILS"}, rows)
- if rep.Healthy {
- output.Success("Doctor checks passed")
- } else {
- output.Warning("Doctor found issues")
- }
+ output.Warning("Doctor found issues")
}
if !rep.Healthy {
@@ -130,5 +125,5 @@ var DoctorCmd = &cobra.Command{
}
func init() {
- DoctorCmd.Flags().BoolVar(&jsonOutput, "json", false, "Output in JSON format")
+ Cmd.Flags().BoolVar(&jsonOutput, "json", false, "Output in JSON format")
}
diff --git a/cli/pkg/environments/cmd.go b/cli/pkg/environments/cmd.go
index 97da6a108f..5bcf2cc1c7 100644
--- a/cli/pkg/environments/cmd.go
+++ b/cli/pkg/environments/cmd.go
@@ -39,8 +39,7 @@ var (
enabledStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#6d28d9"))
)
-// EnvironmentsCmd is the parent command for environment operations
-var EnvironmentsCmd = &cobra.Command{
+var Cmd = &cobra.Command{
Use: "environments",
Aliases: []string{"environment", "env", "e"},
Short: "Manage environments",
@@ -75,12 +74,7 @@ var listCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result)
}
headers := []string{"ID", "NAME", "API URL", "STATUS", "ENABLED"}
@@ -165,12 +159,7 @@ var getCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(buildEnvironmentPayloadInternal(result.Data), "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(buildEnvironmentPayloadInternal(result.Data))
}
output.Header("Environment Details")
@@ -205,12 +194,10 @@ var testCmd = &cobra.Command{
if jsonOutput {
var result base.ApiResponse[any]
- if err := json.NewDecoder(resp.Body).Decode(&result); err == nil {
- if resultBytes, err := json.MarshalIndent(result.Data, "", " "); err == nil {
- fmt.Println(string(resultBytes))
- }
+ if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
+ return fmt.Errorf("failed to parse response: %w", err)
}
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Success("Environment connection test successful")
@@ -374,12 +361,7 @@ var updateCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(buildEnvironmentPayloadInternal(result.Data), "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(buildEnvironmentPayloadInternal(result.Data))
}
output.Success("Environment updated successfully")
@@ -426,12 +408,7 @@ var versionCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result)
}
output.Header("Environment Version")
@@ -475,13 +452,13 @@ func buildEnvironmentPayloadInternal(env environment.Environment) map[string]any
}
func init() {
- EnvironmentsCmd.AddCommand(listCmd)
- EnvironmentsCmd.AddCommand(getCmd)
- EnvironmentsCmd.AddCommand(testCmd)
- EnvironmentsCmd.AddCommand(deleteCmd)
- EnvironmentsCmd.AddCommand(switchCmd)
- EnvironmentsCmd.AddCommand(updateCmd)
- EnvironmentsCmd.AddCommand(versionCmd)
+ Cmd.AddCommand(listCmd)
+ Cmd.AddCommand(getCmd)
+ Cmd.AddCommand(testCmd)
+ Cmd.AddCommand(deleteCmd)
+ Cmd.AddCommand(switchCmd)
+ Cmd.AddCommand(updateCmd)
+ Cmd.AddCommand(versionCmd)
// List command flags
listCmd.Flags().IntVarP(&limitFlag, "limit", "n", 20, "Number of environments to show")
diff --git a/cli/pkg/generate/api_key.go b/cli/pkg/generate/api_key.go
index f19013a5ae..6c18cf3af7 100644
--- a/cli/pkg/generate/api_key.go
+++ b/cli/pkg/generate/api_key.go
@@ -23,10 +23,10 @@ var apiKeyCmd = &cobra.Command{
}
func init() {
- GenerateCmd.AddCommand(apiKeyCmd)
+ Cmd.AddCommand(apiKeyCmd)
}
-func GenerateAPIKey() (string, error) {
+func APIKey() (string, error) {
keyBytes := make([]byte, apiKeyLength)
if _, err := crand.Read(keyBytes); err != nil {
return "", fmt.Errorf("failed to generate API key: %w", err)
@@ -36,7 +36,7 @@ func GenerateAPIKey() (string, error) {
}
func generateAPIKeyOutputInternal() error {
- apiKey, err := GenerateAPIKey()
+ apiKey, err := APIKey()
if err != nil {
return err
}
diff --git a/cli/pkg/generate/api_key_test.go b/cli/pkg/generate/api_key_test.go
index 0c7fec83c6..d2617d81d6 100644
--- a/cli/pkg/generate/api_key_test.go
+++ b/cli/pkg/generate/api_key_test.go
@@ -8,7 +8,7 @@ import (
)
func TestAPIKeyDefaultOutput(t *testing.T) {
- cmd := gen.GenerateCmd
+ cmd := gen.Cmd
cmd.SetArgs([]string{"api-key"})
out, err := captureOutput(func() error {
@@ -31,7 +31,7 @@ func TestAPIKeyDefaultOutput(t *testing.T) {
}
func TestGenerateAPIKeyProducesArcanePrefix(t *testing.T) {
- apiKey, err := gen.GenerateAPIKey()
+ apiKey, err := gen.APIKey()
if err != nil {
t.Fatalf("GenerateAPIKey failed: %v", err)
}
diff --git a/cli/pkg/generate/certs.go b/cli/pkg/generate/certs.go
index 4c2d5b3a38..7b66f57a74 100644
--- a/cli/pkg/generate/certs.go
+++ b/cli/pkg/generate/certs.go
@@ -53,8 +53,8 @@ var tlsCmd = &cobra.Command{
}
func init() {
- GenerateCmd.AddCommand(mtlsCmd)
- GenerateCmd.AddCommand(tlsCmd)
+ Cmd.AddCommand(mtlsCmd)
+ Cmd.AddCommand(tlsCmd)
mtlsCmd.Flags().StringVar(&mtlsOutDir, "out-dir", "./edge-mtls", "directory to write generated edge mTLS assets")
mtlsCmd.Flags().StringVar(&mtlsEnvID, "env-id", "local-edge", "environment identifier to embed in the generated edge agent certificate")
diff --git a/cli/pkg/generate/certs_test.go b/cli/pkg/generate/certs_test.go
index 4d5d6ed1c5..da45472a50 100644
--- a/cli/pkg/generate/certs_test.go
+++ b/cli/pkg/generate/certs_test.go
@@ -16,7 +16,7 @@ import (
func TestGenerateMTLSCommandWritesECDSAP384Assets(t *testing.T) {
outDir := t.TempDir()
- cmd := gen.GenerateCmd
+ cmd := gen.Cmd
cmd.SetArgs([]string{"mtls", "--out-dir", outDir, "--env-id", "env-123", "--app-url", "https://manager.example.com"})
_, err := captureOutput(func() error {
@@ -39,7 +39,7 @@ func TestGenerateMTLSCommandWritesECDSAP384Assets(t *testing.T) {
func TestGenerateTLSCommandWritesECDSAP384ServerCert(t *testing.T) {
outDir := t.TempDir()
- cmd := gen.GenerateCmd
+ cmd := gen.Cmd
cmd.SetArgs([]string{"tls", "--out-dir", outDir, "--common-name", "localhost", "--host", "localhost", "--host", "127.0.0.1", "--cert-name", "local-manager.crt", "--key-name", "local-manager.key"})
_, err := captureOutput(func() error {
@@ -64,7 +64,7 @@ func TestGenerateTLSCommandWritesECDSAP384ServerCert(t *testing.T) {
func TestGenerateTLSCommandOverwritesCertificateAtomically(t *testing.T) {
outDir := t.TempDir()
- cmd := gen.GenerateCmd
+ cmd := gen.Cmd
cmd.SetArgs([]string{"tls", "--out-dir", outDir, "--common-name", "localhost", "--host", "localhost", "--cert-name", "server.crt", "--key-name", "server.key"})
_, err := captureOutput(func() error {
_, err := cmd.ExecuteC()
diff --git a/cli/pkg/generate/generate.go b/cli/pkg/generate/generate.go
index cdaf9dfa01..e48a13426e 100644
--- a/cli/pkg/generate/generate.go
+++ b/cli/pkg/generate/generate.go
@@ -4,7 +4,7 @@ import (
"github.com/spf13/cobra"
)
-var GenerateCmd = &cobra.Command{
+var Cmd = &cobra.Command{
Use: "generate",
Aliases: []string{"gen", "g"},
Short: "Generate secrets and bootstrap credentials for Arcane",
diff --git a/cli/pkg/generate/secret.go b/cli/pkg/generate/secret.go
index 4297ba39b0..37034941e0 100644
--- a/cli/pkg/generate/secret.go
+++ b/cli/pkg/generate/secret.go
@@ -24,7 +24,7 @@ var secretCmd = &cobra.Command{
}
func init() {
- GenerateCmd.AddCommand(secretCmd)
+ Cmd.AddCommand(secretCmd)
secretCmd.Flags().StringVarP(&secretFormat, "format", "f", "base64", "output format: base64, hex, env, docker, all")
secretCmd.Flags().IntVarP(&secretLength, "length", "l", 32, "secret length in bytes (default: 32 for AES-256)")
}
diff --git a/cli/pkg/generate/secret_test.go b/cli/pkg/generate/secret_test.go
index 37cf2b5093..a0010ebbcd 100644
--- a/cli/pkg/generate/secret_test.go
+++ b/cli/pkg/generate/secret_test.go
@@ -36,7 +36,7 @@ func captureOutput(fn func() error) (string, error) {
}
func TestSecretDefaultBase64(t *testing.T) {
- cmd := gen.GenerateCmd
+ cmd := gen.Cmd
cmd.SetArgs([]string{"secret"})
out, err := captureOutput(func() error {
@@ -83,7 +83,7 @@ func TestSecretDefaultBase64(t *testing.T) {
}
func TestSecretAllFormatContainsSections(t *testing.T) {
- cmd := gen.GenerateCmd
+ cmd := gen.Cmd
cmd.SetArgs([]string{"secret", "-f", "all"})
out, err := captureOutput(func() error {
diff --git a/cli/pkg/gitops/cmd.go b/cli/pkg/gitops/cmd.go
index 798fd2a264..274ff35795 100644
--- a/cli/pkg/gitops/cmd.go
+++ b/cli/pkg/gitops/cmd.go
@@ -38,8 +38,7 @@ var (
const maxPromptOptions = 20
-// GitopsCmd is the parent command for gitops sync operations
-var GitopsCmd = &cobra.Command{
+var Cmd = &cobra.Command{
Use: "gitops",
Aliases: []string{"gitops-syncs", "gs"},
Short: "Manage GitOps syncs",
@@ -74,12 +73,7 @@ var listCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result)
}
headers := []string{"ID", "NAME", "BRANCH", "AUTO-SYNC", "LAST STATUS", "LAST SYNC"}
@@ -159,12 +153,7 @@ var createCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Success("GitOps sync %s created successfully (ID: %s)", result.Data.Name, result.Data.ID)
@@ -204,12 +193,7 @@ var getCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(resolved, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(resolved)
}
output.Header("GitOps Sync Details")
@@ -361,12 +345,7 @@ var statusCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Header("GitOps Sync Status")
@@ -419,12 +398,7 @@ var syncCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
if result.Data.Success {
@@ -469,12 +443,7 @@ var filesCmd = &cobra.Command{
files := result.Data.Files
if jsonOutput {
- resultBytes, err := json.MarshalIndent(files, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(files)
}
headers := []string{"NAME", "TYPE", "PATH", "SIZE"}
@@ -527,12 +496,7 @@ var importCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Success("Import completed: %d succeeded, %d failed", result.Data.SuccessCount, result.Data.FailedCount)
@@ -547,15 +511,15 @@ var importCmd = &cobra.Command{
}
func init() {
- GitopsCmd.AddCommand(listCmd)
- GitopsCmd.AddCommand(createCmd)
- GitopsCmd.AddCommand(getCmd)
- GitopsCmd.AddCommand(updateCmd)
- GitopsCmd.AddCommand(deleteCmd)
- GitopsCmd.AddCommand(statusCmd)
- GitopsCmd.AddCommand(syncCmd)
- GitopsCmd.AddCommand(filesCmd)
- GitopsCmd.AddCommand(importCmd)
+ Cmd.AddCommand(listCmd)
+ Cmd.AddCommand(createCmd)
+ Cmd.AddCommand(getCmd)
+ Cmd.AddCommand(updateCmd)
+ Cmd.AddCommand(deleteCmd)
+ Cmd.AddCommand(statusCmd)
+ Cmd.AddCommand(syncCmd)
+ Cmd.AddCommand(filesCmd)
+ Cmd.AddCommand(importCmd)
// List command flags
listCmd.Flags().IntVarP(&limitFlag, "limit", "n", 20, "Number of syncs to show")
diff --git a/cli/pkg/images/cmd.go b/cli/pkg/images/cmd.go
index 7da6ef08a0..b358037259 100644
--- a/cli/pkg/images/cmd.go
+++ b/cli/pkg/images/cmd.go
@@ -69,8 +69,7 @@ var (
const maxPromptOptions = 20
-// ImagesCmd is the parent command for image operations
-var ImagesCmd = &cobra.Command{
+var Cmd = &cobra.Command{
Use: "images",
Aliases: []string{"image", "i"},
Short: "Manage images",
@@ -151,12 +150,7 @@ var imagesListCmd = &cobra.Command{
result.Pagination.TotalItems = int64(len(result.Data))
if cmdutil.JSONOutputEnabled(cmd) {
- resultBytes, err := json.MarshalIndent(result, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result)
}
headers := []string{"ID", "REPOSITORY:TAG", "SIZE", "IN USE"}
@@ -704,7 +698,7 @@ var imagesUploadCmd = &cobra.Command{
}
func init() {
- ImagesCmd.AddCommand(imagesListCmd)
+ Cmd.AddCommand(imagesListCmd)
imagesListCmd.Flags().IntVarP(&imagesLimit, "limit", "n", 0, "Number of images to show (server default 20)")
imagesListCmd.Flags().IntVar(&imagesStart, "start", 0, "Offset for pagination")
imagesListCmd.Flags().StringVar(&imagesSort, "sort", "", "Field to sort by")
@@ -713,20 +707,20 @@ func init() {
imagesListCmd.Flags().BoolVar(&imagesInUseOnly, "inuse", false, "Only show images currently in use")
imagesListCmd.Flags().BoolVar(&imagesUnusedOnly, "unused", false, "Only show images not in use")
- ImagesCmd.AddCommand(imagesGetCmd)
+ Cmd.AddCommand(imagesGetCmd)
- ImagesCmd.AddCommand(imagesRemoveCmd)
+ Cmd.AddCommand(imagesRemoveCmd)
imagesRemoveCmd.Flags().BoolVarP(&removeForce, "force", "f", false, "Force removal of image")
- ImagesCmd.AddCommand(imagesPullCmd)
+ Cmd.AddCommand(imagesPullCmd)
- ImagesCmd.AddCommand(imagesPruneCmd)
+ Cmd.AddCommand(imagesPruneCmd)
imagesPruneCmd.Flags().BoolVar(&pruneDangling, "dangling", false, "Only remove dangling images")
- ImagesCmd.AddCommand(imagesCountsCmd)
- ImagesCmd.AddCommand(updates.UpdatesCmd)
+ Cmd.AddCommand(imagesCountsCmd)
+ Cmd.AddCommand(updates.Cmd)
- ImagesCmd.AddCommand(imagesUploadCmd)
+ Cmd.AddCommand(imagesUploadCmd)
}
func resolveImageID(ctx context.Context, c *client.Client, identifier string, allowPrompt bool) (string, error) {
diff --git a/cli/pkg/images/updates/cmd.go b/cli/pkg/images/updates/cmd.go
index 249b551c57..fc638b123a 100644
--- a/cli/pkg/images/updates/cmd.go
+++ b/cli/pkg/images/updates/cmd.go
@@ -5,6 +5,7 @@ import (
"fmt"
"github.com/getarcaneapp/arcane/cli/internal/client"
+ "github.com/getarcaneapp/arcane/cli/internal/cmdutil"
"github.com/getarcaneapp/arcane/cli/internal/output"
"github.com/getarcaneapp/arcane/cli/internal/types"
"github.com/getarcaneapp/arcane/types/base"
@@ -14,8 +15,7 @@ import (
var jsonOutput bool
-// UpdatesCmd is the parent command for image update operations.
-var UpdatesCmd = &cobra.Command{
+var Cmd = &cobra.Command{
Use: "updates",
Short: "Check for image updates",
}
@@ -42,12 +42,7 @@ var checkCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Header("Image Update Check Results")
@@ -86,12 +81,7 @@ var checkAllCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Header("Check All Results")
@@ -131,12 +121,7 @@ var checkImageCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Header("Image Update Status")
@@ -174,12 +159,7 @@ var summaryCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Header("Image Updates Summary")
@@ -192,10 +172,10 @@ var summaryCmd = &cobra.Command{
}
func init() {
- UpdatesCmd.AddCommand(checkCmd)
- UpdatesCmd.AddCommand(checkAllCmd)
- UpdatesCmd.AddCommand(checkImageCmd)
- UpdatesCmd.AddCommand(summaryCmd)
+ Cmd.AddCommand(checkCmd)
+ Cmd.AddCommand(checkAllCmd)
+ Cmd.AddCommand(checkImageCmd)
+ Cmd.AddCommand(summaryCmd)
checkCmd.Flags().BoolVar(&jsonOutput, "json", false, "Output in JSON format")
checkAllCmd.Flags().BoolVar(&jsonOutput, "json", false, "Output in JSON format")
diff --git a/cli/pkg/jobs/cmd.go b/cli/pkg/jobs/cmd.go
index b15419e870..6b992f49d2 100644
--- a/cli/pkg/jobs/cmd.go
+++ b/cli/pkg/jobs/cmd.go
@@ -15,8 +15,7 @@ import (
var jsonOutput bool
-// JobsCmd is the parent command for job schedule operations.
-var JobsCmd = &cobra.Command{
+var Cmd = &cobra.Command{
Use: "jobs",
Aliases: []string{"job"},
Short: "Manage background jobs",
@@ -47,12 +46,7 @@ var getCmd = &cobra.Command{
}
if jsonOutput {
- b, err := json.MarshalIndent(cfg, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(b))
- return nil
+ return cmdutil.PrintJSON(cfg)
}
output.Header("Job Schedules")
@@ -101,12 +95,7 @@ var updateCmd = &cobra.Command{
}
if jsonOutput {
- b, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(b))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Success("Job schedules updated")
@@ -117,8 +106,8 @@ var updateCmd = &cobra.Command{
}
func init() {
- JobsCmd.AddCommand(getCmd)
- JobsCmd.AddCommand(updateCmd)
+ Cmd.AddCommand(getCmd)
+ Cmd.AddCommand(updateCmd)
getCmd.Flags().BoolVar(&jsonOutput, "json", false, "Output in JSON format")
updateCmd.Flags().BoolVar(&jsonOutput, "json", false, "Output in JSON format")
diff --git a/cli/pkg/networks/cmd.go b/cli/pkg/networks/cmd.go
index 4780169a50..3ac3c903be 100644
--- a/cli/pkg/networks/cmd.go
+++ b/cli/pkg/networks/cmd.go
@@ -30,8 +30,7 @@ var (
const maxPromptOptions = 20
-// NetworksCmd is the parent command for network operations
-var NetworksCmd = &cobra.Command{
+var Cmd = &cobra.Command{
Use: "networks",
Aliases: []string{"network", "net", "n"},
Short: "Manage networks",
@@ -71,12 +70,7 @@ var listCmd = &cobra.Command{
result.Pagination.TotalItems = int64(len(result.Data))
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result)
}
headers := []string{"ID", "NAME", "DRIVER", "SCOPE", "CREATED", "IN USE"}
@@ -131,12 +125,7 @@ var getCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
display := resolvedName
@@ -205,12 +194,7 @@ var deleteCmd = &cobra.Command{
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return fmt.Errorf("failed to parse response: %w", err)
}
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Success("Network %s deleted successfully", display)
@@ -240,12 +224,7 @@ var countsCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Header("Network Usage Counts")
@@ -292,12 +271,7 @@ var pruneCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Success("Networks pruned successfully")
@@ -307,11 +281,11 @@ var pruneCmd = &cobra.Command{
}
func init() {
- NetworksCmd.AddCommand(listCmd)
- NetworksCmd.AddCommand(getCmd)
- NetworksCmd.AddCommand(deleteCmd)
- NetworksCmd.AddCommand(countsCmd)
- NetworksCmd.AddCommand(pruneCmd)
+ Cmd.AddCommand(listCmd)
+ Cmd.AddCommand(getCmd)
+ Cmd.AddCommand(deleteCmd)
+ Cmd.AddCommand(countsCmd)
+ Cmd.AddCommand(pruneCmd)
// List command flags
listCmd.Flags().IntVarP(&limitFlag, "limit", "n", 20, "Number of networks to show")
diff --git a/cli/pkg/projects/cmd.go b/cli/pkg/projects/cmd.go
index 039a590737..b500fbe501 100644
--- a/cli/pkg/projects/cmd.go
+++ b/cli/pkg/projects/cmd.go
@@ -40,8 +40,7 @@ var (
const maxPromptOptions = 20
-// ProjectsCmd is the parent command for project operations
-var ProjectsCmd = &cobra.Command{
+var Cmd = &cobra.Command{
Use: "projects",
Aliases: []string{"project", "proj", "p"},
Short: "Manage projects",
@@ -88,12 +87,7 @@ func runProjectsList(cmd *cobra.Command, forceHasUpdateFilter bool) error {
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result)
}
effectiveUpdatesFilter := strings.TrimSpace(projectsUpdatesFilter)
@@ -254,12 +248,7 @@ var getCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(resolved, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(resolved)
}
output.Header("Project Details")
@@ -474,12 +463,7 @@ var createCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Success("Project %s created successfully", result.Data.Name)
@@ -544,12 +528,7 @@ var updateCmd = &cobra.Command{
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return fmt.Errorf("failed to parse response: %w", err)
}
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Success("Project %s updated successfully", resolved.Name)
@@ -597,12 +576,7 @@ var updateIncludesCmd = &cobra.Command{
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return fmt.Errorf("failed to parse response: %w", err)
}
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Success("Include file %s updated successfully for project %s", filepath.Base(includesFile), resolved.Name)
@@ -632,12 +606,7 @@ var countsCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Header("Project Counts")
@@ -649,19 +618,19 @@ var countsCmd = &cobra.Command{
}
func init() {
- ProjectsCmd.AddCommand(listCmd)
- ProjectsCmd.AddCommand(updatesCmd)
- ProjectsCmd.AddCommand(getCmd)
- ProjectsCmd.AddCommand(upCmd)
- ProjectsCmd.AddCommand(downCmd)
- ProjectsCmd.AddCommand(restartCmd)
- ProjectsCmd.AddCommand(redeployCmd)
- ProjectsCmd.AddCommand(pullCmd)
- ProjectsCmd.AddCommand(countsCmd)
- ProjectsCmd.AddCommand(destroyCmd)
- ProjectsCmd.AddCommand(createCmd)
- ProjectsCmd.AddCommand(updateCmd)
- ProjectsCmd.AddCommand(updateIncludesCmd)
+ Cmd.AddCommand(listCmd)
+ Cmd.AddCommand(updatesCmd)
+ Cmd.AddCommand(getCmd)
+ Cmd.AddCommand(upCmd)
+ Cmd.AddCommand(downCmd)
+ Cmd.AddCommand(restartCmd)
+ Cmd.AddCommand(redeployCmd)
+ Cmd.AddCommand(pullCmd)
+ Cmd.AddCommand(countsCmd)
+ Cmd.AddCommand(destroyCmd)
+ Cmd.AddCommand(createCmd)
+ Cmd.AddCommand(updateCmd)
+ Cmd.AddCommand(updateIncludesCmd)
// List command flags
listCmd.Flags().IntVarP(&limitFlag, "limit", "n", 20, "Number of projects to show")
diff --git a/cli/pkg/registries/cmd.go b/cli/pkg/registries/cmd.go
index 80110ae714..58f4e4c3cb 100644
--- a/cli/pkg/registries/cmd.go
+++ b/cli/pkg/registries/cmd.go
@@ -26,7 +26,7 @@ var (
registryUpdateDisabled bool
)
-var RegistriesCmd = &cobra.Command{
+var Cmd = &cobra.Command{
Use: "registries",
Aliases: []string{"registry", "reg"},
Short: "Manage container registries",
@@ -61,12 +61,7 @@ var listCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result)
}
headers := []string{"ID", "URL", "USERNAME", "ENABLED", "INSECURE"}
@@ -116,12 +111,10 @@ var syncCmd = &cobra.Command{
if jsonOutput {
var result base.ApiResponse[any]
- if err := json.NewDecoder(resp.Body).Decode(&result); err == nil {
- if resultBytes, err := json.MarshalIndent(result.Data, "", " "); err == nil {
- fmt.Println(string(resultBytes))
- }
+ if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
+ return fmt.Errorf("failed to parse response: %w", err)
}
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Success("Registries synced successfully")
@@ -151,12 +144,10 @@ var testCmd = &cobra.Command{
if jsonOutput {
var result base.ApiResponse[any]
- if err := json.NewDecoder(resp.Body).Decode(&result); err == nil {
- if resultBytes, err := json.MarshalIndent(result.Data, "", " "); err == nil {
- fmt.Println(string(resultBytes))
- }
+ if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
+ return fmt.Errorf("failed to parse response: %w", err)
}
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Success("Registry connection test successful")
@@ -187,12 +178,7 @@ var getCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Header("Registry Details")
@@ -253,12 +239,10 @@ var updateCmd = &cobra.Command{
if jsonOutput {
var result base.ApiResponse[any]
- if err := json.NewDecoder(resp.Body).Decode(&result); err == nil {
- if resultBytes, err := json.MarshalIndent(result.Data, "", " "); err == nil {
- fmt.Println(string(resultBytes))
- }
+ if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
+ return fmt.Errorf("failed to parse response: %w", err)
}
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Success("Registry updated successfully")
@@ -304,12 +288,12 @@ var deleteCmd = &cobra.Command{
}
func init() {
- RegistriesCmd.AddCommand(listCmd)
- RegistriesCmd.AddCommand(getCmd)
- RegistriesCmd.AddCommand(syncCmd)
- RegistriesCmd.AddCommand(testCmd)
- RegistriesCmd.AddCommand(updateCmd)
- RegistriesCmd.AddCommand(deleteCmd)
+ Cmd.AddCommand(listCmd)
+ Cmd.AddCommand(getCmd)
+ Cmd.AddCommand(syncCmd)
+ Cmd.AddCommand(testCmd)
+ Cmd.AddCommand(updateCmd)
+ Cmd.AddCommand(deleteCmd)
listCmd.Flags().IntVarP(&limitFlag, "limit", "n", 20, "Number of registries to show")
listCmd.Flags().IntVar(&startFlag, "start", 0, "Offset for pagination")
diff --git a/cli/pkg/repos/cmd.go b/cli/pkg/repos/cmd.go
index a21b0bd3dc..8873cd89b4 100644
--- a/cli/pkg/repos/cmd.go
+++ b/cli/pkg/repos/cmd.go
@@ -29,8 +29,7 @@ var (
const maxPromptOptions = 20
-// ReposCmd is the parent command for git repository operations.
-var ReposCmd = &cobra.Command{
+var Cmd = &cobra.Command{
Use: "repos",
Aliases: []string{"repo", "git-repositories", "git-repos"},
Short: "Manage git repositories",
@@ -97,12 +96,7 @@ var listCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result)
}
headers := []string{"ID", "NAME", "URL", "AUTH TYPE", "ENABLED", "CREATED"}
@@ -182,12 +176,7 @@ var createCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Success("Repository created successfully")
@@ -217,12 +206,7 @@ var getCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(resolved, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(resolved)
}
output.Header("Repository Details")
@@ -315,12 +299,7 @@ var updateCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Success("Repository updated successfully")
@@ -402,12 +381,10 @@ var testCmd = &cobra.Command{
if jsonOutput {
var result base.ApiResponse[any]
- if err := json.NewDecoder(resp.Body).Decode(&result); err == nil {
- if resultBytes, err := json.MarshalIndent(result.Data, "", " "); err == nil {
- fmt.Println(string(resultBytes))
- }
+ if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
+ return fmt.Errorf("failed to parse response: %w", err)
}
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Success("Repository connection test successful")
@@ -443,12 +420,7 @@ var branchesCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
headers := []string{"BRANCH", "DEFAULT"}
@@ -511,12 +483,7 @@ var filesCmd = &cobra.Command{
files := result.Data.Files
if jsonOutput {
- resultBytes, err := json.MarshalIndent(files, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(files)
}
headers := []string{"NAME", "TYPE", "PATH", "SIZE"}
@@ -561,12 +528,10 @@ var syncCmd = &cobra.Command{
if jsonOutput {
var result base.ApiResponse[any]
- if err := json.NewDecoder(resp.Body).Decode(&result); err == nil {
- if resultBytes, err := json.MarshalIndent(result.Data, "", " "); err == nil {
- fmt.Println(string(resultBytes))
- }
+ if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
+ return fmt.Errorf("failed to parse response: %w", err)
}
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Success("Repositories synced successfully")
@@ -644,15 +609,15 @@ func resolveGitRepository(ctx context.Context, c *client.Client, identifier stri
}
func init() {
- ReposCmd.AddCommand(listCmd)
- ReposCmd.AddCommand(createCmd)
- ReposCmd.AddCommand(getCmd)
- ReposCmd.AddCommand(updateCmd)
- ReposCmd.AddCommand(deleteCmd)
- ReposCmd.AddCommand(testCmd)
- ReposCmd.AddCommand(branchesCmd)
- ReposCmd.AddCommand(filesCmd)
- ReposCmd.AddCommand(syncCmd)
+ Cmd.AddCommand(listCmd)
+ Cmd.AddCommand(createCmd)
+ Cmd.AddCommand(getCmd)
+ Cmd.AddCommand(updateCmd)
+ Cmd.AddCommand(deleteCmd)
+ Cmd.AddCommand(testCmd)
+ Cmd.AddCommand(branchesCmd)
+ Cmd.AddCommand(filesCmd)
+ Cmd.AddCommand(syncCmd)
// List command flags
listCmd.Flags().IntVarP(&limitFlag, "limit", "n", 20, "Number of repositories to show")
diff --git a/cli/pkg/root.go b/cli/pkg/root.go
index 7969d4f18c..cfd60b9c86 100644
--- a/cli/pkg/root.go
+++ b/cli/pkg/root.go
@@ -223,28 +223,30 @@ func init() {
rootCmd.PersistentFlags().BoolVar(&logJSONOutput, "log-json", false, "Log in JSON format")
rootCmd.Flags().BoolVarP(&showVersion, "version", "v", false, "Print version information")
- rootCmd.AddCommand(configClient.ConfigCmd)
- rootCmd.AddCommand(completion.NewCommand(rootCmd))
- rootCmd.AddCommand(doctor.DoctorCmd)
- rootCmd.AddCommand(generate.GenerateCmd)
- rootCmd.AddCommand(version.VersionCmd)
- rootCmd.AddCommand(auth.AuthCmd)
- rootCmd.AddCommand(alerts.AlertsCmd)
- rootCmd.AddCommand(containers.ContainersCmd)
- rootCmd.AddCommand(images.ImagesCmd)
- rootCmd.AddCommand(volumes.VolumesCmd)
- rootCmd.AddCommand(networks.NetworksCmd)
- rootCmd.AddCommand(projects.ProjectsCmd)
- rootCmd.AddCommand(environments.EnvironmentsCmd)
- rootCmd.AddCommand(registries.RegistriesCmd)
- rootCmd.AddCommand(repos.ReposCmd)
- rootCmd.AddCommand(templates.TemplatesCmd)
- rootCmd.AddCommand(settings.SettingsCmd)
- rootCmd.AddCommand(jobs.JobsCmd)
- rootCmd.AddCommand(system.SystemCmd)
- rootCmd.AddCommand(updater.UpdaterCmd)
- rootCmd.AddCommand(admin.AdminCmd)
- rootCmd.AddCommand(gitops.GitopsCmd)
+ rootCmd.AddCommand(
+ configClient.Cmd,
+ completion.NewCommand(rootCmd),
+ doctor.Cmd,
+ generate.Cmd,
+ version.Cmd,
+ auth.Cmd,
+ alerts.Cmd,
+ containers.Cmd,
+ images.Cmd,
+ volumes.Cmd,
+ networks.Cmd,
+ projects.Cmd,
+ environments.Cmd,
+ registries.Cmd,
+ repos.Cmd,
+ templates.Cmd,
+ settings.Cmd,
+ jobs.Cmd,
+ system.Cmd,
+ updater.Cmd,
+ admin.Cmd,
+ gitops.Cmd,
+ )
}
func renderCommandHelp(cmd *cobra.Command) {
diff --git a/cli/pkg/settings/cmd.go b/cli/pkg/settings/cmd.go
index bb4c0a6e1a..5dd3de9d85 100644
--- a/cli/pkg/settings/cmd.go
+++ b/cli/pkg/settings/cmd.go
@@ -15,8 +15,7 @@ import (
var jsonOutput bool
-// SettingsCmd is the parent command for settings operations
-var SettingsCmd = &cobra.Command{
+var Cmd = &cobra.Command{
Use: "settings",
Aliases: []string{"setting"},
Short: "Manage settings",
@@ -45,12 +44,7 @@ var listCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result)
}
headers := []string{"KEY", "TYPE", "VALUE"}
@@ -102,12 +96,10 @@ var updateCmd = &cobra.Command{
if jsonOutput {
var result any
- if err := json.NewDecoder(resp.Body).Decode(&result); err == nil {
- if resultBytes, err := json.MarshalIndent(result, "", " "); err == nil {
- fmt.Println(string(resultBytes))
- }
+ if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
+ return fmt.Errorf("failed to parse response: %w", err)
}
- return nil
+ return cmdutil.PrintJSON(result)
}
output.Success("Settings updated successfully")
@@ -137,12 +129,7 @@ var publicCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result)
}
headers := []string{"KEY", "TYPE", "VALUE"}
@@ -158,9 +145,9 @@ var publicCmd = &cobra.Command{
}
func init() {
- SettingsCmd.AddCommand(listCmd)
- SettingsCmd.AddCommand(updateCmd)
- SettingsCmd.AddCommand(publicCmd)
+ Cmd.AddCommand(listCmd)
+ Cmd.AddCommand(updateCmd)
+ Cmd.AddCommand(publicCmd)
listCmd.Flags().BoolVar(&jsonOutput, "json", false, "Output in JSON format")
diff --git a/cli/pkg/system/cmd.go b/cli/pkg/system/cmd.go
index fed117495a..c8935500e3 100644
--- a/cli/pkg/system/cmd.go
+++ b/cli/pkg/system/cmd.go
@@ -16,8 +16,7 @@ import (
var jsonOutput bool
-// SystemCmd is the parent command for system operations
-var SystemCmd = &cobra.Command{
+var Cmd = &cobra.Command{
Use: "system",
Aliases: []string{"sys"},
Short: "System operations",
@@ -51,12 +50,7 @@ var pruneCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Header("System Prune Results")
@@ -87,12 +81,7 @@ var dockerInfoCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Header("Docker Info")
@@ -167,12 +156,10 @@ var startStoppedCmd = &cobra.Command{
if jsonOutput {
var result base.ApiResponse[any]
- if err := json.NewDecoder(resp.Body).Decode(&result); err == nil {
- if resultBytes, err := json.MarshalIndent(result.Data, "", " "); err == nil {
- fmt.Println(string(resultBytes))
- }
+ if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
+ return fmt.Errorf("failed to parse response: %w", err)
}
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Success("Started all stopped containers")
@@ -217,12 +204,7 @@ var convertCmd = &cobra.Command{
"envVars": result.EnvVars,
"serviceName": result.ServiceName,
}
- resultBytes, err := json.MarshalIndent(out, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(out)
}
output.Header("Conversion Result")
@@ -275,12 +257,10 @@ var upgradeCmd = &cobra.Command{
if jsonOutput {
var result base.ApiResponse[any]
- if err := json.NewDecoder(resp.Body).Decode(&result); err == nil {
- if resultBytes, err := json.MarshalIndent(result.Data, "", " "); err == nil {
- fmt.Println(string(resultBytes))
- }
+ if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
+ return fmt.Errorf("failed to parse response: %w", err)
}
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Success("System upgrade initiated")
@@ -318,12 +298,7 @@ var upgradeCheckCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result)
}
output.Header("Upgrade Check")
@@ -337,14 +312,14 @@ var upgradeCheckCmd = &cobra.Command{
}
func init() {
- SystemCmd.AddCommand(pruneCmd)
- SystemCmd.AddCommand(dockerInfoCmd)
- SystemCmd.AddCommand(containersStartAllCmd)
- SystemCmd.AddCommand(containersStopAllCmd)
- SystemCmd.AddCommand(startStoppedCmd)
- SystemCmd.AddCommand(convertCmd)
- SystemCmd.AddCommand(upgradeCmd)
- SystemCmd.AddCommand(upgradeCheckCmd)
+ Cmd.AddCommand(pruneCmd)
+ Cmd.AddCommand(dockerInfoCmd)
+ Cmd.AddCommand(containersStartAllCmd)
+ Cmd.AddCommand(containersStopAllCmd)
+ Cmd.AddCommand(startStoppedCmd)
+ Cmd.AddCommand(convertCmd)
+ Cmd.AddCommand(upgradeCmd)
+ Cmd.AddCommand(upgradeCheckCmd)
pruneCmd.Flags().BoolVar(&jsonOutput, "json", false, "Output in JSON format")
dockerInfoCmd.Flags().BoolVar(&jsonOutput, "json", false, "Output in JSON format")
diff --git a/cli/pkg/templates/cmd.go b/cli/pkg/templates/cmd.go
index 96042c84ad..569e1c2969 100644
--- a/cli/pkg/templates/cmd.go
+++ b/cli/pkg/templates/cmd.go
@@ -51,8 +51,7 @@ var (
templateRegUpdateDisabled bool
)
-// TemplatesCmd is the parent command for template operations
-var TemplatesCmd = &cobra.Command{
+var Cmd = &cobra.Command{
Use: "templates",
Aliases: []string{"template", "tpl"},
Short: "Manage Docker Compose templates",
@@ -119,12 +118,7 @@ var listCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(jsonPayload, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(jsonPayload)
}
headers := []string{"NAME", "CUSTOM", "REMOTE", "DESCRIPTION"}
@@ -174,12 +168,7 @@ var defaultCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Header("Default Templates")
@@ -212,12 +201,7 @@ var contentCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Header("Template Content")
@@ -258,12 +242,7 @@ var registriesCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
headers := []string{"ID", "NAME", "URL", "ENABLED"}
@@ -310,12 +289,7 @@ var variablesCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
headers := []string{"KEY", "VALUE"}
@@ -423,12 +397,7 @@ var getCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(resolved, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(resolved)
}
tpl := *resolved
@@ -770,13 +739,13 @@ func minInt(values ...int) int {
if len(values) == 0 {
return 0
}
- min := values[0]
+ minValue := values[0]
for _, value := range values[1:] {
- if value < min {
- min = value
+ if value < minValue {
+ minValue = value
}
}
- return min
+ return minValue
}
func maxInt(a, b int) int {
@@ -855,12 +824,7 @@ var createCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Success("Template created successfully")
@@ -914,12 +878,7 @@ var updateCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Success("Template updated successfully")
@@ -1012,12 +971,7 @@ var defaultsSaveCmd = &cobra.Command{
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return fmt.Errorf("failed to parse response: %w", err)
}
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Success("Default templates saved successfully")
@@ -1060,12 +1014,7 @@ var variablesUpdateCmd = &cobra.Command{
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return fmt.Errorf("failed to parse response: %w", err)
}
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Success("Template variables updated successfully")
@@ -1103,12 +1052,7 @@ var registriesUpdateCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Success("Template registry updated successfully")
@@ -1142,12 +1086,7 @@ var fetchCmd = &cobra.Command{
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return fmt.Errorf("failed to parse response: %w", err)
}
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Success("Remote templates fetched successfully")
@@ -1156,21 +1095,21 @@ var fetchCmd = &cobra.Command{
}
func init() {
- TemplatesCmd.AddCommand(listCmd)
- TemplatesCmd.AddCommand(defaultCmd)
- TemplatesCmd.AddCommand(contentCmd)
- TemplatesCmd.AddCommand(registriesCmd)
- TemplatesCmd.AddCommand(variablesCmd)
- TemplatesCmd.AddCommand(deleteCmd)
- TemplatesCmd.AddCommand(deleteRegistryCmd)
- TemplatesCmd.AddCommand(getCmd)
- TemplatesCmd.AddCommand(createCmd)
- TemplatesCmd.AddCommand(updateCmd)
- TemplatesCmd.AddCommand(downloadCmd)
- TemplatesCmd.AddCommand(defaultsSaveCmd)
- TemplatesCmd.AddCommand(variablesUpdateCmd)
- TemplatesCmd.AddCommand(registriesUpdateCmd)
- TemplatesCmd.AddCommand(fetchCmd)
+ Cmd.AddCommand(listCmd)
+ Cmd.AddCommand(defaultCmd)
+ Cmd.AddCommand(contentCmd)
+ Cmd.AddCommand(registriesCmd)
+ Cmd.AddCommand(variablesCmd)
+ Cmd.AddCommand(deleteCmd)
+ Cmd.AddCommand(deleteRegistryCmd)
+ Cmd.AddCommand(getCmd)
+ Cmd.AddCommand(createCmd)
+ Cmd.AddCommand(updateCmd)
+ Cmd.AddCommand(downloadCmd)
+ Cmd.AddCommand(defaultsSaveCmd)
+ Cmd.AddCommand(variablesUpdateCmd)
+ Cmd.AddCommand(registriesUpdateCmd)
+ Cmd.AddCommand(fetchCmd)
listCmd.Flags().IntVarP(&limitFlag, "limit", "n", 20, "Number of templates to show")
listCmd.Flags().IntVar(&startFlag, "start", 0, "Offset for pagination")
diff --git a/cli/pkg/updater/cmd.go b/cli/pkg/updater/cmd.go
index 1bfd280c73..9f773b07f4 100644
--- a/cli/pkg/updater/cmd.go
+++ b/cli/pkg/updater/cmd.go
@@ -6,6 +6,7 @@ import (
"time"
"github.com/getarcaneapp/arcane/cli/internal/client"
+ "github.com/getarcaneapp/arcane/cli/internal/cmdutil"
"github.com/getarcaneapp/arcane/cli/internal/output"
"github.com/getarcaneapp/arcane/cli/internal/types"
"github.com/getarcaneapp/arcane/types/base"
@@ -15,8 +16,7 @@ import (
var jsonOutput bool
-// UpdaterCmd is the parent command for updater operations
-var UpdaterCmd = &cobra.Command{
+var Cmd = &cobra.Command{
Use: "updater",
Aliases: []string{"upd"},
Short: "Auto-updater operations",
@@ -44,12 +44,7 @@ var statusCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Header("Updater Status")
@@ -84,12 +79,7 @@ var runCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Header("Updater Results")
@@ -124,12 +114,7 @@ var historyCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
headers := []string{"CHECKED", "UPDATED", "FAILED", "DURATION"}
@@ -150,9 +135,9 @@ var historyCmd = &cobra.Command{
}
func init() {
- UpdaterCmd.AddCommand(statusCmd)
- UpdaterCmd.AddCommand(runCmd)
- UpdaterCmd.AddCommand(historyCmd)
+ Cmd.AddCommand(statusCmd)
+ Cmd.AddCommand(runCmd)
+ Cmd.AddCommand(historyCmd)
statusCmd.Flags().BoolVar(&jsonOutput, "json", false, "Output in JSON format")
runCmd.Flags().BoolVar(&jsonOutput, "json", false, "Output in JSON format")
diff --git a/cli/pkg/version/cmd.go b/cli/pkg/version/cmd.go
index b393457202..08f8f6f830 100644
--- a/cli/pkg/version/cmd.go
+++ b/cli/pkg/version/cmd.go
@@ -14,8 +14,7 @@ import (
"github.com/spf13/cobra"
)
-// VersionCmd gets the server version
-var VersionCmd = &cobra.Command{
+var Cmd = &cobra.Command{
Use: "version",
Short: "Get the Arcane server version",
SilenceUsage: true,
diff --git a/cli/pkg/volumes/cmd.go b/cli/pkg/volumes/cmd.go
index 15caa46c0f..2a3df28d60 100644
--- a/cli/pkg/volumes/cmd.go
+++ b/cli/pkg/volumes/cmd.go
@@ -37,8 +37,7 @@ var (
const maxPromptOptions = 20
-// VolumesCmd is the parent command for volume operations
-var VolumesCmd = &cobra.Command{
+var Cmd = &cobra.Command{
Use: "volumes",
Aliases: []string{"volume", "vol", "v"},
Short: "Manage volumes",
@@ -78,12 +77,7 @@ var listCmd = &cobra.Command{
result.Pagination.TotalItems = int64(len(result.Data))
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result)
}
headers := []string{"NAME", "DRIVER", "MOUNTPOINT", "CREATED", "IN USE"}
@@ -126,12 +120,7 @@ var getCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(resolved, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(resolved)
}
output.Header("Volume Details")
@@ -213,12 +202,7 @@ var countsCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Header("Volume Usage Counts")
@@ -267,12 +251,7 @@ var pruneCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Success("Volumes pruned successfully")
@@ -302,12 +281,7 @@ var sizesCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Header("Volume Sizes")
@@ -349,12 +323,7 @@ var usageCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Header("Volume Usage: %s", resolved.Name)
@@ -418,12 +387,7 @@ var createCmd = &cobra.Command{
}
if jsonOutput {
- resultBytes, err := json.MarshalIndent(result.Data, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal JSON: %w", err)
- }
- fmt.Println(string(resultBytes))
- return nil
+ return cmdutil.PrintJSON(result.Data)
}
output.Success("Volume %s created successfully", result.Data.Name)
@@ -435,14 +399,14 @@ var createCmd = &cobra.Command{
}
func init() {
- VolumesCmd.AddCommand(listCmd)
- VolumesCmd.AddCommand(getCmd)
- VolumesCmd.AddCommand(deleteCmd)
- VolumesCmd.AddCommand(countsCmd)
- VolumesCmd.AddCommand(pruneCmd)
- VolumesCmd.AddCommand(sizesCmd)
- VolumesCmd.AddCommand(usageCmd)
- VolumesCmd.AddCommand(createCmd)
+ Cmd.AddCommand(listCmd)
+ Cmd.AddCommand(getCmd)
+ Cmd.AddCommand(deleteCmd)
+ Cmd.AddCommand(countsCmd)
+ Cmd.AddCommand(pruneCmd)
+ Cmd.AddCommand(sizesCmd)
+ Cmd.AddCommand(usageCmd)
+ Cmd.AddCommand(createCmd)
// List command flags
listCmd.Flags().IntVarP(&limitFlag, "limit", "n", 20, "Number of volumes to show")
diff --git a/types/apikey/apikey.go b/types/apikey/apikey.go
index bbb165d105..12c952f04c 100644
--- a/types/apikey/apikey.go
+++ b/types/apikey/apikey.go
@@ -2,7 +2,7 @@ package apikey
import "time"
-// Create represents the request body for creating an API key.
+// CreateApiKey represents the request body for creating an API key.
type CreateApiKey struct {
Name string `json:"name" minLength:"1" maxLength:"255" doc:"Name of the API key" example:"My API Key"`
Description *string `json:"description,omitempty" maxLength:"1000" doc:"Optional description of the API key"`
@@ -29,7 +29,7 @@ type ApiKeyCreatedDto struct {
Key string `json:"key" doc:"The full API key secret (only shown once)"`
}
-// Update represents the request body for updating an API key.
+// UpdateApiKey represents the request body for updating an API key.
type UpdateApiKey struct {
Name *string `json:"name,omitempty" maxLength:"255" doc:"New name for the API key"`
Description *string `json:"description,omitempty" maxLength:"1000" doc:"New description for the API key"`
diff --git a/types/container/container.go b/types/container/container.go
index 55452116e2..1a416b764a 100644
--- a/types/container/container.go
+++ b/types/container/container.go
@@ -6,7 +6,7 @@ import (
"strings"
"time"
- containerregistry "github.com/getarcaneapp/arcane/types/containerregistry"
+ "github.com/getarcaneapp/arcane/types/containerregistry"
imagetypes "github.com/getarcaneapp/arcane/types/image"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/network"
diff --git a/types/containerregistry/container_registry.go b/types/containerregistry/container_registry.go
index 748b9a1cd9..7388e89d2d 100644
--- a/types/containerregistry/container_registry.go
+++ b/types/containerregistry/container_registry.go
@@ -2,7 +2,7 @@ package containerregistry
import "time"
-// Registry represents a container registry in API responses.
+// ContainerRegistry represents a container registry in API responses.
type ContainerRegistry struct {
// ID of the container registry.
//
diff --git a/types/go.mod b/types/go.mod
index a65f0f00f4..6cb70afc6e 100644
--- a/types/go.mod
+++ b/types/go.mod
@@ -22,7 +22,7 @@ require (
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
- github.com/mattn/go-shellwords v1.0.12 // indirect
+ github.com/mattn/go-shellwords v1.0.13 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
@@ -30,11 +30,9 @@ require (
github.com/sirupsen/logrus v1.9.4 // indirect
github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
- go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect
go.opentelemetry.io/otel v1.43.0 // indirect
go.opentelemetry.io/otel/metric v1.43.0 // indirect
- go.opentelemetry.io/otel/sdk v1.43.0 // indirect
- go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect
go.opentelemetry.io/otel/trace v1.43.0 // indirect
go.yaml.in/yaml/v4 v4.0.0-rc.4 // indirect
golang.org/x/sync v0.20.0 // indirect
diff --git a/types/go.sum b/types/go.sum
index d76261b47e..0a2b70587a 100644
--- a/types/go.sum
+++ b/types/go.sum
@@ -33,8 +33,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
-github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
+github.com/mattn/go-shellwords v1.0.13 h1:DC0OMEpGjm6LfNFU4ckYcvbQKyp2vE8atyFGXNtDcf4=
+github.com/mattn/go-shellwords v1.0.13/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/moby/api v1.54.2 h1:wiat9QAhnDQjA7wk1kh/TqHz2I1uUA7M7t9SAl/JNXg=
@@ -57,8 +57,7 @@ github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8
github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
-go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18=
-go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY=
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
diff --git a/types/image/image.go b/types/image/image.go
index dc8d051730..aaba3eb49c 100644
--- a/types/image/image.go
+++ b/types/image/image.go
@@ -5,7 +5,7 @@ import (
"strings"
"time"
- containerregistry "github.com/getarcaneapp/arcane/types/containerregistry"
+ "github.com/getarcaneapp/arcane/types/containerregistry"
"github.com/getarcaneapp/arcane/types/vulnerability"
"github.com/moby/moby/api/types/image"
)
diff --git a/types/imageupdate/image_update.go b/types/imageupdate/image_update.go
index 94ca0808b7..bd4c92d80d 100644
--- a/types/imageupdate/image_update.go
+++ b/types/imageupdate/image_update.go
@@ -3,7 +3,7 @@ package imageupdate
import (
"time"
- containerregistry "github.com/getarcaneapp/arcane/types/containerregistry"
+ "github.com/getarcaneapp/arcane/types/containerregistry"
)
type Response struct {
diff --git a/types/meta/template_meta.go b/types/meta/template_meta.go
index aa197abd4f..0f004fee44 100644
--- a/types/meta/template_meta.go
+++ b/types/meta/template_meta.go
@@ -1,6 +1,6 @@
package meta
-// Template represents metadata about a template.
+// TemplateMeta represents metadata about a template.
type TemplateMeta struct {
// Version of the template.
//
diff --git a/types/template/template.go b/types/template/template.go
index 4cdbb7a237..4d7a34f9bc 100644
--- a/types/template/template.go
+++ b/types/template/template.go
@@ -101,7 +101,7 @@ type RemoteRegistry struct {
Templates []RemoteTemplate `json:"templates"`
}
-// Registry represents a local registry configuration.
+// TemplateRegistry represents a local registry configuration.
type TemplateRegistry struct {
BaseRegistry
diff --git a/types/types.go b/types/types.go
index 750651d18e..666ea55227 100644
--- a/types/types.go
+++ b/types/types.go
@@ -1,5 +1,5 @@
package types
const (
- LOCAL_DOCKER_ENVIRONMENT_ID = "0"
+ LocalDockerEnvironmentId = "0"
)