@pagopa/dx-cli 0.21.1 → 0.21.3
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.
- package/dist/adapters/azure/__tests__/cloud-account-service.test.js +9 -1
- package/dist/adapters/azure/cloud-account-service.js +7 -0
- package/dist/adapters/commander/commands/__tests__/add.test.js +12 -4
- package/dist/adapters/commander/commands/__tests__/init.test.d.ts +4 -0
- package/dist/adapters/commander/commands/__tests__/init.test.js +48 -0
- package/dist/adapters/commander/commands/add.js +5 -2
- package/dist/adapters/commander/commands/init.d.ts +2 -0
- package/dist/adapters/commander/commands/init.js +30 -4
- package/dist/adapters/pagopa-technology/__tests__/authorization.test.js +349 -31
- package/dist/adapters/pagopa-technology/azure-authorization-config.d.ts +13 -0
- package/dist/adapters/pagopa-technology/azure-authorization-config.js +43 -0
- package/dist/adapters/pagopa-technology/{authorization.d.ts → azure-authorization.d.ts} +2 -2
- package/dist/adapters/pagopa-technology/azure-authorization.js +239 -0
- package/dist/adapters/plop/actions/__tests__/init-cloud-accounts.test.js +7 -0
- package/dist/adapters/plop/actions/__tests__/provision-terraform-backend.test.js +3 -0
- package/dist/adapters/plop/generators/environment/__tests__/actions.test.js +1 -0
- package/dist/adapters/plop/generators/environment/__tests__/prompts.test.js +156 -2
- package/dist/adapters/plop/generators/environment/prompts.d.ts +10 -0
- package/dist/adapters/plop/generators/environment/prompts.js +45 -1
- package/dist/domain/authorization.d.ts +6 -9
- package/dist/domain/authorization.js +27 -10
- package/dist/domain/github.d.ts +1 -0
- package/dist/domain/github.js +1 -0
- package/dist/index.js +2 -2
- package/dist/use-cases/__tests__/request-authorization.test.js +5 -3
- package/dist/use-cases/request-authorization.d.ts +2 -2
- package/dist/use-cases/request-authorization.js +2 -2
- package/package.json +1 -1
- package/dist/adapters/pagopa-technology/authorization.js +0 -124
package/dist/domain/github.d.ts
CHANGED
|
@@ -94,6 +94,7 @@ export declare class RepositoryNotFoundError extends Error {
|
|
|
94
94
|
constructor(owner: string, name: string);
|
|
95
95
|
}
|
|
96
96
|
export declare const githubAppCredentialsSchema: z.ZodObject<{
|
|
97
|
+
clientId: z.ZodString;
|
|
97
98
|
id: z.ZodString;
|
|
98
99
|
installationId: z.ZodString;
|
|
99
100
|
key: z.ZodString;
|
package/dist/domain/github.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import { makeValidationReporter } from "./adapters/logtape/validation-reporter.j
|
|
|
8
8
|
import { makePackageJsonReader } from "./adapters/node/package-json.js";
|
|
9
9
|
import { makeRepositoryReader } from "./adapters/node/repository.js";
|
|
10
10
|
import { getGitHubPAT, OctokitGitHubService, } from "./adapters/octokit/index.js";
|
|
11
|
-
import {
|
|
11
|
+
import { makeAzureAuthorizationService } from "./adapters/pagopa-technology/azure-authorization.js";
|
|
12
12
|
import { getConfig } from "./config.js";
|
|
13
13
|
import { getInfo } from "./domain/info.js";
|
|
14
14
|
import { applyCodemodById } from "./use-cases/apply-codemod.js";
|
|
@@ -60,7 +60,7 @@ export const runCli = async (version) => {
|
|
|
60
60
|
auth,
|
|
61
61
|
});
|
|
62
62
|
const gitHubService = new OctokitGitHubService(octokit);
|
|
63
|
-
const authorizationService =
|
|
63
|
+
const authorizationService = makeAzureAuthorizationService(gitHubService);
|
|
64
64
|
const deps = {
|
|
65
65
|
authorizationService,
|
|
66
66
|
gitHubService,
|
|
@@ -4,10 +4,12 @@
|
|
|
4
4
|
import { errAsync, okAsync } from "neverthrow";
|
|
5
5
|
import { describe, expect, it } from "vitest";
|
|
6
6
|
import { mock } from "vitest-mock-extended";
|
|
7
|
-
import { AuthorizationError, AuthorizationResult,
|
|
7
|
+
import { AuthorizationError, AuthorizationResult, requestAuthorizationInputSchema, } from "../../domain/authorization.js";
|
|
8
8
|
import { requestAuthorization } from "../request-authorization.js";
|
|
9
9
|
const makeSampleInput = () => requestAuthorizationInputSchema.parse({
|
|
10
10
|
bootstrapIdentityId: "test-bootstrap-identity-id",
|
|
11
|
+
envShort: "d",
|
|
12
|
+
prefix: "test",
|
|
11
13
|
repoName: "test-repo",
|
|
12
14
|
subscriptionName: "test-subscription",
|
|
13
15
|
});
|
|
@@ -25,10 +27,10 @@ describe("requestAuthorization", () => {
|
|
|
25
27
|
it("should propagate errors from the authorization service", async () => {
|
|
26
28
|
const authorizationService = mock();
|
|
27
29
|
const input = makeSampleInput();
|
|
28
|
-
authorizationService.requestAuthorization.mockReturnValue(errAsync(new
|
|
30
|
+
authorizationService.requestAuthorization.mockReturnValue(errAsync(new AuthorizationError("Unable to update file")));
|
|
29
31
|
const result = await requestAuthorization(authorizationService)(input);
|
|
30
32
|
expect(result.isErr()).toBe(true);
|
|
31
|
-
expect(result._unsafeUnwrapErr()).toBeInstanceOf(
|
|
33
|
+
expect(result._unsafeUnwrapErr()).toBeInstanceOf(AuthorizationError);
|
|
32
34
|
});
|
|
33
35
|
it("should propagate generic authorization errors", async () => {
|
|
34
36
|
const authorizationService = mock();
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
* Request Authorization Use Case
|
|
3
3
|
*
|
|
4
4
|
* Orchestrates an authorization request by delegating to the
|
|
5
|
-
*
|
|
5
|
+
* AuthorizationService.
|
|
6
6
|
*/
|
|
7
7
|
import { ResultAsync } from "neverthrow";
|
|
8
8
|
import { AuthorizationError, AuthorizationResult, AuthorizationService, RequestAuthorizationInput } from "../domain/authorization.js";
|
|
9
9
|
/**
|
|
10
10
|
* Creates a function that requests authorization for a bootstrap identity.
|
|
11
11
|
*
|
|
12
|
-
* @param authorizationService - The service handling
|
|
12
|
+
* @param authorizationService - The service handling authorization logic
|
|
13
13
|
* @returns A function that takes input and returns a ResultAsync with the authorization result
|
|
14
14
|
*/
|
|
15
15
|
export declare const requestAuthorization: (authorizationService: AuthorizationService) => (input: RequestAuthorizationInput) => ResultAsync<AuthorizationResult, AuthorizationError>;
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
* Request Authorization Use Case
|
|
3
3
|
*
|
|
4
4
|
* Orchestrates an authorization request by delegating to the
|
|
5
|
-
*
|
|
5
|
+
* AuthorizationService.
|
|
6
6
|
*/
|
|
7
7
|
/**
|
|
8
8
|
* Creates a function that requests authorization for a bootstrap identity.
|
|
9
9
|
*
|
|
10
|
-
* @param authorizationService - The service handling
|
|
10
|
+
* @param authorizationService - The service handling authorization logic
|
|
11
11
|
* @returns A function that takes input and returns a ResultAsync with the authorization result
|
|
12
12
|
*/
|
|
13
13
|
export const requestAuthorization = (authorizationService) => (input) => authorizationService.requestAuthorization(input);
|
package/package.json
CHANGED
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PagoPA Technology Authorization Adapter
|
|
3
|
-
*
|
|
4
|
-
* Implements the AuthorizationService interface for the PagoPA Azure
|
|
5
|
-
* authorization workflow. Encapsulates all platform-specific details:
|
|
6
|
-
* the target GitHub repository, file paths, branch naming, JSON file
|
|
7
|
-
* parsing, and pull request creation.
|
|
8
|
-
*/
|
|
9
|
-
import { getLogger } from "@logtape/logtape";
|
|
10
|
-
import { err, errAsync, ok, okAsync, ResultAsync } from "neverthrow";
|
|
11
|
-
import { z } from "zod";
|
|
12
|
-
import { AuthorizationError, AuthorizationResult, IdentityAlreadyExistsError, InvalidAuthorizationFileFormatError, } from "../../domain/authorization.js";
|
|
13
|
-
const authorizationFileSchema = z
|
|
14
|
-
.object({
|
|
15
|
-
directory_readers: z
|
|
16
|
-
.object({
|
|
17
|
-
service_principals_name: z.array(z.string()),
|
|
18
|
-
})
|
|
19
|
-
.loose(),
|
|
20
|
-
})
|
|
21
|
-
.loose();
|
|
22
|
-
const addIdentity = (content, identityId) => {
|
|
23
|
-
let parsed;
|
|
24
|
-
try {
|
|
25
|
-
parsed = JSON.parse(content);
|
|
26
|
-
}
|
|
27
|
-
catch {
|
|
28
|
-
return err(new InvalidAuthorizationFileFormatError("File content is not valid JSON"));
|
|
29
|
-
}
|
|
30
|
-
const result = authorizationFileSchema.safeParse(parsed);
|
|
31
|
-
if (!result.success) {
|
|
32
|
-
return err(new InvalidAuthorizationFileFormatError("Could not find directory_readers.service_principals_name list"));
|
|
33
|
-
}
|
|
34
|
-
const jsonContent = result.data;
|
|
35
|
-
if (jsonContent.directory_readers.service_principals_name.includes(identityId)) {
|
|
36
|
-
return err(new IdentityAlreadyExistsError(identityId));
|
|
37
|
-
}
|
|
38
|
-
const updated = {
|
|
39
|
-
...jsonContent,
|
|
40
|
-
directory_readers: {
|
|
41
|
-
...jsonContent.directory_readers,
|
|
42
|
-
service_principals_name: [
|
|
43
|
-
...jsonContent.directory_readers.service_principals_name,
|
|
44
|
-
identityId,
|
|
45
|
-
],
|
|
46
|
-
},
|
|
47
|
-
};
|
|
48
|
-
return ok(JSON.stringify(updated, null, 2));
|
|
49
|
-
};
|
|
50
|
-
const REPO_OWNER = "pagopa";
|
|
51
|
-
const REPO_NAME = "eng-azure-authorization";
|
|
52
|
-
const BASE_BRANCH = "main";
|
|
53
|
-
export const makeAuthorizationService = (gitHubService) => ({
|
|
54
|
-
requestAuthorization(input) {
|
|
55
|
-
const logger = getLogger(["dx-cli", "pagopa-authorization"]);
|
|
56
|
-
const { bootstrapIdentityId, repoName, subscriptionName } = input;
|
|
57
|
-
const filePath = `src/azure-subscriptions/subscriptions/${subscriptionName}/terraform.tfvars.json`;
|
|
58
|
-
const branchName = `feats/add-${repoName}-${subscriptionName}-bootstrap-identity`;
|
|
59
|
-
return (
|
|
60
|
-
// Step 1: Create branch first to avoid race condition with main branch updates
|
|
61
|
-
ResultAsync.fromPromise(gitHubService.createBranch({
|
|
62
|
-
branchName,
|
|
63
|
-
fromRef: BASE_BRANCH,
|
|
64
|
-
owner: REPO_OWNER,
|
|
65
|
-
repo: REPO_NAME,
|
|
66
|
-
}), () => new AuthorizationError(`Unable to create branch ${branchName} in ${REPO_OWNER}/${REPO_NAME}`))
|
|
67
|
-
.orTee((error) => {
|
|
68
|
-
logger.error(error.message);
|
|
69
|
-
})
|
|
70
|
-
// Step 2: Fetch file content from the newly created branch
|
|
71
|
-
.andThen(() => ResultAsync.fromPromise(gitHubService.getFileContent({
|
|
72
|
-
owner: REPO_OWNER,
|
|
73
|
-
path: filePath,
|
|
74
|
-
ref: branchName,
|
|
75
|
-
repo: REPO_NAME,
|
|
76
|
-
}), () => new AuthorizationError(`Unable to get ${filePath} in ${REPO_OWNER}/${REPO_NAME}`)))
|
|
77
|
-
.orTee((error) => {
|
|
78
|
-
logger.error(error.message);
|
|
79
|
-
})
|
|
80
|
-
// Modify the file content, detecting duplicates and format errors
|
|
81
|
-
.andThen(({ content, sha }) => addIdentity(content, bootstrapIdentityId)
|
|
82
|
-
.mapErr((error) => {
|
|
83
|
-
if (error instanceof IdentityAlreadyExistsError) {
|
|
84
|
-
logger.warn("Identity already exists", {
|
|
85
|
-
identityId: bootstrapIdentityId,
|
|
86
|
-
subscription: subscriptionName,
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
logger.error("Failed to modify tfvars", {
|
|
91
|
-
error: error.message,
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
return error;
|
|
95
|
-
})
|
|
96
|
-
.match((updatedContent) => okAsync({ sha, updatedContent }), (error) => errAsync(error)))
|
|
97
|
-
// Update the file on the new branch
|
|
98
|
-
.andThen(({ sha, updatedContent }) => ResultAsync.fromPromise(gitHubService.updateFile({
|
|
99
|
-
branch: branchName,
|
|
100
|
-
content: updatedContent,
|
|
101
|
-
message: `Add directory reader for ${subscriptionName}`,
|
|
102
|
-
owner: REPO_OWNER,
|
|
103
|
-
path: filePath,
|
|
104
|
-
repo: REPO_NAME,
|
|
105
|
-
sha,
|
|
106
|
-
}), () => new AuthorizationError(`Unable to update ${filePath} on branch ${branchName} in ${REPO_OWNER}/${REPO_NAME}`)))
|
|
107
|
-
.orTee((error) => {
|
|
108
|
-
logger.error(error.message);
|
|
109
|
-
})
|
|
110
|
-
// Create a pull request for review
|
|
111
|
-
.andThen(() => ResultAsync.fromPromise(gitHubService.createPullRequest({
|
|
112
|
-
base: BASE_BRANCH,
|
|
113
|
-
body: `This PR adds the bootstrap identity \`${bootstrapIdentityId}\` to the directory readers for subscription \`${subscriptionName}\`.`,
|
|
114
|
-
head: branchName,
|
|
115
|
-
owner: REPO_OWNER,
|
|
116
|
-
repo: REPO_NAME,
|
|
117
|
-
title: `Add directory reader for ${subscriptionName}`,
|
|
118
|
-
}), () => new AuthorizationError(`Unable to create pull request from ${branchName} to ${BASE_BRANCH} in ${REPO_OWNER}/${REPO_NAME}`)))
|
|
119
|
-
.orTee((error) => {
|
|
120
|
-
logger.error(error.message);
|
|
121
|
-
})
|
|
122
|
-
.map((pr) => new AuthorizationResult(pr.url)));
|
|
123
|
-
},
|
|
124
|
-
});
|