Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion backend/cli/generate/api_key_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
)

func TestAPIKeyCommandAvailableInBackendCLI(t *testing.T) {
cmd := GenerateCmd
cmd := Cmd
cmd.SetArgs([]string{"api-key"})

out, err := captureOutput(func() error {
Expand Down
2 changes: 1 addition & 1 deletion backend/cli/generate/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ import (
"github.com/spf13/cobra"
)

var GenerateCmd *cobra.Command = cligenerate.GenerateCmd
var Cmd *cobra.Command = cligenerate.Cmd
4 changes: 2 additions & 2 deletions backend/cli/generate/secret_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion backend/cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func Execute() {

func init() {
rootCmd.AddCommand(upgrade.UpgradeCmd)
rootCmd.AddCommand(generate.GenerateCmd)
rootCmd.AddCommand(generate.Cmd)
}

func getVersion() string {
Expand Down
2 changes: 1 addition & 1 deletion backend/internal/api/diagnostics_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down
68 changes: 34 additions & 34 deletions backend/internal/bootstrap/jobs_bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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
Expand Down Expand Up @@ -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":
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion backend/internal/bootstrap/router_bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
18 changes: 18 additions & 0 deletions backend/internal/common/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
2 changes: 2 additions & 0 deletions backend/internal/config/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
16 changes: 4 additions & 12 deletions backend/internal/database/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 4 additions & 4 deletions backend/internal/huma/handlers/apikeys.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand All @@ -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())
Expand Down
4 changes: 2 additions & 2 deletions backend/internal/huma/handlers/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down Expand Up @@ -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"`
Expand Down
4 changes: 2 additions & 2 deletions backend/internal/huma/handlers/customize.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions backend/internal/huma/handlers/environments.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down Expand Up @@ -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 != "" {
Expand Down
2 changes: 1 addition & 1 deletion backend/internal/huma/handlers/projects.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 2 additions & 2 deletions backend/internal/huma/handlers/swarm.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand Down Expand Up @@ -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.
Expand Down
2 changes: 0 additions & 2 deletions backend/internal/models/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ type (
)

const (
// Event types
EventTypeContainerStart EventType = "container.start"
EventTypeContainerStop EventType = "container.stop"
EventTypeContainerRestart EventType = "container.restart"
Expand Down Expand Up @@ -88,7 +87,6 @@ const (
EventTypeWebhookDelete EventType = "webhook.delete"
EventTypeWebhookTrigger EventType = "webhook.trigger"

// Event severities
EventSeverityInfo EventSeverity = "info"
EventSeverityWarning EventSeverity = "warning"
EventSeverityError EventSeverity = "error"
Expand Down
2 changes: 1 addition & 1 deletion backend/internal/models/vulnerability_ignore.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading