Skip to content

Commit cb21d68

Browse files
authored
feat: pass registry auth secret as --image-pull-secret to func CLI (#126)
1 parent 6838e53 commit cb21d68

6 files changed

Lines changed: 210 additions & 1 deletion

File tree

.golangci.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,11 @@ linters:
3434
- lll
3535
path: api/*
3636
- linters:
37-
- dupl
3837
- lll
3938
path: internal/*
39+
- linters:
40+
- dupl
41+
path: test/*
4042
# ignore errcheck in defer
4143
- linters:
4244
- errcheck

internal/controller/function_controller_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,72 @@ var _ = Describe("Function Controller", func() {
530530
},
531531
}),
532532
)
533+
534+
It("should pass ImagePullSecret to deploy when registry authSecretRef is set", func() {
535+
registrySecretName := "my-registry-secret"
536+
537+
By("Creating the registry auth secret")
538+
secret := &v1.Secret{
539+
ObjectMeta: metav1.ObjectMeta{
540+
Name: registrySecretName,
541+
Namespace: resourceNamespace,
542+
},
543+
Type: v1.SecretTypeDockerConfigJson,
544+
Data: map[string][]byte{
545+
v1.DockerConfigJsonKey: []byte(`{"auths":{"registry.example.com":{"auth":"dGVzdDp0ZXN0"}}}`),
546+
},
547+
}
548+
Expect(k8sClient.Create(ctx, secret)).To(Succeed())
549+
defer func() { _ = k8sClient.Delete(ctx, secret) }()
550+
551+
By("Creating the Function with registry authSecretRef")
552+
spec := functionsdevv1alpha1.FunctionSpec{
553+
Repository: functionsdevv1alpha1.FunctionSpecRepository{
554+
URL: "https://github.com/foo/bar",
555+
Branch: "my-branch",
556+
},
557+
Registry: functionsdevv1alpha1.FunctionSpecRegistry{
558+
AuthSecretRef: &v1.LocalObjectReference{
559+
Name: registrySecretName,
560+
},
561+
},
562+
}
563+
Expect(createFunctionResource(resourceName, resourceNamespace, spec)).To(Succeed())
564+
565+
By("Setting up mocks")
566+
funcCliManagerMock := funccli.NewMockManager(GinkgoT())
567+
gitManagerMock := git.NewMockManager(GinkgoT())
568+
569+
funcCliManagerMock.EXPECT().Describe(mock.Anything, functionName, resourceNamespace).Return(functions.Instance{
570+
Middleware: functions.Middleware{Version: "v1.0.0"},
571+
}, nil)
572+
funcCliManagerMock.EXPECT().GetLatestMiddlewareVersion(mock.Anything, mock.Anything, mock.Anything).Return("v2.0.0", nil)
573+
574+
funcCliManagerMock.EXPECT().Deploy(mock.Anything, mock.Anything, resourceNamespace, mock.MatchedBy(func(opts funccli.DeployOptions) bool {
575+
return opts.ImagePullSecret == registrySecretName && opts.RegistryAuthFile != ""
576+
})).Return(nil)
577+
578+
gitManagerMock.EXPECT().CloneRepository(mock.Anything, "https://github.com/foo/bar", "", "my-branch", mock.Anything).Return(createTmpGitRepo(functions.Function{Name: "func-go"}), nil)
579+
580+
operatorNs := fmt.Sprintf("func-operator-%s", rand.String(6))
581+
Expect(createNamespace(operatorNs)).To(Succeed())
582+
Expect(createControllerConfig(operatorNs, nil)).To(Succeed())
583+
584+
By("Reconciling")
585+
controllerReconciler := &FunctionReconciler{
586+
Client: k8sClient,
587+
Scheme: k8sClient.Scheme(),
588+
Recorder: &events.FakeRecorder{},
589+
FuncCliManager: funcCliManagerMock,
590+
GitManager: gitManagerMock,
591+
OperatorNamespace: operatorNs,
592+
}
593+
594+
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
595+
NamespacedName: typeNamespacedName,
596+
})
597+
Expect(err).NotTo(HaveOccurred())
598+
})
533599
})
534600
})
535601

internal/controller/function_deploy.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ func (r *FunctionReconciler) deploy(ctx context.Context, function *v1alpha1.Func
4949
defer os.Remove(authFile)
5050

5151
deployOptions.RegistryAuthFile = authFile
52+
deployOptions.ImagePullSecret = function.Spec.Registry.AuthSecretRef.Name
5253
}
5354

5455
logger.Info("Deploying function", "deployOptions", deployOptions)

internal/controller/function_rbac.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ func (r *FunctionReconciler) ensureDeployFunctionRole(ctx context.Context, names
7070
APIGroups: []string{""},
7171
Resources: []string{"services", "pods"},
7272
Verbs: []string{"create", "delete", "get", "list", "patch", "update", "watch"},
73+
}, {
74+
APIGroups: []string{""},
75+
Resources: []string{"secrets"},
76+
Verbs: []string{"get", "list"},
7377
}, {
7478
APIGroups: []string{"http.keda.sh"},
7579
Resources: []string{"httpscaledobjects"},

internal/funccli/manager.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ type Manager interface {
4343

4444
type DeployOptions struct {
4545
RegistryAuthFile string
46+
ImagePullSecret string
4647
}
4748

4849
var _ Manager = &managerImpl{}
@@ -223,6 +224,10 @@ func (m *managerImpl) Deploy(ctx context.Context, repoPath string, namespace str
223224
deployArgs = append(deployArgs, "--registry-authfile", opts.RegistryAuthFile)
224225
}
225226

227+
if opts.ImagePullSecret != "" {
228+
deployArgs = append(deployArgs, "--image-pull-secret", opts.ImagePullSecret)
229+
}
230+
226231
out, err := m.Run(ctx, repoPath, deployArgs...)
227232
if err != nil {
228233
return fmt.Errorf("failed to deploy function: %q. %w", out, err)

test/e2e/func_deploy_test.go

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package e2e
1818

1919
import (
20+
"encoding/json"
2021
"fmt"
2122
"os"
2223
"os/exec"
@@ -30,6 +31,7 @@ import (
3031
v1 "k8s.io/api/core/v1"
3132
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3233
"k8s.io/apimachinery/pkg/types"
34+
funcfn "knative.dev/func/pkg/functions"
3335
)
3436

3537
// expectFunctionConditionTrue returns a Gomega function that checks if a Function
@@ -746,4 +748,133 @@ var _ = Describe("Operator", func() {
746748
Eventually(functionNotReadyWithAuthError(functionName, functionNamespace), 2*time.Minute).Should(Succeed())
747749
})
748750
})
751+
// This test verifies that the operator passes the registry auth secret as
752+
// --image-pull-secret to the func CLI during a redeploy, which causes the
753+
// func CLI to set imagePullSecrets on the function's pod spec.
754+
//
755+
// It uses a dummy dockerconfigjson secret and the unauthenticated kind-registry
756+
// because the kind-registry's built-in htpasswd auth is all-or-nothing (no
757+
// per-repository scoping), so enabling auth would break all other tests. The
758+
// unit tests in function_controller_test.go verify the --image-pull-secret flag
759+
// is passed; this test confirms the Knative Service's pod template actually
760+
// receives the imagePullSecrets after a real redeploy.
761+
Context("with a registry auth secret", func() {
762+
var repoURL string
763+
var repoDir string
764+
var functionName, functionNamespace string
765+
766+
BeforeEach(func() {
767+
if os.Getenv("DEFAULT_DEPLOYER") == "keda" ||
768+
os.Getenv("DEFAULT_DEPLOYER") == "raw" {
769+
Skip("Skipping registry auth test for Keda & raw deployer, " +
770+
"as test inspect KService directly")
771+
}
772+
773+
var err error
774+
775+
username, password, _, cleanup, err := repoProvider.CreateRandomUser()
776+
Expect(err).NotTo(HaveOccurred())
777+
utils.DeferCleanupOnSuccess(cleanup)
778+
779+
_, repoURL, cleanup, err = repoProvider.CreateRandomRepo(username, false)
780+
Expect(err).NotTo(HaveOccurred())
781+
utils.DeferCleanupOnSuccess(cleanup)
782+
783+
functionNamespace, err = utils.GetTestNamespace()
784+
Expect(err).NotTo(HaveOccurred())
785+
utils.DeferCleanupOnSuccess(cleanupNamespaces, functionNamespace)
786+
787+
oldFuncVersion := "v1.20.2"
788+
repoDir, err = utils.InitializeRepoWithFunction(
789+
repoURL,
790+
username,
791+
password,
792+
"go",
793+
utils.WithCliVersion(oldFuncVersion))
794+
Expect(err).NotTo(HaveOccurred())
795+
utils.DeferCleanupOnSuccess(os.RemoveAll, repoDir)
796+
797+
out, err := utils.RunFuncDeploy(repoDir,
798+
utils.WithNamespace(functionNamespace),
799+
utils.WithDeployCliVersion(oldFuncVersion))
800+
Expect(err).NotTo(HaveOccurred())
801+
_, _ = fmt.Fprint(GinkgoWriter, out)
802+
803+
utils.DeferCleanupOnSuccess(func() {
804+
_, _ = utils.RunFunc("delete", "--path", repoDir, "--namespace", functionNamespace)
805+
})
806+
807+
err = utils.CommitAndPush(repoDir, "Update func.yaml after deploy", "func.yaml")
808+
Expect(err).NotTo(HaveOccurred())
809+
})
810+
811+
AfterEach(func() {
812+
logFailedTestDetails(functionName, functionNamespace)
813+
})
814+
815+
It("should set imagePullSecrets on the Knative Service", func() {
816+
funcMetadata, err := funcfn.NewFunction(repoDir)
817+
Expect(err).NotTo(HaveOccurred())
818+
deployedFunctionName := funcMetadata.Name
819+
820+
secret := &v1.Secret{
821+
ObjectMeta: metav1.ObjectMeta{
822+
GenerateName: "registry-auth-",
823+
Namespace: functionNamespace,
824+
},
825+
Type: v1.SecretTypeDockerConfigJson,
826+
Data: map[string][]byte{
827+
v1.DockerConfigJsonKey: []byte(`{"auths":{"kind-registry:5000":{"auth":"dGVzdDp0ZXN0"}}}`),
828+
},
829+
}
830+
err = k8sClient.Create(ctx, secret)
831+
Expect(err).NotTo(HaveOccurred())
832+
utils.DeferCleanupOnSuccess(func() {
833+
_ = k8sClient.Delete(ctx, secret)
834+
})
835+
836+
function := &functionsdevv1alpha1.Function{
837+
ObjectMeta: metav1.ObjectMeta{
838+
GenerateName: "my-function-pullsecret-",
839+
Namespace: functionNamespace,
840+
},
841+
Spec: functionsdevv1alpha1.FunctionSpec{
842+
Repository: functionsdevv1alpha1.FunctionSpecRepository{
843+
URL: repoURL,
844+
},
845+
Registry: functionsdevv1alpha1.FunctionSpecRegistry{
846+
AuthSecretRef: &v1.LocalObjectReference{
847+
Name: secret.Name,
848+
},
849+
},
850+
},
851+
}
852+
853+
err = k8sClient.Create(ctx, function)
854+
Expect(err).NotTo(HaveOccurred())
855+
utils.DeferCleanupOnSuccess(func() {
856+
_, _ = utils.RunCmd("kubectl", "delete", "function", function.Name, "--namespace", function.Namespace)
857+
})
858+
859+
functionName = function.Name
860+
861+
Eventually(functionBecomesReady(functionName, functionNamespace)).Should(Succeed())
862+
863+
// Verify the Knative Service has imagePullSecrets set on its pod template.
864+
Eventually(func(g Gomega) {
865+
cmd := exec.Command("kubectl", "get", "ksvc", deployedFunctionName,
866+
"-n", functionNamespace,
867+
"-o", "jsonpath={.spec.template.spec.imagePullSecrets}")
868+
out, err := utils.Run(cmd)
869+
g.Expect(err).NotTo(HaveOccurred())
870+
g.Expect(out).NotTo(BeEmpty(), "imagePullSecrets not set on Knative Service %s", deployedFunctionName)
871+
872+
var pullSecrets []v1.LocalObjectReference
873+
g.Expect(json.Unmarshal([]byte(out), &pullSecrets)).To(Succeed())
874+
g.Expect(pullSecrets).To(ContainElement(v1.LocalObjectReference{
875+
Name: secret.Name,
876+
}))
877+
}, 2*time.Minute).Should(Succeed())
878+
})
879+
})
749880
})

0 commit comments

Comments
 (0)