Skip to content

Commit 61a409b

Browse files
simonbairdclaude
andcommitted
Support creating ConfigMaps in acceptance tests
Also namespaces, since we want the ConfigMap in a particular namespace. An RBAC is created also so the ConfigMap is readable by every service account. This will be used in the acceptance test added in an upcoming commit. Ref: https://issues.redhat.com/browse/EC-1695 Co-authored-by: Claude Code <noreply@anthropic.com>
1 parent 0440b67 commit 61a409b

4 files changed

Lines changed: 165 additions & 0 deletions

File tree

acceptance/kubernetes/kind/kubernetes.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ import (
3232
pipeline "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1"
3333
tekton "github.com/tektoncd/pipeline/pkg/client/clientset/versioned/typed/pipeline/v1"
3434
v1 "k8s.io/api/core/v1"
35+
rbacv1 "k8s.io/api/rbac/v1"
36+
apierrors "k8s.io/apimachinery/pkg/api/errors"
3537
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3638
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
3739
"k8s.io/apimachinery/pkg/runtime"
@@ -189,6 +191,137 @@ func (k *kindCluster) CreateNamedSnapshot(ctx context.Context, name string, spec
189191
return k.createSnapshot(ctx, snapshot)
190192
}
191193

194+
// CreateConfigMap creates a ConfigMap with the given name and namespace with the provided content
195+
// Also creates necessary RBAC permissions for cross-namespace access
196+
func (k *kindCluster) CreateConfigMap(ctx context.Context, name, namespace, content string) error {
197+
var data map[string]string
198+
199+
// Parse JSON content and extract individual fields as ConfigMap data keys
200+
if strings.HasPrefix(strings.TrimSpace(content), "{") {
201+
// Parse JSON content
202+
var jsonData map[string]interface{}
203+
if err := json.Unmarshal([]byte(content), &jsonData); err != nil {
204+
return fmt.Errorf("failed to parse JSON content: %w", err)
205+
}
206+
207+
// Convert to string map for ConfigMap data
208+
data = make(map[string]string)
209+
for key, value := range jsonData {
210+
if value != nil {
211+
data[key] = fmt.Sprintf("%v", value)
212+
}
213+
}
214+
} else {
215+
// For non-JSON content, store as-is under a single key
216+
data = map[string]string{
217+
"content": content,
218+
}
219+
}
220+
221+
configMap := &v1.ConfigMap{
222+
ObjectMeta: metav1.ObjectMeta{
223+
Name: name,
224+
Namespace: namespace,
225+
},
226+
Data: data,
227+
}
228+
229+
// Create the ConfigMap (or update if it already exists)
230+
if _, err := k.client.CoreV1().ConfigMaps(namespace).Create(ctx, configMap, metav1.CreateOptions{}); err != nil {
231+
if apierrors.IsAlreadyExists(err) {
232+
// ConfigMap exists, so get the existing one to retrieve its ResourceVersion
233+
existing, err := k.client.CoreV1().ConfigMaps(namespace).Get(ctx, name, metav1.GetOptions{})
234+
if err != nil {
235+
return fmt.Errorf("failed to get existing ConfigMap: %w", err)
236+
}
237+
// Set the ResourceVersion from the existing ConfigMap
238+
configMap.ResourceVersion = existing.ResourceVersion
239+
// Now update with the proper ResourceVersion
240+
if _, err := k.client.CoreV1().ConfigMaps(namespace).Update(ctx, configMap, metav1.UpdateOptions{}); err != nil {
241+
return fmt.Errorf("failed to update existing ConfigMap: %w", err)
242+
}
243+
} else {
244+
return err
245+
}
246+
}
247+
248+
// Create RBAC permissions for cross-namespace ConfigMap access
249+
// This allows any service account to read ConfigMaps from any namespace
250+
if err := k.ensureConfigMapRBAC(ctx); err != nil {
251+
return fmt.Errorf("failed to create RBAC permissions: %w", err)
252+
}
253+
254+
return nil
255+
}
256+
257+
// ensureConfigMapRBAC creates necessary RBAC permissions for ConfigMap access across namespaces
258+
func (k *kindCluster) ensureConfigMapRBAC(ctx context.Context) error {
259+
// Create ClusterRole for ConfigMap reading (idempotent)
260+
clusterRole := &rbacv1.ClusterRole{
261+
ObjectMeta: metav1.ObjectMeta{
262+
Name: "acceptance-configmap-reader",
263+
},
264+
Rules: []rbacv1.PolicyRule{
265+
{
266+
APIGroups: []string{""},
267+
Resources: []string{"configmaps"},
268+
Verbs: []string{"get", "list"},
269+
},
270+
},
271+
}
272+
273+
if _, err := k.client.RbacV1().ClusterRoles().Create(ctx, clusterRole, metav1.CreateOptions{}); err != nil {
274+
// Ignore error if ClusterRole already exists
275+
if !strings.Contains(err.Error(), "already exists") {
276+
return fmt.Errorf("failed to create ClusterRole: %w", err)
277+
}
278+
}
279+
280+
// Create ClusterRoleBinding for all service accounts (idempotent)
281+
clusterRoleBinding := &rbacv1.ClusterRoleBinding{
282+
ObjectMeta: metav1.ObjectMeta{
283+
Name: "acceptance-configmap-reader-binding",
284+
},
285+
RoleRef: rbacv1.RoleRef{
286+
APIGroup: "rbac.authorization.k8s.io",
287+
Kind: "ClusterRole",
288+
Name: "acceptance-configmap-reader",
289+
},
290+
Subjects: []rbacv1.Subject{
291+
{
292+
Kind: "Group",
293+
Name: "system:serviceaccounts",
294+
APIGroup: "rbac.authorization.k8s.io",
295+
},
296+
},
297+
}
298+
299+
if _, err := k.client.RbacV1().ClusterRoleBindings().Create(ctx, clusterRoleBinding, metav1.CreateOptions{}); err != nil {
300+
// Ignore error if ClusterRoleBinding already exists
301+
if !strings.Contains(err.Error(), "already exists") {
302+
return fmt.Errorf("failed to create ClusterRoleBinding: %w", err)
303+
}
304+
}
305+
306+
return nil
307+
}
308+
309+
// CreateNamedNamespace creates a namespace with the specified name
310+
func (k *kindCluster) CreateNamedNamespace(ctx context.Context, name string) error {
311+
_, err := k.client.CoreV1().Namespaces().Create(ctx, &v1.Namespace{
312+
ObjectMeta: metav1.ObjectMeta{
313+
Name: name,
314+
},
315+
}, metav1.CreateOptions{})
316+
317+
// Ignore error if namespace already exists
318+
if err != nil && strings.Contains(err.Error(), "already exists") {
319+
return nil
320+
}
321+
322+
return err
323+
}
324+
192325
// CreateNamespace creates a randomly-named namespace for the test to execute in
193326
// and stores it in the test context
194327
func (k *kindCluster) CreateNamespace(ctx context.Context) (context.Context, error) {

acceptance/kubernetes/kubernetes.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,26 @@ func createNamedSnapshot(ctx context.Context, name string, specification *godog.
174174
return c.cluster.CreateNamedSnapshot(ctx, name, specification.Content)
175175
}
176176

177+
func createConfigMap(ctx context.Context, name, namespace string, content *godog.DocString) error {
178+
c := testenv.FetchState[ClusterState](ctx)
179+
180+
if err := mustBeUp(ctx, *c); err != nil {
181+
return err
182+
}
183+
184+
return c.cluster.CreateConfigMap(ctx, name, namespace, content.Content)
185+
}
186+
187+
func createNamedNamespace(ctx context.Context, name string) error {
188+
c := testenv.FetchState[ClusterState](ctx)
189+
190+
if err := mustBeUp(ctx, *c); err != nil {
191+
return err
192+
}
193+
194+
return c.cluster.CreateNamedNamespace(ctx, name)
195+
}
196+
177197
func createNamedSnapshotWithManyComponents(ctx context.Context, name string, amount int, key string) (context.Context, error) {
178198
c := testenv.FetchState[ClusterState](ctx)
179199

@@ -493,6 +513,8 @@ func AddStepsTo(sc *godog.ScenarioContext) {
493513
sc.Step(`^the task results should match the snapshot$`, taskResultsShouldMatchTheSnapshot)
494514
sc.Step(`^the task result "([^"]*)" should equal "([^"]*)"$`, taskResultShouldEqual)
495515
sc.Step(`^policy configuration named "([^"]*)" with (\d+) policy sources from "([^"]*)"(?:, patched with)$`, createNamedPolicyWithManySources)
516+
sc.Step(`^a namespace named "([^"]*)" exists$`, createNamedNamespace)
517+
sc.Step(`^a ConfigMap "([^"]*)" in namespace "([^"]*)" with content:$`, createConfigMap)
496518
// stop usage of the cluster once a test is done, godog will call this
497519
// function on failure and on the last step, so more than once if the
498520
// failure is not on the last step and once if there was no failure or the

acceptance/kubernetes/stub/stub.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,14 @@ func (s stubCluster) CreateNamedSnapshot(ctx context.Context, name string, speci
135135
}))).WithHeaders(map[string]string{"Content-Type": "application/json"}).WithStatus(200)))
136136
}
137137

138+
func (s stubCluster) CreateConfigMap(_ context.Context, _, _, _ string) error {
139+
return errors.New("ConfigMap creation is not supported when using the stub Kubernetes")
140+
}
141+
142+
func (s stubCluster) CreateNamedNamespace(_ context.Context, _ string) error {
143+
return errors.New("Named namespace creation is not supported when using the stub Kubernetes")
144+
}
145+
138146
func (s stubCluster) CreatePolicy(_ context.Context, _ string) error {
139147
return errors.New("use `Given policy configuration named \"<name>\" with specification` when using the stub Kubernetes")
140148
}

acceptance/kubernetes/types/types.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ type Cluster interface {
3131
AwaitUntilTaskIsDone(context.Context) (bool, error)
3232
TaskInfo(context.Context) (*TaskInfo, error)
3333
CreateNamedSnapshot(context.Context, string, string) error
34+
CreateNamedNamespace(context.Context, string) error
35+
CreateConfigMap(context.Context, string, string, string) error
3436
Registry(context.Context) (string, error)
3537
BuildSnapshotArtifact(context.Context, string) (context.Context, error)
3638
}

0 commit comments

Comments
 (0)