mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-02-22 07:03:28 +00:00
add fake-registry-server command to agnhost
* command run the limited subset of OCI API and serve static images from the registry (which are pre-loaded when building an image) * add pause version to zeitgeist rule for agnhost * bump version of agnhost to 2.57
This commit is contained in:
@@ -238,6 +238,8 @@ dependencies:
|
||||
match: registry.k8s.io\/pause:\d+\.\d+
|
||||
- path: test/utils/image/manifest.go
|
||||
match: configs\[Pause\] = Config{list\.GcRegistry, "pause", "\d+\.\d+(.\d+)?"}
|
||||
- path: test/images/agnhost/fakeregistryserver/images.txt
|
||||
match: pause\s
|
||||
|
||||
- name: "registry.k8s.io/build-image/setcap: dependents"
|
||||
version: bookworm-v1.0.4
|
||||
|
||||
@@ -13,7 +13,22 @@
|
||||
# limitations under the License.
|
||||
|
||||
ARG BASEIMAGE
|
||||
FROM $BASEIMAGE
|
||||
ARG GOLANG_VERSION
|
||||
|
||||
FROM golang:$GOLANG_VERSION AS preparer
|
||||
|
||||
RUN go install github.com/google/go-containerregistry/cmd/crane@latest && \
|
||||
apt-get update && apt-get install -y jq
|
||||
|
||||
COPY fakeregistryserver/prepare_registry.sh /prepare_registry.sh
|
||||
COPY fakeregistryserver/images.txt /images.txt
|
||||
|
||||
RUN chmod +x /prepare_registry.sh
|
||||
|
||||
# run the script during the build to create the artifact inside the image
|
||||
RUN /prepare_registry.sh
|
||||
|
||||
FROM $BASEIMAGE AS main
|
||||
|
||||
CROSS_BUILD_COPY qemu-QEMUARCH-static /usr/bin/
|
||||
|
||||
@@ -38,7 +53,7 @@ RUN tar -xzvf /coredns.tgz && rm -f /coredns.tgz
|
||||
# PORT 8080 needed by: netexec, nettest, resource-consumer, resource-consumer-controller
|
||||
# PORT 8081 needed by: netexec
|
||||
# PORT 9376 needed by: serve-hostname
|
||||
# PORT 5000 needed by: grpc-health-checking
|
||||
# PORT 5000 needed by: grpc-health-checking, fake-registry-server
|
||||
EXPOSE 80 8080 8081 9376 5000
|
||||
|
||||
# from netexec
|
||||
@@ -49,6 +64,7 @@ ADD porter/localhost.crt localhost.crt
|
||||
ADD porter/localhost.key localhost.key
|
||||
|
||||
ADD agnhost agnhost
|
||||
COPY --from=preparer /registry /var/registry
|
||||
|
||||
# needed for the entrypoint-tester related tests. Some of the entrypoint-tester related tests
|
||||
# overrides this image's entrypoint with agnhost-2 binary, and will verify that the correct
|
||||
|
||||
@@ -182,6 +182,23 @@ Usage:
|
||||
kubectl exec test-agnhost -- /agnhost fake-gitserver
|
||||
```
|
||||
|
||||
### fake-registry-server
|
||||
|
||||
Starts a fake OCI registry server that serves static image files. This can be used to test
|
||||
pulling images from a private (with `--private` flag) or public registry.
|
||||
|
||||
Private registry has static credentials `user:password`
|
||||
|
||||
Usage:
|
||||
|
||||
```console
|
||||
kubectl exec test-agnhost -- /agnhost fake-registry-server [--private]
|
||||
```
|
||||
|
||||
#### Adding new image to the registry
|
||||
|
||||
Adding a new image requires a new version of agnhost. To add new image, add a new line
|
||||
to `test/images/agnhost/fakeregistryserver/images.txt` in format `<image> <tag> <internal tag>`
|
||||
|
||||
### guestbook
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
2.56
|
||||
2.57
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"k8s.io/kubernetes/test/images/agnhost/dns"
|
||||
"k8s.io/kubernetes/test/images/agnhost/entrypoint-tester"
|
||||
"k8s.io/kubernetes/test/images/agnhost/fakegitserver"
|
||||
"k8s.io/kubernetes/test/images/agnhost/fakeregistryserver"
|
||||
grpchealthchecking "k8s.io/kubernetes/test/images/agnhost/grpc-health-checking"
|
||||
"k8s.io/kubernetes/test/images/agnhost/guestbook"
|
||||
"k8s.io/kubernetes/test/images/agnhost/inclusterclient"
|
||||
@@ -68,6 +69,7 @@ func main() {
|
||||
rootCmd.AddCommand(dns.CmdEtcHosts)
|
||||
rootCmd.AddCommand(entrypoint.CmdEntrypointTester)
|
||||
rootCmd.AddCommand(fakegitserver.CmdFakeGitServer)
|
||||
rootCmd.AddCommand(fakeregistryserver.CmdFakeRegistryServer)
|
||||
rootCmd.AddCommand(guestbook.CmdGuestbook)
|
||||
rootCmd.AddCommand(inclusterclient.CmdInClusterClient)
|
||||
rootCmd.AddCommand(liveness.CmdLiveness)
|
||||
|
||||
1
test/images/agnhost/fakeregistryserver/images.txt
Normal file
1
test/images/agnhost/fakeregistryserver/images.txt
Normal file
@@ -0,0 +1 @@
|
||||
pause 3.10.1 testing
|
||||
88
test/images/agnhost/fakeregistryserver/prepare_registry.sh
Executable file
88
test/images/agnhost/fakeregistryserver/prepare_registry.sh
Executable file
@@ -0,0 +1,88 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright 2025 The Kubernetes Authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
set -o pipefail
|
||||
|
||||
readonly REGISTRY_URL="registry.k8s.io"
|
||||
readonly REGISTRY_DIR="/registry"
|
||||
|
||||
# This script prepares a directory with container images to be used as a fake registry,
|
||||
# then creates a tarball of that directory inside the container.
|
||||
|
||||
# function to download an image manifest and its blobs to create a fake registry layout.
|
||||
prepare_image() {
|
||||
local image_name="$1"
|
||||
local tag="$2"
|
||||
local internal_tag="$3"
|
||||
local image_dir="$REGISTRY_DIR/$image_name"
|
||||
|
||||
echo "--- Preparing image: ${image_name}:${tag} as ${image_name}:${internal_tag} ---"
|
||||
|
||||
mkdir -p "$image_dir/manifests"
|
||||
mkdir -p "$image_dir/blobs"
|
||||
|
||||
echo "Downloading and filtering manifest list for $image_name:$tag..."
|
||||
local tmp_manifest_path="$image_dir/manifests/tmp_${internal_tag}"
|
||||
# download the manifest and pipe it to jq to filter out windows images
|
||||
crane manifest "$REGISTRY_URL/$image_name:$tag" | jq '.manifests |= map(select(.platform.os != "windows"))' > "$tmp_manifest_path"
|
||||
echo "Saved manifest list to $tmp_manifest_path"
|
||||
|
||||
local manifest_digest
|
||||
manifest_digest="sha256:$(sha256sum < "$tmp_manifest_path" | awk '{print $1}')"
|
||||
mv "$tmp_manifest_path" "$image_dir/manifests/$manifest_digest"
|
||||
echo "Saved manifest list to $image_dir/manifests/$manifest_digest"
|
||||
|
||||
# the file named after the tag now contains only the digest, acting as a redirect pointer
|
||||
echo "$manifest_digest" > "$image_dir/manifests/${internal_tag}"
|
||||
echo "Created tag file ${internal_tag} pointing to digest $manifest_digest"
|
||||
|
||||
echo "Parsing manifest list and downloading individual manifests and blobs..."
|
||||
|
||||
jq -r '.manifests[].digest' < "$image_dir/manifests/$manifest_digest" | while read -r individual_manifest_digest; do
|
||||
echo " Downloading manifest $individual_manifest_digest..."
|
||||
local individual_manifest_path="$image_dir/manifests/$individual_manifest_digest"
|
||||
crane manifest "$REGISTRY_URL/$image_name@$individual_manifest_digest" > "$individual_manifest_path"
|
||||
echo " Saved manifest to $individual_manifest_path"
|
||||
|
||||
local config_digest
|
||||
config_digest=$(jq -r '.config.digest' < "$individual_manifest_path")
|
||||
echo " Downloading config blob $config_digest..."
|
||||
crane blob "$REGISTRY_URL/$image_name@$config_digest" > "$image_dir/blobs/$config_digest"
|
||||
echo " Saved config blob to $image_dir/blobs/$config_digest"
|
||||
|
||||
jq -r '.layers[].digest' < "$individual_manifest_path" | while read -r layer_digest; do
|
||||
echo " Downloading layer blob $layer_digest..."
|
||||
crane blob "$REGISTRY_URL/$image_name@$layer_digest" > "$image_dir/blobs/$layer_digest"
|
||||
echo " Saved layer blob to $image_dir/blobs/$layer_digest"
|
||||
done
|
||||
done
|
||||
|
||||
echo "--- Successfully prepared ${image_name}:${internal_tag} ---"
|
||||
}
|
||||
|
||||
# create the registry directory
|
||||
mkdir -p "$REGISTRY_DIR"
|
||||
|
||||
echo "--> Processing images.txt..."
|
||||
while read -r image tag internal_tag; do
|
||||
# skip empty lines or comments
|
||||
[[ -z "$image" || "$image" == \#* ]] && continue
|
||||
prepare_image "$image" "$tag" "$internal_tag"
|
||||
done < /images.txt
|
||||
|
||||
echo "--> Done"
|
||||
161
test/images/agnhost/fakeregistryserver/registryserver.go
Normal file
161
test/images/agnhost/fakeregistryserver/registryserver.go
Normal file
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
Copyright 2025 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package fakeregistryserver
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
port int
|
||||
private bool
|
||||
registryDir = "/var/registry"
|
||||
)
|
||||
|
||||
const (
|
||||
privateRegistryUser = "user"
|
||||
privateRegistryPass = "password"
|
||||
)
|
||||
|
||||
func init() {
|
||||
CmdFakeRegistryServer.Flags().IntVar(&port, "port", 5000, "Port number.")
|
||||
CmdFakeRegistryServer.Flags().BoolVar(&private, "private", false, "Enable authentication for the registry.")
|
||||
}
|
||||
|
||||
// CmdFakeRegistryServer is the cobra command for the fake registry server
|
||||
var CmdFakeRegistryServer = &cobra.Command{
|
||||
Use: "fake-registry-server",
|
||||
Short: "Starts a fake registry server for testing",
|
||||
Long: fmt.Sprintf("Starts a fake registry server that serves static OCI image files from %s folder", registryDir),
|
||||
Run: main,
|
||||
}
|
||||
|
||||
func main(cmd *cobra.Command, args []string) {
|
||||
registryMux := NewRegistryServerMux(private)
|
||||
|
||||
addr := fmt.Sprintf(":%d", port)
|
||||
log.Printf("HTTP server starting to listen on %s", addr)
|
||||
if err := http.ListenAndServe(addr, registryMux); err != nil {
|
||||
log.Fatalf("Error while starting the HTTP server: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func NewRegistryServerMux(isPrivate bool) *http.ServeMux {
|
||||
mux := http.NewServeMux()
|
||||
|
||||
var v2Handler http.Handler = http.HandlerFunc(handleV2)
|
||||
if isPrivate {
|
||||
v2Handler = auth(v2Handler)
|
||||
}
|
||||
mux.Handle("/v2/", v2Handler)
|
||||
|
||||
return mux
|
||||
}
|
||||
|
||||
func auth(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
user, pass, ok := r.BasicAuth()
|
||||
if !ok || user != privateRegistryUser || pass != privateRegistryPass {
|
||||
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
_, _ = w.Write([]byte("Unauthorized\n"))
|
||||
return
|
||||
}
|
||||
h.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// handleBlobs serves blob requests
|
||||
func handleBlobs(w http.ResponseWriter, r *http.Request, imageName, identifier string) {
|
||||
filePath := fmt.Sprintf("%s/%s/blobs/%s", registryDir, imageName, identifier)
|
||||
w.Header().Set("Content-Type", "application/octet-stream")
|
||||
log.Printf("Serving blob: %s", filePath)
|
||||
http.ServeFile(w, r, filePath)
|
||||
}
|
||||
|
||||
// handleManifests serves manifest requests. It dynamically sets the Content-Type
|
||||
// based on the manifest's mediaType field. If the identifier is a tag, it
|
||||
// reads the digest from the tag file and issues a redirect.
|
||||
func handleManifests(w http.ResponseWriter, r *http.Request, imageName, identifier string) {
|
||||
filePath := fmt.Sprintf("%s/%s/manifests/%s", registryDir, imageName, identifier)
|
||||
|
||||
// if the identifier is not a digest, assume it's a tag and perform a redirect.
|
||||
if !strings.HasPrefix(identifier, "sha256:") {
|
||||
digest, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
redirectURL := strings.Replace(r.URL.String(), identifier, strings.TrimSpace(string(digest)), 1)
|
||||
w.Header().Set("Location", redirectURL)
|
||||
w.WriteHeader(http.StatusTemporaryRedirect)
|
||||
return
|
||||
}
|
||||
|
||||
manifestContent, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
var manifestData struct {
|
||||
MediaType string `json:"mediaType"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(manifestContent, &manifestData); err == nil && manifestData.MediaType != "" {
|
||||
w.Header().Set("Content-Type", manifestData.MediaType)
|
||||
}
|
||||
|
||||
log.Printf("Serving manifest: %s", filePath)
|
||||
_, _ = w.Write(manifestContent)
|
||||
}
|
||||
|
||||
func handleV2(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Docker-Distribution-Api-Version", "registry/2.0")
|
||||
|
||||
if r.URL.Path == "/v2/" {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
path := strings.TrimPrefix(r.URL.Path, "/v2/")
|
||||
parts := strings.Split(path, "/")
|
||||
if len(parts) < 3 {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
imageName := parts[0]
|
||||
objectType := parts[1]
|
||||
identifier := parts[2]
|
||||
|
||||
switch objectType {
|
||||
case "blobs":
|
||||
handleBlobs(w, r, imageName, identifier)
|
||||
case "manifests":
|
||||
handleManifests(w, r, imageName, identifier)
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
}
|
||||
310
test/images/agnhost/fakeregistryserver/registryserver_test.go
Normal file
310
test/images/agnhost/fakeregistryserver/registryserver_test.go
Normal file
@@ -0,0 +1,310 @@
|
||||
/*
|
||||
Copyright 2025 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package fakeregistryserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
testImageName = "pause"
|
||||
testTag = "testing"
|
||||
testManifestDigest = "sha256:f11bf0cbf1d8f08b83261a5bde660d016fbad261f5a84e7c603c0eba4e217811"
|
||||
testBlobDigest = "sha256:19e4906e80f6945d2222896120e909003ebf8028c30ebc8c99c3c42a35fb6b7f"
|
||||
testManifestContent = `{
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
|
||||
"manifests": [
|
||||
{
|
||||
"digest": "sha256:e5b941ef8f71de54dc3a13398226c269ba217d06650a21bd3afcf9d890cf1f41",
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"platform": {
|
||||
"architecture": "amd64",
|
||||
"os": "linux"
|
||||
},
|
||||
"size": 528
|
||||
}
|
||||
],
|
||||
"schemaVersion": 2
|
||||
}`
|
||||
testBlobContent = `this is a fake blob`
|
||||
)
|
||||
|
||||
func closeBody(t *testing.T, resp *http.Response) {
|
||||
err := resp.Body.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("Error closing response body: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// setupTestRegistry creates a temporary directory structure for the fake registry.
|
||||
func setupTestRegistry(t *testing.T) (string, func() error) {
|
||||
t.Helper()
|
||||
tempDir, err := os.MkdirTemp("", "fake-registry-")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp dir: %v", err)
|
||||
}
|
||||
|
||||
manifestsDir := filepath.Join(tempDir, testImageName, "manifests")
|
||||
blobsDir := filepath.Join(tempDir, testImageName, "blobs")
|
||||
if err := os.MkdirAll(manifestsDir, 0755); err != nil {
|
||||
t.Fatalf("Failed to create manifests dir: %v", err)
|
||||
}
|
||||
if err := os.MkdirAll(blobsDir, 0755); err != nil {
|
||||
t.Fatalf("Failed to create blobs dir: %v", err)
|
||||
}
|
||||
|
||||
// write the manifest file
|
||||
if err := os.WriteFile(filepath.Join(manifestsDir, testManifestDigest), []byte(testManifestContent), 0644); err != nil {
|
||||
t.Fatalf("Failed to write manifest file: %v", err)
|
||||
}
|
||||
|
||||
// write the tag file
|
||||
if err := os.WriteFile(filepath.Join(manifestsDir, testTag), []byte(testManifestDigest), 0644); err != nil {
|
||||
t.Fatalf("Failed to write tag file: %v", err)
|
||||
}
|
||||
|
||||
// write the blob file
|
||||
if err := os.WriteFile(filepath.Join(blobsDir, testBlobDigest), []byte(testBlobContent), 0644); err != nil {
|
||||
t.Fatalf("Failed to write blob file: %v", err)
|
||||
}
|
||||
|
||||
cleanup := func() error {
|
||||
return os.RemoveAll(tempDir)
|
||||
}
|
||||
|
||||
return tempDir, cleanup
|
||||
}
|
||||
|
||||
func TestRegistryServer(t *testing.T) {
|
||||
tempDir, cleanup := setupTestRegistry(t)
|
||||
defer func() {
|
||||
if err := cleanup(); err != nil {
|
||||
t.Fatalf("Failed to cleanup temp dir: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
originalRegistryDir := registryDir
|
||||
registryDir = tempDir
|
||||
defer func() { registryDir = originalRegistryDir }()
|
||||
|
||||
t.Run("Public Mode", func(t *testing.T) {
|
||||
server := httptest.NewServer(NewRegistryServerMux(false))
|
||||
defer server.Close()
|
||||
client := server.Client()
|
||||
|
||||
t.Run("GET /v2/", func(t *testing.T) {
|
||||
resp, err := client.Get(server.URL + "/v2/")
|
||||
if err != nil {
|
||||
t.Fatalf("Request failed: %v", err)
|
||||
}
|
||||
defer closeBody(t, resp)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Errorf("Expected status OK; got %v", resp.Status)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("HEAD manifest tag not exists", func(t *testing.T) {
|
||||
url := fmt.Sprintf("%s/v2/%s/manifests/%s", server.URL, testImageName, "non-exists")
|
||||
resp, err := client.Head(url)
|
||||
if err != nil {
|
||||
t.Fatalf("Request failed: %v", err)
|
||||
}
|
||||
defer closeBody(t, resp)
|
||||
|
||||
if resp.StatusCode != http.StatusNotFound {
|
||||
t.Errorf("Expected status NotFound; got %v", resp.Status)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("GET manifest by tag", func(t *testing.T) {
|
||||
client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
defer func() { client.CheckRedirect = nil }()
|
||||
|
||||
url := fmt.Sprintf("%s/v2/%s/manifests/%s", server.URL, testImageName, testTag)
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
t.Fatalf("Request failed: %v", err)
|
||||
}
|
||||
defer closeBody(t, resp)
|
||||
|
||||
if resp.StatusCode != http.StatusTemporaryRedirect {
|
||||
t.Errorf("Expected status Temporary Redirect; got %v", resp.Status)
|
||||
}
|
||||
expectedLocation := fmt.Sprintf("/v2/%s/manifests/%s", testImageName, testManifestDigest)
|
||||
if loc := resp.Header.Get("Location"); loc != expectedLocation {
|
||||
t.Errorf("Expected redirect to %q; got %q", expectedLocation, loc)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("HEAD manifest by digest", func(t *testing.T) {
|
||||
url := fmt.Sprintf("%s/v2/%s/manifests/%s", server.URL, testImageName, testManifestDigest)
|
||||
resp, err := client.Head(url)
|
||||
if err != nil {
|
||||
t.Fatalf("Request failed: %v", err)
|
||||
}
|
||||
defer closeBody(t, resp)
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Errorf("Expected status OK; got %v", resp.Status)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("HEAD manifest digest not exists", func(t *testing.T) {
|
||||
url := fmt.Sprintf("%s/v2/%s/manifests/%s", server.URL, testImageName, "sha256:non-exists")
|
||||
resp, err := client.Head(url)
|
||||
if err != nil {
|
||||
t.Fatalf("Request failed: %v", err)
|
||||
}
|
||||
defer closeBody(t, resp)
|
||||
|
||||
if resp.StatusCode != http.StatusNotFound {
|
||||
t.Errorf("Expected status NotFound; got %v", resp.Status)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("GET manifest by digest", func(t *testing.T) {
|
||||
url := fmt.Sprintf("%s/v2/%s/manifests/%s", server.URL, testImageName, testManifestDigest)
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
t.Fatalf("Request failed: %v", err)
|
||||
}
|
||||
defer closeBody(t, resp)
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Errorf("Expected status OK; got %v", resp.Status)
|
||||
}
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
if string(body) != testManifestContent {
|
||||
t.Errorf("Expected body %q; got %q", testManifestContent, string(body))
|
||||
}
|
||||
if ctype := resp.Header.Get("Content-Type"); ctype != "application/vnd.docker.distribution.manifest.list.v2+json" {
|
||||
t.Errorf("Expected Content-Type header to be set from manifest; got %q", ctype)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("HEAD blob", func(t *testing.T) {
|
||||
url := fmt.Sprintf("%s/v2/%s/blobs/%s", server.URL, testImageName, testBlobDigest)
|
||||
resp, err := client.Head(url)
|
||||
if err != nil {
|
||||
t.Fatalf("Request failed: %v", err)
|
||||
}
|
||||
defer closeBody(t, resp)
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Errorf("Expected status OK; got %v", resp.Status)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("HEAD blob not exists", func(t *testing.T) {
|
||||
url := fmt.Sprintf("%s/v2/%s/blobs/%s", server.URL, testImageName, "non-exists")
|
||||
resp, err := client.Head(url)
|
||||
if err != nil {
|
||||
t.Fatalf("Request failed: %v", err)
|
||||
}
|
||||
defer closeBody(t, resp)
|
||||
|
||||
if resp.StatusCode != http.StatusNotFound {
|
||||
t.Errorf("Expected status NotFound; got %v", resp.Status)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("GET blob", func(t *testing.T) {
|
||||
url := fmt.Sprintf("%s/v2/%s/blobs/%s", server.URL, testImageName, testBlobDigest)
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
t.Fatalf("Request failed: %v", err)
|
||||
}
|
||||
defer closeBody(t, resp)
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Errorf("Expected status OK; got %v", resp.Status)
|
||||
}
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
if string(body) != testBlobContent {
|
||||
t.Errorf("Expected body %q; got %q", testBlobContent, string(body))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Private Mode", func(t *testing.T) {
|
||||
server := httptest.NewServer(NewRegistryServerMux(true))
|
||||
defer server.Close()
|
||||
client := server.Client()
|
||||
|
||||
t.Run("GET blob without auth", func(t *testing.T) {
|
||||
url := fmt.Sprintf("%s/v2/%s/blobs/%s", server.URL, testImageName, testBlobDigest)
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
t.Fatalf("Request failed: %v", err)
|
||||
}
|
||||
defer closeBody(t, resp)
|
||||
if resp.StatusCode != http.StatusUnauthorized {
|
||||
t.Errorf("Expected status Unauthorized; got %v", resp.Status)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("GET blob with correct auth", func(t *testing.T) {
|
||||
url := fmt.Sprintf("%s/v2/%s/blobs/%s", server.URL, testImageName, testBlobDigest)
|
||||
req, _ := http.NewRequest(http.MethodGet, url, nil)
|
||||
req.SetBasicAuth(privateRegistryUser, privateRegistryPass)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("Request failed: %v", err)
|
||||
}
|
||||
defer closeBody(t, resp)
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Errorf("Expected status OK; got %v", resp.Status)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("GET manifest without auth", func(t *testing.T) {
|
||||
url := fmt.Sprintf("%s/v2/%s/manifests/%s", server.URL, testImageName, testManifestDigest)
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
t.Fatalf("Request failed: %v", err)
|
||||
}
|
||||
defer closeBody(t, resp)
|
||||
if resp.StatusCode != http.StatusUnauthorized {
|
||||
t.Errorf("Expected status Unauthorized; got %v", resp.Status)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("GET manifest with correct auth", func(t *testing.T) {
|
||||
url := fmt.Sprintf("%s/v2/%s/manifests/%s", server.URL, testImageName, testManifestDigest)
|
||||
req, _ := http.NewRequest(http.MethodGet, url, nil)
|
||||
req.SetBasicAuth(privateRegistryUser, privateRegistryPass)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("Request failed: %v", err)
|
||||
}
|
||||
defer closeBody(t, resp)
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Errorf("Expected status OK; got %v", resp.Status)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -187,7 +187,7 @@ build() {
|
||||
|
||||
# `--provenance=false --sbom=false` is set to avoid creating a manifest list: https://github.com/kubernetes/kubernetes/issues/123266
|
||||
docker buildx build --progress=plain --no-cache --pull --output=type="${output_type}" --platform "${os_name}/${arch}" --provenance=false --sbom=false \
|
||||
--build-arg BASEIMAGE="${base_image}" --build-arg REGISTRY="${REGISTRY}" --build-arg OS_VERSION="${os_version}" \
|
||||
--build-arg BASEIMAGE="${base_image}" --build-arg REGISTRY="${REGISTRY}" --build-arg OS_VERSION="${os_version}" --build-arg GOLANG_VERSION="${GOLANG_VERSION}" \
|
||||
-t "${REGISTRY}/${image}:${TAG}-${suffix}" -f "${dockerfile_name}" \
|
||||
--label "image_version=${TAG}" --label "commit_id=${GIT_COMMIT_ID}" \
|
||||
--label "git_url=https://github.com/kubernetes/kubernetes/tree/${GIT_COMMIT_ID}/test/images/${img_folder}" .
|
||||
|
||||
Reference in New Issue
Block a user