Skip to content

Commit e7bf1f9

Browse files
committed
fix(SECURESIGN-1855): Initialize TUF repository with user-provided PVC
Signed-off-by: Jan Bouska <[email protected]>
1 parent 1e42a99 commit e7bf1f9

File tree

6 files changed

+304
-55
lines changed

6 files changed

+304
-55
lines changed

internal/controller/tuf/actions/tuf_init_job.go

Lines changed: 48 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ import (
1414
"github.com/securesign/operator/internal/labels"
1515
"github.com/securesign/operator/internal/utils/kubernetes"
1616
"github.com/securesign/operator/internal/utils/kubernetes/ensure"
17-
"github.com/securesign/operator/internal/utils/kubernetes/job"
17+
jobUtils "github.com/securesign/operator/internal/utils/kubernetes/job"
1818
v2 "k8s.io/api/batch/v1"
1919
v1 "k8s.io/api/core/v1"
2020
"k8s.io/apimachinery/pkg/api/meta"
2121
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
22-
"sigs.k8s.io/controller-runtime/pkg/client"
22+
apilabels "k8s.io/apimachinery/pkg/labels"
2323
"sigs.k8s.io/controller-runtime/pkg/reconcile"
2424
)
2525

@@ -41,61 +41,62 @@ func (i initJobAction) CanHandle(_ context.Context, instance *rhtasv1alpha1.Tuf)
4141
}
4242

4343
func (i initJobAction) Handle(ctx context.Context, instance *rhtasv1alpha1.Tuf) *action.Result {
44-
if instance.Spec.Pvc.Name != "" {
45-
meta.SetStatusCondition(&instance.Status.Conditions, metav1.Condition{
46-
Type: tufConstants.RepositoryCondition,
47-
Status: metav1.ConditionTrue,
48-
Reason: constants.Ready,
49-
Message: "Using self-managed tuf repository.",
50-
})
51-
return i.StatusUpdate(ctx, instance)
44+
jobLabels := labels.ForResource(tufConstants.ComponentName, tufConstants.InitJobName, instance.Name, instance.Status.PvcName)
45+
initJobList := &v2.JobList{}
46+
selector := apilabels.SelectorFromSet(jobLabels)
47+
if err := kubernetes.FindByLabelSelector(ctx, i.Client, initJobList, instance.Namespace, selector.String()); err != nil {
48+
return i.Error(ctx, err, instance)
5249
}
5350

54-
if j, err := job.GetJob(ctx, i.Client, instance.Namespace, tufConstants.InitJobName); j != nil {
55-
i.Logger.Info("Tuf tuf-repository-init is already present.", "Succeeded", j.Status.Succeeded, "Failures", j.Status.Failed)
56-
if job.IsCompleted(*j) {
57-
if !job.IsFailed(*j) {
58-
meta.SetStatusCondition(&instance.Status.Conditions, metav1.Condition{
59-
Type: tufConstants.RepositoryCondition,
60-
Status: metav1.ConditionTrue,
61-
Reason: constants.Ready,
62-
Message: "tuf-repository-init job passed",
63-
})
64-
return i.StatusUpdate(ctx, instance)
65-
} else {
66-
err = fmt.Errorf("tuf-repository-init job failed")
67-
meta.SetStatusCondition(&instance.Status.Conditions, metav1.Condition{
68-
Type: tufConstants.RepositoryCondition,
69-
Status: metav1.ConditionFalse,
70-
Reason: constants.Failure,
71-
Message: err.Error(),
72-
})
73-
return i.Error(ctx, reconcile.TerminalError(err), instance)
74-
}
51+
switch {
52+
case len(initJobList.Items) > 1:
53+
return i.Error(ctx, reconcile.TerminalError(fmt.Errorf("multiple %s jobs present", tufConstants.InitJobName)), instance)
54+
case len(initJobList.Items) == 1:
55+
return i.jobPresent(ctx, &initJobList.Items[0], instance)
56+
default:
57+
return i.ensureInitJob(ctx, jobLabels, instance)
58+
}
59+
}
60+
61+
func (i initJobAction) jobPresent(ctx context.Context, job *v2.Job, instance *rhtasv1alpha1.Tuf) *action.Result {
62+
i.Logger.Info("Tuf tuf-repository-init is present.", "Succeeded", job.Status.Succeeded, "Failures", job.Status.Failed)
63+
if jobUtils.IsCompleted(*job) {
64+
if !jobUtils.IsFailed(*job) {
65+
meta.SetStatusCondition(&instance.Status.Conditions, metav1.Condition{
66+
Type: tufConstants.RepositoryCondition,
67+
Status: metav1.ConditionTrue,
68+
Reason: constants.Ready,
69+
Message: "tuf-repository-init job passed",
70+
})
71+
return i.StatusUpdate(ctx, instance)
7572
} else {
76-
// job not completed yet
77-
return i.Requeue()
73+
err := fmt.Errorf("tuf-repository-init job failed")
74+
meta.SetStatusCondition(&instance.Status.Conditions, metav1.Condition{
75+
Type: tufConstants.RepositoryCondition,
76+
Status: metav1.ConditionFalse,
77+
Reason: constants.Failure,
78+
Message: err.Error(),
79+
})
80+
return i.Error(ctx, reconcile.TerminalError(err), instance)
7881
}
79-
} else if client.IgnoreNotFound(err) != nil {
80-
return i.Error(ctx, err, instance)
81-
82+
} else {
83+
// job not completed yet
84+
return i.Requeue()
8285
}
86+
}
8387

84-
l := labels.For(tufConstants.ComponentName, tufConstants.InitJobName, instance.Name)
85-
pvc, err := kubernetes.GetPVC(ctx, i.Client, instance.Namespace, instance.Status.PvcName)
86-
if err != nil {
87-
return i.Error(ctx, fmt.Errorf("could not resolve PVC: %w", err), instance)
88-
}
89-
if _, err = kubernetes.CreateOrUpdate(ctx, i.Client,
88+
func (i initJobAction) ensureInitJob(ctx context.Context, labels map[string]string, instance *rhtasv1alpha1.Tuf) *action.Result {
89+
90+
if _, err := kubernetes.CreateOrUpdate(ctx, i.Client,
9091
&v2.Job{
9192
ObjectMeta: metav1.ObjectMeta{
92-
Name: tufConstants.InitJobName,
93-
Namespace: instance.Namespace,
93+
GenerateName: tufConstants.InitJobName + "-",
94+
Namespace: instance.Namespace,
9495
},
9596
},
96-
utils.EnsureTufInitJob(instance, tufConstants.RBACInitJobName, l),
97-
ensure.ControllerReference[*v2.Job](pvc, i.Client),
98-
ensure.Labels[*v2.Job](slices.Collect(maps.Keys(l)), l),
97+
utils.EnsureTufInitJob(instance, tufConstants.RBACInitJobName, labels),
98+
ensure.ControllerReference[*v2.Job](instance, i.Client),
99+
ensure.Labels[*v2.Job](slices.Collect(maps.Keys(labels)), labels),
99100
func(object *v2.Job) error {
100101
ensure.SetProxyEnvs(object.Spec.Template.Spec.Containers)
101102
return nil

internal/controller/tuf/tuf_controller_test.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import (
2525
tufConstants "github.com/securesign/operator/internal/controller/tuf/constants"
2626
"github.com/securesign/operator/internal/labels"
2727
k8sTest "github.com/securesign/operator/internal/testing/kubernetes"
28+
"github.com/securesign/operator/internal/utils/kubernetes"
29+
apilabels "k8s.io/apimachinery/pkg/labels"
2830

2931
"github.com/securesign/operator/api/v1alpha1"
3032
actions2 "github.com/securesign/operator/internal/controller/ctlog/actions"
@@ -166,14 +168,22 @@ var _ = Describe("TUF controller", func() {
166168
})
167169

168170
By("Waiting until Tuf init job is created")
169-
initJob := &batchv1.Job{}
170-
Eventually(func() error {
171-
e := suite.Client().Get(ctx, types.NamespacedName{Name: tufConstants.InitJobName, Namespace: namespace.Name}, initJob)
172-
return e
173-
}).Should(Not(HaveOccurred()))
171+
found := &v1alpha1.Tuf{}
172+
Eventually(func(g Gomega) string {
173+
g.Expect(suite.Client().Get(ctx, typeNamespaceName, found)).Should(Succeed())
174+
return found.Status.PvcName
175+
}).ShouldNot(BeEmpty())
176+
initJobList := &batchv1.JobList{}
177+
Eventually(func() []batchv1.Job {
178+
jobLabels := labels.ForResource(tufConstants.ComponentName, tufConstants.InitJobName, TufName, found.Status.PvcName)
179+
selector := apilabels.SelectorFromSet(jobLabels)
180+
Expect(kubernetes.FindByLabelSelector(ctx, suite.Client(), initJobList, TufNamespace, selector.String())).To(Succeed())
181+
return initJobList.Items
182+
}).Should(HaveLen(1))
174183

175184
By("Move to Job to completed")
176185
// Workaround to succeed condition for Ready phase
186+
initJob := &initJobList.Items[0]
177187
initJob.Status.Conditions = []batchv1.JobCondition{
178188
{Status: corev1.ConditionTrue, Type: batchv1.JobComplete, Reason: constants.Ready}}
179189
Expect(suite.Client().Status().Update(ctx, initJob)).Should(Succeed())

internal/controller/tuf/utils/tuf_init_job.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package utils
22

33
import (
4+
"fmt"
45
"path/filepath"
6+
"strings"
57

68
rhtasv1alpha1 "github.com/securesign/operator/api/v1alpha1"
79
"github.com/securesign/operator/internal/controller/tuf/constants"
@@ -63,7 +65,12 @@ func EnsureTufInitJob(instance *rhtasv1alpha1.Tuf, sa string, labels map[string]
6365
container.Image = images.Registry.Get(images.Tuf)
6466
env := kubernetes.FindEnvByNameOrCreate(container, "NAMESPACE")
6567
env.Value = instance.Namespace
66-
container.Args = args
68+
container.Command = []string{"/bin/bash", "-c"}
69+
container.Args = []string{
70+
fmt.Sprintf("tuf-repo-init.sh %s; ", strings.Join(args, " ")) +
71+
"exit_code=$?; " +
72+
"if [ $exit_code -eq 2 ]; then exit 0; else exit $exit_code; fi",
73+
}
6774
container.VolumeMounts = []v1.VolumeMount{
6875
{
6976
Name: "tuf-secrets",

test/e2e/securesign_1855_test.go

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
//go:build integration
2+
3+
package e2e
4+
5+
import (
6+
"context"
7+
8+
"github.com/securesign/operator/internal/controller/tuf/constants"
9+
"github.com/securesign/operator/internal/labels"
10+
"github.com/securesign/operator/internal/utils/kubernetes"
11+
"github.com/securesign/operator/internal/utils/kubernetes/job"
12+
testSupportKubernetes "github.com/securesign/operator/test/e2e/support/kubernetes"
13+
"github.com/securesign/operator/test/e2e/support/tas/tuf"
14+
v3 "k8s.io/api/batch/v1"
15+
"k8s.io/apimachinery/pkg/api/resource"
16+
apilabels "k8s.io/apimachinery/pkg/labels"
17+
"sigs.k8s.io/controller-runtime/pkg/client"
18+
19+
. "github.com/onsi/ginkgo/v2"
20+
. "github.com/onsi/gomega"
21+
"github.com/securesign/operator/api/v1alpha1"
22+
"github.com/securesign/operator/test/e2e/support"
23+
v1 "k8s.io/api/core/v1"
24+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
25+
)
26+
27+
const (
28+
tufPvcName = "tuf-init-test"
29+
)
30+
31+
var _ = Describe("Securesign tuf-repository init test", Ordered, func() {
32+
cli, _ := support.CreateClient()
33+
ctx := context.TODO()
34+
35+
var namespace *v1.Namespace
36+
var s *v1alpha1.Tuf
37+
38+
AfterEach(func() {
39+
if CurrentSpecReport().Failed() && support.IsCIEnvironment() {
40+
support.DumpNamespace(ctx, cli, namespace.Name)
41+
}
42+
})
43+
44+
BeforeAll(func() {
45+
namespace = support.CreateTestNamespace(ctx, cli)
46+
DeferCleanup(func() {
47+
_ = cli.Delete(ctx, namespace)
48+
})
49+
s = createInstance("test", namespace.Name)
50+
})
51+
52+
Describe("Install with empty pre-created tuf volume", func() {
53+
It("create mock secret", func() {
54+
pub, priv, crt, err := support.CreateCertificates(false)
55+
Expect(err).NotTo(HaveOccurred())
56+
Expect(cli.Create(ctx, &v1.Secret{
57+
ObjectMeta: metav1.ObjectMeta{
58+
Name: "test",
59+
Namespace: namespace.Name,
60+
},
61+
Data: map[string][]byte{"public": pub, "private": priv, "cert": crt},
62+
})).To(Succeed())
63+
})
64+
65+
It("Create tuf PVC and Tuf resource", func() {
66+
Expect(cli.Create(ctx, &v1.PersistentVolumeClaim{
67+
ObjectMeta: metav1.ObjectMeta{
68+
Name: tufPvcName,
69+
Namespace: namespace.Name,
70+
},
71+
Spec: v1.PersistentVolumeClaimSpec{
72+
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
73+
Resources: v1.VolumeResourceRequirements{
74+
Requests: v1.ResourceList{
75+
v1.ResourceStorage: resource.MustParse("1Gi"),
76+
},
77+
},
78+
},
79+
})).To(Succeed())
80+
Expect(cli.Create(ctx, s)).To(Succeed())
81+
})
82+
83+
It("Tuf is running", func() {
84+
tuf.Verify(ctx, cli, namespace.Name, s.Name)
85+
})
86+
87+
It("Tuf repository was initialized", func() {
88+
verifyInitJob(ctx, cli, namespace.Name, s.Name, "Initializing empty repository")
89+
})
90+
91+
It("Delete Tuf", func() {
92+
name := s.Name
93+
Expect(cli.Delete(ctx, s)).To(Succeed())
94+
Eventually(cli.Get).WithArguments(ctx, client.ObjectKey{Name: name, Namespace: s.Namespace}, s).ShouldNot(Succeed())
95+
96+
Eventually(func(g Gomega) []v1.PersistentVolumeClaim {
97+
pvcList := &v1.PersistentVolumeClaimList{}
98+
g.Expect(cli.List(ctx, pvcList, client.InNamespace(namespace.Name))).To(Succeed())
99+
return pvcList.Items
100+
}).Should(HaveLen(1), "Tuf PVC retained")
101+
102+
Eventually(func(g Gomega) []v3.Job {
103+
jobLabels := labels.ForResource(constants.ComponentName, constants.InitJobName, name, tufPvcName)
104+
initJobList := &v3.JobList{}
105+
selector := apilabels.SelectorFromSet(jobLabels)
106+
g.Expect(kubernetes.FindByLabelSelector(ctx, cli, initJobList, namespace.Name, selector.String())).To(Succeed())
107+
return initJobList.Items
108+
}).Should(BeEmpty())
109+
})
110+
111+
It("Create Tuf with already initialized repo", func() {
112+
s = createInstance("test1", namespace.Name)
113+
e := cli.Create(ctx, s)
114+
Expect(e).To(Succeed())
115+
tuf.Verify(ctx, cli, namespace.Name, s.Name)
116+
verifyInitJob(ctx, cli, namespace.Name, s.Name, "Repo seems to already be initialized")
117+
})
118+
119+
})
120+
})
121+
122+
func createInstance(name, ns string) *v1alpha1.Tuf {
123+
return &v1alpha1.Tuf{
124+
ObjectMeta: metav1.ObjectMeta{
125+
Namespace: ns,
126+
Name: name,
127+
},
128+
Spec: v1alpha1.TufSpec{
129+
Keys: []v1alpha1.TufKey{
130+
{
131+
Name: "rekor.pub",
132+
SecretRef: &v1alpha1.SecretKeySelector{
133+
LocalObjectReference: v1alpha1.LocalObjectReference{
134+
Name: "test",
135+
},
136+
Key: "public",
137+
},
138+
},
139+
{
140+
Name: "ctfe.pub",
141+
SecretRef: &v1alpha1.SecretKeySelector{
142+
LocalObjectReference: v1alpha1.LocalObjectReference{
143+
Name: "test",
144+
},
145+
Key: "public",
146+
},
147+
},
148+
{
149+
Name: "fulcio_v1.crt.pem",
150+
SecretRef: &v1alpha1.SecretKeySelector{
151+
LocalObjectReference: v1alpha1.LocalObjectReference{
152+
Name: "test",
153+
},
154+
Key: "cert",
155+
},
156+
},
157+
{
158+
Name: "tsa.certchain.pem",
159+
SecretRef: &v1alpha1.SecretKeySelector{
160+
LocalObjectReference: v1alpha1.LocalObjectReference{
161+
Name: "test",
162+
},
163+
Key: "cert",
164+
},
165+
},
166+
},
167+
Pvc: v1alpha1.TufPvc{
168+
Name: tufPvcName,
169+
},
170+
},
171+
}
172+
}
173+
174+
func verifyInitJob(ctx context.Context, cli client.Client, ns, name, logOutput string) {
175+
jobLabels := labels.ForResource(constants.ComponentName, constants.InitJobName, name, tufPvcName)
176+
initJobList := &v3.JobList{}
177+
selector := apilabels.SelectorFromSet(jobLabels)
178+
Expect(kubernetes.FindByLabelSelector(ctx, cli, initJobList, ns, selector.String())).To(Succeed())
179+
180+
Expect(initJobList.Items).To(HaveLen(1))
181+
Expect(job.IsCompleted(initJobList.Items[0])).To(BeTrue())
182+
Expect(job.IsFailed(initJobList.Items[0])).To(BeFalse())
183+
184+
podList := &v1.PodList{}
185+
Expect(kubernetes.FindByLabelSelector(ctx, cli, podList, ns, "job-name = "+initJobList.Items[0].Name)).To(Succeed())
186+
Expect(podList.Items).To(HaveLen(1))
187+
188+
logs, err := testSupportKubernetes.GetPodLogs(ctx, podList.Items[0].Name, "tuf-init", ns)
189+
Expect(err).NotTo(HaveOccurred())
190+
Expect(logs).To(ContainSubstring(logOutput))
191+
}

0 commit comments

Comments
 (0)