Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
11 changes: 11 additions & 0 deletions components/registry-facade/pkg/registry/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"io"
"mime"
"net/http"
"strconv"
"strings"
"time"

Expand Down Expand Up @@ -185,13 +186,23 @@ func (mh *manifestHandler) getManifest(w http.ResponseWriter, r *http.Request) {
return err
}

originImageSize := 0
for _, layer := range manifest.Layers {
originImageSize += int(layer.Size)
}

// modify config
addonLayer, err := mh.ConfigModifier(ctx, mh.Spec, cfg)
if err != nil {
log.WithError(err).WithFields(logFields).Error("cannot modify config")
return err
}
manifest.Layers = append(manifest.Layers, addonLayer...)
if manifest.Annotations == nil {
manifest.Annotations = make(map[string]string)
}
manifest.Annotations["io.gitpod.workspace-image.size"] = strconv.Itoa(originImageSize)
manifest.Annotations["io.gitpod.workspace-image.ref"] = mh.Spec.BaseRef

// place config in store
rawCfg, err := json.Marshal(cfg)
Expand Down
3 changes: 2 additions & 1 deletion components/ws-daemon/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ require (
github.com/containerd/continuity v0.4.2 // indirect
github.com/containerd/fifo v1.1.0 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect
github.com/containerd/ttrpc v1.2.2 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/cyphar/filepath-securejoin v0.2.3 // indirect
Expand Down Expand Up @@ -144,7 +145,7 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b // indirect
github.com/opencontainers/image-spec v1.1.0-rc5 // indirect
github.com/opencontainers/selinux v1.11.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
Expand Down
4 changes: 4 additions & 0 deletions components/ws-daemon/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions components/ws-daemon/pkg/container/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"context"

"golang.org/x/xerrors"

workspacev1 "github.com/gitpod-io/gitpod/ws-manager/api/crd/v1"
)

// Runtime abstracts over the different container runtimes out there w.r.t. to the features we need from those runtimes
Expand Down Expand Up @@ -47,6 +49,8 @@ type Runtime interface {

// IsContainerdReady returns is the status of containerd.
IsContainerdReady(ctx context.Context) (bool, error)

GetContainerImageInfo(ctx context.Context, id ID) (*workspacev1.WorkspaceImageInfo, error)
}

var (
Expand Down
40 changes: 40 additions & 0 deletions components/ws-daemon/pkg/container/containerd.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"fmt"
"path/filepath"
"regexp"
"strconv"
"strings"
"sync"
"time"
Expand All @@ -20,6 +21,8 @@ import (
"github.com/containerd/containerd/api/types"
"github.com/containerd/containerd/containers"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/images"
"github.com/containerd/platforms"
"github.com/containerd/typeurl/v2"
ocispecs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opentracing/opentracing-go"
Expand All @@ -28,6 +31,7 @@ import (
wsk8s "github.com/gitpod-io/gitpod/common-go/kubernetes"
"github.com/gitpod-io/gitpod/common-go/log"
"github.com/gitpod-io/gitpod/common-go/tracing"
workspacev1 "github.com/gitpod-io/gitpod/ws-manager/api/crd/v1"
)

const (
Expand Down Expand Up @@ -92,6 +96,7 @@ type containerInfo struct {
UpperDir string
CGroupPath string
PID uint32
ImageRef string
}

// start listening to containerd
Expand Down Expand Up @@ -276,6 +281,7 @@ func (s *Containerd) handleNewContainer(c containers.Container) {
info.ID = c.ID
info.SnapshotKey = c.SnapshotKey
info.Snapshotter = c.Snapshotter
info.ImageRef = c.Image

s.cntIdx[c.ID] = info
log.WithField("podname", podName).WithFields(log.OWI(info.OwnerID, info.WorkspaceID, info.InstanceID)).WithField("ID", c.ID).Debug("found workspace container - updating label cache")
Expand Down Expand Up @@ -480,6 +486,40 @@ func (s *Containerd) ContainerPID(ctx context.Context, id ID) (pid uint64, err e
return uint64(info.PID), nil
}

func (s *Containerd) GetContainerImageInfo(ctx context.Context, id ID) (*workspacev1.WorkspaceImageInfo, error) {
info, ok := s.cntIdx[string(id)]
if !ok {
return nil, ErrNotFound
}

image, err := s.Client.GetImage(ctx, info.ImageRef)
if err != nil {
return nil, err
}
size, err := image.Size(ctx)
if err != nil {
return nil, err
}

wsImageInfo := &workspacev1.WorkspaceImageInfo{
TotalSize: size,
}

// Fetch the manifest
manifest, err := images.Manifest(ctx, s.Client.ContentStore(), image.Target(), platforms.Default())
if err != nil {
log.WithError(err).WithField("image", info.ImageRef).Error("Failed to get manifest")
return wsImageInfo, nil
}
if manifest.Annotations != nil {
wsImageInfo.WorkspaceImageRef = manifest.Annotations["io.gitpod.workspace-image.ref"]
if size, err := strconv.Atoi(manifest.Annotations["io.gitpod.workspace-image.size"]); err == nil {
wsImageInfo.WorkspaceImageSize = int64(size)
}
}
return wsImageInfo, nil
}

func (s *Containerd) IsContainerdReady(ctx context.Context) (bool, error) {
if len(s.registryFacadeHost) == 0 {
return s.Client.IsServing(ctx)
Expand Down
11 changes: 6 additions & 5 deletions components/ws-daemon/pkg/controller/mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion components/ws-daemon/pkg/controller/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ var _ = BeforeSuite(func() {
Expect(err).ToNot(HaveOccurred())
ctx, cancel = context.WithCancel(context.Background())

workspaceCtrl, err = NewWorkspaceController(k8sClient, record.NewFakeRecorder(100), NodeName, secretsNamespace, 5, nil, ctrl_metrics.Registry)
workspaceCtrl, err = NewWorkspaceController(k8sClient, record.NewFakeRecorder(100), NodeName, secretsNamespace, 5, nil, ctrl_metrics.Registry, nil)
Expect(err).NotTo(HaveOccurred())

Expect(workspaceCtrl.SetupWithManager(k8sManager)).To(Succeed())
Expand Down
29 changes: 27 additions & 2 deletions components/ws-daemon/pkg/controller/workspace_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,10 @@ type WorkspaceController struct {
metrics *workspaceMetrics
secretNamespace string
recorder record.EventRecorder
runtime container.Runtime
}

func NewWorkspaceController(c client.Client, recorder record.EventRecorder, nodeName, secretNamespace string, maxConcurrentReconciles int, ops WorkspaceOperations, reg prometheus.Registerer) (*WorkspaceController, error) {
func NewWorkspaceController(c client.Client, recorder record.EventRecorder, nodeName, secretNamespace string, maxConcurrentReconciles int, ops WorkspaceOperations, reg prometheus.Registerer, runtime container.Runtime) (*WorkspaceController, error) {
metrics := newWorkspaceMetrics()
reg.Register(metrics)

Expand All @@ -75,6 +76,7 @@ func NewWorkspaceController(c client.Client, recorder record.EventRecorder, node
metrics: metrics,
secretNamespace: secretNamespace,
recorder: recorder,
runtime: runtime,
}, nil
}

Expand Down Expand Up @@ -219,7 +221,30 @@ func (wsc *WorkspaceController) handleWorkspaceRunning(ctx context.Context, ws *
span, ctx := opentracing.StartSpanFromContext(ctx, "handleWorkspaceRunning")
defer tracing.FinishSpan(span, &err)

return ctrl.Result{}, wsc.operations.SetupWorkspace(ctx, ws.Name)
var imageInfo *workspacev1.WorkspaceImageInfo = nil
if ws.Status.ImageInfo == nil {
id, err := wsc.runtime.WaitForContainer(ctx, ws.Name)
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to wait for container: %w", err)
}
info, err := wsc.runtime.GetContainerImageInfo(ctx, id)
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to get container image info: %w", err)
}

err = retry.RetryOnConflict(retryParams, func() error {
if err := wsc.Get(ctx, req.NamespacedName, ws); err != nil {
return err
}
ws.Status.ImageInfo = info
return wsc.Status().Update(ctx, ws)
})
if err != nil {
glog.WithFields(ws.OWI()).WithField("workspace", req.NamespacedName).WithField("phase", ws.Status.Phase).Errorf("failed to update workspace with image info: %v", err)
}
imageInfo = info
}
return ctrl.Result{}, wsc.operations.SetupWorkspace(ctx, ws.Name, imageInfo)
}

func (wsc *WorkspaceController) handleWorkspaceStop(ctx context.Context, ws *workspacev1.Workspace, req ctrl.Request) (result ctrl.Result, err error) {
Expand Down
33 changes: 29 additions & 4 deletions components/ws-daemon/pkg/controller/workspace_operations.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package controller

import (
"context"
"encoding/json"
"errors"
"fmt"
"io/fs"
Expand All @@ -22,6 +23,7 @@ import (
"github.com/gitpod-io/gitpod/content-service/pkg/storage"
"github.com/gitpod-io/gitpod/ws-daemon/pkg/content"
"github.com/gitpod-io/gitpod/ws-daemon/pkg/internal/session"
workspacev1 "github.com/gitpod-io/gitpod/ws-manager/api/crd/v1"
"github.com/opentracing/opentracing-go"
"github.com/prometheus/client_golang/prometheus"
"github.com/sirupsen/logrus"
Expand Down Expand Up @@ -71,7 +73,7 @@ type WorkspaceOperations interface {
// Snapshot takes a snapshot of the workspace
Snapshot(ctx context.Context, instanceID, snapshotName string) (err error)
// Setup ensures that the workspace has been setup
SetupWorkspace(ctx context.Context, instanceID string) error
SetupWorkspace(ctx context.Context, instanceID string, imageInfo *workspacev1.WorkspaceImageInfo) error
}

type DefaultWorkspaceOperations struct {
Expand Down Expand Up @@ -211,12 +213,15 @@ func (wso *DefaultWorkspaceOperations) creator(owner, workspaceID, instanceID st
}
}

func (wso *DefaultWorkspaceOperations) SetupWorkspace(ctx context.Context, instanceID string) error {
_, err := wso.provider.GetAndConnect(ctx, instanceID)
func (wso *DefaultWorkspaceOperations) SetupWorkspace(ctx context.Context, instanceID string, imageInfo *workspacev1.WorkspaceImageInfo) error {
ws, err := wso.provider.GetAndConnect(ctx, instanceID)
if err != nil {
return fmt.Errorf("cannot setup workspace %s: %w", instanceID, err)
}

err = wso.writeImageInfo(ctx, ws, imageInfo)
if err != nil {
glog.WithError(err).WithFields(ws.OWI()).Error("cannot write image info")
}
return nil
}

Expand Down Expand Up @@ -513,6 +518,26 @@ func (wso *DefaultWorkspaceOperations) uploadWorkspaceContent(ctx context.Contex
return nil
}

func (wso *DefaultWorkspaceOperations) writeImageInfo(_ context.Context, ws *session.Workspace, imageInfo *workspacev1.WorkspaceImageInfo) error {
if imageInfo == nil {
return nil
}

b, err := json.Marshal(imageInfo)
if err != nil {
return fmt.Errorf("cannot marshal image info: %w", err)
}
uid := (wsinit.GitpodUID + 100000 - 1)
gid := (wsinit.GitpodGID + 100000 - 1)
fp := filepath.Join(ws.Location, ".gitpod/image")
err = os.WriteFile(fp, b, 0644)
if err != nil {
return fmt.Errorf("cannot write image info: %w", err)
}
os.Chown(fp, uid, gid)
return nil
}

func retryIfErr(ctx context.Context, attempts int, log *logrus.Entry, op func(ctx context.Context) error) (err error) {
//nolint:ineffassign
span, ctx := opentracing.StartSpanFromContext(ctx, "retryIfErr")
Expand Down
2 changes: 1 addition & 1 deletion components/ws-daemon/pkg/daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ func NewDaemon(config Config) (*Daemon, error) {
}

wsctrl, err := controller.NewWorkspaceController(
mgr.GetClient(), mgr.GetEventRecorderFor("workspace"), nodename, config.Runtime.SecretsNamespace, config.WorkspaceController.MaxConcurrentReconciles, workspaceOps, wrappedReg)
mgr.GetClient(), mgr.GetEventRecorderFor("workspace"), nodename, config.Runtime.SecretsNamespace, config.WorkspaceController.MaxConcurrentReconciles, workspaceOps, wrappedReg, containerRuntime)
if err != nil {
return nil, err
}
Expand Down
14 changes: 14 additions & 0 deletions components/ws-manager-api/go/crd/v1/workspace_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,17 @@ func (ps PortSpec) Equal(other PortSpec) bool {
return true
}

type WorkspaceImageInfo struct {
// +kubebuilder:validation:Required
TotalSize int64 `json:"totalSize"`

// +kubebuilder:validation:Optional
WorkspaceImageSize int64 `json:"workspaceImageSize,omitempty"`

// +kubebuilder:validation:Optional
WorkspaceImageRef string `json:"workspaceImageRef,omitempty"`
}

// WorkspaceStatus defines the observed state of Workspace
type WorkspaceStatus struct {
PodStarts int `json:"podStarts"`
Expand All @@ -193,6 +204,9 @@ type WorkspaceStatus struct {
Storage StorageStatus `json:"storage,omitempty"`

LastActivity *metav1.Time `json:"lastActivity,omitempty"`

// +kubebuilder:validation:Optional
ImageInfo *WorkspaceImageInfo `json:"imageInfo,omitempty"`
}

func (s *WorkspaceStatus) SetCondition(cond metav1.Condition) {
Expand Down
Loading
Loading