service

package
v0.4.40 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 13, 2026 License: Apache-2.0 Imports: 75 Imported by: 0

Documentation

Overview

@Author: LinkLeong link@icewhale.com

*@Date: 2022-07-12 09:48:56
*@LastEditors: LinkLeong
*@LastEditTime: 2022-09-02 22:10:05
*@FilePath: /CasaOS/service/service.go
*@Description:
*@Website: https://www.casaos.io
*Copyright (c) 2022 by icewhale, All Rights Reserved.

Index

Constants

View Source
const BackupFormatVersion = 1

BackupFormatVersion is bumped when the layout changes in a way a reader must know about.

View Source
const ManifestFileName = "manifest.json"

ManifestFileName sits at the root of a backup.

Variables

View Source
var (
	ErrNotAppStore             = fmt.Errorf("not an appstore")
	ErrDefaultAppStoreNotFound = fmt.Errorf("default appstore not found")
)
View Source
var (
	ErrComposeAppNotFound                  = fmt.Errorf("compose app not found")
	ErrComposeAppNotMatch                  = fmt.Errorf("compose app not match")
	ErrComposeExtensionNameXCasaOSNotFound = fmt.Errorf("extension `%s` not found", common.ComposeExtensionNameXCasaOS)
	ErrComposeFileNotFound                 = fmt.Errorf("compose file not found")
	ErrInvalidComposeAppStatus             = fmt.Errorf("invalid compose app status")
	ErrMainAppNotFound                     = fmt.Errorf("main app not found")
	ErrNotFoundInAppStore                  = fmt.Errorf("not found in app store")
	ErrSetStoreAppID                       = fmt.Errorf("failed to set store app ID")
	ErrStoreInfoNotFound                   = fmt.Errorf("store info not found")
	ErrMainServiceNotSpecified             = fmt.Errorf("main service not been specified")
)
View Source
var BackupHoldPath = "/var/lib/casaos/backups/holding.json"

BackupHoldPath records which apps are being held still, and which containers were running when they were.

View Source
var BackupLogPath = "/var/lib/casaos/backups/runs.json"

BackupLogPath is where the record lives. A variable so a test can put it somewhere of its own.

View Source
var BackupSchedulePath = "/var/lib/casaos/backups/schedules.json"

BackupSchedulePath is where the standing arrangements live.

View Source
var ErrAppStoreSourceExists = fmt.Errorf("appstore source already exists")
View Source
var (
	NewVersionApp map[string]string // TODO - make use of NewVersionApp map
)
View Source
var NoUpdateBlacklist = []string{
	"johnguan/stable-diffusion-webui:latest",
}

the patch is have no choice the digest compare is not work for these images I don't know why, but I have to do this I will remove the patch after I rewrite the digest compare

Functions

func ApiService

func ApiService() (api.Service, client.APIClient, error)

func BuildCatalog

func BuildCatalog(storeRoot string) (map[string]*ComposeApp, error)

func CheckImageUpdates

func CheckImageUpdates(ctx context.Context) (*codegen.ImageUpdateCheckResult, error)

CheckImageUpdates asks every installed app's registry what its tags point at now and compares that with the copy on disk.

This is what sees an app that came from no app store. The store's upgradable list compares tags against a catalogue, so it is blind to an app nobody publishes a catalogue entry for, and equally blind to a tag that has been republished under the same name -- which is what `latest` does every time.

func CheckImageUpdatesForApp

func CheckImageUpdatesForApp(ctx context.Context, composeApp *ComposeApp) error

CheckImageUpdatesForApp answers for one app and records it, leaving every other app's answer alone.

This is what makes the per-app button honest. It is called `Check then update`, and for an app that came from no store there is nothing else to check: without this it would answer from whatever the last sweep happened to leave in the cache, or from nothing at all if no sweep has run.

func DanglingImages

func DanglingImages(ctx context.Context) (*codegen.DanglingImages, error)

DanglingImages reports what PruneDanglingImages would free, without freeing it.

func GenerateYAMLFromComposeApp

func GenerateYAMLFromComposeApp(compose ComposeApp) ([]byte, error)

GenerateYAMLFromComposeApp writes an app loaded by the settings pipeline (ComposeAppFromSettingsYAML, LoadComposeAppForEditing) back to compose text; its strings are already escaped for compose.

func ImageUpdateAvailable

func ImageUpdateAvailable(appName string) *bool

ImageUpdateAvailable reports whether an update is on offer for an app, or nil if nothing has looked since this service started. Nil is not "up to date": the dashboard shows a badge for true and nothing for either other case, and conflating them would claim an app is current when nobody has looked.

func IsDue added in v0.4.37

func IsDue(schedule BackupSchedule, now time.Time) (bool, error)

IsDue reports whether this schedule has a slot it has not run yet.

func LoadCategoryMap

func LoadCategoryMap(storeRoot string) map[string]codegen.CategoryInfo

func LoadImageUpdates

func LoadImageUpdates()

LoadImageUpdates restores what the last run found, so the dashboard's first paint after a restart shows the badges it showed before it.

func LoadRecommend

func LoadRecommend(storeRoot string) []string

func NewScheduledBackupRunner added in v0.4.37

func NewScheduledBackupRunner() *scheduledBackupRunner

NewScheduledBackupRunner is what the scheduler drives in production.

func ParseEnvFile

func ParseEnvFile(text []byte) (map[string]string, error)

ParseEnvFile validates `.env` text as the runtime will read it; a reserved key is refused rather than ignored.

func PruneDanglingImages

func PruneDanglingImages(ctx context.Context) (*codegen.DanglingImages, error)

PruneDanglingImages deletes them and reports what the daemon says it actually freed, which can be less than DanglingImages estimated if something claimed a layer since.

func PublishEventWrapper

func PublishEventWrapper(ctx context.Context, eventType message_bus.EventType, properties map[string]string)

func RecordBackupRun added in v0.4.37

func RecordBackupRun(record BackupRunRecord) error

RecordBackupRun appends one run and keeps the file to its last few hundred.

A record that cannot be written is an error the caller hears about rather than a silence: losing the run is bad, and not knowing the log is broken is worse.

func RecoverHeldApps added in v0.4.38

func RecoverHeldApps(docker containerStopStarter)

RecoverHeldApps starts back anything a backup stopped and never restarted.

Run at startup. Starting a container that is already running is what StartContainer does nothing about, so this is safe to run when the note is merely stale -- and a stale note is a far better problem than an app nobody noticed was off.

func RootFor added in v0.4.37

func RootFor(app, stamp string) string

RootFor is where one run of a backup goes on a destination.

The app first, then the moment, so a destination holding several apps stays readable in a file browser and one app's history sits together. `stamp` is passed in rather than taken from the clock here: a plan that reads the clock cannot be tested, and the caller already knows when the run started.

func RunDueBackups added in v0.4.37

func RunDueBackups(ctx context.Context, now time.Time, runner backupRunner)

RunDueBackups runs everything that has an unrun slot, records each one, and applies retention.

It returns rather than loops, and takes `now`, so the whole of the decision can be exercised in a test. main drives it from the crontab that is already there,

which is the only part that reads a clock.

A schedule's LastRun is written whether the run SUCCEEDED or not. Retrying a failing backup every minute until the disk fills is worse than waiting for tomorrow's slot, and the failure is in the log where somebody can see it.

func RunsToPurge added in v0.4.37

func RunsToPurge(stamps []string, keep int) []string

RunsToPurge names the runs that fall outside a retention of `keep`.

Runs are named by their stamp, which sorts, so the newest are simply the last. `keep` of zero or less keeps everything -- deleting backups is not something to do because a field defaulted, and a schedule saved without a retention means "no policy", not "keep none".

One run is always kept whatever the arithmetic says. A retention that empties a destination is not a retention.

func SaveBackupSchedules added in v0.4.37

func SaveBackupSchedules(schedules []BackupSchedule) error

SaveBackupSchedules replaces the lot.

func Standardize

func Standardize(text string) string

func StoreRoot

func StoreRoot(workdir string) (string, error)

func VolumesToRemove added in v0.4.38

func VolumesToRemove(volumes []ContainerVolume, requested []string) (remove []string, refused map[string]string)

VolumesToRemove filters what the caller asked to delete down to what may be.

The caller's list is never trusted: a screen drawn a minute ago is a screen that has been overtaken, and by the time somebody presses the button another container may have started using one of these. Everything is re-decided here against what the daemon says now.

A name the container does not mount at all is refused outright rather than passed through -- that is the shape of a request that would delete somebody else's volume through this container's door.

Types

type App

type App types.ServiceConfig

func (*App) StoreInfo

func (a *App) StoreInfo() (codegen.AppStoreInfo, error)

type AppStore

type AppStore interface {
	Catalog() (map[string]*ComposeApp, error)
	CategoryMap() (map[string]codegen.CategoryInfo, error)
	ComposeApp(id string) (*ComposeApp, error)
	Recommend() ([]string, error)
	UpdateCatalog() error
	WorkDir() (string, error)
}

func AppStoreByURL

func AppStoreByURL(appstoreURL string) (AppStore, error)

func NewDefaultAppStore

func NewDefaultAppStore() (AppStore, error)

type AppStoreManagement

type AppStoreManagement struct {
	// contains filtered or unexported fields
}

func NewAppStoreManagement

func NewAppStoreManagement() *AppStoreManagement

func (*AppStoreManagement) AppStoreList

func (a *AppStoreManagement) AppStoreList() []codegen.AppStoreMetadata

func (*AppStoreManagement) AppStoreMap

func (a *AppStoreManagement) AppStoreMap() (map[string]AppStore, error)

func (*AppStoreManagement) Catalog

func (a *AppStoreManagement) Catalog() (map[string]*ComposeApp, error)

func (*AppStoreManagement) CategoryMap

func (a *AppStoreManagement) CategoryMap() (map[string]codegen.CategoryInfo, error)

AppStore interface

func (*AppStoreManagement) ChangeGlobal

func (a *AppStoreManagement) ChangeGlobal(key string, value string) error

func (*AppStoreManagement) ComposeApp

func (a *AppStoreManagement) ComposeApp(id string) (*ComposeApp, error)

func (*AppStoreManagement) DeleteGlobal

func (a *AppStoreManagement) DeleteGlobal(key string) error

func (*AppStoreManagement) FinishUpgrade

func (a *AppStoreManagement) FinishUpgrade(appID string)

func (*AppStoreManagement) ForgetUpgradable

func (a *AppStoreManagement) ForgetUpgradable(appID string)

ForgetUpgradable drops one app's cached answer so the next question is asked afresh. An hour of cache is right for a list rendered in the background and wrong immediately after someone pressed a button with the word "check" on it.

func (*AppStoreManagement) IsUpdateAvailable

func (a *AppStoreManagement) IsUpdateAvailable(composeApp *ComposeApp) bool

func (*AppStoreManagement) IsUpdateAvailableWith

func (a *AppStoreManagement) IsUpdateAvailableWith(composeApp *ComposeApp, storeComposeApp *ComposeApp) (bool, string, error)

IsUpdateAvailableWith decides an update SERVICE BY SERVICE, because that is what an update writes: every service the catalogue names takes the catalogue's image.

It is on offer when no service would go backwards and something would actually change -- the catalogue names an image this app does not run, or the registries say an image it does run has moved. Reading the MAIN service's tag alone made a catalogue that bumps only a sidecar invisible, which is what froze the multi-service stacks; and demanding every image be identical before believing the registries threw their answer away over any difference at all, including a service the owner added by hand.

It answers with a reason whenever it refuses, because "no update, and nothing on screen saying why" is indistinguishable from a broken button.

func (*AppStoreManagement) IsUpdating

func (a *AppStoreManagement) IsUpdating(appID string) bool

func (*AppStoreManagement) OnAppStoreRegister

func (a *AppStoreManagement) OnAppStoreRegister(fn func(string) error)

func (*AppStoreManagement) OnAppStoreUnregister

func (a *AppStoreManagement) OnAppStoreUnregister(fn func(string) error)

func (*AppStoreManagement) Recommend

func (a *AppStoreManagement) Recommend() ([]string, error)

func (*AppStoreManagement) RegisterAppStore

func (a *AppStoreManagement) RegisterAppStore(ctx context.Context, appstoreURL string, callbacks ...func(*codegen.AppStoreMetadata)) error

func (*AppStoreManagement) RegisterAppStoreSync

func (a *AppStoreManagement) RegisterAppStoreSync(ctx context.Context, appstoreURL string, callbacks ...func(*codegen.AppStoreMetadata)) error

TODO: refactor the function and above function

func (*AppStoreManagement) StartUpgrade

func (a *AppStoreManagement) StartUpgrade(appID string)

func (*AppStoreManagement) UnregisterAppStore

func (a *AppStoreManagement) UnregisterAppStore(appStoreID uint) error

func (*AppStoreManagement) UpdateAvailability

func (a *AppStoreManagement) UpdateAvailability(composeApp *ComposeApp) (bool, string)

UpdateAvailability is IsUpdateAvailable plus why not. The reason is empty when an update is on offer and when the app is simply current; it is filled only when the catalogue holds something this app must not be given -- an older image for one of its services, a service an update cannot create, a pair of tags nothing can order.

func (*AppStoreManagement) UpdateCatalog

func (a *AppStoreManagement) UpdateCatalog() error

func (*AppStoreManagement) WorkDir

func (a *AppStoreManagement) WorkDir() (string, error)

type BackupCopier added in v0.4.37

type BackupCopier interface {
	CopyDirectory(ctx context.Context, source, destinationName, destinationPath string) error
	CopyFile(ctx context.Context, source, destinationName, destinationPath string) error
}

BackupCopier is the destination, as this needs it. *rclone.Client is one.

type BackupEntry added in v0.4.37

type BackupEntry struct {
	Kind BackupKind `json:"kind"`
	// Path on the host. Empty for an entry that was skipped before it could be
	// resolved, and for a named volume until Docker has been asked where it lives.
	Path string `json:"path"`
	// Service that mounts it, empty for the app's own files
	Service string `json:"service,omitempty"`
	// Target inside the container, for a volume or a bind
	Target string `json:"target,omitempty"`
	// Volume name as the compose file writes it, for a named volume
	Volume string `json:"volume,omitempty"`
	// Skip is why this is not going in the backup. Empty means it is.
	Skip string `json:"skip,omitempty"`
}

BackupEntry is one thing on disk that belongs to an app -- or one that looked like it did and does not, which is worth saying rather than dropping.

func (BackupEntry) IsIncluded added in v0.4.37

func (e BackupEntry) IsIncluded() bool

IsIncluded reports whether this entry is part of the backup.

type BackupKind added in v0.4.37

type BackupKind string

BackupKind says why a path is in the list, which is also what a restore needs to know to put it back.

const (
	// BackupKindCompose is the app's docker-compose.yml
	BackupKindCompose BackupKind = "compose"
	// BackupKindEnv is the .env beside it
	BackupKindEnv BackupKind = "env"
	// BackupKindVolume is a named volume, which Docker keeps somewhere of its own
	BackupKindVolume BackupKind = "volume"
	// BackupKindBind is a host path a service mounts directly
	BackupKindBind BackupKind = "bind"
)

type BackupManifest added in v0.4.37

type BackupManifest struct {
	// FormatVersion so a later reader knows what it is looking at. A backup outlives
	// the code that wrote it, which is the whole point of one.
	FormatVersion int    `json:"format_version"`
	App           string `json:"app"`
	// Operations, in the order they were planned
	Operations []BackupOperation `json:"operations"`
	// Skipped says what was deliberately left out, with the reason, so the person
	// restoring finds out here rather than by noticing something missing later.
	Skipped []BackupEntry `json:"skipped,omitempty"`
	// ContainersStopped records whether the app was held still while this was
	// copied. A backup taken from a running database may not restore, and the only
	// thing worse than knowing that is not knowing it.
	ContainersStopped bool `json:"containers_stopped"`
}

BackupManifest is written at the root of every backup, and is the only thing a restore needs to read.

func BackupOnDemand added in v0.4.39

func BackupOnDemand(ctx context.Context, app *ComposeApp, docker backupDocker, copier BackupCopier, opts BackupOptions) (BackupManifest, error)

BackupOnDemand runs one backup somebody asked for, and records it.

func PlanBackup added in v0.4.37

func PlanBackup(app string, inventory []BackupEntry, volumeMountpoints map[string]string) BackupManifest

PlanBackup turns an app's inventory into the copies that would make it.

`volumeMountpoints` maps a compose volume name to where Docker actually keeps it. A volume with no entry is one the daemon could not place, and it is moved to the skipped list rather than copied from a guessed path -- a guess that happens to name an empty folder produces a backup that reports success and restores nothing.

func RunBackup added in v0.4.37

func RunBackup(ctx context.Context, app *ComposeApp, docker backupDocker, copier BackupCopier, opts BackupOptions) (BackupManifest, error)

RunBackup copies an app to a destination and returns the manifest it wrote.

The manifest goes last, after everything it describes. A run interrupted half-way leaves files and no manifest, which reads as what it is -- an incomplete backup -- rather than as a complete one missing its contents.

func (*BackupManifest) MarkBindFiles added in v0.4.37

func (m *BackupManifest) MarkBindFiles(isFile func(path string) bool)

MarkBindFiles corrects the operations whose source turned out to be a single file rather than a folder.

A bind mounts either, and the compose file does not say which -- `./php.ini` and `./config` look identical until something checks the disk. rclone needs to be told: a file copied as though it were a directory copies its whole parent, which on `/DATA/photos/one.jpg` means the entire photo library.

`isFile` is passed in rather than called here so the planner stays testable without a filesystem.

func (*BackupManifest) TotalBySource added in v0.4.37

func (m *BackupManifest) TotalBySource() []string

TotalBySource is what a plan addresses, deduplicated and sorted: two services mounting the same folder is one thing to copy, not two.

type BackupOperation added in v0.4.37

type BackupOperation struct {
	Kind BackupKind `json:"kind"`
	// Source on this host
	Source string `json:"source"`
	// Destination relative to the root of this backup, always with forward slashes
	Destination string `json:"destination"`
	// Directory is true for a folder, false for a single file. rclone copies the
	// two with different calls, and getting it wrong copies a file's parent.
	Directory bool `json:"directory"`
	// Volume is the compose name of the volume this came from, when it came from one
	Volume string `json:"volume,omitempty"`
	// Service that mounted it, and where
	Service string `json:"service,omitempty"`
	Target  string `json:"target,omitempty"`
}

BackupOperation is one copy: one host path to one place under the backup root.

type BackupOptions added in v0.4.37

type BackupOptions struct {
	// Destination is the name of an rclone remote this project manages
	Destination string
	// Stamp names this run inside the destination. Passed in, not read from a
	// clock, so a caller can name a run and find it again.
	Stamp string
	// HoldStill stops the app's containers for the length of the copy. Off means a
	// copy taken from a running app, which for anything holding a database may not
	// restore -- the manifest records which was done.
	HoldStill bool
	// Containers answers which containers the app has, for HoldStill. Nil means ask
	// the app, which is what production does; a test supplies its own so that
	// exercising the hold does not require a daemon -- and does not leave the
	// daemon's client goroutines behind for the next test to trip over.
	Containers func(context.Context) (map[string][]codegen.ContainerSummary, error)
}

BackupOptions is one run.

type BackupRunRecord added in v0.4.37

type BackupRunRecord struct {
	App         string    `json:"app"`
	Destination string    `json:"destination"`
	Stamp       string    `json:"stamp"`
	StartedAt   time.Time `json:"started_at"`
	FinishedAt  time.Time `json:"finished_at"`
	// Scheduled tells a run that went off by itself from one somebody asked for.
	Scheduled bool `json:"scheduled"`
	// ContainersStopped says whether the app was held still, which decides whether
	// anything holding a database can be trusted to restore.
	ContainersStopped bool `json:"containers_stopped"`
	Copied            int  `json:"copied"`
	SkippedCount      int  `json:"skipped_count"`
	// Error is empty when the run succeeded.
	Error string `json:"error,omitempty"`
}

BackupRunRecord is one run, finished or failed.

func BackupRuns added in v0.4.37

func BackupRuns() ([]BackupRunRecord, error)

BackupRuns is what happened, newest first.

func (BackupRunRecord) Succeeded added in v0.4.37

func (r BackupRunRecord) Succeeded() bool

Succeeded reports whether this run finished.

type BackupSchedule added in v0.4.37

type BackupSchedule struct {
	App         string        `json:"app"`
	Destination string        `json:"destination"`
	Every       ScheduleEvery `json:"every"`
	// At is local time, "HH:MM". Local because somebody chose it looking at their
	// own clock, and "back up at 3am" means their 3am.
	At string `json:"at"`
	// Weekday applies to weekly only.
	Weekday time.Weekday `json:"weekday"`
	// Keep is how many runs survive at the destination. Zero keeps everything,
	// which is the safe reading of a field nobody filled in -- deleting backups is
	// not something to do because a number defaulted.
	Keep int `json:"keep"`
	// HoldStill stops the app for the copy.
	HoldStill bool `json:"hold_still"`
	Enabled   bool `json:"enabled"`
	// LastRun is when this last went off, and the only thing that stops it going
	// off again for the same slot.
	LastRun time.Time `json:"last_run"`
}

BackupSchedule is one standing arrangement: this app, to this destination, at this time.

func BackupSchedules added in v0.4.37

func BackupSchedules() ([]BackupSchedule, error)

BackupSchedules reads the standing arrangements.

A file that will not parse is an error, not an empty list: starting over silently would switch off every backup on the box and leave nothing saying so.

func DueSchedules added in v0.4.37

func DueSchedules(schedules []BackupSchedule, now time.Time) (due []BackupSchedule, broken []error)

DueSchedules is every schedule with an unrun slot, in a stable order.

A schedule whose time of day cannot be read is reported rather than skipped: silently ignoring it is how a box backs nothing up for months while its interface says it is scheduled.

type ComposeApp

type ComposeApp codegen.ComposeApp

func ComposeAppFromSettingsYAML

func ComposeAppFromSettingsYAML(buf []byte, keep map[string]struct{}) (*ComposeApp, error)

ComposeAppFromSettingsYAML parses the compose YAML the settings page (or any YAML-speaking editor) sends back to ApplyComposeAppSettings. Interpolation must stay ON: that YAML came from GenerateYAMLFromComposeApp and already has `$` escaped as `$$`; substituteEscaping collapses it and escapes the result exactly once (#1988). keep is ComposeApp.EnvKeys(): references to the app's `.env` are copied verbatim instead of being resolved.

func LoadComposeAppForEditing

func LoadComposeAppForEditing(appID, configFile string, keep map[string]struct{}) (*ComposeApp, error)

LoadComposeAppForEditing loads an installed app for the settings editor: a reference to a key of keep or to a key the runtime defines (settingsKeep) comes out as written, a bare `KEY` entry for one of them as `${KEY}`, and `.env` itself is never read. LoadComposeAppFromConfigFile (what docker runs) keeps resolving everything.

func LoadComposeAppFromConfigFile

func LoadComposeAppFromConfigFile(appID string, configFile string) (*ComposeApp, error)

func NewComposeAppFromYAML

func NewComposeAppFromYAML(yaml []byte, skipInterpolation, skipValidation bool) (*ComposeApp, error)

func (*ComposeApp) App

func (a *ComposeApp) App(name string) *App

TODO rename the function to service and add error return value

func (*ComposeApp) Apply

func (a *ComposeApp) Apply(ctx context.Context, newComposeYAML []byte) error

func (*ComposeApp) ApplyEnv

func (a *ComposeApp) ApplyEnv(ctx context.Context, env []byte) error

ApplyEnv replaces the `.env` of the app (empty removes it) and re-creates it from its unchanged compose file: a restart would keep the old environment, the apply reloads the project first.

func (*ComposeApp) Apps

func (a *ComposeApp) Apps() map[string]*App

func (*ComposeApp) AuthorType

func (a *ComposeApp) AuthorType() codegen.StoreAppAuthorType

func (*ComposeApp) BackupInventory added in v0.4.37

func (a *ComposeApp) BackupInventory() []BackupEntry

BackupInventory is everything on disk that belongs to this app, decided without copying anything and without asking Docker.

It lists what is left OUT as well as what goes in, each with its reason. A backup that quietly drops a mount is discovered on the day it is restored, which is the worst possible day to discover it.

A named volume comes back carrying its name and no path: where Docker put it is the daemon's answer, asked separately, and the list of which volumes belong to this app does not depend on the daemon being reachable.

func (*ComposeApp) Containers

func (a *ComposeApp) Containers(ctx context.Context) (map[string][]api.ContainerSummary, error)

func (*ComposeApp) Create

func (a *ComposeApp) Create(ctx context.Context, options api.CreateOptions, service api.Service) error

func (*ComposeApp) DockerVolumeName added in v0.4.37

func (a *ComposeApp) DockerVolumeName(composeName string) string

DockerVolumeName is the name Docker actually knows a compose volume by.

A compose file says `backend-storage`; Docker stores it as `<project>_backend-storage` unless the file declares a name of its own or marks it external. compose-go has already worked that out at load time, so the answer is read rather than rebuilt -- rebuilding it is how a backup addresses a volume that does not exist and reports success having copied nothing.

func (*ComposeApp) EnvFile

func (a *ComposeApp) EnvFile() string

EnvFile is the `.env` docker compose reads next to the compose file of an installed app.

func (*ComposeApp) EnvFileText

func (a *ComposeApp) EnvFileText() ([]byte, error)

EnvFileText returns the raw `.env`, empty when there is none.

func (*ComposeApp) EnvKeys

func (a *ComposeApp) EnvKeys() (map[string]struct{}, error)

EnvKeys is the keep-set of the settings round trip: the names defined in the app's `.env`. Values of those stay `${KEY}` references in docker-compose.yml instead of being baked in, otherwise the first settings save would copy the secrets into the compose file and every later `.env` edit would be a no-op. A reserved key (see reservedEnvKey) is left out: the editor then shows the value the runtime uses. A missing file yields an empty set; an unreadable one is an error, an empty set would bake every value on the next save.

func (*ComposeApp) GetPortsInUse

func (*ComposeApp) HealthCheck

func (a *ComposeApp) HealthCheck() (bool, error)

func (*ComposeApp) IsUncontrolled

func (a *ComposeApp) IsUncontrolled() bool

IsUncontrolled reports whether the app declares `x-casaos.is_uncontrolled`.

The read this replaces was one expression whose FIRST type assertion had no comma-ok:

Extensions[x].(map[string]interface{})[y].(bool)

The guard above it tests whether the KEY is present, which is not the same as the value being a map: `x-casaos:` with nothing after it parses to a nil value under a present key, and asserting that to a map type panics -- taking down whichever service called it. The copy of this line in the app grid did exactly that to a whole host in v0.4.61.

Indexing a nil map is fine in Go. Asserting a nil interface to a map type is not.

func (*ComposeApp) Logs

func (a *ComposeApp) Logs(ctx context.Context, lines int, services ...string) ([]byte, error)

Logs returns the logs of the app. Naming services narrows it to those; naming none keeps every service of the app, which is what the app-wide viewer asks for.

func (*ComposeApp) MainService

func (a *ComposeApp) MainService() (*App, error)

MainService is the service MainServiceName names. Reading it out of StoreInfo instead meant a stack with no `x-casaos` had no main service at all, and every caller of this and of MainTag failed on it -- including the check that runs when the owner saves the settings of such an app.

func (*ComposeApp) MainServiceName

func (a *ComposeApp) MainServiceName() string

MainServiceName is the service an app's interface is about: what `x-casaos.main` names, and otherwise the alphabetically first service, which is the rule StoreInfo has always applied when that key is absent.

A stack somebody assembled by hand has no `x-casaos` at all, and gets the same fallback rather than no answer. The extension carries presentation; which service leads is a question about the compose file, and it has an answer either way.

func (*ComposeApp) MainTag

func (a *ComposeApp) MainTag() (string, error)

func (*ComposeApp) Pull

func (a *ComposeApp) Pull(ctx context.Context) error

func (*ComposeApp) PullAndApply

func (a *ComposeApp) PullAndApply(ctx context.Context, newComposeYAML []byte) error

func (*ComposeApp) PullAndInstall

func (a *ComposeApp) PullAndInstall(ctx context.Context) error

func (*ComposeApp) SetStatus

func (a *ComposeApp) SetStatus(ctx context.Context, status codegen.RequestComposeAppStatus) error

func (*ComposeApp) SetStoreAppID

func (a *ComposeApp) SetStoreAppID(storeAppID string) (string, bool)

func (*ComposeApp) SetTitle

func (a *ComposeApp) SetTitle(title, lang string)

func (*ComposeApp) SetUncontrolled

func (a *ComposeApp) SetUncontrolled(uncontrolled bool) error

func (*ComposeApp) StoreInfo

func (a *ComposeApp) StoreInfo(includeApps bool) (*codegen.ComposeAppStoreInfo, error)

func (*ComposeApp) SupportsArchitecture

func (a *ComposeApp) SupportsArchitecture(arch string) bool

SupportsArchitecture reports whether the compose app runs on arch (a runtime.GOARCH value: amd64, arm64, arm). A missing or empty `architectures` list means every architecture, matching the UI (AppPanel.vue `unuseable`).

func (*ComposeApp) Uninstall

func (a *ComposeApp) Uninstall(ctx context.Context, deleteConfigFolder bool) error

func (*ComposeApp) Up

func (a *ComposeApp) Up(ctx context.Context, service api.Service) error

func (*ComposeApp) UpWithCheckRequire

func (a *ComposeApp) UpWithCheckRequire(ctx context.Context, service api.Service) error

func (*ComposeApp) Update

func (a *ComposeApp) Update(ctx context.Context) error

func (*ComposeApp) UpdateEventPropertiesFromStoreInfo

func (a *ComposeApp) UpdateEventPropertiesFromStoreInfo(eventProperties map[string]string) error

Try to update AppIcon and AppTitle in given event properties from store info

func (*ComposeApp) WriteEnvFile

func (a *ComposeApp) WriteEnvFile(text []byte) error

WriteEnvFile replaces the `.env`; an empty body removes it.

type ComposeService

type ComposeService struct {
	// contains filtered or unexported fields
}

func NewComposeService

func NewComposeService() *ComposeService

func (*ComposeService) Install

func (s *ComposeService) Install(ctx context.Context, composeApp *ComposeApp) error

func (*ComposeService) IsInstalling

func (s *ComposeService) IsInstalling(appName string) bool

func (*ComposeService) List

func (s *ComposeService) List(ctx context.Context) (map[string]*ComposeApp, error)

func (*ComposeService) PrepareWorkingDirectory

func (s *ComposeService) PrepareWorkingDirectory(name string) (string, error)

func (*ComposeService) RecoverAppsWaitingForStorage

func (s *ComposeService) RecoverAppsWaitingForStorage(ctx context.Context)

RecoverAppsWaitingForStorage starts compose apps that were abandoned because the default storage was not ready when they first tried to start. It is meant to be called periodically after service startup; it stops acting once the recovery pass has run or the wait window has expired.

func (*ComposeService) Status

func (s *ComposeService) Status(ctx context.Context, appID string) (string, error)

func (*ComposeService) Uninstall

func (s *ComposeService) Uninstall(ctx context.Context, composeApp *ComposeApp, deleteConfigFolder bool) error

type ContainerVolume added in v0.4.38

type ContainerVolume struct {
	// Name is the volume's name in Docker.
	Name string `json:"name"`
	// Target is where the container mounts it.
	Target string `json:"target"`
	// Size in bytes, or -1 when the driver cannot say. Shown so nobody deletes
	// 1.4 GB thinking it is a cache.
	Size int64 `json:"size"`
	// Containers is how many containers reference it, this one included, or -1
	// when the daemon cannot say.
	Containers int64 `json:"containers"`
	// Removable is false when this must not be offered for deletion at all.
	Removable bool `json:"removable"`
	// Reason says why, when it is not.
	Reason string `json:"reason,omitempty"`
}

ContainerVolume is one named volume a container mounts.

func DescribeVolumesForRemoval added in v0.4.38

func DescribeVolumesForRemoval(volumes []ContainerVolume) []ContainerVolume

DescribeVolumesForRemoval marks which of a container's volumes may be offered.

A volume another container still uses is never offered: deleting it takes data out from under something that is running, and the person clicking is looking at a card for a different container entirely.

A daemon that cannot count references is treated as "cannot say", which is treated as "do not offer". Guessing wrong in that direction costs disk space; guessing wrong in the other costs somebody's database.

type DockerService

type DockerService interface {
	// image
	IsExistImage(imageName string) bool
	PullImage(ctx context.Context, imageName string) error
	PullLatestImage(ctx context.Context, imageName string) (bool, error)
	RemoveImage(name string) error

	// container
	CheckContainerHealth(id string) (bool, error)
	CreateContainer(m model.CustomizationPostData, id string) (containerID string, err error)
	CreateContainerShellSession(container, row, col string) (hr types.HijackedResponse, err error)
	DescribeContainer(ctx context.Context, name string) (*types.ContainerJSON, error)
	GetContainer(id string) (types.Container, error)
	GetContainerAppList(name, image, state *string) (*[]model.MyAppList, *[]model.MyAppList)
	GetContainerByName(name string) (*types.Container, error)
	GetContainerLog(name string) ([]byte, error)
	GetContainerStats() []model.DockerStatsModel
	RecreateContainer(ctx context.Context, id string, pull bool, force bool) error
	RemoveContainer(name string, update bool) error
	RestartContainer(id string) error
	SampleContainerStats(ctx context.Context, ids []string) map[string]*types.StatsJSON
	VolumeMountpoints(ctx context.Context, names []string) map[string]string
	VolumeUsage(ctx context.Context) map[string]docker.VolumeSpace
	RemoveVolume(ctx context.Context, name string) error
	RenameContainer(name, id string) (err error)
	StartContainer(name string) error
	StopContainer(id string) error

	// network
	GetNetworkList() []types.NetworkResource

	// docker server
	GetServerInfo() (system.Info, error)
}

func NewDockerService

func NewDockerService() DockerService

type ProgressDetail

type ProgressDetail struct {
	Current int64 `json:"current"`
	Total   int64 `json:"total"`
}

type PullOut

type PullOut struct {
	Status         StatusType     `json:"status"`
	ProgressDetail ProgressDetail `json:"progressDetail"`
	Id             string         `json:"id"`
}

type ScheduleEvery added in v0.4.37

type ScheduleEvery string

ScheduleEvery is how often a schedule comes round.

const (
	ScheduleDaily  ScheduleEvery = "daily"
	ScheduleWeekly ScheduleEvery = "weekly"
)

type Services

type Services interface {
	AppStoreManagement() *AppStoreManagement

	// Git() *GitService
	Compose() *ComposeService
	Docker() DockerService
	Gateway() external.ManagementService
	Notify() external.NotifyService
	MessageBus() *message_bus.ClientWithResponses
}
var (
	MyService Services
)

func NewService

func NewService(RuntimePath string) Services

type StatusType

type StatusType string
const (
	Pull         StatusType = "Pulling fs layer"
	PullComplete StatusType = "Pull complete"
	// AlreadyExists is what the daemon sends for a layer this host already has. It
	// arrives INSTEAD of the pulling/complete pair, never alongside it, so a layer
	// reported this way is one that is both known and finished.
	AlreadyExists StatusType = "Already exists"
)

type Throttler

type Throttler struct {
	InvokeInterval time.Duration
	LastInvokeTime time.Time
}

func NewThrottler

func NewThrottler(interval time.Duration) *Throttler

func (*Throttler) ThrottleFunc

func (t *Throttler) ThrottleFunc(f func())

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL