service

package
v0.4.36 Latest Latest
Warning

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

Go to latest
Published: Sep 10, 2026 License: Apache-2.0 Imports: 73 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

This section is empty.

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 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 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 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 Standardize

func Standardize(text string) string

func StoreRoot

func StoreRoot(workdir string) (string, error)

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 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) 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) 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 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
	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 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