-
Notifications
You must be signed in to change notification settings - Fork 556
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement container registry proxy. Signed-off-by: Dmitriy Matrenichev <[email protected]>
- Loading branch information
Showing
8 changed files
with
613 additions
and
0 deletions.
There are no files selected for viewing
41 changes: 41 additions & 0 deletions
41
internal/app/machined/pkg/system/services/registry/app/main.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
30
internal/app/machined/pkg/system/services/registry/errors.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
73
internal/app/machined/pkg/system/services/registry/params.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
86
internal/app/machined/pkg/system/services/registry/readerat.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.