Skip to content

Commit

Permalink
add registry proxy support
Browse files Browse the repository at this point in the history
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
  • Loading branch information
Adphi committed Oct 25, 2023
1 parent 3bf3429 commit cd794cd
Show file tree
Hide file tree
Showing 16 changed files with 583 additions and 150 deletions.
6 changes: 3 additions & 3 deletions cmd/lkar/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import (
"github.com/spf13/cobra"
"go.linka.cloud/printer"

repository2 "go.linka.cloud/artifact-registry/pkg/repository"
"go.linka.cloud/artifact-registry/pkg/api"
"go.linka.cloud/artifact-registry/pkg/slices"
)

Expand Down Expand Up @@ -57,7 +57,7 @@ var (
}
return errors.New(string(b))
}
var repos []repository2.Repository
var repos []api.Repository
if err := json.NewDecoder(res.Body).Decode(&repos); err != nil {
return err
}
Expand All @@ -71,7 +71,7 @@ var (
MetadataFiles int64 `json:"metadataFiles" print:"METADATA FILES"`
MetadataSize int64 `json:"metadataSize" print:"METADATA SIZE"`
}
out := slices.Map(repos, func(v repository2.Repository) Repo {
out := slices.Map(repos, func(v api.Repository) Repo {
return Repo{
Image: v.Name + ":" + v.Type,
Type: v.Type,
Expand Down
71 changes: 58 additions & 13 deletions cmd/lkard/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ import (
"go.linka.cloud/grpc-toolkit/react"

artifact_registry "go.linka.cloud/artifact-registry"
"go.linka.cloud/artifact-registry/pkg/api"
"go.linka.cloud/artifact-registry/pkg/packages"
"go.linka.cloud/artifact-registry/pkg/repository"
"go.linka.cloud/artifact-registry/pkg/registry"
"go.linka.cloud/artifact-registry/pkg/storage"
"go.linka.cloud/artifact-registry/ui"
)
Expand All @@ -52,11 +53,18 @@ const (
EnvTLSCert = "ARTIFACT_REGISTRY_TLS_CERT"
EnvTLSKey = "ARTIFACT_REGISTRY_TLS_KEY"
EnvDisableUI = "ARTIFACT_REGISTRY_DISABLE_UI"

EnvProxy = "ARTIFACT_REGISTRY_PROXY"
EnvProxyNoHTTPS = "ARTIFACT_REGISTRY_PROXY_NO_HTTPS"
EnvProxyInsecure = "ARTIFACT_REGISTRY_PROXY_INSECURE"
EnvProxyClientCA = "ARTIFACT_REGISTRY_PROXY_CLIENT_CA"
EnvProxyUser = "ARTIFACT_REGISTRY_PROXY_USER"
EnvProxyPassword = "ARTIFACT_REGISTRY_PROXY_PASSWORD"
)

var (
addr = ":9887"
// backend = "192.168.10.11:5000"

backend = "docker.io"

domain = ""
Expand All @@ -75,6 +83,13 @@ var (

disableUI = false

proxyAddr string
proxyNoHTTPS = false
proxyInsecure = false
proxyClientCA string
proxyUser string
proxyPassword string

cmd = &cobra.Command{
Use: "artifact-registry (repository)",
Args: cobra.MaximumNArgs(1),
Expand All @@ -85,18 +100,16 @@ var (
repo = args[0]
}
// TODO(adphi): validate host
opts := []storage.Option{storage.WithHost(backend)}
ropts := []registry.Option{
registry.WithProxy(proxyAddr),
registry.WithProxyUser(proxyUser),
registry.WithProxyPassword(proxyPassword),
}
if noHTTPS {
opts = append(opts, storage.WithPlainHTTP())
ropts = append(ropts, registry.WithPlainHTTP())
}
if insecure {
opts = append(opts, storage.WithInsecure())
}
if tagPerArtifact {
opts = append(opts, storage.WithArtifactTags())
}
if repo != "" {
opts = append(opts, storage.WithRepo(repo))
ropts = append(ropts, registry.WithInsecure())
}
if clientCA != "" {
p := x509.NewCertPool()
Expand All @@ -107,7 +120,32 @@ var (
if !p.AppendCertsFromPEM(b) {
logger.C(cmd.Context()).Fatal(err)
}
opts = append(opts, storage.WithClientCA(p))
ropts = append(ropts, registry.WithClientCA(p))
}
if proxyNoHTTPS {
ropts = append(ropts, registry.WithProxyPlainHTTP())
}
if proxyInsecure {
ropts = append(ropts, registry.WithProxyInsecure())
}
if proxyClientCA != "" {
p := x509.NewCertPool()
b, err := os.ReadFile(proxyClientCA)
if err != nil {
logger.C(cmd.Context()).Fatal(err)
}
if !p.AppendCertsFromPEM(b) {
logger.C(cmd.Context()).Fatal(err)
}
ropts = append(ropts, registry.WithProxyClientCA(p))
}
opts := []storage.Option{
storage.WithHost(backend),
storage.WithRepo(repo),
storage.WithRegistryOptions(ropts...),
}
if tagPerArtifact {
opts = append(opts, storage.WithArtifactTags())
}
if err := run(cmd.Context(), repo, opts...); err != nil {
logger.C(cmd.Context()).Fatal(err)
Expand Down Expand Up @@ -136,6 +174,13 @@ func main() {
cmd.Flags().StringVar(&key, "tls-key", env.Get[string](EnvTLSKey), "tls key [$"+EnvTLSKey+"]")
cmd.Flags().BoolVar(&disableUI, "disable-ui", env.GetDefault(EnvDisableUI, disableUI), "disable the Web UI [$"+EnvDisableUI+"]")

cmd.Flags().StringVar(&proxyAddr, "proxy", env.GetDefault(EnvProxy, proxyAddr), "proxy backend registry hostname (and port if not 443 or 80) [$"+EnvProxy+"]")
cmd.Flags().BoolVar(&proxyNoHTTPS, "proxy-no-https", env.GetDefault(EnvProxyNoHTTPS, noHTTPS), "disable proxy registry client https [$"+EnvProxyNoHTTPS+"]")
cmd.Flags().BoolVar(&proxyInsecure, "proxy-insecure", env.GetDefault(EnvProxyInsecure, insecure), "disable proxy registry client tls verification [$"+EnvProxyInsecure+"]")
cmd.Flags().StringVar(&proxyClientCA, "proxy-client-ca", env.Get[string](EnvProxyClientCA), "proxy tls client certificate authority [$"+EnvProxyClientCA+"]")
cmd.Flags().StringVar(&proxyUser, "proxy-user", env.GetDefault(EnvProxyUser, proxyUser), "proxy registry user [$"+EnvProxyUser+"]")
cmd.Flags().StringVar(&proxyPassword, "proxy-password", env.GetDefault(EnvProxyPassword, proxyPassword), "proxy registry password [$"+EnvProxyPassword+"]")

if err := cmd.Execute(); err != nil {
os.Exit(1)
}
Expand Down Expand Up @@ -191,7 +236,7 @@ func run(ctx context.Context, repo string, opts ...storage.Option) error {
router.Path("/_/health").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
if err := repository.Init(ctx, router, domain, repo); err != nil {
if err := api.Init(ctx, router, domain, repo); err != nil {
return err
}
if err := packages.Init(ctx, router, domain, repo); err != nil {
Expand Down
32 changes: 11 additions & 21 deletions pkg/repository/repository.go → pkg/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

package repository
package api

import (
"context"
Expand All @@ -29,19 +29,17 @@ import (
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"go.linka.cloud/grpc-toolkit/logger"
"golang.org/x/sync/errgroup"
"oras.land/oras-go/v2/registry/remote"
"oras.land/oras-go/v2/registry"

cache2 "go.linka.cloud/artifact-registry/pkg/cache"
"go.linka.cloud/artifact-registry/pkg/auth"
"go.linka.cloud/artifact-registry/pkg/cache"
"go.linka.cloud/artifact-registry/pkg/packages"
"go.linka.cloud/artifact-registry/pkg/slices"
"go.linka.cloud/artifact-registry/pkg/storage"
"go.linka.cloud/artifact-registry/pkg/storage/auth"
)

const sessionName = "auth"

var cache = cache2.New()

type Stats struct {
Size int64 `json:"size"`
Count int64 `json:"count"`
Expand All @@ -62,17 +60,16 @@ type handler struct {

func (h *handler) Login(w http.ResponseWriter, r *http.Request) {
ctx := auth.Context(r.Context(), r)
o := storage.Options(ctx)
name, typ := mux.Vars(r)["repo"], mux.Vars(r)["type"]
if n := storage.Options(ctx).Repo(); name == "" && n != "" {
o := storage.Options(ctx)
if n := o.Repo(); name == "" && n != "" {
name = n
}
reg, err := remote.NewRegistry(o.Host())
reg, err := o.NewRegistry(ctx)
if err != nil {
storage.Error(w, err)
return
}
o.SetClient(ctx, (*remote.Repository)(&reg.RepositoryOptions))
if name == "" {
skip := errors.New("skip")
if err := reg.Repositories(ctx, "", func(r []string) error {
Expand Down Expand Up @@ -150,17 +147,13 @@ type credentials struct {

func (h *handler) Credentials(w http.ResponseWriter, r *http.Request) {
u, p, _ := r.BasicAuth()
// if !ok {
// http.Error(w, "No credentials", http.StatusUnauthorized)
// return
// }
if err := json.NewEncoder(w).Encode(credentials{User: u, Password: p}); err != nil {
storage.Error(w, err)
return
}
}

func listImageRepositories(ctx context.Context, reg *remote.Registry, name string, typ ...string) ([]*Repository, error) {
func listImageRepositories(ctx context.Context, reg registry.Registry, name string, typ ...string) ([]*Repository, error) {
repo, err := reg.Repository(ctx, name)
if err != nil {
if storage.IsNotFound(err) {
Expand Down Expand Up @@ -204,7 +197,7 @@ func listImageRepositories(ctx context.Context, reg *remote.Registry, name strin
} else {
m = v.(ocispec.Manifest)
}
cache.Set(desc.Digest.String(), m, cache2.WithTTL(5*time.Minute))
cache.Set(desc.Digest.String(), m, cache.WithTTL(cache.DefaultTTL))
t, err := time.Parse(time.RFC3339, m.Annotations[ocispec.AnnotationCreated])
if err != nil {
return err
Expand Down Expand Up @@ -253,12 +246,11 @@ func (h *handler) ListRepositories(w http.ResponseWriter, r *http.Request) {
ctx := auth.Context(r.Context(), r)
o := storage.Options(ctx)
typ := mux.Vars(r)["type"]
reg, err := remote.NewRegistry(o.Host())
reg, err := o.NewRegistry(ctx)
if err != nil {
storage.Error(w, err)
return
}
o.SetClient(ctx, (*remote.Repository)(&reg.RepositoryOptions))
var repos []string
if err := reg.Repositories(ctx, "", func(r []string) error {
repos = append(repos, r...)
Expand Down Expand Up @@ -298,17 +290,15 @@ func (h *handler) ListRepositories(w http.ResponseWriter, r *http.Request) {

func (h *handler) ListImageRepositories(w http.ResponseWriter, r *http.Request) {
ctx := auth.Context(r.Context(), r)
o := storage.Options(ctx)
name, typ := mux.Vars(r)["repo"], mux.Vars(r)["type"]
if n := storage.Options(ctx).Repo(); name == "" && n != "" {
name = n
}
reg, err := remote.NewRegistry(o.Host())
reg, err := storage.Options(ctx).NewRegistry(ctx)
if err != nil {
storage.Error(w, err)
return
}
o.SetClient(ctx, (*remote.Repository)(&reg.RepositoryOptions))
out, err := listImageRepositories(ctx, reg, name, typ)
if err != nil {
storage.Error(w, err)
Expand Down
File renamed without changes.
9 changes: 0 additions & 9 deletions pkg/cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,6 @@ type item struct {
exp time.Time
}

func Get[T any](c Cache, key string) (z T, ok bool) {
v, ok := c.Get(key)
if !ok {
return z, false
}
z, ok = v.(T)
return
}

type Option func(*item)

func WithTTL(d time.Duration) Option {
Expand Down
31 changes: 31 additions & 0 deletions pkg/cache/default.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright 2023 Linka Cloud All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cache

import (
"time"
)

var (
DefaultTTL = 5 * time.Minute
d = New()
)

func Set(key string, value any, opts ...Option) {
d.Set(key, value, opts...)
}
func Get(key string) (any, bool) {
return d.Get(key)
}
51 changes: 51 additions & 0 deletions pkg/registry/blobs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright 2023 Linka Cloud All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package registry

import (
"context"
"io"

ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"oras.land/oras-go/v2/content"
"oras.land/oras-go/v2/registry"
)

type BlobStore = registry.BlobStore

type BlobProxy interface {
registry.ReferenceFetcher
content.Fetcher
}

type blobs struct {
BlobStore
p BlobProxy
}

func (b *blobs) Fetch(ctx context.Context, target ocispec.Descriptor) (io.ReadCloser, error) {
return b.maybeProxy().Fetch(ctx, target)
}

func (b *blobs) FetchReference(ctx context.Context, reference string) (ocispec.Descriptor, io.ReadCloser, error) {
return b.maybeProxy().FetchReference(ctx, reference)
}

func (b *blobs) maybeProxy() BlobProxy {
if b.p != nil {
return b.p
}
return b.BlobStore
}
Loading

0 comments on commit cd794cd

Please sign in to comment.