@@ -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
194327func (k * kindCluster ) CreateNamespace (ctx context.Context ) (context.Context , error ) {
0 commit comments