diff --git a/Dockerfile b/Dockerfile index a550b24b..1bd86540 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 660e8815..2654c624 100644 --- a/controllers/componentversion_controller.go +++ b/controllers/componentversion_controller.go @@ -166,6 +166,9 @@ func (r *ComponentVersionReconciler) Reconcile(ctx context.Context, req ctrl.Req return ctrl.Result{}, nil } + defer func() { + _ = octx.Finalize() + }() // 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 12ded475..831f29c4 100644 --- a/controllers/mutation_reconcile_looper.go +++ b/controllers/mutation_reconcile_looper.go @@ -307,6 +307,9 @@ func (m *MutationReconcileLooper) fetchDataFromComponentVersion(ctx context.Cont if err != nil { return nil, fmt.Errorf("failed to create authenticated client: %w", err) } + defer func() { + _ = octx.Finalize() + }() if obj.ResourceRef == nil { return nil, fmt.Errorf("no resource ref found for %s", key) @@ -344,6 +347,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 +379,9 @@ func (m *MutationReconcileLooper) createSubstitutionRulesForLocalization( if err != nil { return nil, fmt.Errorf("failed to create authenticated client: %w", err) } + defer func() { + _ = octx.Finalize() + }() 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 86c78a99..a1ac3526 100644 --- a/controllers/resource_controller.go +++ b/controllers/resource_controller.go @@ -194,6 +194,9 @@ func (r *ResourceReconciler) reconcile( return ctrl.Result{}, nil } + defer func() { + _ = octx.Finalize() + }() 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 b1f39f9c..cdc90fe6 100644 --- a/deploy/templates/deployment_manager.yaml +++ b/deploy/templates/deployment_manager.yaml @@ -33,6 +33,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.image.fullyQualifiedImageName }} image: "{{ .Values.manager.image.fullyQualifiedImageName }}" {{- else }} diff --git a/deploy/values.yaml b/deploy/values.yaml index a31d9592..8aba9877 100644 --- a/deploy/values.yaml +++ b/deploy/values.yaml @@ -83,6 +83,7 @@ manager: requests: cpu: 200m memory: 512Mi + pprofBindAddress: "" # optional values defined by the user nodeSelector: {} tolerations: [] diff --git a/main.go b/main.go index 68e2692a..77d40d10 100644 --- a/main.go +++ b/main.go @@ -55,6 +55,7 @@ func main() { eventsAddr string enableLeaderElection bool probeAddr string + pprofAddr string ociRegistryAddr string ociRegistryCertSecretName string ociRegistryInsecureSkipVerify bool @@ -74,6 +75,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", @@ -128,6 +135,7 @@ func main() { BindAddress: metricsAddr, }, HealthProbeBindAddress: probeAddr, + PprofBindAddress: pprofAddr, LeaderElection: enableLeaderElection, LeaderElectionID: "f8b21459.ocm.software", }) @@ -154,7 +162,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 7b22816e..8e703fc6 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{} //nolint:gosec // must provide lower version for quay.io 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 b43788ff..a3cb09e4 100644 --- a/pkg/ocm/ocm.go +++ b/pkg/ocm/ocm.go @@ -269,6 +269,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 51b42b7d..aecc8c78 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) 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()