Files
zeekayandHanzo Dev a386271c80 isolation is a value the org sets, and an empty one is not runc
Every sandbox was created privileged under the daemon's default runtime, and
the setting that would have changed it defaulted to an empty string that
silently meant runc. For a service whose whole premise is running code
somebody else wrote, that is not a boundary.

An org sets its isolation and the create request carries it, so a sandbox
records what it was made with: changing the policy moves new sandboxes and
leaves running ones alone. gvisor is the default. firecracker is delivered
through kata's shim, which is why it is one name rather than two. runc is
refused unless the runner names that org, and an empty list names nobody.

Absent means the default, unknown is a refusal, and a runtime the daemon does
not have is a refusal. Nothing substitutes quietly.

The runner took an organization in a field called UserId with a note beside
it saying so. Deciding who may have runc through a field named after
something else is not reviewable.

Privileged is replaced by dropping everything and granting the eight the
daemon uses. Nothing the sandbox does broke; what stopped working is mount,
mknod, raw sockets, chroot, writing to proc, and seeing the host's disks.
AUDIT_WRITE is among them because without it sudo works and complains every
time.

The creation histogram's lowest bucket was a tenth of a second, so the metric
could not observe the claim the product makes about its own speed. It can
now, and it carries the isolation, so comparing them is a query.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-11 13:47:31 -07:00

168 lines
5.2 KiB
Go

// Copyright 2025 Hanzo Industries Inc.
// SPDX-License-Identifier: AGPL-3.0
package snapshot
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"os"
"strings"
"time"
apiclient "github.com/hanzoai/runtime/libs/api-client-go"
apiclient_cli "github.com/hanzoai/runtime/apps/cli/apiclient"
"github.com/hanzoai/runtime/apps/cli/cmd/common"
"github.com/hanzoai/runtime/apps/cli/docker"
views_common "github.com/hanzoai/runtime/apps/cli/views/common"
views_util "github.com/hanzoai/runtime/apps/cli/views/util"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/registry"
"github.com/docker/docker/client"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/spf13/cobra"
)
var PushCmd = &cobra.Command{
Use: "push [SNAPSHOT]",
Short: "Push local snapshot",
Long: "Push a local Docker image to Runtime. To securely build it on our infrastructure, use 'runtime snapshot build'",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
sourceImage := args[0]
err := common.ValidateImageName(sourceImage)
if err != nil {
return err
}
dockerClient, err := client.NewClientWithOpts(
client.FromEnv,
client.WithAPIVersionNegotiation(),
)
if err != nil {
return fmt.Errorf("failed to create Docker client: %w", err)
}
defer dockerClient.Close()
// Check if the image exists locally when not building
if exists, err := docker.ImageExistsLocally(ctx, dockerClient, sourceImage); err != nil {
return err
} else if !exists {
return fmt.Errorf("image '%s' not found locally. Please ensure the image exists and try again", sourceImage)
}
// Validate image architecture
isArchAmd, err := docker.CheckAmdArchitecture(ctx, dockerClient, sourceImage)
if err != nil {
return fmt.Errorf("failed to check image architecture: %w", err)
}
if !isArchAmd {
return fmt.Errorf("image '%s' is not compatible with AMD architecture", sourceImage)
}
apiClient, err := apiclient_cli.GetApiClient(nil, nil)
if err != nil {
return err
}
tokenResponse, res, err := apiClient.DockerRegistryAPI.GetTransientPushAccess(ctx).Execute()
if err != nil {
return apiclient_cli.HandleErrorResponse(res, err)
}
encodedAuthConfig, err := json.Marshal(registry.AuthConfig{
Username: tokenResponse.Username,
Password: tokenResponse.Secret,
ServerAddress: tokenResponse.RegistryUrl,
})
if err != nil {
return fmt.Errorf("failed to marshal auth config: %w", err)
}
// Tag image
targetImage := fmt.Sprintf("%s/%s/%s", tokenResponse.RegistryUrl, tokenResponse.Project, sourceImage)
err = dockerClient.ImageTag(ctx, sourceImage, targetImage)
if err != nil {
return fmt.Errorf("failed to tag image: %w", err)
}
// Push image to transient registry
pushReader, err := dockerClient.ImagePush(ctx, targetImage, image.PushOptions{
RegistryAuth: base64.URLEncoding.EncodeToString(encodedAuthConfig),
})
if err != nil {
return fmt.Errorf("failed to push image: %w", err)
}
defer pushReader.Close()
err = jsonmessage.DisplayJSONMessagesStream(pushReader, os.Stdout, 0, true, nil)
if err != nil {
return err
}
createSnapshot := apiclient.NewCreateSnapshot(nameFlag)
createSnapshot.SetImageName(targetImage)
if entrypointFlag != "" {
createSnapshot.SetEntrypoint(strings.Split(entrypointFlag, " "))
}
// Poll until the image is really available on the registry
// This is a workaround for harbor's delay in making newly created images available
for {
_, err := dockerClient.DistributionInspect(ctx, targetImage,
base64.URLEncoding.EncodeToString(encodedAuthConfig))
if err == nil {
break
}
time.Sleep(time.Second)
}
if cpuFlag != 0 {
createSnapshot.SetCpu(cpuFlag)
}
if memoryFlag != 0 {
createSnapshot.SetMemory(memoryFlag)
}
if diskFlag != 0 {
createSnapshot.SetDisk(diskFlag)
}
_, res, err = apiClient.SnapshotsAPI.CreateSnapshot(ctx).CreateSnapshot(*createSnapshot).Execute()
if err != nil {
return apiclient_cli.HandleErrorResponse(res, err)
}
views_common.RenderInfoMessageBold(fmt.Sprintf("Successfully pushed %s to Runtime", sourceImage))
err = views_util.WithInlineSpinner("Waiting for the snapshot to be validated", func() error {
return common.AwaitSnapshotState(ctx, apiClient, nameFlag, apiclient.SNAPSHOTSTATE_ACTIVE)
})
if err != nil {
return err
}
views_common.RenderInfoMessage(fmt.Sprintf("%s Use '%s' to create a new sandbox using this snapshot", views_common.Checkmark, targetImage))
return nil
},
}
var (
nameFlag string
)
func init() {
PushCmd.Flags().StringVarP(&entrypointFlag, "entrypoint", "e", "", "The entrypoint command for the image")
PushCmd.Flags().StringVarP(&nameFlag, "name", "n", "", "Specify the Snapshot name")
PushCmd.Flags().Int32Var(&cpuFlag, "cpu", 0, "CPU cores that will be allocated to the underlying sandboxes (default: 1)")
PushCmd.Flags().Int32Var(&memoryFlag, "memory", 0, "Memory that will be allocated to the underlying sandboxes in GB (default: 1)")
PushCmd.Flags().Int32Var(&diskFlag, "disk", 0, "Disk space that will be allocated to the underlying sandboxes in GB (default: 3)")
_ = PushCmd.MarkFlagRequired("name")
}