From 6f91b09c0c8723be1c0058e8f2f2719f392b1b8e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 27 Aug 2024 19:47:05 +0100 Subject: [PATCH 1/2] Release v0.8.1 (#258) Co-authored-by: github-actions[bot] Co-authored-by: David Collom --- .github/workflows/helm-docs.yaml | 4 ++- .github/workflows/release.yaml | 32 ++++++++++++++++++++++-- Makefile | 2 +- deploy/charts/version-checker/Chart.yaml | 4 +-- deploy/charts/version-checker/README.md | 6 ++--- deploy/yaml/deploy.yaml | 2 +- 6 files changed, 40 insertions(+), 10 deletions(-) diff --git a/.github/workflows/helm-docs.yaml b/.github/workflows/helm-docs.yaml index dad0635..e903b13 100644 --- a/.github/workflows/helm-docs.yaml +++ b/.github/workflows/helm-docs.yaml @@ -1,5 +1,7 @@ name: Generate Helm Docs on: + # Allow other workflows to trigger + workflow_call: push: paths: - '!*.md' @@ -64,4 +66,4 @@ jobs: with: github_token: ${{ secrets.GITHUB_TOKEN }} branch: ${{ github.head_ref }} - repository: ${{ github.event.pull_request.head.repo.full_name }} \ No newline at end of file + repository: ${{ github.event.pull_request.head.repo.full_name }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 7362629..1f0284f 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -5,7 +5,7 @@ on: branches: - "release-v*" tags: - - "*" + - "v*" concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -27,6 +27,7 @@ jobs: with: value: ${{github.ref_name}} index_of_str: "release-" + - name: Find and Replace Helm Chart Version uses: jacobtomlinson/gha-find-replace@v3 with: @@ -34,6 +35,7 @@ jobs: replace: "${{steps.release_number.outputs.substring}}" include: "deploy/charts/version-checker/Chart.yaml" regex: true + - name: Find and Replace Kubernetes Manifests uses: jacobtomlinson/gha-find-replace@v3 with: @@ -41,6 +43,7 @@ jobs: replace: "${{steps.release_number.outputs.substring}}" include: "deploy/yaml/deploy.yaml" regex: true + - name: Find and Replace Makefile versions uses: jacobtomlinson/gha-find-replace@v3 with: @@ -48,6 +51,18 @@ jobs: replace: "${{steps.release_number.outputs.substring}}" include: "Makefile" regex: true + + + - name: Install Helm Docs + uses: envoy/install-helm-docs@v1.0.0 + with: + version: 1.11.0 + - name: Update Helm Docs + run: | + set -ex + cd deploy/charts/version-checker + helm-docs + - name: Detect any Local Changes uses: dorny/paths-filter@v3 id: filter @@ -58,6 +73,8 @@ jobs: - 'Makefile' - 'deploy/yaml/deploy.yaml' - 'deploy/charts/version-checker/Chart.yaml' + - 'deploy/charts/version-checker/README.md' + - name: Commit files if: steps.filter.outputs.versions == 'true' run: | @@ -65,19 +82,30 @@ jobs: git config --local user.name "github-actions[bot]" git status git commit -a -m "Bump versions to ${{steps.release_number.outputs.substring}} " + - name: Push changes if: steps.filter.outputs.versions == 'true' uses: ad-m/github-push-action@v0.8.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} branch: ${{ github.ref_name }} + + - name: Build Changelog + id: github_release + uses: mikepenz/release-changelog-builder-action@v5 + + - name: Create Release PR uses: devops-infra/action-pull-request@v0.5.5 with: github_token: ${{ secrets.GITHUB_TOKEN }} target_branch: main title: "Release ${{steps.release_number.outputs.substring}}" - body: "**Automated Release Pull Request**" + body: |- + "**Automated Release Pull Request** + + ## Change log: + ${{steps.github_release.outputs.changelog}} draft: false get_diff: false allow_no_diff: false diff --git a/Makefile b/Makefile index 1af196f..4844e40 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ verify: test build ## tests and builds version-checker image: ## build docker image GOARCH=$(ARCH) GOOS=linux CGO_ENABLED=0 go build -o ./bin/version-checker-linux ./cmd/. - docker build -t quay.io/jetstack/version-checker:v0.8.0 . + docker build -t quay.io/jetstack/version-checker:v0.8.1 . clean: ## clean up created files rm -rf \ diff --git a/deploy/charts/version-checker/Chart.yaml b/deploy/charts/version-checker/Chart.yaml index 28deb3c..988fec6 100644 --- a/deploy/charts/version-checker/Chart.yaml +++ b/deploy/charts/version-checker/Chart.yaml @@ -1,6 +1,6 @@ apiVersion: v2 -appVersion: "v0.8.0" -version: "v0.8.0" +appVersion: "v0.8.1" +version: "v0.8.1" description: A Helm chart for version-checker home: https://github.com/jetstack/version-checker name: version-checker diff --git a/deploy/charts/version-checker/README.md b/deploy/charts/version-checker/README.md index a8742e8..53a6416 100644 --- a/deploy/charts/version-checker/README.md +++ b/deploy/charts/version-checker/README.md @@ -1,6 +1,6 @@ # version-checker -![Version: v0.7.0](https://img.shields.io/badge/Version-v0.7.0-informational?style=flat-square) ![AppVersion: v0.7.0](https://img.shields.io/badge/AppVersion-v0.7.0-informational?style=flat-square) +![Version: v0.8.1](https://img.shields.io/badge/Version-v0.8.1-informational?style=flat-square) ![AppVersion: v0.8.1](https://img.shields.io/badge/AppVersion-v0.8.1-informational?style=flat-square) A Helm chart for version-checker @@ -55,7 +55,7 @@ A Helm chart for version-checker | readinessProbe.periodSeconds | int | `3` | How often (in seconds) to perform the readinessProbe. | | replicaCount | int | `1` | Replica Count for version-checker | | resources | object | `{}` | Setup version-checkers resource requests/limits | -| securityContext | object | `{}` | Set container-level security context | +| securityContext | object | `{"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]},"readOnlyRootFilesystem":true,"runAsNonRoot":true,"runAsUser":65534,"seccompProfile":{"type":"RuntimeDefault"}}` | Set container-level security context | | selfhosted | []{name: "", host: "", username:"", password:"", token:""}] | `[]` | Setup a number of SelfHosted Repositories and their credentials | | service.annotations | object | `{}` | Additional annotations to add to the service | | service.labels | object | `{}` | Additional labels to add to the service | @@ -69,4 +69,4 @@ A Helm chart for version-checker | versionChecker.testAllContainers | bool | `true` | Enable/Disable the requirement for an enable.version-checker.io annotation on pods. | ---------------------------------------------- -Autogenerated from chart metadata using [helm-docs v1.14.2](https://github.com/norwoodj/helm-docs/releases/v1.14.2) +Autogenerated from chart metadata using [helm-docs v1.11.0](https://github.com/norwoodj/helm-docs/releases/v1.11.0) diff --git a/deploy/yaml/deploy.yaml b/deploy/yaml/deploy.yaml index 628011d..f5d7283 100644 --- a/deploy/yaml/deploy.yaml +++ b/deploy/yaml/deploy.yaml @@ -49,7 +49,7 @@ spec: spec: serviceAccountName: version-checker containers: - - image: quay.io/jetstack/version-checker:v0.8.0 + - image: quay.io/jetstack/version-checker:v0.8.1 imagePullPolicy: Always ports: - containerPort: 8080 From 15e945d41a3df8721ed373bdb05caeaf4e4a1e73 Mon Sep 17 00:00:00 2001 From: David Collom Date: Tue, 27 Aug 2024 19:47:53 +0100 Subject: [PATCH 2/2] Adding UnitTests for SelfHosted Client (#257) --- pkg/client/selfhosted/errors/errors.go | 4 +- pkg/client/selfhosted/path_test.go | 1 - pkg/client/selfhosted/selfhosted.go | 90 ++++-- pkg/client/selfhosted/selfhosted_test.go | 359 +++++++++++++++++++++++ 4 files changed, 419 insertions(+), 35 deletions(-) create mode 100644 pkg/client/selfhosted/selfhosted_test.go diff --git a/pkg/client/selfhosted/errors/errors.go b/pkg/client/selfhosted/errors/errors.go index 9da9aa5..4a194ef 100644 --- a/pkg/client/selfhosted/errors/errors.go +++ b/pkg/client/selfhosted/errors/errors.go @@ -1,7 +1,5 @@ package errors -import "fmt" - type HTTPError struct { Body []byte StatusCode int @@ -15,7 +13,7 @@ func NewHTTPError(statusCode int, body []byte) *HTTPError { } func (h *HTTPError) Error() string { - return fmt.Sprintf("%s", h.Body) + return string(h.Body) } func IsHTTPError(err error) (*HTTPError, bool) { diff --git a/pkg/client/selfhosted/path_test.go b/pkg/client/selfhosted/path_test.go index b78f6f1..6824427 100644 --- a/pkg/client/selfhosted/path_test.go +++ b/pkg/client/selfhosted/path_test.go @@ -115,7 +115,6 @@ func TestRepoImage(t *testing.T) { t.Errorf("%s: unexpected repo/image, exp=%s/%s got=%s/%s", test.path, test.expRepo, test.expImage, repo, image) } - }) } } diff --git a/pkg/client/selfhosted/selfhosted.go b/pkg/client/selfhosted/selfhosted.go index da89163..b836191 100644 --- a/pkg/client/selfhosted/selfhosted.go +++ b/pkg/client/selfhosted/selfhosted.go @@ -85,47 +85,72 @@ func New(ctx context.Context, log *logrus.Entry, opts *Options) (*Client, error) log: log.WithField("client", opts.Host), } - // Set up client with host matching if set - if opts.Host != "" { - hostRegex, scheme, err := parseURL(opts.Host) - if err != nil { - return nil, fmt.Errorf("failed parsing url: %s", err) - } - client.hostRegex = hostRegex - client.httpScheme = scheme + if err := configureHost(ctx, client, opts); err != nil { + return nil, err + } - // Setup Auth if username and password used. - if len(opts.Username) > 0 || len(opts.Password) > 0 { - if len(opts.Bearer) > 0 { - return nil, errors.New("cannot specify Bearer token as well as username/password") - } + if err := configureTLS(client, opts); err != nil { + return nil, err + } - tokenPath := opts.TokenPath - if tokenPath == "" { - tokenPath = defaultTokenPath - } + return client, nil +} - token, err := client.setupBasicAuth(ctx, opts.Host, tokenPath) - if httpErr, ok := selfhostederrors.IsHTTPError(err); ok { - return nil, fmt.Errorf("failed to setup token auth (%d): %s", - httpErr.StatusCode, httpErr.Body) - } +func configureHost(ctx context.Context, client *Client, opts *Options) error { + if opts.Host == "" { + return nil + } - if err != nil { - return nil, fmt.Errorf("failed to setup token auth: %s", err) - } - client.Bearer = token - } + hostRegex, scheme, err := parseURL(opts.Host) + if err != nil { + return fmt.Errorf("failed parsing url: %s", err) + } + client.hostRegex = hostRegex + client.httpScheme = scheme + + if err := configureAuth(ctx, client, opts); err != nil { + return err } - // Default to https if no scheme set + return nil +} + +func configureAuth(ctx context.Context, client *Client, opts *Options) error { + if len(opts.Username) == 0 && len(opts.Password) == 0 { + return nil + } + + if len(opts.Bearer) > 0 { + return errors.New("cannot specify Bearer token as well as username/password") + } + + tokenPath := opts.TokenPath + if tokenPath == "" { + tokenPath = defaultTokenPath + } + + token, err := client.setupBasicAuth(ctx, opts.Host, tokenPath) + if httpErr, ok := selfhostederrors.IsHTTPError(err); ok { + return fmt.Errorf("failed to setup token auth (%d): %s", + httpErr.StatusCode, httpErr.Body) + } + if err != nil { + return fmt.Errorf("failed to setup token auth: %s", err) + } + + client.Bearer = token + return nil +} + +func configureTLS(client *Client, opts *Options) error { if client.httpScheme == "" { client.httpScheme = "https" } + if client.httpScheme == "https" { tlsConfig, err := newTLSConfig(opts.Insecure, opts.CAPath) if err != nil { - return nil, err + return err } client.Client.Transport = &http.Transport{ @@ -134,7 +159,7 @@ func New(ctx context.Context, log *logrus.Entry, opts *Options) (*Client, error) } } - return client, nil + return nil } // Name returns the name of the host URL for the selfhosted client @@ -229,11 +254,13 @@ func (c *Client) doRequest(ctx context.Context, url, header string, obj interfac if err != nil { return nil, fmt.Errorf("failed to get docker image: %s", err) } + defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return nil, err } + defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, selfhostederrors.NewHTTPError(resp.StatusCode, body) @@ -268,6 +295,7 @@ func (c *Client) setupBasicAuth(ctx context.Context, url, tokenPath string) (str return "", fmt.Errorf("failed to send basic auth request %q: %s", req.URL, err) } + defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { @@ -296,7 +324,7 @@ func newTLSConfig(insecure bool, CAPath string) (*tls.Config, error) { if CAPath != "" { certs, err := os.ReadFile(CAPath) if err != nil { - return nil, fmt.Errorf("Failed to append %q to RootCAs: %v", CAPath, err) + return nil, fmt.Errorf("failed to append %q to RootCAs: %v", CAPath, err) } rootCAs.AppendCertsFromPEM(certs) } diff --git a/pkg/client/selfhosted/selfhosted_test.go b/pkg/client/selfhosted/selfhosted_test.go new file mode 100644 index 0000000..e2ff714 --- /dev/null +++ b/pkg/client/selfhosted/selfhosted_test.go @@ -0,0 +1,359 @@ +package selfhosted + +import ( + "context" + "errors" + "net/http" + "net/http/httptest" + "net/url" + "os" + "testing" + + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" + + "github.com/jetstack/version-checker/pkg/api" + selfhostederrors "github.com/jetstack/version-checker/pkg/client/selfhosted/errors" +) + +func TestNew(t *testing.T) { + log := logrus.NewEntry(logrus.New()) + ctx := context.Background() + + t.Run("successful client creation with username and password", func(t *testing.T) { + opts := &Options{ + Host: "https://testregistry.com", + Username: "testuser", + Password: "testpass", + } + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "/v2/token", r.URL.Path) + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{"token":"testtoken"}`)) + })) + defer server.Close() + + opts.Host = server.URL + client, err := New(ctx, log, opts) + + assert.NoError(t, err) + assert.Equal(t, "testtoken", client.Bearer) + }) + + t.Run("error on invalid URL", func(t *testing.T) { + opts := &Options{ + Host: "://invalid-url", + } + + client, err := New(ctx, log, opts) + + assert.Nil(t, client) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed parsing url") + }) + + t.Run("error on username/password and bearer token both specified", func(t *testing.T) { + opts := &Options{ + Host: "https://testregistry.com", + Username: "testuser", + Password: "testpass", + Bearer: "testtoken", + } + + client, err := New(ctx, log, opts) + + assert.Nil(t, client) + assert.EqualError(t, err, "cannot specify Bearer token as well as username/password") + }) + + t.Run("successful client creation with bearer token", func(t *testing.T) { + opts := &Options{ + Host: "https://testregistry.com", + Bearer: "testtoken", + } + + client, err := New(ctx, log, opts) + + assert.NoError(t, err) + assert.Equal(t, "testtoken", client.Bearer) + }) + + t.Run("error on invalid CA path", func(t *testing.T) { + opts := &Options{ + Host: "https://testregistry.com", + CAPath: "invalid/path", + Insecure: true, + } + + client, err := New(ctx, log, opts) + + assert.Nil(t, client) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to append") + }) +} + +func TestName(t *testing.T) { + log := logrus.NewEntry(logrus.New()) + client := &Client{ + Options: &Options{ + Host: "testhost", + }, + log: log, + } + + assert.Equal(t, "testhost", client.Name()) + + client.Options.Host = "" + assert.Equal(t, "dockerapi", client.Name()) +} + +func TestTags(t *testing.T) { + log := logrus.NewEntry(logrus.New()) + ctx := context.Background() + + t.Run("successful Tags fetch", func(t *testing.T) { + client := &Client{ + Client: &http.Client{}, + log: log, + Options: &Options{ + Host: "testregistry.com", + }, + httpScheme: "http", + } + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/v2/repo/image/tags/list": + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{"tags":["v1.0.0","v2.0.0"]}`)) + case "/v2/repo/image/manifests/v1.0.0": + w.Header().Add("Docker-Content-Digest", "sha256:abcdef") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{"architecture":"amd64","history":[{"v1Compatibility":"{\"created\":\"2023-08-27T12:00:00Z\"}"}]}`)) + case "/v2/repo/image/manifests/v2.0.0": + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{}`)) // Write some blank content + } + })) + defer server.Close() + + h, err := url.Parse(server.URL) + assert.NoError(t, err) + + tags, err := client.Tags(ctx, h.Host, "repo", "image") + + assert.NoError(t, err) + assert.Len(t, tags, 2) + assert.Equal(t, "v1.0.0", tags[0].Tag) + assert.Equal(t, api.Architecture("amd64"), tags[0].Architecture) + assert.Equal(t, "sha256:abcdef", tags[0].SHA) + assert.Equal(t, "v2.0.0", tags[1].Tag) + }) + + t.Run("error fetching tags", func(t *testing.T) { + client := &Client{ + Client: &http.Client{}, + log: log, + Options: &Options{ + Host: "https://testregistry.com", + }, + } + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + })) + defer server.Close() + + tags, err := client.Tags(ctx, server.URL, "repo", "image") + assert.Nil(t, tags) + assert.Error(t, err) + }) +} + +func TestDoRequest(t *testing.T) { + log := logrus.NewEntry(logrus.New()) + ctx := context.Background() + + client := &Client{ + Client: &http.Client{}, + Options: &Options{ + Host: "testhost", + }, + log: log, + httpScheme: "http", + } + + t.Run("successful request", func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "/v2/repo/image/tags/list", r.URL.Path) + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{"tags":["v1","v2"]}`)) + })) + defer server.Close() + + h, err := url.Parse(server.URL) + assert.NoError(t, err) + + var tagResponse TagResponse + headers, err := client.doRequest(ctx, h.Host+"/v2/repo/image/tags/list", "", &tagResponse) + + assert.NoError(t, err) + assert.NotNil(t, headers) + assert.Equal(t, []string{"v1", "v2"}, tagResponse.Tags) + }) + + t.Run("error on non-200 status", func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte("not found")) + })) + defer server.Close() + + h, err := url.Parse(server.URL) + assert.NoError(t, err) + + var tagResponse TagResponse + headers, err := client.doRequest(ctx, h.Host+"/v2/repo/image/tags/list", "", &tagResponse) + + assert.Nil(t, headers) + assert.Error(t, err) + var httpErr *selfhostederrors.HTTPError + if errors.As(err, &httpErr) { + assert.Equal(t, http.StatusNotFound, httpErr.StatusCode) + assert.Equal(t, "not found", string(httpErr.Body)) + } + }) + + t.Run("error on invalid json response", func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("invalid json")) + })) + defer server.Close() + + h, err := url.Parse(server.URL) + assert.NoError(t, err) + + var tagResponse TagResponse + headers, err := client.doRequest(ctx, h.Host+"/v2/repo/image/tags/list", "", &tagResponse) + + assert.Nil(t, headers) + assert.Error(t, err) + assert.Contains(t, err.Error(), "unexpected") + }) +} + +func TestSetupBasicAuth(t *testing.T) { + log := logrus.NewEntry(logrus.New()) + ctx := context.Background() + + client := &Client{ + Client: &http.Client{}, + Options: &Options{ + Username: "testuser", + Password: "testpass", + }, + log: log, + } + + t.Run("successful auth setup", func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "/v2/token", r.URL.Path) + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{"token":"testtoken"}`)) + })) + defer server.Close() + + token, err := client.setupBasicAuth(ctx, server.URL, "/v2/token") + assert.NoError(t, err) + assert.Equal(t, "testtoken", token) + }) + + t.Run("error on invalid json response", func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("invalid json")) + })) + defer server.Close() + + token, err := client.setupBasicAuth(ctx, server.URL, "/v2/token") + assert.Empty(t, token) + assert.Error(t, err) + assert.Contains(t, err.Error(), "invalid character") + }) + + t.Run("error on non-200 status code", func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusUnauthorized) + _, _ = w.Write([]byte("unauthorized")) + })) + defer server.Close() + + token, err := client.setupBasicAuth(ctx, server.URL, "/v2/token") + assert.Empty(t, token) + assert.Error(t, err) + var httpErr *selfhostederrors.HTTPError + if errors.As(err, &httpErr) { + assert.Equal(t, http.StatusUnauthorized, httpErr.StatusCode) + assert.Equal(t, "unauthorized", string(httpErr.Body)) + } + }) + + t.Run("error on request creation failure", func(t *testing.T) { + client := &Client{ + Client: &http.Client{}, + Options: &Options{ + Username: "testuser", + Password: "testpass", + }, + log: log, + } + + token, err := client.setupBasicAuth(ctx, "localhost:999999", "/v2/token") + assert.Empty(t, token) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to send basic auth request") + }) +} + +func TestNewTLSConfig(t *testing.T) { + t.Run("successful TLS config creation with valid CA path", func(t *testing.T) { + caFile, err := os.CreateTemp("", "ca.pem") + assert.NoError(t, err) + defer os.Remove(caFile.Name()) + + _, err = caFile.WriteString(`-----BEGIN CERTIFICATE----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwf3Kq/BnEePvM6rSGPP6 +6uUbzIAdx0+EjHRJ1yqCqk8MzY+m5OncEjgpG0FDDpdqYPOUE4EzjjIlNInxG8Vi +DfWmi8csEQYrtyNzzlF+bWwWv/1U+UuRgZqtwFZxC4DLIE1Bke4isr7g91DU5B8G +b+6eGHjql0zPz9bL7s5er8kpDp1o6ZZtGPE3F18LPS48pZyRIN/T4vPz4uA/Zay/ +aEB8E+yoI8dw48LUVZDjDN3mthBb8k68ngLqBaIgF+1EQpe2I1a/nZBQTu9yn8Z1 +Y7nG8XdxKAr5e+CZ8x8NUvydF1DZDSV1Mf1GriMEwLkA5P4oY8EbOxDJTuJrAXjZ +tQIDAQAB +-----END CERTIFICATE-----`) + assert.NoError(t, err) + err = caFile.Close() + assert.NoError(t, err) + + tlsConfig, err := newTLSConfig(false, caFile.Name()) + assert.NoError(t, err) + assert.NotNil(t, tlsConfig) + assert.False(t, tlsConfig.InsecureSkipVerify) + }) + + t.Run("successful TLS config creation with empty CA path", func(t *testing.T) { + tlsConfig, err := newTLSConfig(true, "") + assert.NoError(t, err) + assert.NotNil(t, tlsConfig) + assert.True(t, tlsConfig.InsecureSkipVerify) + }) + + t.Run("error on invalid CA path", func(t *testing.T) { + tlsConfig, err := newTLSConfig(false, "/invalid/path") + assert.Nil(t, tlsConfig) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to append") + }) +}