Skip to content

Commit 62b2441

Browse files
Extract a reusable test OCI container registry
Also, the docker config json generator can generate configs for multiple servers
1 parent 4b489b8 commit 62b2441

File tree

12 files changed

+366
-255
lines changed

12 files changed

+366
-255
lines changed

api/repositories/dockercfg/json.go

-35
This file was deleted.

api/repositories/dockercfg/json_test.go

-58
This file was deleted.

api/repositories/package_repository.go

+13-15
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ import (
88
"code.cloudfoundry.org/korifi/api/authorization"
99
apierrors "code.cloudfoundry.org/korifi/api/errors"
1010
"code.cloudfoundry.org/korifi/api/repositories/conditions"
11-
"code.cloudfoundry.org/korifi/api/repositories/dockercfg"
1211
korifiv1alpha1 "code.cloudfoundry.org/korifi/controllers/api/v1alpha1"
1312
"code.cloudfoundry.org/korifi/controllers/controllers/shared"
1413
"code.cloudfoundry.org/korifi/controllers/controllers/workloads"
14+
"code.cloudfoundry.org/korifi/tools/dockercfg"
1515
"code.cloudfoundry.org/korifi/tools/k8s"
1616
"github.com/google/go-containerregistry/pkg/name"
1717
"github.com/google/uuid"
@@ -180,28 +180,26 @@ func createImagePullSecret(ctx context.Context, userClient client.Client, cfPack
180180
if err != nil {
181181
return fmt.Errorf("failed to parse image ref: %w", err)
182182
}
183-
dockerCfg, err := dockercfg.GenerateDockerCfgSecretData(*message.Data.Username, *message.Data.Password, ref.Context().RegistryStr())
184-
if err != nil {
185-
return fmt.Errorf("failed to generate dockercfgjson: %w", err)
186-
}
187183

188-
imgPullSecret := corev1.Secret{
189-
ObjectMeta: metav1.ObjectMeta{
190-
Namespace: cfPackage.Namespace,
191-
Name: cfPackage.Name,
184+
imgPullSecret, err := dockercfg.CreateDockerConfigSecret(
185+
cfPackage.Namespace,
186+
cfPackage.Name,
187+
dockercfg.DockerServerConfig{
188+
Server: ref.Context().RegistryStr(),
189+
Username: *message.Data.Username,
190+
Password: *message.Data.Password,
192191
},
193-
Data: map[string][]byte{
194-
corev1.DockerConfigJsonKey: dockerCfg,
195-
},
196-
Type: corev1.SecretTypeDockerConfigJson,
192+
)
193+
if err != nil {
194+
return fmt.Errorf("failed to generate image pull secret: %w", err)
197195
}
198196

199-
err = controllerutil.SetOwnerReference(cfPackage, &imgPullSecret, scheme.Scheme)
197+
err = controllerutil.SetOwnerReference(cfPackage, imgPullSecret, scheme.Scheme)
200198
if err != nil {
201199
return fmt.Errorf("failed to set ownership from the package to the image pull secret: %w", err)
202200
}
203201

204-
err = userClient.Create(ctx, &imgPullSecret)
202+
err = userClient.Create(ctx, imgPullSecret)
205203
if err != nil {
206204
return fmt.Errorf("failed create the image pull secret: %w", err)
207205
}

go.mod

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ require (
1414
github.com/buildpacks/pack v0.30.0
1515
github.com/cloudfoundry/cf-test-helpers v1.0.1-0.20220603211108-d498b915ef74
1616
github.com/distribution/distribution/v3 v3.0.0-20230223072852-e5d5810851d1
17+
github.com/foomo/htpasswd v0.0.0-20200116085101-e3a90e78da9c
1718
github.com/go-chi/chi v4.1.2+incompatible
1819
github.com/go-logr/logr v1.2.4
1920
github.com/go-resty/resty/v2 v2.7.0
@@ -44,6 +45,7 @@ require (
4445
)
4546

4647
require (
48+
github.com/GehirnInc/crypt v0.0.0-20190301055215-6c0105aabd46 // indirect
4749
github.com/google/gnostic-models v0.6.8 // indirect
4850
github.com/vmware-labs/reconciler-runtime v0.12.0 // indirect
4951
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect

go.sum

+5
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBp
3939
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
4040
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
4141
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
42+
github.com/GehirnInc/crypt v0.0.0-20190301055215-6c0105aabd46 h1:rs0kDBt2zF4/CM9rO5/iH+U22jnTygPlqWgX55Ufcxg=
43+
github.com/GehirnInc/crypt v0.0.0-20190301055215-6c0105aabd46/go.mod h1:kC29dT1vFpj7py2OvG1khBdQpo3kInWP+6QipLbdngo=
4244
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
4345
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
4446
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
@@ -175,6 +177,8 @@ github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBD
175177
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
176178
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
177179
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
180+
github.com/foomo/htpasswd v0.0.0-20200116085101-e3a90e78da9c h1:DBGU7zCwrrPPDsD6+gqKG8UfMxenWg9BOJE/Nmfph+4=
181+
github.com/foomo/htpasswd v0.0.0-20200116085101-e3a90e78da9c/go.mod h1:SHawtolbB0ZOFoRWgDwakX5WpwuIWAK88bUXVZqK0Ss=
178182
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
179183
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
180184
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
@@ -470,6 +474,7 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf
470474
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
471475
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
472476
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
477+
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
473478
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
474479
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
475480
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=

tests/helpers/oci/registry.go

+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package oci
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/http/httptest"
7+
"net/url"
8+
"os"
9+
10+
"github.com/distribution/distribution/v3/configuration"
11+
dcontext "github.com/distribution/distribution/v3/context"
12+
_ "github.com/distribution/distribution/v3/registry/auth/htpasswd"
13+
"github.com/distribution/distribution/v3/registry/handlers"
14+
_ "github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
15+
"github.com/foomo/htpasswd"
16+
"github.com/google/go-containerregistry/pkg/authn"
17+
"github.com/google/go-containerregistry/pkg/name"
18+
v1 "github.com/google/go-containerregistry/pkg/v1"
19+
"github.com/google/go-containerregistry/pkg/v1/empty"
20+
"github.com/google/go-containerregistry/pkg/v1/mutate"
21+
"github.com/google/go-containerregistry/pkg/v1/remote"
22+
. "github.com/onsi/ginkgo/v2" //lint:ignore ST1001 this is a test file
23+
. "github.com/onsi/gomega" //lint:ignore ST1001 this is a test file
24+
"github.com/sirupsen/logrus"
25+
)
26+
27+
func init() {
28+
logger := logrus.New()
29+
logger.SetLevel(logrus.DebugLevel)
30+
logger.SetOutput(GinkgoWriter)
31+
dcontext.SetDefaultLogger(logrus.NewEntry(logger))
32+
}
33+
34+
type Registry struct {
35+
server *httptest.Server
36+
username string
37+
password string
38+
}
39+
40+
func (r *Registry) URL() string {
41+
return r.server.URL
42+
}
43+
44+
func (r *Registry) ImageRef(relativeImageRef string) string {
45+
serverURL, err := url.Parse(r.URL())
46+
Expect(err).NotTo(HaveOccurred())
47+
return fmt.Sprintf("%s/%s", serverURL.Host, relativeImageRef)
48+
}
49+
50+
func (r *Registry) PushImage(repoRef string, imageConfig *v1.ConfigFile) {
51+
image, err := mutate.ConfigFile(empty.Image, imageConfig)
52+
Expect(err).NotTo(HaveOccurred())
53+
54+
ref, err := name.ParseReference(repoRef)
55+
Expect(err).NotTo(HaveOccurred())
56+
57+
pushOpts := []remote.Option{}
58+
if r.username != "" && r.password != "" {
59+
pushOpts = append(pushOpts, remote.WithAuth(&authn.Basic{
60+
Username: r.username,
61+
Password: r.password,
62+
}))
63+
}
64+
Expect(remote.Write(ref, image, pushOpts...)).To(Succeed())
65+
}
66+
67+
func NewContainerRegistry(username, password string) *Registry {
68+
htpasswdFile := generateHtpasswdFile(username, password)
69+
70+
registry := &Registry{
71+
server: httptest.NewServer(handlers.NewApp(context.Background(), &configuration.Configuration{
72+
Auth: configuration.Auth{
73+
"htpasswd": configuration.Parameters{
74+
"realm": "Registry Realm",
75+
"path": htpasswdFile,
76+
},
77+
},
78+
Storage: configuration.Storage{
79+
"inmemory": configuration.Parameters{},
80+
"delete": configuration.Parameters{"enabled": true},
81+
},
82+
Loglevel: "debug",
83+
})),
84+
username: username,
85+
password: password,
86+
}
87+
88+
DeferCleanup(func() {
89+
registry.server.Close()
90+
Expect(os.RemoveAll(htpasswdFile)).To(Succeed())
91+
})
92+
93+
return registry
94+
}
95+
96+
func NewNoAuthContainerRegistry() *Registry {
97+
registry := &Registry{
98+
server: httptest.NewServer(handlers.NewApp(context.Background(), &configuration.Configuration{
99+
Storage: configuration.Storage{
100+
"inmemory": configuration.Parameters{},
101+
"delete": configuration.Parameters{"enabled": true},
102+
},
103+
Loglevel: "debug",
104+
})),
105+
}
106+
107+
DeferCleanup(func() {
108+
registry.server.Close()
109+
})
110+
111+
return registry
112+
}
113+
114+
func generateHtpasswdFile(username, password string) string {
115+
htpasswdFile, err := os.CreateTemp("", "")
116+
Expect(err).NotTo(HaveOccurred())
117+
118+
Expect(htpasswd.SetPassword(htpasswdFile.Name(), username, password, htpasswd.HashBCrypt)).To(Succeed())
119+
120+
return htpasswdFile.Name()
121+
}

tools/dockercfg/secret.go

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package dockercfg
2+
3+
import (
4+
"encoding/base64"
5+
"encoding/json"
6+
"fmt"
7+
8+
corev1 "k8s.io/api/core/v1"
9+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
10+
)
11+
12+
func CreateDockerConfigSecret(
13+
secretNamespace string,
14+
secretName string,
15+
dockerConfigs ...DockerServerConfig,
16+
) (*corev1.Secret, error) {
17+
dockerCfg, err := generateDockerCfgSecretData(dockerConfigs...)
18+
if err != nil {
19+
return nil, fmt.Errorf("failed to generate docker config secret data: %w", err)
20+
}
21+
22+
return &corev1.Secret{
23+
ObjectMeta: metav1.ObjectMeta{
24+
Namespace: secretNamespace,
25+
Name: secretName,
26+
},
27+
Data: map[string][]byte{
28+
corev1.DockerConfigJsonKey: dockerCfg,
29+
},
30+
Type: corev1.SecretTypeDockerConfigJson,
31+
}, nil
32+
}
33+
34+
type dockerConfigJSON struct {
35+
Auths map[string]dockerConfigEntry `json:"auths" datapolicy:"token"`
36+
}
37+
38+
type dockerConfigEntry struct {
39+
Auth string `json:"auth,omitempty"`
40+
}
41+
42+
type DockerServerConfig struct {
43+
Server string
44+
Username string
45+
Password string
46+
}
47+
48+
func generateDockerCfgSecretData(entries ...DockerServerConfig) ([]byte, error) {
49+
result := dockerConfigJSON{
50+
Auths: map[string]dockerConfigEntry{},
51+
}
52+
53+
for _, config := range entries {
54+
server := config.Server
55+
if server == "" || server == "index.docker.io" {
56+
server = "https://index.docker.io/v1/"
57+
}
58+
59+
result.Auths[server] = dockerConfigEntry{
60+
Auth: encodeDockerConfigFieldAuth(config.Username, config.Password),
61+
}
62+
63+
}
64+
65+
return json.Marshal(result)
66+
}
67+
68+
func encodeDockerConfigFieldAuth(username, password string) string {
69+
fieldValue := username + ":" + password
70+
return base64.StdEncoding.EncodeToString([]byte(fieldValue))
71+
}

0 commit comments

Comments
 (0)