diff --git a/Dockerfile b/Dockerfile index a550b24b..1dfd9a5e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Build the manager binary -FROM docker.io/library/golang:1.25.4 as builder +FROM docker.io/library/golang:1.26.2 as builder WORKDIR /workspace # Copy the Go Modules manifests @@ -14,7 +14,6 @@ COPY main.go main.go COPY api/ api/ COPY controllers/ controllers/ COPY pkg/ pkg/ -COPY internal/ internal/ # Build RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o manager main.go diff --git a/controllers/componentversion_controller.go b/controllers/componentversion_controller.go index b304479e..127f2379 100644 --- a/controllers/componentversion_controller.go +++ b/controllers/componentversion_controller.go @@ -19,6 +19,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" kuberecorder "k8s.io/client-go/tools/record" + "ocm.software/ocm/api/datacontext" "ocm.software/ocm/api/ocm" ocmdesc "ocm.software/ocm/api/ocm/compdesc" compdesc "ocm.software/ocm/api/ocm/compdesc/versions/ocm.software/v3alpha1" @@ -166,6 +167,9 @@ func (r *ComponentVersionReconciler) Reconcile(ctx context.Context, req ctrl.Req return ctrl.Result{}, nil } + defer func() { + _ = datacontext.Close(octx) + }() // reconcile the version before calling reconcile func update, version, err := r.checkVersion(ctx, octx, obj) diff --git a/controllers/configuration_strat_merge_patch.go b/controllers/configuration_strat_merge_patch.go index 46bf904d..6543f45f 100644 --- a/controllers/configuration_strat_merge_patch.go +++ b/controllers/configuration_strat_merge_patch.go @@ -27,6 +27,8 @@ func (m *MutationReconcileLooper) strategicMergePatch( gzipSnapshot := &bytes.Buffer{} gz := gzip.NewWriter(gzipSnapshot) if _, err := gz.Write(resource); err != nil { + gz.Close() + return "", err } @@ -89,6 +91,8 @@ func (m *MutationReconcileLooper) strategicMergePatch( } if _, err := patched.Write(contents); err != nil { + patched.Close() + return "", err } diff --git a/controllers/mutation_reconcile_looper.go b/controllers/mutation_reconcile_looper.go index ddd8a622..dc913418 100644 --- a/controllers/mutation_reconcile_looper.go +++ b/controllers/mutation_reconcile_looper.go @@ -31,6 +31,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/dynamic" + "ocm.software/ocm/api/datacontext" ocmcore "ocm.software/ocm/api/ocm" "ocm.software/ocm/api/ocm/resourcerefs" "ocm.software/ocm/api/utils/tarutils" @@ -307,6 +308,9 @@ func (m *MutationReconcileLooper) fetchDataFromComponentVersion(ctx context.Cont if err != nil { return nil, fmt.Errorf("failed to create authenticated client: %w", err) } + defer func() { + _ = datacontext.Close(octx) + }() if obj.ResourceRef == nil { return nil, fmt.Errorf("no resource ref found for %s", key) @@ -344,6 +348,7 @@ func (m *MutationReconcileLooper) getSnapshotBytes(ctx context.Context, snapshot if err != nil { return nil, fmt.Errorf("failed to fetch data: %w", err) } + defer reader.Close() if uncompress { uncompressed, _, err := compression.AutoDecompress(reader) @@ -375,6 +380,9 @@ func (m *MutationReconcileLooper) createSubstitutionRulesForLocalization( if err != nil { return nil, fmt.Errorf("failed to create authenticated client: %w", err) } + defer func() { + _ = datacontext.Close(octx) + }() compvers, err := m.OCMClient.GetComponentVersion(ctx, octx, cv.GetRepositoryURL(), cv.Spec.Component, cv.Status.ReconciledVersion) if err != nil { diff --git a/controllers/resource_controller.go b/controllers/resource_controller.go index d7e28060..86cc4bcd 100644 --- a/controllers/resource_controller.go +++ b/controllers/resource_controller.go @@ -25,6 +25,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" kuberecorder "k8s.io/client-go/tools/record" + "ocm.software/ocm/api/datacontext" ocmmetav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" @@ -195,6 +196,9 @@ func (r *ResourceReconciler) reconcile( return ctrl.Result{}, nil } + defer func() { + _ = datacontext.Close(octx) + }() reader, digest, size, err := r.OCMClient.GetResource(ctx, octx, componentVersion, obj.Spec.SourceRef.ResourceRef) if err != nil { diff --git a/deploy/templates/deployment_manager.yaml b/deploy/templates/deployment_manager.yaml index 5769eaca..7ce551b8 100644 --- a/deploy/templates/deployment_manager.yaml +++ b/deploy/templates/deployment_manager.yaml @@ -36,6 +36,9 @@ spec: {{- if not .Values.registry.tls.enabled }} - --oci-registry-insecure-skip-verify {{- end }} + {{- if .Values.manager.pprofBindAddress }} + - --pprof-bind-address={{ .Values.manager.pprofBindAddress }} + {{- end }} {{- if .Values.manager.kubeAPI }} {{- if .Values.manager.kubeAPI.rateLimiterDisabled }} - --kube-api-rate-limiter-disabled diff --git a/deploy/values.yaml b/deploy/values.yaml index 20762a6d..8fd6e801 100644 --- a/deploy/values.yaml +++ b/deploy/values.yaml @@ -87,6 +87,7 @@ manager: requests: cpu: 200m memory: 512Mi + pprofBindAddress: "" kubeAPI: qps: 20 burst: 30 diff --git a/go.mod b/go.mod index 87dedace..68ff7fe4 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,8 @@ replace github.com/opencontainers/go-digest => github.com/opencontainers/go-dige replace github.com/ThalesIgnite/crypto11 => github.com/ThalesGroup/crypto11 v1.6.0 +replace ocm.software/ocm => github.com/Skarlso/ocm v0.0.0-20260420070955-22d2eada8ccc + require ( cuelang.org/go v0.16.1 github.com/Masterminds/semver/v3 v3.5.0 diff --git a/go.sum b/go.sum index 6c907a59..1a644dda 100644 --- a/go.sum +++ b/go.sum @@ -98,6 +98,8 @@ github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBi github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= github.com/Shopify/toxiproxy/v2 v2.12.0 h1:d1x++lYZg/zijXPPcv7PH0MvHMzEI5aX/YuUi/Sw+yg= github.com/Shopify/toxiproxy/v2 v2.12.0/go.mod h1:R9Z38Pw6k2cGZWXHe7tbxjGW9azmY1KbDQJ1kd+h7Tk= +github.com/Skarlso/ocm v0.0.0-20260420070955-22d2eada8ccc h1:8Pz3V7LCATRp7WnAqXc42NjzUi/FE2bSjHhPHymfKJc= +github.com/Skarlso/ocm v0.0.0-20260420070955-22d2eada8ccc/go.mod h1:yeVH6XVnwZz/R31ty7+ui9vPVg3SNLP79tDvbnisTAI= github.com/ThalesGroup/crypto11 v1.6.0 h1:Og9EMn44fBS4GNnGnH1aqHnF2wL6F7IU/RhpJajWX/4= github.com/ThalesGroup/crypto11 v1.6.0/go.mod h1:H6LRjN5R5SHxTrLqGNteisLDI0/IC6+SGx1pHtbwizE= github.com/a8m/envsubst v1.4.3 h1:kDF7paGK8QACWYaQo6KtyYBozY2jhQrTuNNuUxQkhJY= @@ -598,8 +600,8 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20260402051712-545e8a4df936 h1:EwtI+Al+DeppwYX2oXJCETMO23COyaKGP6fHVpkpWpg= -github.com/google/pprof v0.0.0-20260402051712-545e8a4df936/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= +github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc= +github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/trillian v1.7.2 h1:EPBxc4YWY4Ak8tcuhyFleY+zYlbCDCa4Sn24e1Ka8Js= @@ -677,8 +679,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw= -github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= +github.com/jackc/pgx/v5 v5.8.0 h1:TYPDoleBBme0xGSAX3/+NujXXtpZn9HBONkQC7IEZSo= +github.com/jackc/pgx/v5 v5.8.0/go.mod h1:QVeDInX2m9VyzvNeiCJVjCkNFqzsNb43204HshNSZKw= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= @@ -835,8 +837,8 @@ github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vv github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.28.3 h1:4JvMdwtFU0imd8fHx25OJXoDMRexnf8v5NHKYSTTji4= -github.com/onsi/ginkgo/v2 v2.28.3/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44= +github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI= +github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= @@ -1434,8 +1436,6 @@ k8s.io/streaming v0.36.1 h1:L+K68n4Gg940BGNNYtUBvL1WTLL0YnKT3s+P1MNAmR4= k8s.io/streaming v0.36.1/go.mod h1:z6fV3D+NVkoeqRMtWwlUZK6U17SY/LqNzOxWL6GyR/s= k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 h1:kBawHLSnx/mYHmRnNUf9d4CpjREbeZuxoSGOX/J+aYM= k8s.io/utils v0.0.0-20260319190234-28399d86e0b5/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= -ocm.software/ocm v0.42.0 h1:sW2WPcPhm6HRIBrqMZok/IylM8IZa5fEsTJ3GTsOXXw= -ocm.software/ocm v0.42.0/go.mod h1:jxkW9oj8T1Tyi46lCG41GHQTqMJPzxmFBwD4G+DKO0Q= oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc= oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o= pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= diff --git a/main.go b/main.go index 771bfe64..1452dd35 100644 --- a/main.go +++ b/main.go @@ -57,6 +57,7 @@ func main() { eventsAddr string enableLeaderElection bool probeAddr string + pprofAddr string ociRegistryAddr string ociRegistryCertSecretName string ociRegistryInsecureSkipVerify bool @@ -79,6 +80,12 @@ func main() { ":8081", "The address the probe endpoint binds to.", ) + flag.StringVar( + &pprofAddr, + "pprof-bind-address", + "", + "The address the pprof endpoint binds to. Disabled if empty.", + ) flag.StringVar( &ociRegistryAddr, "oci-registry-addr", @@ -154,6 +161,7 @@ func main() { BindAddress: metricsAddr, }, HealthProbeBindAddress: probeAddr, + PprofBindAddress: pprofAddr, LeaderElection: enableLeaderElection, LeaderElectionID: "f8b21459.ocm.software", }) @@ -180,7 +188,6 @@ func main() { } ctx := ctrl.SetupSignalHandler() - setupLog.Info("starting manager") if err := mgr.Start(ctx); err != nil { setupLog.Error(err, "problem running manager") diff --git a/pkg/oci/repository.go b/pkg/oci/repository.go index b5a0fd69..85518cf9 100644 --- a/pkg/oci/repository.go +++ b/pkg/oci/repository.go @@ -1,6 +1,7 @@ package oci import ( + "bytes" "context" "crypto/tls" "crypto/x509" @@ -10,6 +11,7 @@ import ( "io" "net/http" "strings" + "sync" ociname "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" @@ -87,9 +89,11 @@ type Client struct { Namespace string CertSecretName string - certPem []byte - keyPem []byte - ca []byte + mu sync.Mutex + certPem []byte + keyPem []byte + ca []byte + transport *http.Transport } // WithTransport sets up insecure TLS so the library is forced to use HTTPS. @@ -99,49 +103,57 @@ func (c *Client) WithTransport(ctx context.Context) Option { return nil } - // always refresh certificates to handle cert-manager rotation - if err := c.setupCertificates(ctx); err != nil { - return fmt.Errorf("failed to set up certificates for transport: %w", err) + t, err := c.getOrRefreshTransport(ctx) + if err != nil { + return fmt.Errorf("failed to set up transport: %w", err) } - o.remoteOpts = append(o.remoteOpts, remote.WithTransport(c.constructTLSRoundTripper())) + o.remoteOpts = append(o.remoteOpts, remote.WithTransport(t)) return nil } } -func (c *Client) setupCertificates(ctx context.Context) error { +func (c *Client) getOrRefreshTransport(ctx context.Context) (http.RoundTripper, error) { + c.mu.Lock() + defer c.mu.Unlock() + if c.Client == nil { - return fmt.Errorf("client must not be nil if certificate is requested, please set WithClient when creating the oci cache") + return nil, fmt.Errorf("client must not be nil if certificate is requested, please set WithClient when creating the oci cache") } + registryCerts := &corev1.Secret{} if err := c.Client.Get(ctx, apitypes.NamespacedName{Name: c.CertSecretName, Namespace: c.Namespace}, registryCerts); err != nil { - return fmt.Errorf("unable to find the secret containing the registry certificates: %w", err) + return nil, fmt.Errorf("unable to find the secret containing the registry certificates: %w", err) } certFile, ok := registryCerts.Data["tls.crt"] if !ok { - return fmt.Errorf("tls.crt data not found in registry certificate secret") + return nil, fmt.Errorf("tls.crt data not found in registry certificate secret") } keyFile, ok := registryCerts.Data["tls.key"] if !ok { - return fmt.Errorf("tls.key data not found in registry certificate secret") + return nil, fmt.Errorf("tls.key data not found in registry certificate secret") } caFile, ok := registryCerts.Data["ca.crt"] if !ok { - return fmt.Errorf("ca.crt data not found in registry certificate secret") + return nil, fmt.Errorf("ca.crt data not found in registry certificate secret") + } + + if c.transport != nil && bytes.Equal(c.certPem, certFile) && bytes.Equal(c.keyPem, keyFile) && bytes.Equal(c.ca, caFile) { + return c.transport, nil + } + + if c.transport != nil { + c.transport.CloseIdleConnections() } c.certPem = certFile c.keyPem = keyFile c.ca = caFile - return nil -} - -func (c *Client) constructTLSRoundTripper() http.RoundTripper { tlsConfig := &tls.Config{} caCertPool := x509.NewCertPool() caCertPool.AppendCertsFromPEM(c.ca) @@ -156,10 +168,11 @@ func (c *Client) constructTLSRoundTripper() http.RoundTripper { tlsConfig.RootCAs = caCertPool tlsConfig.InsecureSkipVerify = c.InsecureSkipVerify - // Create a new HTTP transport with the TLS configuration - return &http.Transport{ + c.transport = &http.Transport{ TLSClientConfig: tlsConfig, } + + return c.transport, nil } // NewClient creates a new OCI Client. diff --git a/pkg/ocm/ocm.go b/pkg/ocm/ocm.go index d0ff38e7..d82a40cd 100644 --- a/pkg/ocm/ocm.go +++ b/pkg/ocm/ocm.go @@ -268,6 +268,11 @@ func (c *Client) GetResource( if err != nil { return nil, "", -1, fmt.Errorf("failed to autodecompress content: %w", err) } + defer func() { + if cerr := decompressedReader.Close(); cerr != nil { + err = errors.Join(err, cerr) + } + }() // We need to push the media type... And construct the right layers I guess. digest, size, err := c.cache.PushData(ctx, decompressedReader, mediaType, name, version) diff --git a/pkg/snapshot/tar.go b/pkg/snapshot/tar.go index 9d0cb7c2..c833dac2 100644 --- a/pkg/snapshot/tar.go +++ b/pkg/snapshot/tar.go @@ -72,17 +72,15 @@ func buildTar(artifactPath, sourceDir string) error { } f, err := os.Open(p) //nolint:gosec // path is validated by filepath.Walk which skips symlinks if err != nil { - f.Close() - return err } - if _, err := io.Copy(tw, f); err != nil { - f.Close() + defer f.Close() + if _, err := io.Copy(tw, f); err != nil { return err } - return f.Close() + return nil }); err != nil { tw.Close() tf.Close()