Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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 boot/components/core/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ func All() []boot.Component {
Dispatcher(),
WASMIsolation(),
Profiler(),
Artifacts(),
Registry(),
Finder(),
Security(),
Expand Down
27 changes: 27 additions & 0 deletions boot/components/core/artifact.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: MPL-2.0

package core

import (
"context"
"fmt"

"github.com/wippyai/runtime/api/boot"
"github.com/wippyai/runtime/boot/deps/artifact"
"github.com/wippyai/runtime/boot/deps/artifact/nodepackage"
)

// Artifacts composes the artifact formats available to dependency lifecycle
// operations. Formats are explicit boot dependencies rather than globals.
func Artifacts() boot.Component {
return boot.New(boot.P{
Name: ArtifactName,
Load: func(ctx context.Context) (context.Context, error) {
registry := artifact.NewRegistry()
if err := registry.Register(nodepackage.New()); err != nil {
return ctx, fmt.Errorf("register artifact format: %w", err)
}
return artifact.WithRegistry(ctx, registry), nil
},
})
}
26 changes: 26 additions & 0 deletions boot/components/core/artifact_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: MPL-2.0

package core

import (
"testing"

"github.com/stretchr/testify/require"
bootpkg "github.com/wippyai/runtime/boot"
"github.com/wippyai/runtime/boot/deps/artifact"
"go.uber.org/zap"
)

func TestArtifactsBootRegistration(t *testing.T) {
ctx, err := bootpkg.NewBootstrapContext(zap.NewNop(), nil)
require.NoError(t, err)
loader, err := bootpkg.NewLoader(Artifacts())
require.NoError(t, err)
ctx, err = loader.Load(ctx)
require.NoError(t, err)

registry := artifact.GetRegistry(ctx)
require.NotNil(t, registry)
_, registered := registry.Resolve("node-package")
require.True(t, registered)
}
3 changes: 3 additions & 0 deletions boot/components/core/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const (
PIDGenName boot.Name = "pidgen"
SecurityName boot.Name = "security"
SecurityPolicyName boot.Name = "security.policy"
ArtifactName boot.Name = "artifact"
RegistryName boot.Name = "registry"
FinderName boot.Name = "finder"
SupervisorName boot.Name = "supervisor"
Expand Down Expand Up @@ -49,4 +50,6 @@ const (
RegistryDependencyLockPath boot.Name = "dependency_lock_path"
// RegistryDependencyVendorDir overrides vendor directory for dependency installs.
RegistryDependencyVendorDir boot.Name = "dependency_vendor_dir"
// RegistryDependencyArtifactRoot overrides the root for materialized dependency artifacts.
RegistryDependencyArtifactRoot boot.Name = "dependency_artifact_root"
)
2 changes: 1 addition & 1 deletion boot/components/core/core_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func TestCorePlugins(t *testing.T) {
t.Error("PID generator not available in context")
}

lifecycleLoader, err := bootpkg.NewLoader(Registry(), Supervisor())
lifecycleLoader, err := bootpkg.NewLoader(Artifacts(), Registry(), Supervisor())
require.NoError(t, err)
ctx, err = lifecycleLoader.Load(ctx)
require.NoError(t, err)
Expand Down
12 changes: 10 additions & 2 deletions boot/components/core/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
logapi "github.com/wippyai/runtime/api/logs"
regapi "github.com/wippyai/runtime/api/registry"
bootpkg "github.com/wippyai/runtime/boot"
"github.com/wippyai/runtime/boot/deps/artifact"
hubdeps "github.com/wippyai/runtime/boot/deps/hub"
"github.com/wippyai/runtime/boot/deps/lock"
"github.com/wippyai/runtime/system/registry"
Expand All @@ -33,7 +34,7 @@ func Registry() boot.Component {

return boot.New(boot.P{
Name: RegistryName,
DependsOn: []boot.Name{},
DependsOn: []boot.Name{ArtifactName},
Load: func(ctx context.Context) (context.Context, error) {
logger := logapi.GetLogger(ctx).Named("registry")
bus := event.GetBus(ctx)
Expand Down Expand Up @@ -119,7 +120,7 @@ func Registry() boot.Component {

registryOpts := []registry.Option{}

depHandler, err := newDependencyHandler(cfg, logger.Named("dependency"), resolver)
depHandler, err := newDependencyHandler(ctx, cfg, logger.Named("dependency"), resolver)
if err != nil {
logger.Warn("dependency handler disabled", zap.Error(err))
} else if depHandler != nil {
Expand Down Expand Up @@ -220,6 +221,7 @@ func readKindSlice(cfg boot.Config, key boot.Name) ([]regapi.Kind, bool) {
}

func newDependencyHandler(
ctx context.Context,
cfg boot.Config,
logger *zap.Logger,
resolver regapi.DependencyResolver,
Expand All @@ -233,6 +235,11 @@ func newDependencyHandler(
Logger: logger,
Resolver: resolver,
}
artifactRegistry := artifact.GetRegistry(ctx)
if artifactRegistry == nil {
return nil, fmt.Errorf("artifact registry is not initialized")
}
opts.Artifacts = artifactRegistry
workspaceReplacements, err := lock.WorkspaceReplacements(cfg)
if err != nil {
return nil, fmt.Errorf("load workspace replacements: %w", err)
Expand All @@ -244,6 +251,7 @@ func newDependencyHandler(
opts.DownloadTimeout = registryCfg.GetDuration(RegistryDependencyDownloadTimeout, 0)
opts.LockPath = registryCfg.GetString(RegistryDependencyLockPath, "")
opts.VendorDir = registryCfg.GetString(RegistryDependencyVendorDir, "")
opts.ArtifactRoot = registryCfg.GetString(RegistryDependencyArtifactRoot, "")
}

return hubdeps.NewDependencyHandler(opts)
Expand Down
2 changes: 1 addition & 1 deletion boot/components/core/registry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func TestRegistryPostgresHistoryRequiresDSN(t *testing.T) {
ctx, err := bootpkg.NewBootstrapContext(zap.NewNop(), cfg)
require.NoError(t, err)

loader, err := bootpkg.NewLoader(Registry())
loader, err := bootpkg.NewLoader(Artifacts(), Registry())
require.NoError(t, err)

_, err = loader.Load(ctx)
Expand Down
33 changes: 33 additions & 0 deletions boot/deps/artifact/context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: MPL-2.0

package artifact

import (
"context"

ctxapi "github.com/wippyai/runtime/api/context"
)

var registryKey = &ctxapi.Key{Name: "artifact.registry"}

// WithRegistry stores the boot-composed artifact registry.
func WithRegistry(ctx context.Context, registry *Registry) context.Context {
appCtx := ctxapi.AppFromContext(ctx)
if appCtx == nil {
return ctx
}
if appCtx.Get(registryKey) == nil {
appCtx.With(registryKey, registry)
}
return ctx
}

// GetRegistry retrieves the boot-composed artifact registry.
func GetRegistry(ctx context.Context) *Registry {
appCtx := ctxapi.AppFromContext(ctx)
if appCtx == nil {
return nil
}
registry, _ := appCtx.Get(registryKey).(*Registry)
return registry
}
181 changes: 181 additions & 0 deletions boot/deps/artifact/materialize.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// SPDX-License-Identifier: MPL-2.0

package artifact

import (
"context"
"errors"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"strings"
)

// Materialize validates a resource with its format and transactionally mirrors
// it below root at the format-derived path.
func Materialize(
ctx context.Context,
registry *Registry,
declaration Declaration,
input InspectInput,
root string,
) (Descriptor, string, error) {
descriptor, err := registry.Inspect(ctx, declaration, input)
if err != nil {
return Descriptor{}, "", err
}

rootAbs, err := filepath.Abs(root)
if err != nil {
return Descriptor{}, "", fmt.Errorf("resolve artifact root: %w", err)
}
destination := filepath.Join(rootAbs, filepath.FromSlash(descriptor.RelativePath))
if err := ensureWithinRoot(rootAbs, destination); err != nil {
return Descriptor{}, "", err
}
if err := exactMirror(input.Filesystem, destination); err != nil {
return Descriptor{}, "", fmt.Errorf("materialize %s: %w", input.ResourceID.String(), err)
}
return descriptor, destination, nil
}

func ensureWithinRoot(root, destination string) error {
relative, err := filepath.Rel(root, destination)
if err != nil {
return fmt.Errorf("resolve artifact destination: %w", err)
}
if relative == "." || relative == ".." ||
strings.HasPrefix(relative, ".."+string(filepath.Separator)) {
return errors.New("artifact destination escapes the materialization root")
}
return nil
}

func exactMirror(source fs.FS, destination string) error {
parent := filepath.Dir(destination)
if err := os.MkdirAll(parent, 0o755); err != nil {
return fmt.Errorf("create destination parent: %w", err)
}

stage, err := os.MkdirTemp(parent, "."+filepath.Base(destination)+".stage-*")
if err != nil {
return fmt.Errorf("create staging directory: %w", err)
}
stageActive := true
defer func() {
if stageActive {
_ = os.RemoveAll(stage)
}
}()

if err := copyTree(source, stage); err != nil {
return err
}

backup, err := os.MkdirTemp(parent, "."+filepath.Base(destination)+".backup-*")
if err != nil {
return fmt.Errorf("reserve backup path: %w", err)
}
if err := os.Remove(backup); err != nil {
return fmt.Errorf("prepare backup path: %w", err)
}
hadDestination := false
if _, err := os.Stat(destination); err == nil {
if err := os.Rename(destination, backup); err != nil {
return fmt.Errorf("stage existing destination: %w", err)
}
hadDestination = true
} else if !os.IsNotExist(err) {
return fmt.Errorf("stat destination: %w", err)
}

if err := os.Rename(stage, destination); err != nil {
if hadDestination {
if restoreErr := os.Rename(backup, destination); restoreErr != nil {
return fmt.Errorf(
"activate staged artifact: %w (restore previous destination: %w)",
err,
restoreErr,
)
}
}
return fmt.Errorf("activate staged artifact: %w", err)
}
stageActive = false
if hadDestination {
if err := os.RemoveAll(backup); err != nil {
return fmt.Errorf("remove artifact backup: %w", err)
}
}
return nil
}

func copyTree(source fs.FS, destination string) error {
portablePaths := make(map[string]string)
return fs.WalkDir(source, ".", func(path string, entry fs.DirEntry, walkErr error) error {
if walkErr != nil {
return walkErr
}
if path == "." {
return nil
}
if err := validatePortablePath(path); err != nil {
return fmt.Errorf("invalid resource path %q: %w", path, err)
}
portableKey := strings.ToLower(path)
if previous, exists := portablePaths[portableKey]; exists && previous != path {
return fmt.Errorf("resource paths %q and %q collide on case-insensitive filesystems", previous, path)
}
portablePaths[portableKey] = path
target := filepath.Join(destination, filepath.FromSlash(path))
if err := ensureWithinRoot(destination, target); err != nil {
return err
}

info, err := entry.Info()
if err != nil {
return fmt.Errorf("inspect %q: %w", path, err)
}
if info.Mode()&os.ModeSymlink != 0 {
return fmt.Errorf("symlink %q is not allowed", path)
}
if entry.IsDir() {
return os.MkdirAll(target, 0o755)
}
if !info.Mode().IsRegular() {
return fmt.Errorf("non-regular file %q is not allowed", path)
}

if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
return err
}
src, err := source.Open(path)
if err != nil {
return fmt.Errorf("open %q: %w", path, err)
}

dst, err := os.OpenFile(target, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0o600)
if err != nil {
_ = src.Close()
return fmt.Errorf("create %q: %w", path, err)
}
_, copyErr := io.Copy(dst, src)
sourceCloseErr := src.Close()
closeErr := dst.Close()
if copyErr != nil {
return fmt.Errorf("copy %q: %w", path, copyErr)
}
if sourceCloseErr != nil {
return fmt.Errorf("close source %q: %w", path, sourceCloseErr)
}
if closeErr != nil {
return fmt.Errorf("close %q: %w", path, closeErr)
}
if err := os.Chmod(target, 0o644); err != nil { //nolint:gosec // Artifacts are shared source files, not secrets.
return fmt.Errorf("set permissions on %q: %w", path, err)
}
return nil
})
}
Loading