Repository: cdk8s-team/cdk8s-plus
Pull request #1519 (2022-12-15 14:24:55)

feat(role): imported resources can be granted permissions on
Imported resources could not be used in the `role.allowXXX` methods because they didn't implement the `IApiResource` interface. 

They can easily implement this interface because imports are always done using the resource name, which is what this interface needs. 

Resolves https://github.com/cdk8s-team/cdk8s-plus/issues/1516


Patch:
diff --git a/src/base.ts b/src/base.ts
index 8ef2240d..38e451e5 100644
--- a/src/base.ts
+++ b/src/base.ts
@@ -16,7 +16,7 @@ export interface ResourceProps {
 /**
  * Represents a resource.
  */
-export interface IResource extends IConstruct {
+export interface IResource extends IConstruct, IApiResource {
   /**
    * The Kubernetes name of this resource.
    */
@@ -27,11 +27,6 @@ export interface IResource extends IConstruct {
    */
   readonly apiVersion: string;
 
-  /**
-   * The group portion of the API version (e.g. "authorization.k8s.io")
-   */
-  readonly apiGroup: string;
-
   /**
    * The object kind (e.g. "Deployment").
    */
diff --git a/src/config-map.ts b/src/config-map.ts
index dc7fffa8..60acc9cd 100644
--- a/src/config-map.ts
+++ b/src/config-map.ts
@@ -56,6 +56,8 @@ class ImportedConfigMap extends Construct implements IConfigMap {
 
   private readonly _name: string;
 
+  public readonly resourceType = 'configmaps';
+
   constructor(scope: Construct, id: string, name: string) {
     super(scope, id);
     this._name = name;
@@ -77,6 +79,10 @@ class ImportedConfigMap extends Construct implements IConfigMap {
     return k8s.KubeConfigMap.GVK.kind;
   }
 
+  public get resourceName(): string {
+    return this.name;
+  }
+
 }
 
 /**
diff --git a/src/pv.ts b/src/pv.ts
index 240f7c90..e02495ec 100644
--- a/src/pv.ts
+++ b/src/pv.ts
@@ -79,6 +79,8 @@ class ImportedPersistentVolume extends Construct implements IPersistentVolume {
 
   private readonly _name: string;
 
+  public readonly resourceType = 'persistentvolumes';
+
   constructor(scope: Construct, id: string, name: string) {
     super(scope, id);
     this._name = name;
@@ -100,6 +102,10 @@ class ImportedPersistentVolume extends Construct implements IPersistentVolume {
     return k8s.KubePersistentVolume.GVK.kind;
   }
 
+  public get resourceName(): string {
+    return this.name;
+  }
+
 }
 
 /**
diff --git a/src/pvc.ts b/src/pvc.ts
index 29f02d6e..25c133ae 100644
--- a/src/pvc.ts
+++ b/src/pvc.ts
@@ -75,6 +75,8 @@ class ImportedPersistentVolumeClaim extends Construct implements IPersistentVolu
 
   private readonly _name: string;
 
+  public readonly resourceType = 'persistentvolumeclaims';
+
   constructor(scope: Construct, id: string, name: string) {
     super(scope, id);
     this._name = name;
@@ -96,6 +98,10 @@ class ImportedPersistentVolumeClaim extends Construct implements IPersistentVolu
     return k8s.KubePersistentVolumeClaim.GVK.kind;
   }
 
+  public get resourceName(): string {
+    return this.name;
+  }
+
 }
 
 /**
diff --git a/src/role.ts b/src/role.ts
index 63a87d0e..bdf4eb93 100644
--- a/src/role.ts
+++ b/src/role.ts
@@ -45,6 +45,9 @@ export interface RolePolicyRule {
 class ImportedRole extends Construct implements IRole {
 
   private readonly _name: string;
+
+  public readonly resourceType = 'roles';
+
   constructor(scope: Construct, id: string, name: string) {
     super(scope, id);
     this._name = name;
@@ -65,6 +68,11 @@ class ImportedRole extends Construct implements IRole {
   public get kind(): string {
     return k8s.KubeRole.GVK.kind;
   }
+
+  public get resourceName(): string {
+    return this.name;
+  }
+
 }
 
 /**
@@ -279,6 +287,8 @@ class ImportedClusterRole extends Construct implements IClusterRole {
 
   private readonly _name: string;
 
+  public readonly resourceType: string = 'clusterroles';
+
   constructor(scope: Construct, id: string, name: string) {
     super(scope, id);
     this._name = name;
@@ -300,6 +310,10 @@ class ImportedClusterRole extends Construct implements IClusterRole {
     return k8s.KubeClusterRole.GVK.kind;
   }
 
+  public get resourceName(): string {
+    return this.name;
+  }
+
 }
 
 /**
diff --git a/src/secret.ts b/src/secret.ts
index 194043f7..872cdd4f 100644
--- a/src/secret.ts
+++ b/src/secret.ts
@@ -64,6 +64,8 @@ class ImportedSecret extends Construct implements ISecret {
 
   private readonly _name: string;
 
+  public readonly resourceType = 'secrets';
+
   constructor(scope: Construct, id: string, name: string) {
     super(scope, id);
     this._name = name;
@@ -85,6 +87,10 @@ class ImportedSecret extends Construct implements ISecret {
     return k8s.KubeSecret.GVK.kind;
   }
 
+  public get resourceName(): string {
+    return this.name;
+  }
+
 }
 
 /**
diff --git a/src/service-account.ts b/src/service-account.ts
index ac244c27..3713d7a1 100644
--- a/src/service-account.ts
+++ b/src/service-account.ts
@@ -37,6 +37,8 @@ class ImportedServiceAccount extends Construct implements IServiceAccount {
 
   private readonly _name: string;
 
+  public readonly resourceType = 'serviceaccounts';
+
   constructor(scope: Construct, id: string, name: string) {
     super(scope, id);
     this._name = name;
@@ -58,6 +60,10 @@ class ImportedServiceAccount extends Construct implements IServiceAccount {
     return k8s.KubeServiceAccount.GVK.kind;
   }
 
+  public get resourceName(): string {
+    return this.name;
+  }
+
 }
 
 /**


Test patch:
diff --git a/test/__snapshots__/config-map.test.ts.snap b/test/__snapshots__/config-map.test.ts.snap
index c43a6470..8ffd9aa4 100644
--- a/test/__snapshots__/config-map.test.ts.snap
+++ b/test/__snapshots__/config-map.test.ts.snap
@@ -82,3 +82,33 @@ Array [
   },
 ]
 `;
+
+exports[`can grant permissions on imported 1`] = `
+Array [
+  Object {
+    "apiVersion": "rbac.authorization.k8s.io/v1",
+    "kind": "Role",
+    "metadata": Object {
+      "name": "test-role-c8eba390",
+    },
+    "rules": Array [
+      Object {
+        "apiGroups": Array [
+          "",
+        ],
+        "resourceNames": Array [
+          "name",
+        ],
+        "resources": Array [
+          "configmaps",
+        ],
+        "verbs": Array [
+          "get",
+          "list",
+          "watch",
+        ],
+      },
+    ],
+  },
+]
+`;
diff --git a/test/__snapshots__/pv.test.ts.snap b/test/__snapshots__/pv.test.ts.snap
index 5dad4332..0f4cc568 100644
--- a/test/__snapshots__/pv.test.ts.snap
+++ b/test/__snapshots__/pv.test.ts.snap
@@ -254,6 +254,36 @@ Array [
 ]
 `;
 
+exports[`PersistentVolume can grant permissions on imported 1`] = `
+Array [
+  Object {
+    "apiVersion": "rbac.authorization.k8s.io/v1",
+    "kind": "Role",
+    "metadata": Object {
+      "name": "test-role-c8eba390",
+    },
+    "rules": Array [
+      Object {
+        "apiGroups": Array [
+          "",
+        ],
+        "resourceNames": Array [
+          "vol",
+        ],
+        "resources": Array [
+          "persistentvolumes",
+        ],
+        "verbs": Array [
+          "get",
+          "list",
+          "watch",
+        ],
+      },
+    ],
+  },
+]
+`;
+
 exports[`PersistentVolume custom 1`] = `
 Array [
   Object {
diff --git a/test/__snapshots__/pvc.test.ts.snap b/test/__snapshots__/pvc.test.ts.snap
index 1a335e00..90a908d2 100644
--- a/test/__snapshots__/pvc.test.ts.snap
+++ b/test/__snapshots__/pvc.test.ts.snap
@@ -32,6 +32,36 @@ Array [
 ]
 `;
 
+exports[`can grant permissions on imported 1`] = `
+Array [
+  Object {
+    "apiVersion": "rbac.authorization.k8s.io/v1",
+    "kind": "Role",
+    "metadata": Object {
+      "name": "test-role-c8eba390",
+    },
+    "rules": Array [
+      Object {
+        "apiGroups": Array [
+          "",
+        ],
+        "resourceNames": Array [
+          "claim",
+        ],
+        "resources": Array [
+          "persistentvolumeclaims",
+        ],
+        "verbs": Array [
+          "get",
+          "list",
+          "watch",
+        ],
+      },
+    ],
+  },
+]
+`;
+
 exports[`custom 1`] = `
 Array [
   Object {
diff --git a/test/__snapshots__/role.test.ts.snap b/test/__snapshots__/role.test.ts.snap
new file mode 100644
index 00000000..512f4815
--- /dev/null
+++ b/test/__snapshots__/role.test.ts.snap
@@ -0,0 +1,61 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ClusterRole can grant permissions on imported 1`] = `
+Array [
+  Object {
+    "apiVersion": "rbac.authorization.k8s.io/v1",
+    "kind": "Role",
+    "metadata": Object {
+      "name": "test-role-c8eba390",
+    },
+    "rules": Array [
+      Object {
+        "apiGroups": Array [
+          "rbac.authorization.k8s.io",
+        ],
+        "resourceNames": Array [
+          "r2",
+        ],
+        "resources": Array [
+          "clusterroles",
+        ],
+        "verbs": Array [
+          "get",
+          "list",
+          "watch",
+        ],
+      },
+    ],
+  },
+]
+`;
+
+exports[`Role can grant permissions on imported 1`] = `
+Array [
+  Object {
+    "apiVersion": "rbac.authorization.k8s.io/v1",
+    "kind": "Role",
+    "metadata": Object {
+      "name": "test-role-c8eba390",
+    },
+    "rules": Array [
+      Object {
+        "apiGroups": Array [
+          "rbac.authorization.k8s.io",
+        ],
+        "resourceNames": Array [
+          "r2",
+        ],
+        "resources": Array [
+          "roles",
+        ],
+        "verbs": Array [
+          "get",
+          "list",
+          "watch",
+        ],
+      },
+    ],
+  },
+]
+`;
diff --git a/test/__snapshots__/secret.test.ts.snap b/test/__snapshots__/secret.test.ts.snap
new file mode 100644
index 00000000..3e76482f
--- /dev/null
+++ b/test/__snapshots__/secret.test.ts.snap
@@ -0,0 +1,31 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`can grant permissions on imported 1`] = `
+Array [
+  Object {
+    "apiVersion": "rbac.authorization.k8s.io/v1",
+    "kind": "Role",
+    "metadata": Object {
+      "name": "test-role-c8eba390",
+    },
+    "rules": Array [
+      Object {
+        "apiGroups": Array [
+          "",
+        ],
+        "resourceNames": Array [
+          "secret",
+        ],
+        "resources": Array [
+          "secrets",
+        ],
+        "verbs": Array [
+          "get",
+          "list",
+          "watch",
+        ],
+      },
+    ],
+  },
+]
+`;
diff --git a/test/__snapshots__/service-account.test.ts.snap b/test/__snapshots__/service-account.test.ts.snap
new file mode 100644
index 00000000..a4f6e63b
--- /dev/null
+++ b/test/__snapshots__/service-account.test.ts.snap
@@ -0,0 +1,31 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`can grant permissions on imported 1`] = `
+Array [
+  Object {
+    "apiVersion": "rbac.authorization.k8s.io/v1",
+    "kind": "Role",
+    "metadata": Object {
+      "name": "test-role-c8eba390",
+    },
+    "rules": Array [
+      Object {
+        "apiGroups": Array [
+          "",
+        ],
+        "resourceNames": Array [
+          "service-account",
+        ],
+        "resources": Array [
+          "serviceaccounts",
+        ],
+        "verbs": Array [
+          "get",
+          "list",
+          "watch",
+        ],
+      },
+    ],
+  },
+]
+`;
diff --git a/test/config-map.test.ts b/test/config-map.test.ts
index c5e64f67..ff039ce1 100644
--- a/test/config-map.test.ts
+++ b/test/config-map.test.ts
@@ -1,6 +1,18 @@
 import { Testing, ApiObject } from 'cdk8s';
 import { Node } from 'constructs';
-import { ConfigMap } from '../src';
+import { ConfigMap, Role } from '../src';
+
+test('can grant permissions on imported', () => {
+
+  const chart = Testing.chart();
+  const cm = ConfigMap.fromConfigMapName(chart, 'ConfigMap', 'name');
+
+  const role = new Role(chart, 'Role');
+  role.allowRead(cm);
+
+  expect(Testing.synth(chart)).toMatchSnapshot();
+
+});
 
 test('defaultChild', () => {
 
diff --git a/test/pv.test.ts b/test/pv.test.ts
index 2cbd7e10..dc0baf05 100644
--- a/test/pv.test.ts
+++ b/test/pv.test.ts
@@ -4,6 +4,18 @@ import * as kplus from '../src';
 
 describe('PersistentVolume', () => {
 
+  test('can grant permissions on imported', () => {
+
+    const chart = Testing.chart();
+    const pv = kplus.PersistentVolume.fromPersistentVolumeName(chart, 'Vol', 'vol');
+
+    const role = new kplus.Role(chart, 'Role');
+    role.allowRead(pv);
+
+    expect(Testing.synth(chart)).toMatchSnapshot();
+
+  });
+
   test('defaults', () => {
 
     const chart = cdk8s.Testing.chart();
diff --git a/test/pvc.test.ts b/test/pvc.test.ts
index 10d5ba58..3b1c1bec 100644
--- a/test/pvc.test.ts
+++ b/test/pvc.test.ts
@@ -2,6 +2,18 @@ import * as cdk8s from 'cdk8s';
 import { Testing } from 'cdk8s';
 import * as kplus from '../src';
 
+test('can grant permissions on imported', () => {
+
+  const chart = Testing.chart();
+  const claim = kplus.PersistentVolumeClaim.fromClaimName(chart, 'Claim', 'claim');
+
+  const role = new kplus.Role(chart, 'Role');
+  role.allowRead(claim);
+
+  expect(Testing.synth(chart)).toMatchSnapshot();
+
+});
+
 test('defaults', () => {
 
   const chart = cdk8s.Testing.chart();
diff --git a/test/role.test.ts b/test/role.test.ts
index f527aba6..d63180de 100644
--- a/test/role.test.ts
+++ b/test/role.test.ts
@@ -4,6 +4,19 @@ import * as kplus from '../src';
 import { ApiResource } from '../src';
 
 describe('Role', () => {
+
+  test('can grant permissions on imported', () => {
+
+    const chart = Testing.chart();
+    const r2 = kplus.Role.fromRoleName(chart, 'R2', 'r2');
+
+    const role = new kplus.Role(chart, 'Role');
+    role.allowRead(r2);
+
+    expect(Testing.synth(chart)).toMatchSnapshot();
+
+  });
+
   test('defaultChild', () => {
 
     // GIVEN
@@ -248,6 +261,18 @@ Array [
 
 describe('ClusterRole', () => {
 
+  test('can grant permissions on imported', () => {
+
+    const chart = Testing.chart();
+    const r2 = kplus.ClusterRole.fromClusterRoleName(chart, 'R2', 'r2');
+
+    const role = new kplus.Role(chart, 'Role');
+    role.allowRead(r2);
+
+    expect(Testing.synth(chart)).toMatchSnapshot();
+
+  });
+
   test('defaultChild', () => {
 
     // GIVEN
diff --git a/test/secret.test.ts b/test/secret.test.ts
index b9e4c041..80cc5d1b 100644
--- a/test/secret.test.ts
+++ b/test/secret.test.ts
@@ -2,6 +2,18 @@ import { Testing, ApiObject } from 'cdk8s';
 import { Node } from 'constructs';
 import * as kplus from '../src';
 
+test('can grant permissions on imported', () => {
+
+  const chart = Testing.chart();
+  const secret = kplus.Secret.fromSecretName(chart, 'Secret', 'secret');
+
+  const role = new kplus.Role(chart, 'Role');
+  role.allowRead(secret);
+
+  expect(Testing.synth(chart)).toMatchSnapshot();
+
+});
+
 test('defaultChild', () => {
   const chart = Testing.chart();
 
diff --git a/test/service-account.test.ts b/test/service-account.test.ts
index a4ff05bb..5e734da9 100644
--- a/test/service-account.test.ts
+++ b/test/service-account.test.ts
@@ -2,6 +2,18 @@ import { Testing, ApiObject } from 'cdk8s';
 import { Node } from 'constructs';
 import * as kplus from '../src';
 
+test('can grant permissions on imported', () => {
+
+  const chart = Testing.chart();
+  const sa = kplus.ServiceAccount.fromServiceAccountName(chart, 'ServiceAccount', 'service-account');
+
+  const role = new kplus.Role(chart, 'Role');
+  role.allowRead(sa);
+
+  expect(Testing.synth(chart)).toMatchSnapshot();
+
+});
+
 test('defaultChild', () => {
   const chart = Testing.chart();