Kubernetes Provider Management System
This document is retained as historical reference material. It describes an earlier approach to Kubernetes provider management that has since been replaced by the unified infrastructure provider model.
Deprecation Notice
The kubernetes_providers and kubernetes_clusters tables described here are no longer the active model in the published codebase.
Current implementation: see the unified infrastructure_providers model in the active migrations and backend domain code.
Historical Overview
This document outlines the Kubernetes provider management system that enables Image Factory to connect to and manage builds across different Kubernetes distributions and cloud providers.
Supported Providers
- OpenShift - Red Hat's enterprise Kubernetes platform
- Rancher - Multi-cluster Kubernetes management platform
- AWS EKS - Amazon Elastic Kubernetes Service
- GCP GKE - Google Kubernetes Engine
- Azure AKS - Azure Kubernetes Service
- OCI OKE - Oracle Cloud Infrastructure Container Engine for Kubernetes
- VMware vKS - VMware Kubernetes Service (managed Kubernetes)
- Standard Kubernetes - Vanilla Kubernetes (on-premises, self-managed)
Provider Management Architecture
Core Components
1. Provider Registry
// internal/infrastructure/k8s/providers/registry.go
type ProviderRegistry struct {
providers map[string]Provider
}
type Provider interface {
Name() string
DisplayName() string
AuthMethods() []AuthMethod
Capabilities() []Capability
ValidateConfig(config ProviderConfig) error
CreateConnector(config ProviderConfig) (ClusterConnector, error)
DiscoverClusters(ctx context.Context, config ProviderConfig) ([]ClusterInfo, error)
}
func (r *ProviderRegistry) Register(provider Provider) {
r.providers[provider.Name()] = provider
}
func (r *ProviderRegistry) GetProvider(name string) (Provider, error) {
provider, exists := r.providers[name]
if !exists {
return nil, fmt.Errorf("provider %s not found", name)
}
return provider, nil
}
2. Provider-Specific Implementations
// Provider interface implementation for each provider
type ProviderConfig struct {
Name string `json:"name"`
Provider string `json:"provider"`
Region string `json:"region,omitempty"`
Auth AuthConfig `json:"auth"`
Capabilities map[string]interface{} `json:"capabilities,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
}
type AuthConfig struct {
Method string `json:"method"`
Credentials map[string]string `json:"credentials"`
}
Provider-Specific Implementations
1. AWS EKS Provider
type AWSProvider struct {
region string
}
func (p *AWSProvider) Name() string { return "aws-eks" }
func (p *AWSProvider) DisplayName() string { return "Amazon EKS" }
func (p *AWSProvider) AuthMethods() []AuthMethod {
return []AuthMethod{
{
Name: "iam",
DisplayName: "IAM Role",
Description: "Use AWS IAM roles for authentication",
Fields: []AuthField{
{
Name: "role_arn",
Type: "text",
Label: "IAM Role ARN",
Placeholder: "arn:aws:iam::123456789012:role/image-factory-role",
Required: true,
},
{
Name: "external_id",
Type: "text",
Label: "External ID (optional)",
Placeholder: "image-factory-external-id",
Required: false,
},
},
},
{
Name: "service-account",
DisplayName: "Service Account Token",
Description: "Use Kubernetes service account token",
Fields: []AuthField{
{
Name: "token",
Type: "password",
Label: "Service Account Token",
Required: true,
},
},
},
}
}
func (p *AWSProvider) Capabilities() []Capability {
return []Capability{
"gpu", "arm64", "fargate", "private-networking",
"auto-scaling", "load-balancer", "iam-integration",
}
}
func (p *AWSProvider) CreateConnector(config ProviderConfig) (ClusterConnector, error) {
switch config.Auth.Method {
case "iam":
return &AWSIAMConnector{
region: config.Region,
roleARN: config.Auth.Credentials["role_arn"],
externalID: config.Auth.Credentials["external_id"],
}, nil
case "service-account":
return &ServiceAccountConnector{
endpoint: config.Auth.Credentials["endpoint"],
token: config.Auth.Credentials["token"],
caData: []byte(config.Auth.Credentials["ca_data"]),
}, nil
default:
return nil, fmt.Errorf("unsupported auth method: %s", config.Auth.Method)
}
}
func (p *AWSProvider) DiscoverClusters(ctx context.Context, config ProviderConfig) ([]ClusterInfo, error) {
// Use AWS SDK to discover EKS clusters
sess := session.Must(session.NewSession(&aws.Config{
Region: aws.String(config.Region),
}))
eksClient := eks.New(sess)
result, err := eksClient.ListClusters(&eks.ListClustersInput{})
if err != nil {
return nil, fmt.Errorf("failed to list EKS clusters: %w", err)
}
var clusters []ClusterInfo
for _, clusterName := range result.Clusters {
cluster := &ClusterInfo{
Name: *clusterName,
Provider: "aws-eks",
Region: config.Region,
AuthMethod: config.Auth.Method,
Status: ClusterUnknown,
}
clusters = append(clusters, *cluster)
}
return clusters, nil
}
2. OpenShift Provider
type OpenShiftProvider struct{}
func (p *OpenShiftProvider) Name() string { return "openshift" }
func (p *OpenShiftProvider) DisplayName() string { return "Red Hat OpenShift" }
func (p *OpenShiftProvider) AuthMethods() []AuthMethod {
return []AuthMethod{
{
Name: "service-account",
DisplayName: "Service Account Token",
Description: "Use OpenShift service account token",
Fields: []AuthField{
{
Name: "token",
Type: "password",
Label: "Service Account Token",
Required: true,
},
},
},
{
Name: "oauth",
DisplayName: "OAuth Token",
Description: "Use OpenShift OAuth access token",
Fields: []AuthField{
{
Name: "token",
Type: "password",
Label: "OAuth Access Token",
Required: true,
},
},
},
}
}
func (p *OpenShiftProvider) Capabilities() []Capability {
return []Capability{
"security-context-constraints", "routes", "buildconfigs",
"imagestreams", "deploymentconfigs", "operators",
"multi-tenancy", "compliance", "enterprise-support",
}
}
func (p *OpenShiftProvider) CreateConnector(config ProviderConfig) (ClusterConnector, error) {
return &OpenShiftConnector{
endpoint: config.Auth.Credentials["endpoint"],
token: config.Auth.Credentials["token"],
// OpenShift-specific configuration
project: config.Capabilities["project"].(string),
}, nil
}
3. Rancher Provider
type RancherProvider struct {
endpoint string
}
func (p *RancherProvider) Name() string { return "rancher" }
func (p *RancherProvider) DisplayName() string { return "Rancher" }
func (p *RancherProvider) AuthMethods() []AuthMethod {
return []AuthMethod{
{
Name: "api-token",
DisplayName: "API Token",
Description: "Use Rancher API token for authentication",
Fields: []AuthField{
{
Name: "token",
Type: "password",
Label: "Rancher API Token",
Required: true,
},
{
Name: "cluster_id",
Type: "text",
Label: "Cluster ID",
Placeholder: "c-xxxxx",
Required: true,
},
},
},
}
}
func (p *RancherProvider) Capabilities() []Capability {
return []Capability{
"multi-cluster", "cattle", "longhorn", "monitoring",
"logging", "backup", "security", "policy",
}
}
4. Standard Kubernetes Provider
type StandardKubernetesProvider struct{}
func (p *StandardKubernetesProvider) Name() string { return "kubernetes" }
func (p *StandardKubernetesProvider) DisplayName() string { return "Standard Kubernetes" }
func (p *StandardKubernetesProvider) AuthMethods() []AuthMethod {
return []AuthMethod{
{
Name: "kubeconfig",
DisplayName: "Kubeconfig",
Description: "Use kubeconfig file for authentication",
Fields: []AuthField{
{
Name: "kubeconfig",
Type: "file",
Label: "Kubeconfig File",
Required: true,
},
{
Name: "context",
Type: "text",
Label: "Context (optional)",
Placeholder: "my-cluster",
Required: false,
},
},
},
{
Name: "service-account",
DisplayName: "Service Account",
Description: "Use service account token and certificate",
Fields: []AuthField{
{
Name: "endpoint",
Type: "text",
Label: "API Server Endpoint",
Placeholder: "https://api.example.com:6443",
Required: true,
},
{
Name: "token",
Type: "password",
Label: "Service Account Token",
Required: true,
},
{
Name: "ca_data",
Type: "textarea",
Label: "CA Certificate Data",
Required: true,
},
},
},
{
Name: "client-cert",
DisplayName: "Client Certificate",
Description: "Use client certificate for authentication",
Fields: []AuthField{
{
Name: "endpoint",
Type: "text",
Label: "API Server Endpoint",
Required: true,
},
{
Name: "client_cert",
Type: "textarea",
Label: "Client Certificate",
Required: true,
},
{
Name: "client_key",
Type: "textarea",
Label: "Client Key",
Required: true,
},
{
Name: "ca_data",
Type: "textarea",
Label: "CA Certificate Data",
Required: true,
},
},
},
}
}
func (p *StandardKubernetesProvider) Capabilities() []Capability {
return []Capability{
"vanilla-k8s", "custom-cni", "custom-scheduler",
"bare-metal", "on-premises", "self-managed",
}
}
5. VMware vKS Provider
type VMwareProvider struct {
orgID string
}
func (p *VMwareProvider) Name() string { return "vmware-vks" }
func (p *VMwareProvider) DisplayName() string { return "VMware Kubernetes Service" }
func (p *VMwareProvider) AuthMethods() []AuthMethod {
return []AuthMethod{
{
Name: "api-token",
DisplayName: "API Token",
Description: "Use VMware Cloud Services API token for authentication",
Fields: []AuthField{
{
Name: "api_token",
Type: "password",
Label: "VMware API Token",
Placeholder: "vmware-api-token-here",
Required: true,
},
{
Name: "org_id",
Type: "text",
Label: "Organization ID",
Placeholder: "vmware-org-id",
Required: true,
},
},
},
{
Name: "service-account",
DisplayName: "Service Account Token",
Description: "Use Kubernetes service account token",
Fields: []AuthField{
{
Name: "token",
Type: "password",
Label: "Service Account Token",
Required: true,
},
},
},
}
}
func (p *VMwareProvider) Capabilities() []Capability {
return []Capability{
"vsphere-integration", "tanzu", "nsx", "vrealize",
"multi-cloud", "disaster-recovery", "compliance",
"enterprise-support", "hybrid-cloud",
}
}
func (p *VMwareProvider) CreateConnector(config ProviderConfig) (ClusterConnector, error) {
switch config.Auth.Method {
case "api-token":
return &VMwareAPIConnector{
apiToken: config.Auth.Credentials["api_token"],
orgID: config.Auth.Credentials["org_id"],
}, nil
case "service-account":
return &ServiceAccountConnector{
endpoint: config.Auth.Credentials["endpoint"],
token: config.Auth.Credentials["token"],
caData: []byte(config.Auth.Credentials["ca_data"]),
}, nil
default:
return nil, fmt.Errorf("unsupported auth method: %s", config.Auth.Method)
}
}
func (p *VMwareProvider) DiscoverClusters(ctx context.Context, config ProviderConfig) ([]ClusterInfo, error) {
// Use VMware Cloud Services API to discover vKS clusters
client := &http.Client{}
req, err := http.NewRequest("GET", "https://api.vmware.cloud/vks/orgs/"+config.Auth.Credentials["org_id"]+"/clusters", nil)
if err != nil {
return nil, fmt.Errorf("failed to create discovery request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+config.Auth.Credentials["api_token"])
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to discover clusters: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("discovery API returned status: %s", resp.Status)
}
var response struct {
Clusters []struct {
ID string `json:"id"`
Name string `json:"name"`
Status string `json:"status"`
Region string `json:"region"`
} `json:"clusters"`
}
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
return nil, fmt.Errorf("failed to decode discovery response: %w", err)
}
var clusters []ClusterInfo
for _, cluster := range response.Clusters {
clusters = append(clusters, ClusterInfo{
Name: cluster.Name,
Provider: "vmware-vks",
Region: cluster.Region,
AuthMethod: config.Auth.Method,
Status: ClusterStatus(cluster.Status),
})
}
return clusters, nil
}
🎨 Frontend Provider Selection UI
Provider Selection Component
// src/components/admin/kubernetes/ProviderSelector.tsx
const ProviderSelector: React.FC<{
value: string
onChange: (provider: string) => void
onAuthMethodChange: (method: string) => void
}> = ({ value, onChange, onAuthMethodChange }) => {
const [providers, setProviders] = useState<ProviderInfo[]>([])
const [selectedProvider, setSelectedProvider] = useState<ProviderInfo | null>(null)
useEffect(() => {
// Fetch available providers from backend
kubernetesService.getProviders().then(setProviders)
}, [])
const handleProviderChange = (providerName: string) => {
const provider = providers.find(p => p.name === providerName)
setSelectedProvider(provider)
onChange(providerName)
onAuthMethodChange(provider?.authMethods[0]?.name || '')
}
return (
<div className="space-y-4">
{/* Provider Selection */}
<div>
<label className="block text-sm font-medium text-gray-700">
Kubernetes Provider
</label>
<select
value={value}
onChange={(e) => handleProviderChange(e.target.value)}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm"
>
<option value="">Select Provider</option>
{providers.map(provider => (
<option key={provider.name} value={provider.name}>
{provider.displayName}
</option>
))}
</select>
</div>
{/* Provider Description */}
{selectedProvider && (
<div className="bg-blue-50 p-4 rounded-md">
<h4 className="text-sm font-medium text-blue-800">
{selectedProvider.displayName}
</h4>
<p className="text-sm text-blue-600 mt-1">
{selectedProvider.description}
</p>
<div className="mt-2">
<span className="text-xs text-blue-500">Capabilities:</span>
<div className="flex flex-wrap gap-1 mt-1">
{selectedProvider.capabilities.map(cap => (
<span key={cap} className="inline-flex items-center px-2 py-1 rounded-full text-xs bg-blue-100 text-blue-800">
{cap}
</span>
))}
</div>
</div>
</div>
)}
</div>
)
}
Authentication Configuration Component
// src/components/admin/kubernetes/AuthConfigForm.tsx
const AuthConfigForm: React.FC<{
provider: string
authMethod: string
onAuthMethodChange: (method: string) => void
onConfigChange: (config: AuthConfig) => void
}> = ({ provider, authMethod, onAuthMethodChange, onConfigChange }) => {
const [authMethods, setAuthMethods] = useState<AuthMethod[]>([])
const [selectedMethod, setSelectedMethod] = useState<AuthMethod | null>(null)
useEffect(() => {
if (provider) {
kubernetesService.getAuthMethods(provider).then(setAuthMethods)
}
}, [provider])
useEffect(() => {
const method = authMethods.find(m => m.name === authMethod)
setSelectedMethod(method)
}, [authMethod, authMethods])
const handleMethodChange = (methodName: string) => {
const method = authMethods.find(m => m.name === methodName)
setSelectedMethod(method)
onAuthMethodChange(methodName)
}
return (
<div className="space-y-4">
{/* Auth Method Selection */}
<div>
<label className="block text-sm font-medium text-gray-700">
Authentication Method
</label>
<select
value={authMethod}
onChange={(e) => handleMethodChange(e.target.value)}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm"
>
<option value="">Select Method</option>
{authMethods.map(method => (
<option key={method.name} value={method.name}>
{method.displayName}
</option>
))}
</select>
</div>
{/* Auth Method Description */}
{selectedMethod && (
<div className="bg-gray-50 p-4 rounded-md">
<p className="text-sm text-gray-600">
{selectedMethod.description}
</p>
</div>
)}
{/* Dynamic Auth Fields */}
{selectedMethod && (
<div className="space-y-4">
{selectedMethod.fields.map(field => (
<AuthFieldComponent
key={field.name}
field={field}
onChange={(value) => {
const config = { ...selectedMethod }
config[field.name] = value
onConfigChange(config)
}}
/>
))}
</div>
)}
</div>
)
}
Backend API Endpoints
Provider Management API
// internal/adapters/http/kubernetes.go
type KubernetesHandler struct {
providerRegistry *ProviderRegistry
clusterManager *ClusterManager
}
func (h *KubernetesHandler) GetProviders(w http.ResponseWriter, r *http.Request) {
providers := h.providerRegistry.ListProviders()
response := make([]ProviderInfo, 0, len(providers))
for _, provider := range providers {
response = append(response, ProviderInfo{
Name: provider.Name(),
DisplayName: provider.DisplayName(),
AuthMethods: provider.AuthMethods(),
Capabilities: provider.Capabilities(),
})
}
json.NewEncoder(w).Encode(response)
}
func (h *KubernetesHandler) GetAuthMethods(w http.ResponseWriter, r *http.Request) {
providerName := r.URL.Query().Get("provider")
if providerName == "" {
http.Error(w, "provider parameter required", http.StatusBadRequest)
return
}
provider, err := h.providerRegistry.GetProvider(providerName)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
json.NewEncoder(w).Encode(provider.AuthMethods())
}
func (h *KubernetesHandler) AddCluster(w http.ResponseWriter, r *http.Request) {
var req AddClusterRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Validate provider configuration
provider, err := h.providerRegistry.GetProvider(req.Provider)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if err := provider.ValidateConfig(req.Config); err != nil {
http.Error(w, fmt.Sprintf("invalid config: %v", err), http.StatusBadRequest)
return
}
// Create cluster connector
connector, err := provider.CreateConnector(req.Config)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Test connection
if err := h.testClusterConnection(connector); err != nil {
http.Error(w, fmt.Sprintf("connection test failed: %v", err), http.StatusBadRequest)
return
}
// Save cluster configuration
cluster := &Cluster{
Name: req.Name,
Provider: req.Provider,
Config: req.Config,
Status: ClusterHealthy,
}
if err := h.clusterManager.AddCluster(cluster); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(cluster)
}
func (h *KubernetesHandler) DiscoverClusters(w http.ResponseWriter, r *http.Request) {
var req DiscoverClustersRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
provider, err := h.providerRegistry.GetProvider(req.Provider)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
clusters, err := provider.DiscoverClusters(r.Context(), req.Config)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(clusters)
}
Database Schema
Provider and Cluster Tables
-- Kubernetes providers
CREATE TABLE kubernetes_providers (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(50) UNIQUE NOT NULL,
display_name VARCHAR(100) NOT NULL,
capabilities JSONB DEFAULT '[]',
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
-- Kubernetes clusters
CREATE TABLE kubernetes_clusters (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(100) NOT NULL,
provider_id UUID NOT NULL REFERENCES kubernetes_providers(id),
region VARCHAR(50),
endpoint VARCHAR(255),
config JSONB NOT NULL DEFAULT '{}',
status VARCHAR(20) DEFAULT 'unknown',
capabilities JSONB DEFAULT '[]',
labels JSONB DEFAULT '{}',
last_health_check TIMESTAMP,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
-- Authentication configurations
CREATE TABLE cluster_auth_configs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
cluster_id UUID NOT NULL REFERENCES kubernetes_clusters(id),
auth_method VARCHAR(50) NOT NULL,
credentials JSONB NOT NULL DEFAULT '{}', -- Encrypted
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
-- Indexes
CREATE UNIQUE INDEX idx_clusters_name ON kubernetes_clusters(name);
CREATE INDEX idx_clusters_provider ON kubernetes_clusters(provider_id);
CREATE INDEX idx_clusters_status ON kubernetes_clusters(status);
CREATE INDEX idx_auth_cluster ON cluster_auth_configs(cluster_id);
Security Considerations
Credential Encryption
// internal/infrastructure/security/encryption.go
type CredentialEncryptor struct {
key []byte
}
func (e *CredentialEncryptor) Encrypt(plaintext string) (string, error) {
block, err := aes.NewCipher(e.key)
if err != nil {
return "", err
}
// Use GCM for authenticated encryption
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
nonce := make([]byte, gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return "", err
}
ciphertext := gcm.Seal(nonce, nonce, []byte(plaintext), nil)
return base64.StdEncoding.EncodeToString(ciphertext), nil
}
func (e *CredentialEncryptor) Decrypt(ciphertext string) (string, error) {
data, err := base64.StdEncoding.DecodeString(ciphertext)
if err != nil {
return "", err
}
block, err := aes.NewCipher(e.key)
if err != nil {
return "", err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
nonceSize := gcm.NonceSize()
if len(data) < nonceSize {
return "", fmt.Errorf("ciphertext too short")
}
nonce, ciphertext := data[:nonceSize], data[nonceSize:]
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return "", err
}
return string(plaintext), nil
}
RBAC for Cluster Access
# Cluster role for provider-specific access
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: image-factory-provider-access
rules:
# Base Kubernetes permissions
- apiGroups: [""]
resources: ["pods", "pods/log", "configmaps", "secrets"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
# Tekton permissions
- apiGroups: ["tekton.dev"]
resources: ["pipelineruns", "taskruns", "pipelines", "tasks"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
# Provider-specific permissions
- apiGroups: ["route.openshift.io"] # OpenShift routes
resources: ["routes"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["management.cattle.io"] # Rancher management
resources: ["clusters"]
verbs: ["get", "list", "watch"]
Testing Strategy
Provider Unit Tests
func TestAWSProvider(t *testing.T) {
provider := &AWSProvider{region: "us-east-1"}
// Test provider metadata
assert.Equal(t, "aws-eks", provider.Name())
assert.Equal(t, "Amazon EKS", provider.DisplayName())
// Test auth methods
authMethods := provider.AuthMethods()
assert.Contains(t, authMethods, AuthMethod{Name: "iam"})
assert.Contains(t, authMethods, AuthMethod{Name: "service-account"})
// Test capabilities
capabilities := provider.Capabilities()
assert.Contains(t, capabilities, "gpu")
assert.Contains(t, capabilities, "iam-integration")
}
func TestProviderValidation(t *testing.T) {
provider := &AWSProvider{}
tests := []struct {
name string
config ProviderConfig
wantErr bool
}{
{
name: "valid IAM config",
config: ProviderConfig{
Auth: AuthConfig{
Method: "iam",
Credentials: map[string]string{
"role_arn": "arn:aws:iam::123456789012:role/test-role",
},
},
},
wantErr: false,
},
{
name: "invalid IAM config - missing role",
config: ProviderConfig{
Auth: AuthConfig{
Method: "iam",
Credentials: map[string]string{},
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func t) {
err := provider.ValidateConfig(tt.config)
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
Integration Tests
func TestProviderConnectivity(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test")
}
// Test AWS EKS connectivity
awsProvider := &AWSProvider{region: "us-east-1"}
config := ProviderConfig{
Auth: AuthConfig{
Method: "iam",
Credentials: map[string]string{
"role_arn": os.Getenv("AWS_ROLE_ARN"),
},
},
}
connector, err := awsProvider.CreateConnector(config)
assert.NoError(t, err)
// Test connection
client, err := connector.Connect(context.Background())
assert.NoError(t, err)
assert.NotNil(t, client)
// Test basic operations
version, err := client.clientset.ServerVersion()
assert.NoError(t, err)
assert.NotEmpty(t, version.GitVersion)
}
API Specification
Provider Endpoints
paths:
/api/v1/kubernetes/providers:
get:
summary: Get available Kubernetes providers
responses:
'200':
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Provider'
/api/v1/kubernetes/providers/{provider}/auth-methods:
get:
summary: Get authentication methods for a provider
parameters:
- name: provider
in: path
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/AuthMethod'
/api/v1/kubernetes/clusters:
post:
summary: Add a new Kubernetes cluster
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/AddClusterRequest'
responses:
'201':
content:
application/json:
schema:
$ref: '#/components/schemas/Cluster'
/api/v1/kubernetes/clusters/discover:
post:
summary: Discover clusters for a provider
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/DiscoverClustersRequest'
responses:
'200':
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/ClusterInfo'
Summary
This historical provider management design enabled Image Factory to:
- Support Multiple Kubernetes Distributions: OpenShift, Rancher, AWS EKS, GCP GKE, Azure AKS, OCI OKE, VMware vKS, Standard Kubernetes
- Provider-Specific Authentication: IAM for cloud providers, service accounts for on-premises, OAuth for OpenShift
- Dynamic Configuration: UI adapts based on selected provider and auth method
- Secure Credential Management: Encrypted storage with proper access controls
- Cluster Discovery: Automatic discovery of clusters in cloud environments
- Capability Awareness: Provider-specific features and limitations
The approach provided a unified interface for managing diverse Kubernetes environments while maintaining security and usability. For the active model, refer to the current infrastructure provider documentation and code.