Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
383 additions
and
279 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,315 @@ | ||
package testframework | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"testing" | ||
"time" | ||
|
||
"golang.org/x/crypto/bcrypt" | ||
|
||
corev1 "k8s.io/api/core/v1" | ||
"k8s.io/apimachinery/pkg/api/errors" | ||
"k8s.io/apimachinery/pkg/api/resource" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/apimachinery/pkg/util/intstr" | ||
utilrand "k8s.io/apimachinery/pkg/util/rand" | ||
"k8s.io/apimachinery/pkg/util/wait" | ||
kubeclient "k8s.io/client-go/kubernetes" | ||
"k8s.io/client-go/rest" | ||
|
||
routev1 "github.com/openshift/api/route/v1" | ||
routeclient "github.com/openshift/client-go/route/clientset/versioned" | ||
) | ||
|
||
type AuthConfig struct { | ||
Username string `json:"username,omitempty"` | ||
Password string `json:"password,omitempty"` | ||
} | ||
|
||
type DockerConfig struct { | ||
Auths map[string]AuthConfig `json:"auths"` | ||
} | ||
|
||
func MakeDockerConfigSecret(name string, config *DockerConfig) (*corev1.Secret, error) { | ||
buf, err := json.Marshal(config) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return &corev1.Secret{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: name, | ||
}, | ||
Data: map[string][]byte{ | ||
".dockerconfigjson": buf, | ||
}, | ||
Type: corev1.SecretTypeDockerConfigJson, | ||
}, nil | ||
} | ||
|
||
type CleanupFunc func() | ||
|
||
func CreateEphemeralRegistry(t *testing.T, restConfig *rest.Config, namespace string, accounts map[string]string) (string, CleanupFunc) { | ||
ctx := context.Background() | ||
|
||
kubeClient, err := kubeclient.NewForConfig(restConfig) | ||
if err != nil { | ||
t.Fatalf("failed to create Kubernetes client: %s", err) | ||
} | ||
|
||
routeClient, err := routeclient.NewForConfig(restConfig) | ||
if err != nil { | ||
t.Fatalf("failed to create OpenShift route client: %s", err) | ||
} | ||
|
||
name := "ephemeral-registry-" + utilrand.String(5) | ||
|
||
var cleaners []func() | ||
cleanup := func() { | ||
for _, c := range cleaners { | ||
c() | ||
} | ||
|
||
var lastErr error | ||
err = wait.Poll(time.Second, 30*time.Second, func() (done bool, err error) { | ||
pod, err := kubeClient.CoreV1().Pods(namespace).Get(ctx, name, metav1.GetOptions{}) | ||
if errors.IsNotFound(err) { | ||
return true, nil | ||
} | ||
if err != nil { | ||
lastErr = err | ||
t.Logf("unable to check if pod %s is deleted: %s", name, err) | ||
} | ||
lastErr = fmt.Errorf("pod %s is alive (deletionTimestamp: %s)", name, pod.DeletionTimestamp) | ||
return false, nil | ||
}) | ||
if err != nil { | ||
t.Errorf("failed to delete ephemeral registry %s: %s", name, lastErr) | ||
return | ||
} | ||
t.Logf("deleted ephemeral registry %s", name) | ||
} | ||
|
||
var volumes []corev1.Volume | ||
var mounts []corev1.VolumeMount | ||
var env []corev1.EnvVar | ||
if accounts != nil { | ||
var b bytes.Buffer | ||
for user, password := range accounts { | ||
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) | ||
if err != nil { | ||
t.Fatalf("failed to create bcrypt hash of password: %s", err) | ||
} | ||
fmt.Fprintf(&b, "%s:%s\n", user, hash) | ||
} | ||
|
||
secret := &corev1.Secret{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: name, | ||
}, | ||
Data: map[string][]byte{ | ||
"htpasswd": b.Bytes(), | ||
}, | ||
} | ||
|
||
_, err = kubeClient.CoreV1().Secrets(namespace).Create(ctx, secret, metav1.CreateOptions{}) | ||
if err != nil { | ||
t.Fatalf("failed to create secret for ephemeral registry: %s", err) | ||
} | ||
cleaners = append(cleaners, func() { | ||
err = kubeClient.CoreV1().Secrets(namespace).Delete(ctx, name, metav1.DeleteOptions{}) | ||
if err != nil { | ||
t.Errorf("failed to delete secret %s: %s", name, err) | ||
} | ||
}) | ||
|
||
volumes = append(volumes, corev1.Volume{ | ||
Name: "auth", | ||
VolumeSource: corev1.VolumeSource{ | ||
Secret: &corev1.SecretVolumeSource{ | ||
SecretName: name, | ||
}, | ||
}, | ||
}) | ||
mounts = append(mounts, corev1.VolumeMount{ | ||
Name: "auth", | ||
MountPath: "/auth", | ||
}) | ||
env = append(env, corev1.EnvVar{ | ||
Name: "REGISTRY_AUTH", | ||
Value: "htpasswd", | ||
}) | ||
env = append(env, corev1.EnvVar{ | ||
Name: "REGISTRY_AUTH_HTPASSWD_REALM", | ||
Value: "Registry", | ||
}) | ||
env = append(env, corev1.EnvVar{ | ||
Name: "REGISTRY_AUTH_HTPASSWD_PATH", | ||
Value: "/auth/htpasswd", | ||
}) | ||
} | ||
|
||
pod := &corev1.Pod{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: name, | ||
Labels: map[string]string{ | ||
"name": name, | ||
}, | ||
}, | ||
Spec: corev1.PodSpec{ | ||
Containers: []corev1.Container{ | ||
{ | ||
Name: "registry", | ||
Image: "docker.io/library/registry:2.7.1", | ||
Ports: []corev1.ContainerPort{ | ||
{ | ||
ContainerPort: 5000, | ||
}, | ||
}, | ||
Env: env, | ||
Resources: corev1.ResourceRequirements{ | ||
Requests: corev1.ResourceList{ | ||
corev1.ResourceCPU: resource.MustParse("10m"), | ||
corev1.ResourceMemory: resource.MustParse("50Mi"), | ||
}, | ||
}, | ||
VolumeMounts: mounts, | ||
LivenessProbe: &corev1.Probe{ | ||
Handler: corev1.Handler{ | ||
HTTPGet: &corev1.HTTPGetAction{ | ||
Path: "/", | ||
Port: intstr.FromInt(5000), | ||
}, | ||
}, | ||
}, | ||
ReadinessProbe: &corev1.Probe{ | ||
Handler: corev1.Handler{ | ||
HTTPGet: &corev1.HTTPGetAction{ | ||
Path: "/", | ||
Port: intstr.FromInt(5000), | ||
}, | ||
}, | ||
}, | ||
TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, | ||
}, | ||
}, | ||
Volumes: volumes, | ||
}, | ||
} | ||
|
||
service := &corev1.Service{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: name, | ||
}, | ||
Spec: corev1.ServiceSpec{ | ||
Ports: []corev1.ServicePort{ | ||
{ | ||
Port: 5000, | ||
}, | ||
}, | ||
Selector: map[string]string{ | ||
"name": name, | ||
}, | ||
}, | ||
} | ||
|
||
route := &routev1.Route{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: name, | ||
}, | ||
Spec: routev1.RouteSpec{ | ||
To: routev1.RouteTargetReference{ | ||
Kind: "Service", | ||
Name: name, | ||
}, | ||
Port: &routev1.RoutePort{ | ||
TargetPort: intstr.FromInt(5000), | ||
}, | ||
TLS: &routev1.TLSConfig{ | ||
Termination: routev1.TLSTerminationEdge, | ||
}, | ||
}, | ||
} | ||
|
||
_, err = kubeClient.CoreV1().Pods(namespace).Create(ctx, pod, metav1.CreateOptions{}) | ||
if err != nil { | ||
t.Fatalf("failed to create ephemeral registry pod: %s", err) | ||
} | ||
cleaners = append(cleaners, func() { | ||
err = kubeClient.CoreV1().Pods(namespace).Delete(ctx, name, metav1.DeleteOptions{}) | ||
if err != nil { | ||
t.Errorf("failed to delete pod %s: %s", name, err) | ||
} | ||
}) | ||
|
||
_, err = kubeClient.CoreV1().Services(namespace).Create(ctx, service, metav1.CreateOptions{}) | ||
if err != nil { | ||
t.Fatalf("failed to create service for ephemeral registry: %s", err) | ||
} | ||
cleaners = append(cleaners, func() { | ||
err = kubeClient.CoreV1().Services(namespace).Delete(ctx, name, metav1.DeleteOptions{}) | ||
if err != nil { | ||
t.Errorf("failed to delete service %s: %s", name, err) | ||
} | ||
}) | ||
|
||
_, err = routeClient.RouteV1().Routes(namespace).Create(ctx, route, metav1.CreateOptions{}) | ||
if err != nil { | ||
t.Fatalf("failed to create route for ephemeral registry: %s", err) | ||
} | ||
cleaners = append(cleaners, func() { | ||
err = routeClient.RouteV1().Routes(namespace).Delete(ctx, name, metav1.DeleteOptions{}) | ||
if err != nil { | ||
t.Errorf("failed to delete route %s: %s", name, err) | ||
} | ||
}) | ||
|
||
var lastErr error | ||
err = wait.Poll(time.Second, 30*time.Second, func() (done bool, err error) { | ||
pod, err = kubeClient.CoreV1().Pods(namespace).Get(ctx, name, metav1.GetOptions{}) | ||
if err != nil { | ||
lastErr = err | ||
return false, nil | ||
} | ||
if pod.Status.Phase != "Running" { | ||
lastErr = fmt.Errorf("pod phase is %s, want Running", pod.Status.Phase) | ||
return false, nil | ||
} | ||
for _, c := range pod.Status.ContainerStatuses { | ||
if !c.Ready { | ||
lastErr = fmt.Errorf("container %s is not ready (restartCount: %d, state: %v)", c.Name, c.RestartCount, c.State) | ||
return false, nil | ||
} | ||
} | ||
return true, nil | ||
}) | ||
if err != nil { | ||
t.Fatalf("failed to wait until pod %s is ready: %v", name, lastErr) | ||
} | ||
|
||
var host string | ||
err = wait.Poll(time.Second, 30*time.Second, func() (done bool, err error) { | ||
route, err = routeClient.RouteV1().Routes(namespace).Get(ctx, name, metav1.GetOptions{}) | ||
if err != nil { | ||
lastErr = err | ||
return false, nil | ||
} | ||
for _, ingress := range route.Status.Ingress { | ||
if len(ingress.Host) > 0 { | ||
host = ingress.Host | ||
return true, nil | ||
} | ||
} | ||
lastErr = fmt.Errorf("route %s does not have ingress hosts", name) | ||
return false, nil | ||
}) | ||
if err != nil { | ||
t.Fatalf("failed to wait until route %s is ready: %v", name, lastErr) | ||
} | ||
|
||
t.Logf("created ephemeral registry: %s (%s)", host, name) | ||
return host, cleanup | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.