Initial pull implemented.
Support for proxy repositories by pulling from the proxy and then tagging the image.
This commit is contained in:
@@ -0,0 +1,240 @@
|
||||
package ctrd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"github.com/containerd/containerd"
|
||||
"github.com/containerd/containerd/content"
|
||||
"github.com/containerd/containerd/errdefs"
|
||||
"github.com/containerd/containerd/images"
|
||||
"github.com/containerd/containerd/leases"
|
||||
"github.com/containerd/containerd/namespaces"
|
||||
"github.com/opencontainers/go-digest"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"io"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// Containerd implements ContainerRuntime interface
|
||||
type Containerd struct {
|
||||
client *containerd.Client
|
||||
ctx context.Context
|
||||
namespace string
|
||||
}
|
||||
|
||||
// NewContainerd creates a new Containerd
|
||||
func NewContainerd(socketPath, namespace string) (*Containerd, error) {
|
||||
client, err := containerd.New(socketPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to connect to containerd at %s: %w", socketPath, err)
|
||||
}
|
||||
client.LeasesService()
|
||||
|
||||
ctx := namespaces.WithNamespace(context.Background(), namespace)
|
||||
|
||||
return &Containerd{
|
||||
client: client,
|
||||
ctx: ctx,
|
||||
namespace: namespace,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// List returns all images with their pinned status
|
||||
func (m *Containerd) List() (map[string]bool, error) {
|
||||
// Get all images
|
||||
images, err := m.client.ImageService().List(m.ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list images: %w", err)
|
||||
}
|
||||
|
||||
for _, image := range images {
|
||||
klog.V(3).Infof("Image '%s' digest '%s'", image.Name,
|
||||
image.Target.Digest.String())
|
||||
}
|
||||
|
||||
// Get all leases
|
||||
leases, err := m.client.LeasesService().List(m.ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list leases: %w", err)
|
||||
}
|
||||
|
||||
// Create a map of image references that are pinned
|
||||
pinnedImages := make(map[string]bool)
|
||||
for _, lease := range leases {
|
||||
// Check if lease has labels referencing an image
|
||||
if label, ok := lease.Labels["containerd.io/gc.ref.content.image"]; ok {
|
||||
pinnedImages[label] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Create the result list
|
||||
var result = make(map[string]bool)
|
||||
for _, img := range images {
|
||||
result[img.Name] = pinnedImages[img.Name]
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Pin creates a lease for an image to prevent garbage collection
|
||||
func (m *Containerd) Pin(imageRef string) error {
|
||||
// Create a unique lease ID based on image reference
|
||||
leaseID := fmt.Sprintf("pin-%s", generateID(imageRef))
|
||||
|
||||
// Get the image to validate it exists
|
||||
_, err := m.client.ImageService().Get(m.ctx, imageRef)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get image %s: %w", imageRef, err)
|
||||
}
|
||||
|
||||
leaseList, err := m.findLeases(imageRef)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to get leases for image %s: %v", imageRef, err)
|
||||
}
|
||||
if len(leaseList) > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create a new lease
|
||||
opts := []leases.Opt{
|
||||
leases.WithID(leaseID),
|
||||
leases.WithLabels(map[string]string{
|
||||
"containerd.io/gc.ref.content.image": imageRef,
|
||||
}),
|
||||
}
|
||||
|
||||
_, err = m.client.LeasesService().Create(m.ctx, opts...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create lease: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unpin removes a lease for an image allowing garbage collection
|
||||
func (m *Containerd) Unpin(imageRef string) error {
|
||||
leases, err := m.findLeases(imageRef)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, lease := range leases {
|
||||
if err := m.client.LeasesService().Delete(m.ctx, lease); err != nil {
|
||||
return fmt.Errorf("failed to delete lease %s: %w", lease.ID, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Containerd) findLeases(imageRef string) ([]leases.Lease, error) {
|
||||
// List all leases
|
||||
leaseList, err := m.client.LeasesService().List(m.ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list leases: %w", err)
|
||||
}
|
||||
|
||||
// Find leases that reference our image
|
||||
var leases = make([]leases.Lease, 0)
|
||||
for _, lease := range leaseList {
|
||||
// Check if this lease has a label referencing our image
|
||||
if label, ok := lease.Labels["containerd.io/gc.ref.content.image"]; ok && label == imageRef {
|
||||
leases = append(leases, lease)
|
||||
}
|
||||
}
|
||||
return leases, nil
|
||||
}
|
||||
|
||||
// Pull pulls an image from a registry
|
||||
func (m *Containerd) Pull(imageRef string) error {
|
||||
// Set up pull options
|
||||
pullOpts := []containerd.RemoteOpt{
|
||||
//containerd.WithPlatformMatcher(platforms.Default()),
|
||||
containerd.WithPullUnpack,
|
||||
}
|
||||
|
||||
// Pull the image
|
||||
_, err := m.client.Pull(m.ctx, imageRef, pullOpts...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to pull image %s: %w", imageRef, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Containerd) Tag(imageFrom, imageTo string) error {
|
||||
sourceImage, err := m.client.GetImage(m.ctx, imageFrom)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to get source image: %v", err)
|
||||
}
|
||||
|
||||
// Create a new image with the target name that references the same content
|
||||
newImage := images.Image{
|
||||
Name: imageTo,
|
||||
Target: sourceImage.Target(),
|
||||
}
|
||||
|
||||
// best effort, remove old tag
|
||||
_ = m.Remove(imageTo)
|
||||
|
||||
// Create the new tag
|
||||
if _, err := m.client.ImageService().Create(m.ctx, newImage); err != nil {
|
||||
return fmt.Errorf("Failed to create new image tag: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove deletes an image
|
||||
func (m *Containerd) Remove(imageRef string) error {
|
||||
// Get the image
|
||||
|
||||
_, err := m.client.ImageService().Get(m.ctx, imageRef)
|
||||
if err != nil {
|
||||
if errdefs.IsNotFound(err) {
|
||||
return fmt.Errorf("image %s not found", imageRef)
|
||||
}
|
||||
return fmt.Errorf("failed to get image %s: %w", imageRef, err)
|
||||
}
|
||||
|
||||
// Delete the image
|
||||
err = m.client.ImageService().Delete(m.ctx, imageRef, images.SynchronousDelete())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete image %s: %w", imageRef, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes the containerd client connection
|
||||
func (m *Containerd) Close() error {
|
||||
return m.client.Close()
|
||||
}
|
||||
|
||||
// generateID creates a random unique ID
|
||||
func generateID(image string) string {
|
||||
|
||||
md5Hash := md5.Sum([]byte(image))
|
||||
md5Base64 := base64.StdEncoding.EncodeToString(md5Hash[:])
|
||||
return md5Base64
|
||||
}
|
||||
|
||||
func (m *Containerd) GetBlob(hash string) (io.Reader, error) {
|
||||
store := m.client.ContentStore()
|
||||
descriptor := ocispec.Descriptor{
|
||||
Digest: digest.Digest(hash),
|
||||
}
|
||||
reader, err := store.ReaderAt(m.ctx, descriptor)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return content.NewReader(reader), nil
|
||||
}
|
||||
|
||||
func (m *Containerd) GetBlobMeta(hash string) (content.Info, error) {
|
||||
store := m.client.ContentStore()
|
||||
info, err := store.Info(m.ctx, digest.Digest(hash))
|
||||
if err != nil {
|
||||
return content.Info{}, err
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package runtime
|
||||
|
||||
// ContainerRuntime defines the interface for managing containerd images
|
||||
type ContainerRuntime interface {
|
||||
// List images: returns map of container image to whether or not it is pinned
|
||||
List() (map[string]bool, error)
|
||||
Pin(imageRef string) error
|
||||
Unpin(imageRef string) error
|
||||
Pull(imageRef string) error
|
||||
Tag(imageFrom, imageTo string) error
|
||||
Remove(imageRef string) error
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package support
|
||||
|
||||
import (
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"log"
|
||||
)
|
||||
|
||||
func GetKubernetesConnection() *kubernetes.Clientset {
|
||||
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
|
||||
configOverrides := &clientcmd.ConfigOverrides{}
|
||||
kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides)
|
||||
|
||||
config, err := kubeConfig.ClientConfig()
|
||||
if err != nil {
|
||||
log.Panicln(err.Error())
|
||||
}
|
||||
|
||||
clientset, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
log.Panicln(err.Error())
|
||||
}
|
||||
|
||||
return clientset
|
||||
}
|
||||
Reference in New Issue
Block a user