diff --git a/Tiltfile b/Tiltfile index c67c95b9..bb648078 100644 --- a/Tiltfile +++ b/Tiltfile @@ -59,25 +59,20 @@ k8s_resource( resource_deps=['setup-s3-bucket'], ) -local_resource ('use case 1', +local_resource ('docker build', 'DOCKER_HOST=tcp://127.0.0.1:12376 docker build ./use-case-1', - resource_deps=['wedding'] , + resource_deps=['wedding'], ) -local_resource ('use case 3', - 'cd use-case-3 && DOCKER_HOST=tcp://127.0.0.1:12376 tilt ci --port 0', - resource_deps=['wedding'] , +local_resource ('docker pull tag push', + 'DOCKER_HOST=tcp://127.0.0.1:12376 docker pull alpine && \ + DOCKER_HOST=tcp://127.0.0.1:12376 docker tag alpine wedding-registry:5000/alpine-retag && \ + DOCKER_HOST=tcp://127.0.0.1:12376 docker push wedding-registry:5000/alpine-retag', + resource_deps=['wedding'], ) - -#k8s_yaml('e2e-tests/deployment/tests.yaml') -#docker_build( -# 'e2e-tests-image', -# 'e2e-tests', -# dockerfile='e2e-tests/deployment/Dockerfile', -#) -#k8s_resource( -# 'e2e-tests', -# trigger_mode=TRIGGER_MODE_MANUAL, -# resource_deps=['backend', 'frontend'], -#) +local_resource ('tilt ci', + 'cd use-case-3 && DOCKER_HOST=tcp://127.0.0.1:12376 tilt ci --port 0 && \ + tilt down', + resource_deps=['wedding'], +) diff --git a/pkg/build.go b/pkg/build.go index 45fe8e95..94ad4928 100644 --- a/pkg/build.go +++ b/pkg/build.go @@ -2,7 +2,6 @@ package wedding import ( "context" - "encoding/base64" "encoding/json" "fmt" "io/ioutil" @@ -33,9 +32,9 @@ type buildConfig struct { dockerfile string memoryBytes int target string - tag string + tags []string noCache bool - registryAuth string + registryAuth dockerConfig contextFilePath string } @@ -45,37 +44,35 @@ type ObjectStore struct { Bucket string } -func (s Service) buildHandler() http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() +func (s Service) build(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() - cfg, err := buildParameters(r) - if err != nil { - printBuildHelpText(w, err) - return - } - - err = s.objectStore.storeContext(ctx, r, cfg) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("store context: %v", err))) - log.Printf("execute build: %v", err) - return - } - defer func() { - s.objectStore.deleteContext(ctx, cfg) - }() + cfg, err := buildParameters(r) + if err != nil { + printBuildHelpText(w, err) + return + } - err = s.executeBuild(ctx, cfg, w) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("execute build: %v", err))) - log.Printf("execute build: %v", err) - return - } + err = s.objectStore.storeContext(ctx, r, cfg) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("store context: %v", err))) + log.Printf("execute build: %v", err) + return + } + defer func() { + s.objectStore.deleteContext(ctx, cfg) + }() - w.Write([]byte(`{"aux":{"ID":"sha256:d8f38feb768dd84819b607224c07f2453412e1808b4b4e52894048073e50732d"}}`)) + err = s.executeBuild(ctx, cfg, w) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("execute build: %v", err))) + log.Printf("execute build: %v", err) + return } + + w.Write([]byte(`{"aux":{"ID":"sha256:42341736246f8e99122d49e4c0e414f0a3e5f69a024e72a2ac1a39a2093d483f"}}`)) } func buildParameters(r *http.Request) (*buildConfig, error) { @@ -189,27 +186,20 @@ func buildParameters(r *http.Request) (*buildConfig, error) { cfg.target = r.URL.Query().Get("target") // image tag - tags := r.URL.Query()["t"] - if len(tags) > 1 { - return cfg, fmt.Errorf("wedding does not support setting multiple image tags at a time") - } - // if len(tags) != 1 { - // return cfg, fmt.Errorf("image tag not set") - // } - if len(tags) == 1 { - cfg.tag = tags[0] - } + cfg.tags = r.URL.Query()["t"] // disable cache nocache := r.URL.Query().Get("nocache") cfg.noCache = nocache == "1" // registry authentitation - registryCfg, err := base64.StdEncoding.DecodeString(r.Header.Get("X-Registry-Config")) + log.Printf("X-Registry-Config: %v", r.Header.Get("X-Registry-Config")) + dockerCfg, err := xRegistryConfig(r.Header.Get("X-Registry-Config")).toDockerConfig() if err != nil { - return cfg, fmt.Errorf("decode registry authentication config: %v", err) + return cfg, fmt.Errorf("extract registry config: %v", err) } - cfg.registryAuth = string(registryCfg) + + cfg.registryAuth = dockerCfg return cfg, nil } @@ -287,11 +277,53 @@ func (o ObjectStore) deleteContext(ctx context.Context, cfg *buildConfig) error func (s Service) executeBuild(ctx context.Context, cfg *buildConfig, w http.ResponseWriter) error { + stream(w, fmt.Sprintf("%v", cfg)) + presignedContextURL, err := s.objectStore.presignContext(cfg) if err != nil { return err } + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "wedding-docker-config-", + }, + StringData: map[string]string{ + "config.json": cfg.registryAuth.mustToJSON(), + }, + } + + secretClient := s.kubernetesClient.CoreV1().Secrets(s.namespace) + + secret, err = secretClient.Create(ctx, secret, metav1.CreateOptions{}) + if err != nil { + streamf(w, "Secret creation failed: %v\n", err) + return fmt.Errorf("create secret: %v", err) + } + defer func() { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + err = secretClient.Delete(ctx, secret.Name, metav1.DeleteOptions{}) + if err != nil { + streamf(w, "Secret deletetion failed: %v\n", err) + log.Printf("delete secret: %v", err) + } + }() + + imageNames := "" + for idx, tag := range cfg.tags { + if idx != 0 { + imageNames += "," + } + imageNames += fmt.Sprintf("wedding-registry:5000/images/%s", tag) + } + + output := "--output type=image,push=true,name=wedding-registry:5000/digests" + if imageNames != "" { + output = fmt.Sprintf("--output type=image,push=true,name=\"%s\"", imageNames) + } + // TODO add timeout for script buildScript := fmt.Sprintf(` set -euo pipefail @@ -300,7 +332,7 @@ mkdir ~/context && cd ~/context mkdir -p ~/.config/buildkit/ echo " -[registry.\"cache-registry:5000\"] +[registry.\"wedding-registry:5000\"] http = true insecure = true " > ~/.config/buildkit/buildkitd.toml @@ -316,10 +348,12 @@ buildctl-daemonless.sh \ --local context=. \ --local dockerfile=. \ --opt filename=Dockerfile \ - --output type=image,push=true,name=cache-registry:5000/cache-repo:latest \ - --export-cache=type=registry,ref=cache-registry:5000/cache-repo,mode=max \ - --import-cache=type=registry,ref=cache-registry:5000/cache-repo -`, presignedContextURL) + %s \ + --export-cache=type=registry,ref=wedding-registry:5000/cache-repo,mode=max \ + --import-cache=type=registry,ref=wedding-registry:5000/cache-repo +`, presignedContextURL, output) + + stream(w, buildScript) pod := &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -328,16 +362,28 @@ buildctl-daemonless.sh \ Spec: corev1.PodSpec{ Containers: []corev1.Container{ { - Name: "buildkit", - Image: "moby/buildkit:master-rootless", - ImagePullPolicy: corev1.PullAlways, - // Image: "moby/buildkit:v0.8-beta", - // Image: "moby/buildkit:v0.7.2-rootless", + Name: "buildkit", + Image: "moby/buildkit:v0.7.2-rootless", Command: []string{ "sh", "-c", buildScript, - // "date; sleep 1; date; sleep 1; date; sleep 1; date;", + }, + VolumeMounts: []corev1.VolumeMount{ + { + MountPath: "/home/user/.docker", + Name: "docker-config", + }, + }, + }, + }, + Volumes: []corev1.Volume{ + { + Name: "docker-config", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: secret.Name, + }, }, }, }, diff --git a/pkg/kubernetes.go b/pkg/kubernetes.go index 12c8389d..91dcf57c 100644 --- a/pkg/kubernetes.go +++ b/pkg/kubernetes.go @@ -13,7 +13,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -func (s Service) executePod(ctx context.Context, pod *corev1.Pod, w http.ResponseWriter) error { +func (s Service) executePod(ctx context.Context, pod *corev1.Pod, w io.Writer) error { podClient := s.kubernetesClient.CoreV1().Pods(s.namespace) stream(w, "Creating new pod.\n") @@ -88,6 +88,9 @@ printLogs: if err != nil { if err == io.EOF { stream(w, "End of logs reached.\n") + if failed { + return fmt.Errorf("pod failed") + } return nil } @@ -107,7 +110,7 @@ func (s Service) podStatus(ctx context.Context, podName string) (corev1.PodPhase return pod.Status.Phase, nil } -func stream(w http.ResponseWriter, message string) error { +func stream(w io.Writer, message string) error { b, err := json.Marshal(message) if err != nil { panic(err) // encode a string to json should not fail @@ -127,6 +130,6 @@ func stream(w http.ResponseWriter, message string) error { return nil } -func streamf(w http.ResponseWriter, message string, args ...interface{}) error { +func streamf(w io.Writer, message string, args ...interface{}) error { return stream(w, fmt.Sprintf(message, args...)) } diff --git a/pkg/push.go b/pkg/push.go index 7699283d..2f9c0fd2 100644 --- a/pkg/push.go +++ b/pkg/push.go @@ -1,16 +1,111 @@ package wedding import ( + "context" + "fmt" "log" "net/http" + "time" "github.com/gorilla/mux" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -func pushImage(w http.ResponseWriter, r *http.Request) { +func (s Service) pushImage(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) args := r.URL.Query() - log.Printf("Not implemented yet: push %s:%s", vars["name"], args.Get("tag")) + + from := fmt.Sprintf("wedding-registry:5000/image/%s:%s", vars["name"], args.Get("tag")) + to := fmt.Sprintf("%s:%s", vars["name"], args.Get("tag")) + + dockerCfg, err := xRegistryAuth(r.Header.Get("X-Registry-Auth")).toDockerConfig() + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("extract registry config: %v", err))) + log.Printf("extract registry config: %v", err) + return + } + + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "wedding-docker-config-", + }, + StringData: map[string]string{ + "config.json": dockerCfg.mustToJSON(), + }, + } + + secretClient := s.kubernetesClient.CoreV1().Secrets(s.namespace) + + secret, err = secretClient.Create(r.Context(), secret, metav1.CreateOptions{}) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + streamf(w, "Secret creation failed: %v\n", err) + log.Printf("create secret: %v", err) + return + } + defer func() { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + err = secretClient.Delete(ctx, secret.Name, metav1.DeleteOptions{}) + if err != nil { + streamf(w, "Secret deletetion failed: %v\n", err) + log.Printf("delete secret: %v", err) + } + }() + + // TODO add timeout for script + buildScript := fmt.Sprintf(` +set -euo pipefail + +skopeo copy --src-tls-verify=false --dest-tls-verify=false docker://%s docker://%s +`, from, to) + + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "wedding-push-", + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "skopeo", + Image: "mrliptontea/skopeo:1.2.0", + Command: []string{ + "sh", + "-c", + buildScript, + }, + VolumeMounts: []corev1.VolumeMount{ + { + MountPath: "/root/.docker", + Name: "docker-config", + }, + }, + }, + }, + Volumes: []corev1.Volume{ + { + Name: "docker-config", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: secret.Name, + }, + }, + }, + }, + RestartPolicy: corev1.RestartPolicyNever, + }, + } + + err = s.executePod(r.Context(), pod, w) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + stream(w, fmt.Sprintf("execute push: %v", err)) + log.Printf("execute push: %v", err) + return + } w.WriteHeader(http.StatusOK) } diff --git a/pkg/registry_authentication.go b/pkg/registry_authentication.go new file mode 100644 index 00000000..8958a4f9 --- /dev/null +++ b/pkg/registry_authentication.go @@ -0,0 +1,92 @@ +package wedding + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "log" +) + +type xRegistryConfig string + +type xRegistryAuth string + +type dockerConfig struct { + Auths map[string]dockerAuth `json:"auths"` +} +type dockerAuth struct { + Auth string `json:"auth"` +} + +func (d dockerConfig) mustToJSON() string { + bytes, err := json.Marshal(d) + if err != nil { + log.Fatalf("encode to json failed: %v", err) + } + + return string(bytes) +} + +func (x xRegistryConfig) toDockerConfig() (dockerConfig, error) { + js, err := base64.StdEncoding.DecodeString(string(x)) + if err != nil { + return dockerConfig{}, fmt.Errorf("decode registry authentications: %v", err) + } + + type RegistryCred struct { + username string + password string + serveraddress string + } + creds := map[string]RegistryCred{} + + log.Println(string(js)) + + err = json.Unmarshal(js, &creds) + if err != nil { + return dockerConfig{}, fmt.Errorf("unmarshal registry authentications: %v", err) + } + + dockerCfg := dockerConfig{ + Auths: make(map[string]dockerAuth), + } + for _, cred := range creds { + dockerCfg.Auths[cred.serveraddress] = dockerAuth{ + Auth: base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", cred.username, cred.password))), + } + } + + return dockerCfg, nil +} + +func (x xRegistryAuth) toDockerConfig() (dockerConfig, error) { + js, err := base64.StdEncoding.DecodeString(string(x)) + if err != nil { + return dockerConfig{}, fmt.Errorf("decode registry authentication: %v", err) + } + + type registryCred struct { + Username string + Password string + Serveraddress string + } + cred := registryCred{} + + err = json.Unmarshal(js, &cred) + if err != nil { + return dockerConfig{}, fmt.Errorf("unmarshal registry authentication: %v", err) + } + + dockerCfg := dockerConfig{ + Auths: make(map[string]dockerAuth), + } + if cred.Username == "" && cred.Password == "" && cred.Serveraddress == "" { + return dockerCfg, nil + } + + dockerCfg.Auths[cred.Serveraddress] = dockerAuth{ + Auth: base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", cred.Username, cred.Password))), + } + + return dockerCfg, nil +} diff --git a/pkg/registry_authentication_test.go b/pkg/registry_authentication_test.go new file mode 100644 index 00000000..c48279fd --- /dev/null +++ b/pkg/registry_authentication_test.go @@ -0,0 +1,80 @@ +package wedding + +import ( + "encoding/base64" + "reflect" + "testing" +) + +func Test_xRegistryAuth_toDockerConfig(t *testing.T) { + tests := []struct { + name string + x xRegistryAuth + want dockerConfig + wantErr bool + }{ + { + name: "default", + x: xRegistryAuth(base64.StdEncoding. + EncodeToString([]byte(`{"username":"user", "password":"pass123", "serveraddress":"reg.domain.tld"}`))), + want: dockerConfig{ + Auths: map[string]dockerAuth{ + "reg.domain.tld": dockerAuth{ + Auth: base64.StdEncoding.EncodeToString([]byte("user:pass123")), + }, + }, + }, + }, + { + name: "null", + x: xRegistryAuth(base64.StdEncoding.EncodeToString([]byte(`null`))), + want: dockerConfig{ + Auths: map[string]dockerAuth{}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.x.toDockerConfig() + if (err != nil) != tt.wantErr { + t.Errorf("xRegistryAuth.toDockerConfig() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("xRegistryAuth.toDockerConfig() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_dockerConfig_mustToJSON(t *testing.T) { + tests := []struct { + name string + auths map[string]dockerAuth + want []byte + }{ + { + name: "empty", + auths: map[string]dockerAuth{}, + want: []byte(`{"auths":{}}`), + }, + { + name: "logged in", + auths: map[string]dockerAuth{"reg": dockerAuth{ + Auth: base64.StdEncoding.EncodeToString([]byte("user:pass123")), + }}, + want: []byte(`{"auths":{"reg":{"auth":"dXNlcjpwYXNzMTIz"}}}`), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + d := dockerConfig{ + Auths: tt.auths, + } + if got := d.mustToJSON(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("dockerConfig.mustToJSON() = %v, want %v", string(got), string(tt.want)) + } + }) + } +} diff --git a/pkg/service.go b/pkg/service.go index 676a15db..e4e73d45 100644 --- a/pkg/service.go +++ b/pkg/service.go @@ -13,7 +13,7 @@ const apiVersion = "1.40" // Service runs the wedding server. type Service struct { - router *mux.Router + router http.Handler objectStore *ObjectStore namespace string kubernetesClient *kubernetes.Clientset @@ -37,34 +37,38 @@ func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) { } func (s *Service) routes() { - s.router = mux.NewRouter() - s.router.HandleFunc("/_ping", ping).Methods(http.MethodGet) - s.router.HandleFunc("/session", ignored).Methods(http.MethodPost) - s.router.HandleFunc("/v"+apiVersion+"/version", version).Methods(http.MethodGet) + router := mux.NewRouter() + router.HandleFunc("/_ping", ping).Methods(http.MethodGet) + router.HandleFunc("/session", ignored).Methods(http.MethodPost) + router.HandleFunc("/v"+apiVersion+"/version", version).Methods(http.MethodGet) - s.router.HandleFunc("/v"+apiVersion+"/build", s.buildHandler()).Methods(http.MethodPost) - s.router.HandleFunc("/v"+apiVersion+"/images/{name}/tag", tagImage).Methods(http.MethodPost) - s.router.HandleFunc("/v"+apiVersion+"/images/{name:.+}/push", pushImage).Methods(http.MethodPost) + router.HandleFunc("/v"+apiVersion+"/build", s.build).Methods(http.MethodPost) + router.HandleFunc("/v"+apiVersion+"/images/{name}/tag", s.tagImage).Methods(http.MethodPost) + router.HandleFunc("/v"+apiVersion+"/images/{name:.+}/push", s.pushImage).Methods(http.MethodPost) - s.router.HandleFunc("/v"+apiVersion+"/containers/prune", containersPrune).Methods(http.MethodPost) - s.router.HandleFunc("/v"+apiVersion+"/images/json", imagesJSON).Methods(http.MethodGet) - s.router.HandleFunc("/v"+apiVersion+"/build/prune", buildPrune).Methods(http.MethodPost) + router.HandleFunc("/v"+apiVersion+"/containers/prune", containersPrune).Methods(http.MethodPost) + router.HandleFunc("/v"+apiVersion+"/images/json", imagesJSON).Methods(http.MethodGet) + router.HandleFunc("/v"+apiVersion+"/build/prune", buildPrune).Methods(http.MethodPost) - s.router.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + router.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + stream(w, "This function is not supported by wedding.") + w.WriteHeader(http.StatusNotImplemented) log.Printf("NOT FOUND: %v", r) }) -} -func ignored(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) + s.router = loggingMiddleware(router) } -func logReqResp(fn http.HandlerFunc) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { +func loggingMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if !strings.HasPrefix(r.Header.Get("User-Agent"), "kube-probe/") { - log.Printf("req: %v\n", r) + log.Printf("%s %s\n", r.Method, r.URL) } - fn(w, r) - } + next.ServeHTTP(w, r) + }) +} + +func ignored(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) } diff --git a/pkg/tag.go b/pkg/tag.go index 84da2bde..d6b06554 100644 --- a/pkg/tag.go +++ b/pkg/tag.go @@ -1,16 +1,59 @@ package wedding import ( + "fmt" "log" "net/http" + "os" "github.com/gorilla/mux" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -func tagImage(w http.ResponseWriter, r *http.Request) { +func (s Service) tagImage(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) args := r.URL.Query() - log.Printf("Not implemented yet: tag %s as %s:%s", vars["name"], args.Get("repo"), args.Get("tag")) + + from := fmt.Sprintf("wedding-registry:5000/digests@%s", vars["name"]) + to := fmt.Sprintf("wedding-registry:5000/image/%s:%s", args.Get("repo"), args.Get("tag")) + + log.Printf("tag: %s as %s", from, to) + + // TODO add timeout for script + buildScript := fmt.Sprintf(` +set -euo pipefail + +skopeo copy --src-tls-verify=false --dest-tls-verify=false docker://%s docker://%s +`, from, to) + + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "wedding-tag-", + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "skopeo", + Image: "mrliptontea/skopeo:1.2.0", + Command: []string{ + "sh", + "-c", + buildScript, + }, + }, + }, + RestartPolicy: corev1.RestartPolicyNever, + }, + } + + err := s.executePod(r.Context(), pod, os.Stderr) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("execute tagging: %v", err))) + log.Printf("execute tagging: %v", err) + return + } w.WriteHeader(http.StatusCreated) } diff --git a/services/Tiltfile b/services/Tiltfile index f7ce3c43..ef0031f6 100644 --- a/services/Tiltfile +++ b/services/Tiltfile @@ -11,5 +11,5 @@ k8s_resource( resource_deps=['setup-s3-bucket'], ) -k8s_yaml('cache-registry.yaml') -k8s_resource('cache-registry', port_forwards=5000) +k8s_yaml('wedding-registry.yaml') +k8s_resource('wedding-registry', port_forwards=5000) diff --git a/services/cache-registry.yaml b/services/docker.io-cache.yaml similarity index 83% rename from services/cache-registry.yaml rename to services/docker.io-cache.yaml index 92caf301..46731c93 100644 --- a/services/cache-registry.yaml +++ b/services/docker.io-cache.yaml @@ -1,32 +1,32 @@ apiVersion: v1 kind: Service metadata: - name: cache-registry + name: docker.io-mirror spec: ports: - name: http port: 5000 selector: - app: cache-registry + app: docker.io-mirror --- apiVersion: apps/v1 kind: Deployment metadata: - name: cache-registry + name: docker.io-mirror spec: selector: matchLabels: - app: cache-registry + app: docker.io-mirror strategy: type: Recreate replicas: 1 template: metadata: labels: - app: cache-registry + app: docker.io-mirror spec: containers: - - name: cache-registry + - name: docker.io-mirror image: registry:2.7.1 ports: - name: http @@ -55,12 +55,12 @@ spec: volumes: - name: data persistentVolumeClaim: - claimName: cache-registry + claimName: docker.io-mirror --- apiVersion: v1 kind: PersistentVolumeClaim metadata: - name: cache-registry + name: docker.io-mirror spec: accessModes: - ReadWriteOnce diff --git a/services/wedding-registry.yaml b/services/wedding-registry.yaml new file mode 100644 index 00000000..4c8800fd --- /dev/null +++ b/services/wedding-registry.yaml @@ -0,0 +1,69 @@ +apiVersion: v1 +kind: Service +metadata: + name: wedding-registry +spec: + ports: + - name: http + port: 5000 + selector: + app: wedding-registry +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: wedding-registry +spec: + selector: + matchLabels: + app: wedding-registry + strategy: + type: Recreate + replicas: 1 + template: + metadata: + labels: + app: wedding-registry + spec: + containers: + - name: wedding-registry + image: registry:2.7.1 + ports: + - name: http + containerPort: 5000 + resources: + limits: + cpu: "100m" + memory: "500Mi" + requests: + cpu: "100m" + memory: "500Mi" + readinessProbe: + httpGet: + path: /v2/ + port: 5000 + lifecycle: + preStop: + exec: + command: + - sh + - -c + - "sleep 10" + volumeMounts: + - name: data + mountPath: /var/lib/registry + volumes: + - name: data + persistentVolumeClaim: + claimName: wedding-registry +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: wedding-registry +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Gi