@flowcore/cli-plugin-iam 1.0.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,106 @@
1
+ import { ValidateLogin, } from "@flowcore/cli-plugin-config";
2
+ import { Api as IamApi } from "../utils/clients/iam/Api.js";
3
+ import { PolicyService } from "./policy.resource.js";
4
+ import { PolicyBindingService } from "./policy-binding.resource.js";
5
+ import { RoleService } from "./role.resource.js";
6
+ import { RoleBindingService } from "./role-binding.resource.js";
7
+ const applyOrder = ["Policy", "PolicyBinding", "Role", "RoleBinding"];
8
+ export class IAMApply {
9
+ organizationService;
10
+ cliConfiguration;
11
+ logger;
12
+ name = "iam.flowcore.io/v1";
13
+ iamClient;
14
+ organizations = [];
15
+ policyBindingService;
16
+ policyService;
17
+ roleBindingService;
18
+ roleService;
19
+ constructor(organizationService, cliConfiguration, logger) {
20
+ this.organizationService = organizationService;
21
+ this.cliConfiguration = cliConfiguration;
22
+ this.logger = logger;
23
+ this.getToken = this.getToken.bind(this);
24
+ this.iamClient = new IamApi({
25
+ baseUrl: "https://iam.api.flowcore.io",
26
+ });
27
+ this.policyService = new PolicyService(this.iamClient, this.logger, this.getToken);
28
+ this.roleService = new RoleService(this.iamClient, this.logger, this.getToken);
29
+ this.roleBindingService = new RoleBindingService(this.iamClient, this.logger, this.getToken);
30
+ this.policyBindingService = new PolicyBindingService(this.iamClient, this.logger, this.getToken);
31
+ }
32
+ async apply(resource, skipConfirmation) {
33
+ const organization = await this.fetchOrganizationId(resource.metadata.tenant);
34
+ if (!organization) {
35
+ this.logger.fatal(`Tenant ${resource.metadata.tenant} not found, or you don't have access to it.`);
36
+ }
37
+ switch (resource.kind) {
38
+ case "Policy": {
39
+ return this.policyService.createNewPolicy(organization.id, resource, skipConfirmation);
40
+ }
41
+ case "Role": {
42
+ return this.roleService.createNewRole(organization.id, resource, skipConfirmation);
43
+ }
44
+ case "RoleBinding": {
45
+ return this.roleBindingService.createNewRoleBinding(organization.id, resource, skipConfirmation);
46
+ }
47
+ case "PolicyBinding": {
48
+ return this.policyBindingService.createNewPolicyBinding(organization.id, resource, skipConfirmation);
49
+ }
50
+ default: {
51
+ this.logger.fatal(`Unsupported resource kind: ${resource.kind}`);
52
+ }
53
+ }
54
+ }
55
+ calculateApplyOrder(resources) {
56
+ return resources.sort((a, b) => {
57
+ if (applyOrder.indexOf(a.kind) < applyOrder.indexOf(b.kind)) {
58
+ return -1;
59
+ }
60
+ return 1;
61
+ });
62
+ }
63
+ async delete(resource) {
64
+ const organization = await this.fetchOrganizationId(resource.metadata.tenant);
65
+ if (!organization) {
66
+ this.logger.fatal(`Tenant ${resource.metadata.tenant} not found, or you don't have access to it.`);
67
+ }
68
+ switch (resource.kind) {
69
+ case "Policy": {
70
+ return this.policyService.deletePolicy(organization.id, resource);
71
+ }
72
+ case "Role": {
73
+ return this.roleService.deleteRole(organization.id, resource);
74
+ }
75
+ case "RoleBinding": {
76
+ return this.roleBindingService.deleteRoleBinding(organization.id, resource);
77
+ }
78
+ case "PolicyBinding": {
79
+ return this.policyBindingService.deletePolicyBinding(organization.id, resource);
80
+ }
81
+ default: {
82
+ this.logger.fatal(`Unsupported resource kind: ${resource.kind}`);
83
+ }
84
+ }
85
+ }
86
+ async fetchOrganizationId(tenant) {
87
+ if (this.organizations.length === 0) {
88
+ const organizations = await this.organizationService.getMyOrganizations();
89
+ this.organizations = organizations.me.organizations.map((org) => ({
90
+ id: org.organization.id,
91
+ tenant: org.organization.org,
92
+ }));
93
+ }
94
+ return this.organizations.find((org) => org.tenant === tenant);
95
+ }
96
+ async getToken() {
97
+ const config = this.cliConfiguration.getConfig();
98
+ const login = new ValidateLogin(config.login.url);
99
+ await login.validate(config, this.cliConfiguration, false);
100
+ const { auth } = config;
101
+ if (!auth?.accessToken) {
102
+ this.logger.fatal("Not logged in, run 'flowcore login'");
103
+ }
104
+ return auth?.accessToken;
105
+ }
106
+ }
@@ -0,0 +1,82 @@
1
+ import type { Logger } from "@flowcore/cli-plugin-config";
2
+ import { z } from "zod";
3
+ import type { Api as IamApi } from "../utils/clients/iam/Api.js";
4
+ export declare const policyBindingDto: z.ZodObject<z.objectUtil.extendShape<{
5
+ apiVersion: z.ZodString;
6
+ kind: z.ZodString;
7
+ metadata: z.ZodObject<{
8
+ name: z.ZodString;
9
+ tenant: z.ZodString;
10
+ }, "strip", z.ZodTypeAny, {
11
+ name: string;
12
+ tenant: string;
13
+ }, {
14
+ name: string;
15
+ tenant: string;
16
+ }>;
17
+ }, {
18
+ spec: z.ZodObject<{
19
+ policy: z.ZodString;
20
+ subjects: z.ZodArray<z.ZodObject<{
21
+ id: z.ZodString;
22
+ type: z.ZodEnum<["user", "key"]>;
23
+ }, "strip", z.ZodTypeAny, {
24
+ id: string;
25
+ type: "key" | "user";
26
+ }, {
27
+ id: string;
28
+ type: "key" | "user";
29
+ }>, "many">;
30
+ }, "strip", z.ZodTypeAny, {
31
+ policy: string;
32
+ subjects: {
33
+ id: string;
34
+ type: "key" | "user";
35
+ }[];
36
+ }, {
37
+ policy: string;
38
+ subjects: {
39
+ id: string;
40
+ type: "key" | "user";
41
+ }[];
42
+ }>;
43
+ }>, "strip", z.ZodTypeAny, {
44
+ kind: string;
45
+ apiVersion: string;
46
+ metadata: {
47
+ name: string;
48
+ tenant: string;
49
+ };
50
+ spec: {
51
+ policy: string;
52
+ subjects: {
53
+ id: string;
54
+ type: "key" | "user";
55
+ }[];
56
+ };
57
+ }, {
58
+ kind: string;
59
+ apiVersion: string;
60
+ metadata: {
61
+ name: string;
62
+ tenant: string;
63
+ };
64
+ spec: {
65
+ policy: string;
66
+ subjects: {
67
+ id: string;
68
+ type: "key" | "user";
69
+ }[];
70
+ };
71
+ }>;
72
+ export type PolicyBinding = z.infer<typeof policyBindingDto>;
73
+ export type ApiPolicyResource = Awaited<ReturnType<IamApi["getApiV1PolicyAssociationsOrganizationByOrganizationId"]>>["data"][0];
74
+ export type ApiPolicyAssociationResource = Awaited<ReturnType<IamApi["getApiV1PolicyAssociationsByPolicyId"]>>["data"];
75
+ export declare class PolicyBindingService {
76
+ private readonly iamClient;
77
+ private readonly logger;
78
+ private readonly getToken;
79
+ constructor(iamClient: IamApi, logger: Logger, getToken: () => Promise<string>);
80
+ createNewPolicyBinding(organizationId: string, policyBinding: unknown, skipConfirmation: boolean): Promise<boolean>;
81
+ deletePolicyBinding(organizationId: string, policyBinding: unknown): Promise<boolean>;
82
+ }
@@ -0,0 +1,203 @@
1
+ import { baseResourceDto } from "@flowcore/cli-plugin-core";
2
+ import enquirer from "enquirer";
3
+ import { diffString } from "json-diff";
4
+ import { z } from "zod";
5
+ export const policyBindingDto = baseResourceDto.extend({
6
+ spec: z.object({
7
+ policy: z.string(),
8
+ subjects: z.array(z.object({
9
+ id: z.string(),
10
+ type: z.enum(["user", "key"]),
11
+ })),
12
+ }),
13
+ });
14
+ export class PolicyBindingService {
15
+ iamClient;
16
+ logger;
17
+ getToken;
18
+ constructor(iamClient, logger, getToken) {
19
+ this.iamClient = iamClient;
20
+ this.logger = logger;
21
+ this.getToken = getToken;
22
+ }
23
+ async createNewPolicyBinding(organizationId, policyBinding, skipConfirmation) {
24
+ const parsedPolicyBinding = policyBindingDto.parse(policyBinding);
25
+ try {
26
+ const policies = await this.iamClient.getApiV1PolicyAssociationsOrganizationByOrganizationId(organizationId, {
27
+ headers: {
28
+ Authorization: `Bearer ${await this.getToken()}`,
29
+ },
30
+ });
31
+ const existingPolicy = policies.data.find((p) => p.name === parsedPolicyBinding.spec.policy);
32
+ if (!existingPolicy) {
33
+ this.logger.error(`Policy ${parsedPolicyBinding.spec.policy} not found`);
34
+ return false;
35
+ }
36
+ const associations = await this.iamClient.getApiV1PolicyAssociationsByPolicyId(existingPolicy.id, {
37
+ headers: {
38
+ Authorization: `Bearer ${await this.getToken()}`,
39
+ },
40
+ });
41
+ const usersToDelete = [];
42
+ for (const userAssociation of associations.data.users.filter((u) => u.policyId !== existingPolicy.id)) {
43
+ if (!parsedPolicyBinding.spec.subjects.find((s) => s.type === "user" && s.id === userAssociation.userId)) {
44
+ usersToDelete.push(userAssociation.userId);
45
+ }
46
+ }
47
+ const keysToDelete = [];
48
+ for (const keyAssociation of associations.data.keys.filter((k) => k.policyId !== existingPolicy.id)) {
49
+ if (!parsedPolicyBinding.spec.subjects.find((s) => s.type === "key" && s.id === keyAssociation.keyId)) {
50
+ keysToDelete.push(keyAssociation.keyId);
51
+ }
52
+ }
53
+ if (!skipConfirmation) {
54
+ const diffObject = {
55
+ keys: associations.data.keys.filter((k) => !keysToDelete.includes(k.keyId)),
56
+ users: associations.data.users.filter((u) => !usersToDelete.includes(u.userId)),
57
+ };
58
+ this.logger.info("Modifying the following:");
59
+ this.logger.info(`${diffString(associations.data.users.map((u) => u.userId), diffObject.users, { color: true, full: true })}`);
60
+ this.logger.info(`${diffString(associations.data.keys.map((k) => k.keyId), diffObject.keys, { color: true, full: true })}`);
61
+ const { confirm } = await enquirer.prompt([
62
+ {
63
+ message: `Are you sure you want to remove ${usersToDelete.length} users and ${keysToDelete.length} keys from policy ${existingPolicy.name}?`,
64
+ name: "confirm",
65
+ type: "confirm",
66
+ },
67
+ ]);
68
+ if (!confirm) {
69
+ return false;
70
+ }
71
+ }
72
+ let changed = keysToDelete.length > 0 || usersToDelete.length > 0;
73
+ for (const userId of usersToDelete) {
74
+ const result = await this.iamClient.deleteApiV1PolicyAssociationsUserByUserId(userId, { policyId: existingPolicy.id }, {
75
+ headers: {
76
+ Authorization: `Bearer ${await this.getToken()}`,
77
+ },
78
+ });
79
+ if (result.status !== 200) {
80
+ this.logger.error(`Failed to delete user ${userId} from policy ${existingPolicy.name}: ${result.statusText}`);
81
+ }
82
+ }
83
+ for (const keyId of keysToDelete) {
84
+ const result = await this.iamClient.deleteApiV1PolicyAssociationsKeyByKeyId(keyId, { policyId: existingPolicy.id }, {
85
+ headers: {
86
+ Authorization: `Bearer ${await this.getToken()}`,
87
+ },
88
+ });
89
+ if (result.status !== 200) {
90
+ this.logger.error(`Failed to delete key ${keyId} from policy ${existingPolicy.name}: ${result.statusText}`);
91
+ }
92
+ }
93
+ for (const subject of parsedPolicyBinding.spec.subjects) {
94
+ switch (subject.type) {
95
+ case "user": {
96
+ const existingAssociation = associations.data.users.find((a) => a.userId === subject.id);
97
+ if (existingAssociation) {
98
+ this.logger.debug(`User ${subject.id} already bound to policy ${existingPolicy.name}`);
99
+ continue;
100
+ }
101
+ const result = await this.iamClient.postApiV1PolicyAssociationsUserByUserId(subject.id, { policyId: existingPolicy.id }, {
102
+ headers: {
103
+ Authorization: `Bearer ${await this.getToken()}`,
104
+ },
105
+ });
106
+ if (result.status !== 200) {
107
+ this.logger.error(`Failed to bind user ${subject.id} to policy ${existingPolicy.name}: ${result.statusText}`);
108
+ }
109
+ changed = true;
110
+ break;
111
+ }
112
+ case "key": {
113
+ const existingAssociation = associations.data.keys.find((a) => a.keyId === subject.id);
114
+ if (existingAssociation) {
115
+ this.logger.debug(`Key ${subject.id} already bound to policy ${existingPolicy.name}`);
116
+ continue;
117
+ }
118
+ const result = await this.iamClient.postApiV1PolicyAssociationsKeyByKeyId(subject.id, { policyId: existingPolicy.id }, {
119
+ headers: {
120
+ Authorization: `Bearer ${await this.getToken()}`,
121
+ },
122
+ });
123
+ if (result.status !== 200) {
124
+ this.logger.error(`Failed to bind key ${subject.id} to policy ${existingPolicy.name}: ${result.statusText}`);
125
+ }
126
+ changed = true;
127
+ break;
128
+ }
129
+ }
130
+ }
131
+ return changed;
132
+ }
133
+ catch (error) {
134
+ if (typeof error === "object" && error !== null && "error" in error) {
135
+ const err = error;
136
+ this.logger.fatal(`Failed to create policy binding with error(${err.error.status} - ${err.error.code}): ${err.error.message}`);
137
+ }
138
+ else {
139
+ this.logger.fatal(`Failed to create policy binding with unknown error: ${error}`);
140
+ }
141
+ }
142
+ }
143
+ async deletePolicyBinding(organizationId, policyBinding) {
144
+ const parsedPolicyBinding = policyBindingDto.parse(policyBinding);
145
+ try {
146
+ const policies = await this.iamClient.getApiV1PolicyAssociationsOrganizationByOrganizationId(organizationId, {
147
+ headers: {
148
+ Authorization: `Bearer ${await this.getToken()}`,
149
+ },
150
+ });
151
+ const targetPolicy = policies.data.find((p) => p.name === parsedPolicyBinding.spec.policy);
152
+ if (!targetPolicy) {
153
+ this.logger.fatal(`Policy ${parsedPolicyBinding.spec.policy} not found`);
154
+ }
155
+ const associations = await this.iamClient.getApiV1PolicyAssociationsByPolicyId(targetPolicy.id, {
156
+ headers: {
157
+ Authorization: `Bearer ${await this.getToken()}`,
158
+ },
159
+ });
160
+ for (const subject of parsedPolicyBinding.spec.subjects) {
161
+ switch (subject.type) {
162
+ case "user": {
163
+ const existingAssociation = associations.data.users.find((a) => a.userId === subject.id);
164
+ if (!existingAssociation) {
165
+ this.logger.error(`User ${subject.id} not bound to policy ${targetPolicy.id}`);
166
+ continue;
167
+ }
168
+ const result = await this.iamClient.deleteApiV1PolicyAssociationsUserByUserId(subject.id, { policyId: targetPolicy.id }, {
169
+ headers: {
170
+ Authorization: `Bearer ${await this.getToken()}`,
171
+ },
172
+ });
173
+ if (result.status !== 200) {
174
+ this.logger.error(`Failed to delete policy binding for user ${subject.id}: ${result.statusText}`);
175
+ }
176
+ break;
177
+ }
178
+ case "key": {
179
+ const result = await this.iamClient.deleteApiV1PolicyAssociationsKeyByKeyId(subject.id, { policyId: targetPolicy.id }, {
180
+ headers: {
181
+ Authorization: `Bearer ${await this.getToken()}`,
182
+ },
183
+ });
184
+ if (result.status !== 200) {
185
+ this.logger.error(`Failed to delete policy binding for key ${subject.id}: ${result.statusText}`);
186
+ }
187
+ break;
188
+ }
189
+ }
190
+ }
191
+ return true;
192
+ }
193
+ catch (error) {
194
+ if (typeof error === "object" && error !== null && "error" in error) {
195
+ const err = error;
196
+ this.logger.fatal(`Failed to delete policy with error(${err.error.status} - ${err.error.code}): ${err.error.message}`);
197
+ }
198
+ else {
199
+ this.logger.fatal(`Failed to delete policy with unknown error: ${error}`);
200
+ }
201
+ }
202
+ }
203
+ }
@@ -0,0 +1,110 @@
1
+ import type { Logger } from "@flowcore/cli-plugin-config";
2
+ import { z } from "zod";
3
+ import type { Api as IamApi } from "../utils/clients/iam/Api.js";
4
+ export declare enum PolicyDocumentAction {
5
+ ALL = "*",
6
+ FETCH = "FETCH",
7
+ INGEST = "INGEST",
8
+ READ = "READ",
9
+ WRITE = "WRITE"
10
+ }
11
+ export declare const policyDto: z.ZodObject<z.objectUtil.extendShape<{
12
+ apiVersion: z.ZodString;
13
+ kind: z.ZodString;
14
+ metadata: z.ZodObject<{
15
+ name: z.ZodString;
16
+ tenant: z.ZodString;
17
+ }, "strip", z.ZodTypeAny, {
18
+ name: string;
19
+ tenant: string;
20
+ }, {
21
+ name: string;
22
+ tenant: string;
23
+ }>;
24
+ }, {
25
+ spec: z.ZodObject<{
26
+ description: z.ZodOptional<z.ZodString>;
27
+ flowcoreManaged: z.ZodOptional<z.ZodBoolean>;
28
+ policyDocuments: z.ZodArray<z.ZodObject<{
29
+ action: z.ZodUnion<[z.ZodArray<z.ZodNativeEnum<typeof PolicyDocumentAction>, "many">, z.ZodNativeEnum<typeof PolicyDocumentAction>]>;
30
+ resource: z.ZodString;
31
+ statementId: z.ZodOptional<z.ZodString>;
32
+ }, "strip", z.ZodTypeAny, {
33
+ action: PolicyDocumentAction | PolicyDocumentAction[];
34
+ resource: string;
35
+ statementId?: string | undefined;
36
+ }, {
37
+ action: PolicyDocumentAction | PolicyDocumentAction[];
38
+ resource: string;
39
+ statementId?: string | undefined;
40
+ }>, "many">;
41
+ principal: z.ZodOptional<z.ZodString>;
42
+ version: z.ZodString;
43
+ }, "strip", z.ZodTypeAny, {
44
+ version: string;
45
+ policyDocuments: {
46
+ action: PolicyDocumentAction | PolicyDocumentAction[];
47
+ resource: string;
48
+ statementId?: string | undefined;
49
+ }[];
50
+ description?: string | undefined;
51
+ flowcoreManaged?: boolean | undefined;
52
+ principal?: string | undefined;
53
+ }, {
54
+ version: string;
55
+ policyDocuments: {
56
+ action: PolicyDocumentAction | PolicyDocumentAction[];
57
+ resource: string;
58
+ statementId?: string | undefined;
59
+ }[];
60
+ description?: string | undefined;
61
+ flowcoreManaged?: boolean | undefined;
62
+ principal?: string | undefined;
63
+ }>;
64
+ }>, "strip", z.ZodTypeAny, {
65
+ kind: string;
66
+ apiVersion: string;
67
+ metadata: {
68
+ name: string;
69
+ tenant: string;
70
+ };
71
+ spec: {
72
+ version: string;
73
+ policyDocuments: {
74
+ action: PolicyDocumentAction | PolicyDocumentAction[];
75
+ resource: string;
76
+ statementId?: string | undefined;
77
+ }[];
78
+ description?: string | undefined;
79
+ flowcoreManaged?: boolean | undefined;
80
+ principal?: string | undefined;
81
+ };
82
+ }, {
83
+ kind: string;
84
+ apiVersion: string;
85
+ metadata: {
86
+ name: string;
87
+ tenant: string;
88
+ };
89
+ spec: {
90
+ version: string;
91
+ policyDocuments: {
92
+ action: PolicyDocumentAction | PolicyDocumentAction[];
93
+ resource: string;
94
+ statementId?: string | undefined;
95
+ }[];
96
+ description?: string | undefined;
97
+ flowcoreManaged?: boolean | undefined;
98
+ principal?: string | undefined;
99
+ };
100
+ }>;
101
+ export type Policy = z.infer<typeof policyDto>;
102
+ export type ApiPolicyResource = Awaited<ReturnType<IamApi["getApiV1PolicyAssociationsOrganizationByOrganizationId"]>>["data"][0];
103
+ export declare class PolicyService {
104
+ private readonly iamClient;
105
+ private readonly logger;
106
+ private readonly getToken;
107
+ constructor(iamClient: IamApi, logger: Logger, getToken: () => Promise<string>);
108
+ createNewPolicy(organizationId: string, policy: unknown, skipConfirmation: boolean): Promise<boolean>;
109
+ deletePolicy(organizationId: string, policy: unknown): Promise<boolean>;
110
+ }
@@ -0,0 +1,158 @@
1
+ import { baseResourceDto } from "@flowcore/cli-plugin-core";
2
+ import { diff } from "@opentf/obj-diff";
3
+ import enquirer from "enquirer";
4
+ import { diffString } from "json-diff";
5
+ import { omit } from "radash";
6
+ import { z } from "zod";
7
+ export var PolicyDocumentAction;
8
+ (function (PolicyDocumentAction) {
9
+ PolicyDocumentAction["ALL"] = "*";
10
+ PolicyDocumentAction["FETCH"] = "FETCH";
11
+ PolicyDocumentAction["INGEST"] = "INGEST";
12
+ PolicyDocumentAction["READ"] = "READ";
13
+ PolicyDocumentAction["WRITE"] = "WRITE";
14
+ })(PolicyDocumentAction || (PolicyDocumentAction = {}));
15
+ export const policyDto = baseResourceDto.extend({
16
+ spec: z.object({
17
+ description: z.string().optional(),
18
+ flowcoreManaged: z.boolean().optional(),
19
+ policyDocuments: z.array(z.object({
20
+ action: z
21
+ .array(z.nativeEnum(PolicyDocumentAction))
22
+ .or(z.nativeEnum(PolicyDocumentAction)),
23
+ resource: z.string(),
24
+ statementId: z.string().optional(),
25
+ })),
26
+ principal: z.string().optional(),
27
+ version: z.string(),
28
+ }),
29
+ });
30
+ export class PolicyService {
31
+ iamClient;
32
+ logger;
33
+ getToken;
34
+ constructor(iamClient, logger, getToken) {
35
+ this.iamClient = iamClient;
36
+ this.logger = logger;
37
+ this.getToken = getToken;
38
+ }
39
+ async createNewPolicy(organizationId, policy, skipConfirmation) {
40
+ const parsedPolicy = policyDto.parse(policy);
41
+ try {
42
+ const policies = await this.iamClient.getApiV1PolicyAssociationsOrganizationByOrganizationId(organizationId, {
43
+ headers: {
44
+ Authorization: `Bearer ${await this.getToken()}`,
45
+ },
46
+ });
47
+ const existingPolicy = policies.data.find((p) => p.name === parsedPolicy.metadata.name);
48
+ if (!existingPolicy) {
49
+ const result = await this.iamClient.postApiV1Policies({
50
+ description: parsedPolicy.spec.description ?? undefined,
51
+ name: parsedPolicy.metadata.name,
52
+ organizationId,
53
+ policyDocuments: parsedPolicy.spec.policyDocuments,
54
+ principal: parsedPolicy.spec.principal ?? undefined,
55
+ version: parsedPolicy.spec.version,
56
+ }, {
57
+ headers: {
58
+ Authorization: `Bearer ${await this.getToken()}`,
59
+ },
60
+ });
61
+ if (result.status !== 200) {
62
+ this.logger.fatal(`Failed to create policy: ${result.statusText}`);
63
+ }
64
+ return true;
65
+ }
66
+ let useStatementIds = false;
67
+ const newPolicy = {
68
+ description: parsedPolicy.spec.description ?? "",
69
+ name: parsedPolicy.metadata.name,
70
+ organizationId,
71
+ policyDocuments: parsedPolicy.spec.policyDocuments.map((doc) => {
72
+ if (doc.statementId) {
73
+ useStatementIds = true;
74
+ }
75
+ return {
76
+ ...doc,
77
+ };
78
+ }),
79
+ version: parsedPolicy.spec.version,
80
+ ...(parsedPolicy.spec.principal && {
81
+ principal: parsedPolicy.spec.principal,
82
+ }),
83
+ flowcoreManaged: parsedPolicy.spec.flowcoreManaged ?? false,
84
+ };
85
+ if (diff({
86
+ ...omit(existingPolicy, ["id"]),
87
+ policyDocuments: existingPolicy.policyDocuments.map((doc) => useStatementIds ? doc : omit(doc, ["statementId"])),
88
+ }, newPolicy).length === 0) {
89
+ return false;
90
+ }
91
+ if (!skipConfirmation) {
92
+ this.logger.info("Policy has changed, do you want to apply these changes?");
93
+ this.logger.info(diffString({
94
+ ...omit(existingPolicy, ["id"]),
95
+ policyDocuments: existingPolicy.policyDocuments.map((doc) => useStatementIds ? doc : omit(doc, ["statementId"])),
96
+ }, newPolicy, { color: true, full: true }));
97
+ const { confirm } = await enquirer.prompt({
98
+ message: "Are you sure you want to update the policy?",
99
+ name: "confirm",
100
+ type: "confirm",
101
+ });
102
+ if (!confirm) {
103
+ return false;
104
+ }
105
+ }
106
+ const result = await this.iamClient.patchApiV1PoliciesById(existingPolicy.id, newPolicy, {
107
+ headers: {
108
+ Authorization: `Bearer ${await this.getToken()}`,
109
+ },
110
+ });
111
+ if (result.status !== 200) {
112
+ this.logger.fatal(`Failed to update policy: ${result.statusText}`);
113
+ }
114
+ return true;
115
+ }
116
+ catch (error) {
117
+ if (typeof error === "object" && error !== null && "error" in error) {
118
+ const err = error;
119
+ this.logger.fatal(`Failed to create policy with error(${err.error.status} - ${err.error.code}): ${err.error.message}`);
120
+ }
121
+ else {
122
+ this.logger.fatal(`Failed to create policy with unknown error: ${error}`);
123
+ }
124
+ }
125
+ }
126
+ async deletePolicy(organizationId, policy) {
127
+ const parsedPolicy = policyDto.parse(policy);
128
+ try {
129
+ const policies = await this.iamClient.getApiV1PolicyAssociationsOrganizationByOrganizationId(organizationId, {
130
+ headers: {
131
+ Authorization: `Bearer ${await this.getToken()}`,
132
+ },
133
+ });
134
+ const existingPolicy = policies.data.find((p) => p.name === parsedPolicy.metadata.name);
135
+ if (!existingPolicy) {
136
+ return false;
137
+ }
138
+ const result = await this.iamClient.deleteApiV1PoliciesById(existingPolicy.id, {
139
+ headers: {
140
+ Authorization: `Bearer ${await this.getToken()}`,
141
+ },
142
+ });
143
+ if (result.status !== 200) {
144
+ this.logger.fatal(`Failed to delete policy: ${result.statusText}`);
145
+ }
146
+ return true;
147
+ }
148
+ catch (error) {
149
+ if (typeof error === "object" && error !== null && "error" in error) {
150
+ const err = error;
151
+ this.logger.fatal(`Failed to delete policy with error(${err.error.status} - ${err.error.code}): ${err.error.message}`);
152
+ }
153
+ else {
154
+ this.logger.fatal(`Failed to delete policy with unknown error: ${error}`);
155
+ }
156
+ }
157
+ }
158
+ }