Skip to content

Commit

Permalink
feat: registry proxy
Browse files Browse the repository at this point in the history
Implement container registry proxy.

Signed-off-by: Dmitriy Matrenichev <[email protected]>
  • Loading branch information
DmitriyMV committed Nov 19, 2024
1 parent f400ae9 commit 710172b
Show file tree
Hide file tree
Showing 8 changed files with 613 additions and 0 deletions.
41 changes: 41 additions & 0 deletions internal/app/machined/pkg/system/services/registry/app/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package main

import (
"context"
"fmt"
"os"
"os/signal"
"path/filepath"

"go.uber.org/zap"

"github.com/siderolabs/talos/internal/app/machined/pkg/system/services/registry"
)

func main() {
if err := app(); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
}

func app() error {
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()

development, err := zap.NewDevelopment()
if err != nil {
return fmt.Errorf("failed to create development logger: %w", err)
}

homeDir, err := os.UserHomeDir()
if err != nil {
return fmt.Errorf("failed to get user home directory: %w", err)
}

return registry.NewService(filepath.Join(homeDir, "registry-cache"), development).Run(ctx)
}
30 changes: 30 additions & 0 deletions internal/app/machined/pkg/system/services/registry/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package registry

import (
"net/http"

"github.com/siderolabs/gen/xerrors"
)

func getStatusCode(err error) int {
switch {
case xerrors.TagIs[notFoundTag](err):
return http.StatusNotFound
case xerrors.TagIs[badRequestTag](err):
return http.StatusBadRequest
case xerrors.TagIs[internalErrorTag](err):
fallthrough
default:
return http.StatusInternalServerError
}
}

type (
notFoundTag struct{}
badRequestTag struct{}
internalErrorTag struct{}
)
73 changes: 73 additions & 0 deletions internal/app/machined/pkg/system/services/registry/params.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package registry

import (
"net/http"
"path"
"strings"

"github.com/distribution/reference"
"github.com/siderolabs/gen/xerrors"
)

func extractParams(req *http.Request) (params, error) {
registry := req.URL.Query().Get("ns")
if registry == "" {
return params{}, xerrors.NewTaggedf[badRequestTag]("missing ns")
}

value := req.PathValue("args")

parts := strings.Split(path.Clean(value), "/")
if len(parts) < 4 {
return params{}, xerrors.NewTaggedf[notFoundTag]("incorrect args value '%s'", value)
}

numParts := len(parts)
isBlob := parts[numParts-2] == "blobs"
isManifest := parts[numParts-2] == "manifests"

if !isBlob && !isManifest {
return params{}, xerrors.NewTaggedf[notFoundTag]("incorrect ref: '%s'", parts[numParts-2])
}

name := strings.Join(parts[:numParts-2], "/")
dig := parts[numParts-1]

if !reference.NameRegexp.MatchString(name) {
return params{}, xerrors.NewTaggedf[badRequestTag]("incorrect name: '%s'", name)
}

return params{registry: registry, name: name, dig: dig, isBlob: isBlob}, nil
}

type params struct {
registry string
name string
dig string
isBlob bool
}

func (p params) String() string {
var result strings.Builder

if p.registry != "" {
result.WriteString(p.registry)
result.WriteByte('/')
}

result.WriteString(p.name)

if strings.HasPrefix(p.dig, "sha256:") {
result.WriteByte('@')
result.WriteString(p.dig)
} else {
result.WriteByte(':')
result.WriteString(p.dig)
}

return result.String()
}
86 changes: 86 additions & 0 deletions internal/app/machined/pkg/system/services/registry/readerat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package registry

import (
"errors"
"io"
)

var (
errInvalidSize = errors.New("readerat: invalid size")
errSeekToInvalidWhence = errors.New("readerat: seek to invalid whence")
errSeekToNegativePosition = errors.New("readerat: seek to negative position")
)

// readSeeker is an io.ReadSeeker implementation based on an io.ReaderAt (and
// an int64 size).
//
// For example, an os.File is both an io.ReaderAt and an io.ReadSeeker, but its
// io.ReadSeeker methods are not safe to use concurrently. In comparison,
// multiple readerat.readSeeker values (using the same os.File as their
// io.ReaderAt) are safe to use concurrently. Each can Read and Seek
// independently.
//
// A single readerat.readSeeker is not safe to use concurrently.
//
// Do not modify its exported fields after calling any of its methods.
type readSeeker struct {
ReaderAt io.ReaderAt
Size int64
offset int64
}

// Read implements io.Reader.
func (r *readSeeker) Read(p []byte) (int, error) {
if r.Size < 0 {
return 0, errInvalidSize
} else if r.Size <= r.offset {
return 0, io.EOF
}

if length := r.Size - r.offset; int64(len(p)) > length {
p = p[:length]
}

if len(p) == 0 {
return 0, nil
}

actual, err := r.ReaderAt.ReadAt(p, r.offset)
r.offset += int64(actual)

if err == nil && r.offset == r.Size {
err = io.EOF
}

return actual, err
}

// Seek implements io.Seeker.
func (r *readSeeker) Seek(offset int64, whence int) (int64, error) {
if r.Size < 0 {
return 0, errInvalidSize
}

switch whence {
case io.SeekStart:
// No-op.
case io.SeekCurrent:
offset += r.offset
case io.SeekEnd:
offset += r.Size
default:
return 0, errSeekToInvalidWhence
}

if offset < 0 {
return 0, errSeekToNegativePosition
}

r.offset = offset

return r.offset, nil
}
Loading

0 comments on commit 710172b

Please sign in to comment.