From 007dc4b91a98d59df919a15a88d81c302b4e1359 Mon Sep 17 00:00:00 2001 From: Andy Doan Date: Wed, 5 Nov 2025 13:44:38 -0600 Subject: [PATCH] provider: Introduce support for satellite server The satellite server, https://github.com/foundriesio/dg-satellite, needs a way for devices to pick up registry content from it instead of hub.foundries.io. This change introduces a mechanism to tell our provider code to proxy its requests through the satellite server. The error handling in this is a little awkward but allowed us to bubble up errors w/o having to adjust a lot of users of the API. Signed-off-by: Andy Doan --- pkg/compose/config.go | 10 +++++++-- pkg/compose/provider.go | 44 +++++++++++++++++++++++++++++++++++++--- pkg/compose/v1/config.go | 34 ++++++++++++++++++++++++++++--- 3 files changed, 80 insertions(+), 8 deletions(-) diff --git a/pkg/compose/config.go b/pkg/compose/config.go index fd0eaeb..b9650ed 100644 --- a/pkg/compose/config.go +++ b/pkg/compose/config.go @@ -1,10 +1,13 @@ package compose import ( - "github.com/docker/cli/cli/config/configfile" - specs "github.com/opencontainers/image-spec/specs-go/v1" + "crypto/x509" + "net/url" "path/filepath" "time" + + "github.com/docker/cli/cli/config/configfile" + specs "github.com/opencontainers/image-spec/specs-go/v1" ) type ( @@ -20,6 +23,9 @@ type ( AppStoreFactoryFunc func(c *Config) (AppStore, error) BlockSize int64 DBFilePath string + + ProxyURL *url.URL + ProxyCerts *x509.CertPool } ) diff --git a/pkg/compose/provider.go b/pkg/compose/provider.go index 0b44892..5b982f8 100644 --- a/pkg/compose/provider.go +++ b/pkg/compose/provider.go @@ -4,14 +4,17 @@ import ( "bytes" "context" "fmt" + "io" + "net/http" + "net/url" + "os" + "path" + "github.com/containerd/containerd/content" "github.com/containerd/containerd/reference" "github.com/containerd/containerd/remotes" "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" - "io" - "os" - "path" ) type ( @@ -59,8 +62,43 @@ func NewLocalBlobProvider(fileProvider content.Store) BlobProvider { } } +type tokenProxyTripper struct { + base http.RoundTripper + host *url.URL +} + +func (t tokenProxyTripper) RoundTrip(req *http.Request) (*http.Response, error) { + reqBodyClosed := false + if req.Body != nil { + defer func() { + if !reqBodyClosed { + req.Body.Close() + } + }() + } + + req2 := req.Clone(req.Context()) + req2.URL = t.host.JoinPath(req2.URL.Path) + req2.Host = t.host.Host + + // req.Body is assumed to be closed by the base RoundTripper. + reqBodyClosed = true + return t.base.RoundTrip(req2) +} + func NewRemoteBlobProviderFromConfig(config *Config) BlobProvider { client := NewHttpClient(config.ConnectTimeout, config.ReadTimeout) + if config.ProxyURL != nil { + tpt := tokenProxyTripper{ + base: client.Transport, + host: config.ProxyURL, + } + + if config.ProxyCerts != nil { + client.Transport.(*http.Transport).TLSClientConfig.RootCAs = config.ProxyCerts + } + client.Transport = tpt + } authorizer := NewRegistryAuthorizer(config.DockerCfg, client) resolver := NewResolver(authorizer, client) return newRemoteBlobProvider(resolver) diff --git a/pkg/compose/v1/config.go b/pkg/compose/v1/config.go index 8647ad3..b11e410 100644 --- a/pkg/compose/v1/config.go +++ b/pkg/compose/v1/config.go @@ -1,14 +1,17 @@ package v1 import ( + "crypto/x509" "fmt" - "github.com/containerd/containerd/platforms" - dockercfg "github.com/docker/cli/cli/config" - "github.com/foundriesio/composeapp/pkg/compose" + "net/url" "os" "path" "path/filepath" "time" + + "github.com/containerd/containerd/platforms" + dockercfg "github.com/docker/cli/cli/config" + "github.com/foundriesio/composeapp/pkg/compose" ) type ( @@ -110,6 +113,29 @@ func NewDefaultConfig(options ...ConfigOpt) (*compose.Config, error) { return nil, fmt.Errorf("failed to get file system stat; path: %s, err: %s", opts.StoreRoot, err.Error()) } + var proxyURL *url.URL + var proxyCerts *x509.CertPool + + proxyEnv := os.Getenv("COMPOSE_APPS_PROXY") + if len(proxyEnv) > 0 { + proxyURL, err = url.Parse(proxyEnv) + if err != nil { + return nil, fmt.Errorf("invalid COMPOSE_APPS_PROXY: %s: %w", proxyEnv, err) + } + + proxyCa := os.Getenv("COMPOSE_APPS_PROXY_CA") + if len(proxyCa) > 0 { + proxyCerts = x509.NewCertPool() + + pemData, err := os.ReadFile(proxyCa) + if err != nil { + return nil, fmt.Errorf("unable to read COMPOSE_APPS_PROXY_CA: %w", err) + } else if ok := proxyCerts.AppendCertsFromPEM(pemData); !ok { + return nil, fmt.Errorf("failed to set COMPOSE_APPS_PROXY_CA: %w", err) + } + } + } + // Load docker config dockerCfg, err := dockercfg.Load("") if err != nil { @@ -131,5 +157,7 @@ func NewDefaultConfig(options ...ConfigOpt) (*compose.Config, error) { }, BlockSize: s.BlockSize, DBFilePath: opts.UpdateDBPath, + ProxyURL: proxyURL, + ProxyCerts: proxyCerts, }, nil }