mirror of https://github.com/portainer/portainer
fix(helm): support http and custom tls helm registries, give help when misconfigured - develop [r8s-472] (#1050)
Co-authored-by: testA113 <aliharriss1995@gmail.com>pull/10026/merge
parent
06f6bcc340
commit
58a1392480
|
@ -35,10 +35,10 @@ import (
|
|||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/pkg/libhelm/cache"
|
||||
"github.com/portainer/portainer/pkg/libhelm/options"
|
||||
"github.com/portainer/portainer/pkg/registryhttp"
|
||||
"github.com/rs/zerolog/log"
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
"helm.sh/helm/v3/pkg/registry"
|
||||
"oras.land/oras-go/v2/registry/remote/retry"
|
||||
)
|
||||
|
||||
// IsOCIRegistry returns true if the registry is an OCI registry (not nil), false if it's an HTTP repository (nil)
|
||||
|
@ -140,14 +140,6 @@ func authenticateChartSource(actionConfig *action.Configuration, registry *porta
|
|||
return errors.Wrap(err, "registry credential validation failed")
|
||||
}
|
||||
|
||||
// No authentication required
|
||||
if !registry.Authentication {
|
||||
log.Debug().
|
||||
Str("context", "HelmClient").
|
||||
Msg("No OCI registry authentication required")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Cache Strategy Decision: Use registry ID as cache key
|
||||
// This provides optimal rate limiting protection since each registry only gets
|
||||
// logged into once per Portainer instance, regardless of how many users access it.
|
||||
|
@ -180,14 +172,14 @@ func authenticateChartSource(actionConfig *action.Configuration, registry *porta
|
|||
Str("context", "HelmClient").
|
||||
Msg("Cache miss - creating new registry client")
|
||||
|
||||
registryClient, err := loginToOCIRegistry(registry)
|
||||
registryClient, err := createOCIRegistryClient(registry)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Str("context", "HelmClient").
|
||||
Str("registry_url", registry.URL).
|
||||
Err(err).
|
||||
Msg("Failed to login to registry")
|
||||
return errors.Wrap(err, "failed to login to registry")
|
||||
Msg("Failed to create registry client")
|
||||
return errors.Wrap(err, "failed to create registry client")
|
||||
}
|
||||
|
||||
// Cache the client if login was successful (registry ID-based key)
|
||||
|
@ -230,11 +222,13 @@ func configureOCIChartPathOptions(chartPathOptions *action.ChartPathOptions, reg
|
|||
}
|
||||
}
|
||||
|
||||
// loginToOCIRegistry performs registry login for OCI-based registries using Helm SDK
|
||||
// createOCIRegistryClient creates and optionally authenticates a registry client for OCI-based registries
|
||||
// Handles both authenticated and unauthenticated registries with proper TLS configuration
|
||||
// Tries to get a cached registry client if available, otherwise creates and caches a new one
|
||||
func loginToOCIRegistry(portainerRegistry *portainer.Registry) (*registry.Client, error) {
|
||||
if IsHTTPRepository(portainerRegistry) || !portainerRegistry.Authentication {
|
||||
return nil, nil // No authentication needed
|
||||
func createOCIRegistryClient(portainerRegistry *portainer.Registry) (*registry.Client, error) {
|
||||
// Handle nil registry (HTTP repository)
|
||||
if portainerRegistry == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Check cache first using registry ID-based key
|
||||
|
@ -243,32 +237,70 @@ func loginToOCIRegistry(portainerRegistry *portainer.Registry) (*registry.Client
|
|||
}
|
||||
|
||||
log.Debug().
|
||||
Str("context", "loginToRegistry").
|
||||
Str("context", "HelmClient").
|
||||
Int("registry_id", int(portainerRegistry.ID)).
|
||||
Str("registry_url", portainerRegistry.URL).
|
||||
Msg("Attempting to login to OCI registry")
|
||||
Bool("authentication", portainerRegistry.Authentication).
|
||||
Msg("Creating OCI registry client")
|
||||
|
||||
registryClient, err := registry.NewClient(registry.ClientOptHTTPClient(retry.DefaultClient))
|
||||
// Create an HTTP client with proper TLS configuration
|
||||
httpClient, usePlainHTTP, err := registryhttp.CreateClient(portainerRegistry)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Str("context", "HelmClient").
|
||||
Str("registry_url", portainerRegistry.URL).
|
||||
Err(err).
|
||||
Msg("Failed to create HTTP client for registry")
|
||||
return nil, errors.Wrap(err, "failed to create HTTP client for registry")
|
||||
}
|
||||
|
||||
clientOptions := []registry.ClientOption{
|
||||
registry.ClientOptHTTPClient(httpClient),
|
||||
}
|
||||
|
||||
if usePlainHTTP {
|
||||
clientOptions = append(clientOptions, registry.ClientOptPlainHTTP())
|
||||
}
|
||||
|
||||
registryClient, err := registry.NewClient(clientOptions...)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Str("context", "HelmClient").
|
||||
Str("registry_url", portainerRegistry.URL).
|
||||
Err(err).
|
||||
Msg("Failed to create registry client")
|
||||
return nil, errors.Wrap(err, "failed to create registry client")
|
||||
}
|
||||
|
||||
loginOpts := []registry.LoginOption{
|
||||
registry.LoginOptBasicAuth(portainerRegistry.Username, portainerRegistry.Password),
|
||||
// Only perform login if authentication is enabled
|
||||
if portainerRegistry.Authentication {
|
||||
loginOpts := []registry.LoginOption{
|
||||
registry.LoginOptBasicAuth(portainerRegistry.Username, portainerRegistry.Password),
|
||||
}
|
||||
|
||||
err = registryClient.Login(portainerRegistry.URL, loginOpts...)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Str("context", "HelmClient").
|
||||
Str("registry_url", portainerRegistry.URL).
|
||||
Err(err).
|
||||
Msg("Failed to login to registry")
|
||||
return nil, errors.Wrapf(err, "failed to login to registry %s", portainerRegistry.URL)
|
||||
}
|
||||
|
||||
log.Debug().
|
||||
Str("context", "createOCIRegistryClient").
|
||||
Int("registry_id", int(portainerRegistry.ID)).
|
||||
Str("registry_url", portainerRegistry.URL).
|
||||
Msg("Successfully logged in to OCI registry")
|
||||
} else {
|
||||
log.Debug().
|
||||
Str("context", "createOCIRegistryClient").
|
||||
Int("registry_id", int(portainerRegistry.ID)).
|
||||
Str("registry_url", portainerRegistry.URL).
|
||||
Msg("Created unauthenticated OCI registry client")
|
||||
}
|
||||
|
||||
err = registryClient.Login(portainerRegistry.URL, loginOpts...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to login to registry %s", portainerRegistry.URL)
|
||||
}
|
||||
|
||||
log.Debug().
|
||||
Str("context", "loginToRegistry").
|
||||
Int("registry_id", int(portainerRegistry.ID)).
|
||||
Str("registry_url", portainerRegistry.URL).
|
||||
Msg("Successfully logged in to OCI registry")
|
||||
|
||||
// Cache using registry ID-based key
|
||||
cache.SetCachedRegistryClientByID(portainerRegistry.ID, registryClient)
|
||||
|
||||
return registryClient, nil
|
||||
|
|
|
@ -6,12 +6,17 @@ import (
|
|||
|
||||
"github.com/pkg/errors"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/pkg/fips"
|
||||
helmregistrycache "github.com/portainer/portainer/pkg/libhelm/cache"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
"helm.sh/helm/v3/pkg/registry"
|
||||
)
|
||||
|
||||
func init() {
|
||||
fips.InitFIPS(false)
|
||||
}
|
||||
|
||||
func TestIsOCIRegistry(t *testing.T) {
|
||||
t.Run("should return false for nil registry (HTTP repo)", func(t *testing.T) {
|
||||
assert.False(t, IsOCIRegistry(nil))
|
||||
|
@ -188,19 +193,40 @@ func TestLoginToOCIRegistry(t *testing.T) {
|
|||
is := assert.New(t)
|
||||
|
||||
t.Run("should return nil for HTTP repository (nil registry)", func(t *testing.T) {
|
||||
client, err := loginToOCIRegistry(nil)
|
||||
client, err := createOCIRegistryClient(nil)
|
||||
is.NoError(err)
|
||||
is.Nil(client)
|
||||
})
|
||||
|
||||
t.Run("should return nil for registry with auth disabled", func(t *testing.T) {
|
||||
t.Run("should return client for registry with auth disabled (for TLS support)", func(t *testing.T) {
|
||||
registry := &portainer.Registry{
|
||||
URL: "my-registry.io",
|
||||
Authentication: false,
|
||||
}
|
||||
client, err := loginToOCIRegistry(registry)
|
||||
client, err := createOCIRegistryClient(registry)
|
||||
is.NoError(err)
|
||||
is.Nil(client)
|
||||
is.NotNil(client) // Now returns a client even without auth for potential TLS configuration
|
||||
})
|
||||
|
||||
t.Run("should handle custom TLS configuration without authentication", func(t *testing.T) {
|
||||
registry := &portainer.Registry{
|
||||
URL: "my-registry.io",
|
||||
Authentication: false,
|
||||
ManagementConfiguration: &portainer.RegistryManagementConfiguration{
|
||||
TLSConfig: portainer.TLSConfiguration{
|
||||
TLS: true,
|
||||
TLSSkipVerify: false,
|
||||
// In a real scenario, these would point to actual cert files
|
||||
TLSCACertPath: "",
|
||||
TLSCertPath: "",
|
||||
TLSKeyPath: "",
|
||||
},
|
||||
},
|
||||
}
|
||||
client, err := createOCIRegistryClient(registry)
|
||||
// Should succeed even without cert files when they're empty strings
|
||||
is.NoError(err)
|
||||
is.NotNil(client) // Should get a client configured for TLS
|
||||
})
|
||||
|
||||
t.Run("should return error for invalid credentials", func(t *testing.T) {
|
||||
|
@ -209,7 +235,7 @@ func TestLoginToOCIRegistry(t *testing.T) {
|
|||
Authentication: true,
|
||||
Username: " ",
|
||||
}
|
||||
client, err := loginToOCIRegistry(registry)
|
||||
client, err := createOCIRegistryClient(registry)
|
||||
is.Error(err)
|
||||
is.Nil(client)
|
||||
// The error might be a validation error or a login error, both are acceptable
|
||||
|
@ -227,7 +253,7 @@ func TestLoginToOCIRegistry(t *testing.T) {
|
|||
}
|
||||
// this will fail because it can't connect to the registry,
|
||||
// but it proves that the loginToOCIRegistry function is calling the login function.
|
||||
client, err := loginToOCIRegistry(registry)
|
||||
client, err := createOCIRegistryClient(registry)
|
||||
is.Error(err)
|
||||
is.Nil(client)
|
||||
is.Contains(err.Error(), "failed to login to registry")
|
||||
|
@ -249,7 +275,7 @@ func TestLoginToOCIRegistry(t *testing.T) {
|
|||
}
|
||||
// this will fail because it can't connect to the registry,
|
||||
// but it proves that the loginToOCIRegistry function is calling the login function.
|
||||
client, err := loginToOCIRegistry(registry)
|
||||
client, err := createOCIRegistryClient(registry)
|
||||
is.Error(err)
|
||||
is.Nil(client)
|
||||
is.Contains(err.Error(), "failed to login to registry")
|
||||
|
|
|
@ -4,10 +4,10 @@ import (
|
|||
"strings"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/pkg/registryhttp"
|
||||
"github.com/rs/zerolog/log"
|
||||
"oras.land/oras-go/v2/registry/remote"
|
||||
"oras.land/oras-go/v2/registry/remote/auth"
|
||||
"oras.land/oras-go/v2/registry/remote/retry"
|
||||
)
|
||||
|
||||
func CreateClient(registry portainer.Registry) (*remote.Registry, error) {
|
||||
|
@ -16,6 +16,15 @@ func CreateClient(registry portainer.Registry) (*remote.Registry, error) {
|
|||
log.Error().Err(err).Str("registryUrl", registry.URL).Msg("Failed to create registry client")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Configure HTTP client based on registry type using the shared utility
|
||||
httpClient, usePlainHTTP, err := registryhttp.CreateClient(®istry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
registryClient.PlainHTTP = usePlainHTTP
|
||||
|
||||
// By default, oras sends multiple requests to get the full list of repos/tags/referrers.
|
||||
// set a high page size limit for fewer round trips.
|
||||
// e.g. https://github.com/oras-project/oras-go/blob/v2.6.0/registry/remote/registry.go#L129-L142
|
||||
|
@ -29,7 +38,7 @@ func CreateClient(registry portainer.Registry) (*remote.Registry, error) {
|
|||
strings.TrimSpace(registry.Password) != "" {
|
||||
|
||||
registryClient.Client = &auth.Client{
|
||||
Client: retry.DefaultClient,
|
||||
Client: httpClient,
|
||||
Cache: auth.NewCache(),
|
||||
Credential: auth.StaticCredential(registry.URL, auth.Credential{
|
||||
Username: registry.Username,
|
||||
|
@ -43,8 +52,8 @@ func CreateClient(registry portainer.Registry) (*remote.Registry, error) {
|
|||
Bool("authentication", true).
|
||||
Msg("Created ORAS registry client with authentication")
|
||||
} else {
|
||||
// Use default client for anonymous access
|
||||
registryClient.Client = retry.DefaultClient
|
||||
// Use the configured HTTP client for anonymous access
|
||||
registryClient.Client = httpClient
|
||||
|
||||
log.Debug().
|
||||
Str("registryURL", registry.URL).
|
||||
|
|
|
@ -138,9 +138,16 @@ func TestCreateClient_AuthenticationScenarios(t *testing.T) {
|
|||
assert.NotNil(t, authClient, "Auth client should not be nil")
|
||||
assert.NotNil(t, authClient.Credential, "Credential function should be set")
|
||||
} else {
|
||||
// Should use retry.DefaultClient (no authentication)
|
||||
assert.Equal(t, retry.DefaultClient, client.Client,
|
||||
"Expected retry.DefaultClient for anonymous access")
|
||||
// For anonymous access without custom TLS, all registries should use retry.DefaultClient
|
||||
// (Only registries with custom TLS configuration use a different retry client)
|
||||
if tt.registry.ManagementConfiguration == nil || !tt.registry.ManagementConfiguration.TLSConfig.TLS {
|
||||
assert.Equal(t, retry.DefaultClient, client.Client,
|
||||
"Expected retry.DefaultClient for anonymous access without custom TLS")
|
||||
} else {
|
||||
// Custom TLS configuration means a custom retry client
|
||||
assert.NotEqual(t, retry.DefaultClient, client.Client,
|
||||
"Expected custom retry client for registry with custom TLS")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
package registryhttp
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/crypto"
|
||||
"github.com/rs/zerolog/log"
|
||||
"oras.land/oras-go/v2/registry/remote/retry"
|
||||
)
|
||||
|
||||
// CreateClient creates an HTTP client with appropriate TLS configuration based on registry type.
|
||||
// All registries use retry clients for better resilience.
|
||||
// Returns the HTTP client, whether to use plainHTTP, and any error.
|
||||
func CreateClient(registry *portainer.Registry) (*http.Client, bool, error) {
|
||||
switch registry.Type {
|
||||
case portainer.AzureRegistry, portainer.EcrRegistry, portainer.GithubRegistry, portainer.GitlabRegistry:
|
||||
// Cloud registries use the default retry client with built-in TLS
|
||||
return retry.DefaultClient, false, nil
|
||||
default:
|
||||
// For all other registry types, check if custom TLS is needed
|
||||
if registry.ManagementConfiguration != nil && registry.ManagementConfiguration.TLSConfig.TLS {
|
||||
// Need custom TLS configuration - create a retry client with custom transport
|
||||
baseTransport := &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
}
|
||||
|
||||
tlsConfig, err := crypto.CreateTLSConfigurationFromDisk(
|
||||
registry.ManagementConfiguration.TLSConfig,
|
||||
)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to create TLS configuration")
|
||||
return nil, false, err
|
||||
}
|
||||
baseTransport.TLSClientConfig = tlsConfig
|
||||
|
||||
// Create a retry transport wrapping our custom base transport
|
||||
retryTransport := retry.NewTransport(baseTransport)
|
||||
httpClient := &http.Client{
|
||||
Transport: retryTransport,
|
||||
}
|
||||
return httpClient, false, nil
|
||||
}
|
||||
|
||||
// Default to HTTP for non-cloud registries without TLS configuration
|
||||
return retry.DefaultClient, true, nil
|
||||
}
|
||||
}
|
|
@ -0,0 +1,203 @@
|
|||
package registryhttp
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/pkg/fips"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"oras.land/oras-go/v2/registry/remote/retry"
|
||||
)
|
||||
|
||||
func init() {
|
||||
fips.InitFIPS(false)
|
||||
}
|
||||
|
||||
func TestCreateClient(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
registry *portainer.Registry
|
||||
expectedUsePlainHTTP bool
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "Azure Registry should use default client with HTTPS",
|
||||
registry: &portainer.Registry{
|
||||
Type: portainer.AzureRegistry,
|
||||
URL: "myregistry.azurecr.io",
|
||||
},
|
||||
expectedUsePlainHTTP: false,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "ECR Registry should use default client with HTTPS",
|
||||
registry: &portainer.Registry{
|
||||
Type: portainer.EcrRegistry,
|
||||
URL: "123456789012.dkr.ecr.us-east-1.amazonaws.com",
|
||||
},
|
||||
expectedUsePlainHTTP: false,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "GitHub Registry should use default client with HTTPS",
|
||||
registry: &portainer.Registry{
|
||||
Type: portainer.GithubRegistry,
|
||||
URL: "ghcr.io",
|
||||
},
|
||||
expectedUsePlainHTTP: false,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "GitLab Registry should use default client with HTTPS",
|
||||
registry: &portainer.Registry{
|
||||
Type: portainer.GitlabRegistry,
|
||||
URL: "registry.gitlab.com",
|
||||
},
|
||||
expectedUsePlainHTTP: false,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "Custom registry without TLS should use plain HTTP",
|
||||
registry: &portainer.Registry{
|
||||
Type: portainer.CustomRegistry,
|
||||
URL: "my-custom-registry.local",
|
||||
},
|
||||
expectedUsePlainHTTP: true,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "Custom registry with TLS enabled should use HTTPS",
|
||||
registry: &portainer.Registry{
|
||||
Type: portainer.CustomRegistry,
|
||||
URL: "my-custom-registry.local",
|
||||
ManagementConfiguration: &portainer.RegistryManagementConfiguration{
|
||||
TLSConfig: portainer.TLSConfiguration{
|
||||
TLS: true,
|
||||
TLSSkipVerify: true, // Skip verify to avoid cert file requirements in test
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedUsePlainHTTP: false,
|
||||
expectError: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
client, usePlainHTTP, err := CreateClient(tt.registry)
|
||||
|
||||
if tt.expectError {
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, client)
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, client)
|
||||
assert.Equal(t, tt.expectedUsePlainHTTP, usePlainHTTP)
|
||||
|
||||
// Verify client type based on registry configuration
|
||||
switch tt.registry.Type {
|
||||
case portainer.AzureRegistry, portainer.EcrRegistry, portainer.GithubRegistry, portainer.GitlabRegistry:
|
||||
// Cloud registries should use the default retry client
|
||||
assert.Equal(t, retry.DefaultClient, client)
|
||||
default:
|
||||
// Custom registries with TLS should get a custom client, others use default
|
||||
if tt.registry.ManagementConfiguration != nil && tt.registry.ManagementConfiguration.TLSConfig.TLS {
|
||||
// Custom TLS configuration should create a new client
|
||||
assert.NotEqual(t, retry.DefaultClient, client)
|
||||
assert.IsType(t, &http.Client{}, client)
|
||||
} else {
|
||||
// No TLS configuration should use default client
|
||||
assert.Equal(t, retry.DefaultClient, client)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateClient_CloudRegistries(t *testing.T) {
|
||||
cloudRegistryTypes := []struct {
|
||||
name string
|
||||
registryType portainer.RegistryType
|
||||
}{
|
||||
{"AzureRegistry", portainer.AzureRegistry},
|
||||
{"EcrRegistry", portainer.EcrRegistry},
|
||||
{"GithubRegistry", portainer.GithubRegistry},
|
||||
{"GitlabRegistry", portainer.GitlabRegistry},
|
||||
}
|
||||
|
||||
for _, tt := range cloudRegistryTypes {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
registry := &portainer.Registry{
|
||||
Type: tt.registryType,
|
||||
URL: "example.registry.com",
|
||||
}
|
||||
|
||||
client, usePlainHTTP, err := CreateClient(registry)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, client)
|
||||
assert.False(t, usePlainHTTP, "Cloud registries should use HTTPS")
|
||||
assert.Equal(t, retry.DefaultClient, client, "Cloud registries should use default retry client")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateClient_CustomTLSConfiguration(t *testing.T) {
|
||||
t.Run("TLS enabled with skip verify", func(t *testing.T) {
|
||||
registry := &portainer.Registry{
|
||||
Type: portainer.CustomRegistry,
|
||||
URL: "my-registry.local",
|
||||
ManagementConfiguration: &portainer.RegistryManagementConfiguration{
|
||||
TLSConfig: portainer.TLSConfiguration{
|
||||
TLS: true,
|
||||
TLSSkipVerify: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
client, usePlainHTTP, err := CreateClient(registry)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, client)
|
||||
assert.False(t, usePlainHTTP, "TLS enabled registries should use HTTPS")
|
||||
assert.NotEqual(t, retry.DefaultClient, client, "Custom TLS should create new client")
|
||||
assert.IsType(t, &http.Client{}, client)
|
||||
})
|
||||
|
||||
t.Run("TLS disabled should use plain HTTP", func(t *testing.T) {
|
||||
registry := &portainer.Registry{
|
||||
Type: portainer.CustomRegistry,
|
||||
URL: "my-registry.local",
|
||||
ManagementConfiguration: &portainer.RegistryManagementConfiguration{
|
||||
TLSConfig: portainer.TLSConfiguration{
|
||||
TLS: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
client, usePlainHTTP, err := CreateClient(registry)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, client)
|
||||
assert.True(t, usePlainHTTP, "TLS disabled should use plain HTTP")
|
||||
assert.Equal(t, retry.DefaultClient, client, "No TLS should use default client")
|
||||
})
|
||||
|
||||
t.Run("No management configuration should use plain HTTP", func(t *testing.T) {
|
||||
registry := &portainer.Registry{
|
||||
Type: portainer.CustomRegistry,
|
||||
URL: "my-registry.local",
|
||||
ManagementConfiguration: nil,
|
||||
}
|
||||
|
||||
client, usePlainHTTP, err := CreateClient(registry)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, client)
|
||||
assert.True(t, usePlainHTTP, "No management config should use plain HTTP")
|
||||
assert.Equal(t, retry.DefaultClient, client, "No management config should use default client")
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue