workshop

package
v0.9.4 Latest Latest
Warning

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

Go to latest
Published: Jul 20, 2026 License: GPL-3.0 Imports: 27 Imported by: 0

Documentation

Index

Constants

View Source
const (
	ContextProjectId = ContextKeyProjectId("project-id")
	ContextUser      = ContextKeyUser("user")

	Uid = 1000
	Gid = 1000

	RootUmask   = os.FileMode(0022)
	NormalUmask = os.FileMode(0002)
)
View Source
const (
	ProjectLock = ".workshop.lock"
	// path used in workshop to mount the project directory
	WorkshopProjectPath = "/project"
)
View Source
const MAX_WORKSHOP_NAME_LENGTH = 40

Variables

View Source
var (
	ErrWorkshopNotLaunched   = errors.New("workshop not launched")
	ErrVolumeNotFound        = errors.New("volume not found")
	ErrVolumeAlreadyExists   = errors.New("volume already exists")
	ErrVolumeInUse           = errors.New("volume is in use")
	ErrSnapshotNotFound      = errors.New("snapshot not found")
	ErrSnapshotAlreadyExists = errors.New("snapshot already exists")
	ErrSdkProfileNotFound    = errors.New("sdk profile not found")
	ErrIncompatibleBackend   = errors.New("incompatible backend")

	User = user.User{
		Uid:      "1000",
		Gid:      "1000",
		Username: "workshop",
		HomeDir:  "/home/workshop",
	}
)
View Source
var (
	ErrProjectLockNotFound    = errors.New("project lock file not found")
	ErrProjectAlreadyExists   = errors.New("project already exists")
	ErrNotProject             = errors.New("not a project (no workshop files found)")
	ErrNoRelativePathsAllowed = errors.New("absolute project path must be used")

	NewProjectId = allocateProjectId
)
View Source
var (
	ConfigProjectId               = "user.workshop.project-id"
	ConfigWorkshopName            = "user.workshop.name"
	ConfigWorkshopFile            = "user.workshop.file"
	ConfigWorkshopBase            = "user.workshop.base"
	ConfigWorkshopBaseFingerprint = "user.workshop.base-fingerprint"
	ConfigWorkshopSnapshotType    = "user.workshop.snapshot-type"
	ConfigWorkshopSnapshotFormat  = "user.workshop.format-revision"
	ConfigWorkshopSha3_384        = "user.workshop.sha3-384"
	ConfigProjectPathDevice       = "workshop.project"
	ConfigStateStorageDevice      = "workshop.state-storage"
)
View Source
var (
	SupportedBases = sdk.AllowedBases

	Directory = ".workshop"
	Filenames = []string{"workshop.yaml", ".workshop.yaml"}
)
View Source
var DefaultDevices = defaultDevices
View Source
var InstallTimeNow = time.Now

Functions

func AptCacheDir

func AptCacheDir(pid, w string) string

func CacheDir

func CacheDir(pid, w string) string

func DataDir

func DataDir(pid, w string) string

func FakeDefaultDevices

func FakeDefaultDevices(f func(pid, w string) ([]Mount, []ProxyEntry)) func()

func Filepath

func Filepath(project, name string) string

func IsImplicitSdk

func IsImplicitSdk(name string) bool

IsImplicitSdk checks whether the given SDK is installed regardless of whether it appears in the workshop file.

func IsProjectSdk

func IsProjectSdk(name string) bool

IsProjectSdk checks whether the given SDK is defined in the project directory.

func LocalSdkDir

func LocalSdkDir(userDataDir, pid, w, name string) string

func LockPath

func LockPath(path string) string

func ProjectCacheDir

func ProjectCacheDir(pid string) string

func ProjectDataDir

func ProjectDataDir(pid string) string

func ProjectSdkPath

func ProjectSdkPath(project, name string) string

func ProjectUserData

func ProjectUserData(userDataDir, pid string) string

func ReplaceBackend

func ReplaceBackend(state *state.State, backend Backend)

ReplaceBackend replaces the store used by the manager.

func SdkDeviceName

func SdkDeviceName(sk string) string

func SdkMountDir

func SdkMountDir(userDataDir, pid, w, sdk string) string

func SdkMountHostSource

func SdkMountHostSource(userDataDir, pid, w, sdk, plug string) string

func SdkSourcePath

func SdkSourcePath(userDataDir string, project Project, w, sk string, source sdk.Source) string

func SketchSdkCurrent

func SketchSdkCurrent(userDataDir, pid, w string) string

func SketchSdkDir

func SketchSdkDir(userDataDir, pid, w string) string

func SketchSdkStash

func SketchSdkStash(userDataDir, pid, w string) string

func StateStorageDir

func StateStorageDir(pid, w string) string

func TrySdkDir

func TrySdkDir(userDataDir, sdk string) string

func UserData

func UserData(userDataDir, pid, w string) string

func UserDataRootDir

func UserDataRootDir(homedir string, env map[string]string) string

func ValidateFile

func ValidateFile(file *File) error

ValidateFile validates a workshop File struct.

func ValidateProjectId added in v0.9.2

func ValidateProjectId(projectId string) error

func ValidateSdkInfo

func ValidateSdkInfo(pid, w, base, sk, sdkYaml string) error

Types

type Action

type Action string

func (Action) MarshalYAML

func (a Action) MarshalYAML() (any, error)

func (Action) String

func (a Action) String() string

func (*Action) UnmarshalYAML

func (a *Action) UnmarshalYAML(value *yaml.Node) error

type Backend

type Backend interface {
	Stash
	BaseImageManager
	SdkManager

	// The backend will attempt to load a project for the given path
	// using its mapping between the path and a project id. If the project
	// is not found, e.g. .lock file was removed by the user, but there is still
	// an existing record for the path, it will first attempt to restore .lock.
	// If unsuccessful, a new project will be created for the path provided.
	CreateOrLoadProject(ctx context.Context, path string) (*Project, bool, error)

	// Returns a list of projects known to the backend. The returned map
	// has a username key that the corresponding projects belong to.
	Projects(ctx context.Context) (map[string][]Project, error)

	// Loads a workshop instance.
	Workshop(ctx context.Context, name string) (*Workshop, error)

	// Returns a workshop's file system interface.
	WorkshopFs(ctx context.Context, name string) (fsutil.Fs, error)

	// Returns a list of workshops for the project in context.
	ProjectWorkshops(ctx context.Context) ([]*Workshop, error)

	// Number representing workshop and snapshot compatibility level.
	FormatRevision() sdk.Revision

	// Look up the given snapshot. On success, returns the given snapshot and
	// the workshops that are based on it. On failure, returns an error. In the
	// unlikely event the error is caused by a snapshot conflict (i.e. a name
	// collision), also returns the conflicting snapshot.
	Snapshot(ctx context.Context, snapshot Snapshot) (*SnapshotInfo, error)

	// Launch a clean workshop instance. If the workshop exists, wipe out
	// its rootfs and rebuild it from the given snapshot (which may be just
	// a base image). Configuration and devices of the rebuilt workshop
	// will be reset to the default one.
	LaunchOrRebuildWorkshop(ctx context.Context, file *File, snapshot Snapshot) error

	// Create a snapshot of the workshop's rootfs. The snapshot can be used
	// by passing an identical Snapshot to LaunchOrRebuildWorkshop.
	TakeSnapshot(ctx context.Context, name string, snapshot Snapshot) error

	// Remove the given snapshot.
	RemoveSnapshot(ctx context.Context, snapshot Snapshot) error

	// Delete workshop. Stop the workshop forcefully if not in Stopped before deleting
	RemoveWorkshop(ctx context.Context, name string) error

	// Starts a workshop and waits until it is ready
	// to accept commands
	StartWorkshop(ctx context.Context, name string) error

	// Stops workshop gracefully (i.e. waits for the graceful instance and all
	// its running services termination) unless force is used.
	StopWorkshop(ctx context.Context, name string, force bool) error

	// Adds a workshop mount described by the properties.
	AddWorkshopMount(ctx context.Context, name string, mount Mount) error

	// Removes a workshop mount.
	RemoveWorkshopMount(ctx context.Context, name, mount string) error

	// Mount an SDK volume and add the SDK to the Sdks field.
	InstallSdk(ctx context.Context, name string, setup sdk.Setup) error

	// Remove an SDK from the Sdks field and unmount the SDK volume.
	UninstallSdk(ctx context.Context, name, sk string) error

	// Execute a command in a given workshop. The client should differentiate
	// between the errors that occurred during the execution but not related to
	// the command (i.e. the workshop does not exist) and the errors that were
	// produced by the command itself (i.e. return code != 0). If the latter, an
	// instance of ErrExec with the status code will be returned.
	// The callback ExecContext.WaitExecution will initite the command execution
	// and redirect its IO using args.ExecControls. ExecContext.Environment will
	// contain full (actual)
	Exec(ctx context.Context, name string, args *Execution) (ExecContext, error)
}

func WorkshopBackend

func WorkshopBackend(st *state.State) Backend

Store returns the store service provided by the optional device context or the one used by the snapstate package if the former has no override.

type BaseImage

type BaseImage struct {
	// Base name (e.g. ubuntu@24.04).
	Name string `json:"name"`
	// Base image identifier, typically a hash.
	Fingerprint string `json:"fingerprint"`
}

type BaseImageManager

type BaseImageManager interface {
	// Lookup the latest image for the given base.
	GetBase(ctx context.Context, base string) (BaseImage, error)
	// Download the given base image.
	DownloadBase(ctx context.Context, image BaseImage, report *progress.Reporter) error
}

type Camera

type Camera struct {
	Name string `json:"name"`
}

type Connection

type Connection struct {
	PlugRef PlugRef `yaml:"plug"`
	SlotRef SlotRef `yaml:"slot"`
}

type ContextKeyProjectId

type ContextKeyProjectId string

type ContextKeyUser

type ContextKeyUser string

type CustomDevice added in v0.9.1

type CustomDevice struct {
	Name      string `json:"name"`
	Subsystem string `json:"subsystem,omitempty"`
	VendorID  string `json:"vendorid,omitempty"`
	ProductID string `json:"productid,omitempty"`
}

type Desktop

type Desktop struct {
	Wayland *ProxyEntry
	X11     *ProxyEntry
}

func (*Desktop) Equal

func (d *Desktop) Equal(other *Desktop) bool

type ErrExec

type ErrExec struct {
	Status int
}

func (*ErrExec) Error

func (e *ErrExec) Error() string

type ExecArgs

type ExecArgs struct {
	// Command is the user-requested command. It is used for user-facing
	// summaries and diagnostics.
	Command []string

	// CommandPrefix is a Workshop-managed wrapper prepended to [Command] at the
	// execution boundary. It is not part of the user-requested command.
	CommandPrefix []string
	UserId        int
	GroupId       int
	WorkDir       string
	Timeout       time.Duration
	Environment   map[string]string
	Interactive   bool
	Terminal      bool
	SplitStderr   bool
	Width         int
	Height        int
}

func (ExecArgs) EffectiveCommand added in v0.9.1

func (args ExecArgs) EffectiveCommand() []string

EffectiveCommand returns the command that should be sent to the execution backend, including any Workshop-managed command prefix.

type ExecContext

type ExecContext struct {
	Environment   map[string]string
	WaitExecution func(ctx context.Context) error
}

type ExecControls

type ExecControls struct {
	Stdin   io.Reader
	Stdout  io.Writer
	Stderr  io.Writer
	Control func(conn *websocket.Conn)
}

type Execution

type Execution struct {
	ExecArgs
	ExecControls
}

type File

type File struct {
	Name        string            `yaml:"name"`
	Base        string            `yaml:"base"`
	Sdks        []SdkRecord       `yaml:"sdks,omitempty"`
	Connections []Connection      `yaml:"connections,omitempty"`
	Actions     map[string]Action `yaml:"actions,omitempty"`
}

type Gpu

type Gpu struct {
	Name string
}

type Hostname added in v0.9.2

type Hostname struct {
	// Domain is the workshop's DNS name (e.g. "ws.myapp.wp" or "ws.42424242.wp"),
	// if available.
	Domain string
	// Note explains why Domain uses the project ID instead of basename.
	Note string
}

type Mount

type Mount struct {
	Name      string      `json:"name"`
	Type      MountType   `json:"type"`
	What      string      `json:"what"`
	MakeWhat  bool        `json:"make-what,omitempty"`
	Where     string      `json:"where"`
	MakeWhere bool        `json:"make-where,omitempty"`
	Mode      os.FileMode `json:"mode,omitempty"`
	Owner     sys.UserID  `json:"owner,omitempty"`
	Group     sys.GroupID `json:"group,omitempty"`
	ReadOnly  bool        `json:"readonly"`
}

func SdkMount

func SdkMount(userDataDir, pid, w string, setup sdk.Setup) Mount

type MountType

type MountType int
const (
	HostWorkshop MountType = iota
	WorkshopWorkshop
	Volume
)

type NetworkManager added in v0.9.2

type NetworkManager interface {
	// InterfaceAddrs lists the IP addresses configured for the given device.
	InterfaceAddrs(ctx context.Context, iface string) ([]netip.Addr, error)
}

type PlugOrBind

type PlugOrBind struct {
	Bind *PlugRef
	Plug any
}

func (PlugOrBind) MarshalYAML

func (p PlugOrBind) MarshalYAML() (any, error)

func (*PlugOrBind) UnmarshalYAML

func (p *PlugOrBind) UnmarshalYAML(value *yaml.Node) error

type PlugRef

type PlugRef struct {
	Sdk  string
	Name string
}

func (PlugRef) MarshalYAML

func (b PlugRef) MarshalYAML() (any, error)

func (PlugRef) String

func (p PlugRef) String() string

func (*PlugRef) UnmarshalYAML

func (b *PlugRef) UnmarshalYAML(value *yaml.Node) error

type Project

type Project struct {
	Path      string `json:"path"`
	ProjectId string `json:"id"`
}

func (*Project) Exists

func (p *Project) Exists() bool

func (*Project) ReadWorkshops

func (w *Project) ReadWorkshops() (map[string]string, error)

func (*Project) Workshop

func (w *Project) Workshop(workshop string) (*File, error)

type ProjectTracker

type ProjectTracker struct {
	Projects []Project
}

func (*ProjectTracker) Track

func (t *ProjectTracker) Track(path string) (*Project, TrackResult, error)

Track attempts to locate a known project that contains the given path. If unsuccessful, it creates a new project and begins tracking it. Moved projects will be updated with the new path, whereas copied projects will receive a new project ID.

type ProxyDirection

type ProxyDirection int
const (
	HostToWorkshop ProxyDirection = iota
	WorkshopToHost
)

type ProxyEntry

type ProxyEntry struct {
	Name      string
	Connect   ProxyTarget
	Listen    ProxyTarget
	Direction ProxyDirection
}

func (*ProxyEntry) Equal

func (p *ProxyEntry) Equal(other *ProxyEntry) bool

type ProxyTarget

type ProxyTarget struct {
	Address  string
	Protocol string
}

type SdkInstallation

type SdkInstallation struct {
	sdk.Setup
	// 1-based index of SDK installation (0 is reserved for the base).
	InstallOrder int       `json:"install-order"`
	InstalledAt  time.Time `json:"installed-at"`
}

type SdkManager

type SdkManager interface {
	// Import an SDK tarball as a new volume.
	ImportSdk(ctx context.Context, meta sdk.Meta, tarball *os.File) error

	// Delete an SDK volume. It does not unmount the volume from workshops
	// where it is mounted. No error is returned if the SDK does not exist.
	DeleteSdk(ctx context.Context, setup sdk.Setup) error

	// List available SDK volumes.
	Sdks(ctx context.Context) ([]SdkVolume, error)

	// Get the SDK volume information.
	Sdk(ctx context.Context, setup sdk.Setup) (SdkVolume, error)
}

type SdkProfile

type SdkProfile struct {
	Sdk string

	Camera        *Camera
	CustomDevices []CustomDevice
	Mounts        map[string]Mount
	Tunnels       []Tunnel
	Agent         *SshAgent
	Gpu           *Gpu
	Desktop       *Desktop
}

func NewSdkProfile

func NewSdkProfile(sdkName string) SdkProfile

type SdkRecord

type SdkRecord struct {
	Name    string                `yaml:"name"`
	Channel string                `yaml:"channel,omitempty"`
	Source  sdk.Source            `yaml:"source,omitempty"`
	Plugs   map[string]PlugOrBind `yaml:"plugs,omitempty"`
	Slots   map[string]any        `yaml:"slots,omitempty"`
}

func (SdkRecord) MarshalYAML

func (s SdkRecord) MarshalYAML() (any, error)

func (*SdkRecord) UnmarshalYAML

func (s *SdkRecord) UnmarshalYAML(value *yaml.Node) error

type SdkVolume

type SdkVolume struct {
	sdk.Meta
	// Project ID / Workshop pairs that the volume is attached to.
	Workshops map[string][]string
	// Size reports the current volume usage in bytes when available.
	Size uint64
}

type SlotRef

type SlotRef = PlugRef

type Snapshot

type Snapshot struct {
	Format sdk.Revision
	Image  BaseImage
	Sdks   []sdk.ContentID
}

Snapshot identifies a workshop snapshot without depending on backend-specific details. For example snapshot names in LXD are subject to length limits and a restricted character set.

func BaseOnly

func BaseOnly(format sdk.Revision, name, fingerprint string) Snapshot

BaseOnly identifies a "snapshot" which consists of a base image only.

func SdkSnapshot

func SdkSnapshot(format sdk.Revision, image BaseImage, sdks []sdk.Setup) Snapshot

SdkSnapshot identifies a snapshot consisting of a base image and a sequence of installed SDKs.

func (Snapshot) Equal

func (s Snapshot) Equal(other Snapshot) bool

func (Snapshot) IsBase

func (s Snapshot) IsBase() bool

IsBase returns whether the snapshot is just a base image.

func (Snapshot) IsBasedOn

func (s Snapshot) IsBasedOn(other Snapshot) bool

IsBasedOn returns true if other is a prefix of s.

type SnapshotInfo

type SnapshotInfo struct {
	Snapshot
	// Project ID / Workshop pairs that are based on the snapshot.
	Workshops map[string][]string
}

type SshAgent

type SshAgent struct {
	ProxyEntry
}

func (*SshAgent) Equal

func (s *SshAgent) Equal(other *SshAgent) bool

type Stash

type Stash interface {
	// Make a stash of the workshop. The workshop will be stopped and will not
	// be available to other workshop operations, e.g. list, stop, start and so
	// on. A new workshop with the same name can be launched for the same
	// project-id.
	StashWorkshop(ctx context.Context, name string) error

	// Restore the workshop from the stash (if exists, see StashWorkshop). The
	// workshop will be restored and become visible to the backend operations.
	// Fails if a workshop with the same name exists.
	UnstashWorkshop(ctx context.Context, name string) error

	// Delete the workshop from stash (if exists).
	RemoveWorkshopStash(ctx context.Context, name string) error
}

type TrackResult

type TrackResult int
const (
	ProjectError TrackResult = iota
	ProjectFound
	ProjectMoved
	ProjectAdded
)

type Tunnel

type Tunnel struct {
	ProxyEntry
}

type Workshop

type Workshop struct {
	Backend Backend
	Project Project
	// Workshop file that was used to launch it; it may be out of sync with the
	// file in the project directory due to user's edits, etc.
	File    *File
	Name    string
	Format  sdk.Revision
	Image   BaseImage
	Running bool
	// Installed SDKs.
	Sdks map[string]SdkInstallation
	// Workshop devices installed.
	Profiles map[string]SdkProfile
	Hostname Hostname
}

func (*Workshop) Mounts

func (w *Workshop) Mounts(sdks []*sdk.Info) map[string][]Mount

Mounts returns a map of active SDK mounts for the workshop.

func (*Workshop) SdkInfo

func (w *Workshop) SdkInfo(ctx context.Context, sdkName string) (*sdk.Info, error)

Reads information about the installed SDK from its meta file.

func (*Workshop) SdkInfosByInstallOrder

func (w *Workshop) SdkInfosByInstallOrder(ctx context.Context) ([]*sdk.Info, error)

Returns a map of SDK info for installed SDKs. The info includes SDK details parsed from its sdk.yaml, such as base, plugs, slots, etc.

func (*Workshop) SdksByInstallOrder

func (w *Workshop) SdksByInstallOrder() []SdkInstallation

Returns the list of SDKs of the workshop sorted by installation order.

func (*Workshop) Tunnels

func (w *Workshop) Tunnels(sdks []*sdk.Info) map[string][]Tunnel

Tunnels returns a map of active SDK tunnels for the workshop.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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