@pagopa/dx-cli 0.21.2 → 0.21.4
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/add.js +5 -2
- 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 +96 -2
- package/dist/adapters/plop/generators/environment/prompts.d.ts +1 -0
- package/dist/adapters/plop/generators/environment/prompts.js +6 -0
- 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/templates/environment/bootstrapper/{{env.name}}/data.tf.hbs +0 -16
- package/templates/environment/bootstrapper/{{env.name}}/main.tf.hbs +2 -14
- package/dist/adapters/pagopa-technology/authorization.js +0 -124
|
@@ -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
|
@@ -13,10 +13,6 @@ data "azuread_group" "externals" {
|
|
|
13
13
|
{{/with}}
|
|
14
14
|
|
|
15
15
|
{{#with includesProdIO}}
|
|
16
|
-
data "azurerm_subscription" "PROD_IO" {
|
|
17
|
-
provider = azurerm.PROD-IO
|
|
18
|
-
}
|
|
19
|
-
|
|
20
16
|
data "azurerm_resource_group" "common_itn_01" {
|
|
21
17
|
provider = azurerm.PROD-IO
|
|
22
18
|
name = "io-p-itn-common-rg-01"
|
|
@@ -38,21 +34,9 @@ data "azurerm_container_app_environment" "runner" {
|
|
|
38
34
|
resource_group_name = "io-p-itn-github-runner-rg-01"
|
|
39
35
|
}
|
|
40
36
|
|
|
41
|
-
data "azurerm_api_management" "apim" {
|
|
42
|
-
provider = azurerm.PROD-IO
|
|
43
|
-
name = "io-p-itn-apim-01"
|
|
44
|
-
resource_group_name = data.azurerm_resource_group.common_itn_01.name
|
|
45
|
-
}
|
|
46
|
-
|
|
47
37
|
data "azurerm_key_vault" "common" {
|
|
48
38
|
provider = azurerm.PROD-IO
|
|
49
39
|
name = "io-p-kv-common"
|
|
50
40
|
resource_group_name = data.azurerm_resource_group.common_weu.name
|
|
51
41
|
}
|
|
52
|
-
|
|
53
|
-
data "azurerm_virtual_network" "common" {
|
|
54
|
-
provider = azurerm.PROD-IO
|
|
55
|
-
name = "io-p-itn-common-vnet-01"
|
|
56
|
-
resource_group_name = data.azurerm_resource_group.common_itn_01.name
|
|
57
|
-
}
|
|
58
42
|
{{/with}}
|
|
@@ -8,7 +8,7 @@ locals {
|
|
|
8
8
|
{{#if (eq displayName "PROD-IO")}}
|
|
9
9
|
module "azure-{{displayName}}_bootstrap" {
|
|
10
10
|
source = "pagopa-dx/azure-github-environment-bootstrap/azurerm"
|
|
11
|
-
version = "~>
|
|
11
|
+
version = "~> 4.0"
|
|
12
12
|
|
|
13
13
|
providers = {
|
|
14
14
|
azurerm = azurerm.{{displayName}}
|
|
@@ -16,9 +16,6 @@ module "azure-{{displayName}}_bootstrap" {
|
|
|
16
16
|
|
|
17
17
|
environment = merge(local.environment, local.azure_accounts.{{displayName}})
|
|
18
18
|
|
|
19
|
-
subscription_id = data.azurerm_subscription.PROD_IO.id
|
|
20
|
-
tenant_id = data.azurerm_subscription.PROD_IO.tenant_id
|
|
21
|
-
|
|
22
19
|
entraid_groups = {
|
|
23
20
|
admins_object_id = data.azuread_group.admins.object_id
|
|
24
21
|
devs_object_id = data.azuread_group.developers.object_id
|
|
@@ -37,7 +34,6 @@ module "azure-{{displayName}}_bootstrap" {
|
|
|
37
34
|
|
|
38
35
|
github_private_runner = {
|
|
39
36
|
container_app_environment_id = data.azurerm_container_app_environment.runner.id
|
|
40
|
-
container_app_environment_location = data.azurerm_container_app_environment.runner.location
|
|
41
37
|
key_vault = {
|
|
42
38
|
name = data.azurerm_key_vault.common.name
|
|
43
39
|
resource_group_name = data.azurerm_key_vault.common.resource_group_name
|
|
@@ -45,10 +41,7 @@ module "azure-{{displayName}}_bootstrap" {
|
|
|
45
41
|
use_github_app = true
|
|
46
42
|
}
|
|
47
43
|
|
|
48
|
-
apim_id = data.azurerm_api_management.apim.id
|
|
49
|
-
pep_vnet_id = data.azurerm_virtual_network.common.id
|
|
50
44
|
private_dns_zone_resource_group_id = data.azurerm_resource_group.common_weu.id
|
|
51
|
-
nat_gateway_resource_group_id = data.azurerm_resource_group.common_itn_01.id
|
|
52
45
|
opex_resource_group_id = data.azurerm_resource_group.dashboards.id
|
|
53
46
|
|
|
54
47
|
tags = local.bootstrapper_tags
|
|
@@ -73,7 +66,7 @@ module "azure-{{displayName}}_core_values" {
|
|
|
73
66
|
|
|
74
67
|
module "azure-{{displayName}}_bootstrap" {
|
|
75
68
|
source = "pagopa-dx/azure-github-environment-bootstrap/azurerm"
|
|
76
|
-
version = "~>
|
|
69
|
+
version = "~> 4.0"
|
|
77
70
|
|
|
78
71
|
providers = {
|
|
79
72
|
azurerm = azurerm.{{displayName}}
|
|
@@ -81,9 +74,6 @@ module "azure-{{displayName}}_bootstrap" {
|
|
|
81
74
|
|
|
82
75
|
environment = merge(local.environment, local.azure_accounts.{{displayName}})
|
|
83
76
|
|
|
84
|
-
subscription_id = module.azure-{{displayName}}_core_values.subscription_id
|
|
85
|
-
tenant_id = module.azure-{{displayName}}_core_values.tenant_id
|
|
86
|
-
|
|
87
77
|
entraid_groups = {
|
|
88
78
|
admins_object_id = data.azuread_group.admins.object_id
|
|
89
79
|
devs_object_id = data.azuread_group.developers.object_id
|
|
@@ -102,7 +92,6 @@ module "azure-{{displayName}}_bootstrap" {
|
|
|
102
92
|
|
|
103
93
|
github_private_runner = {
|
|
104
94
|
container_app_environment_id = module.azure-{{displayName}}_core_values.github_runner.environment_id
|
|
105
|
-
container_app_environment_location = local.azure_accounts.{{displayName}}.location
|
|
106
95
|
labels = [
|
|
107
96
|
"{{@root.env.name}}"
|
|
108
97
|
]
|
|
@@ -114,7 +103,6 @@ module "azure-{{displayName}}_bootstrap" {
|
|
|
114
103
|
use_github_app = true
|
|
115
104
|
}
|
|
116
105
|
|
|
117
|
-
pep_vnet_id = module.azure-{{displayName}}_core_values.common_vnet.id
|
|
118
106
|
private_dns_zone_resource_group_id = module.azure-{{displayName}}_core_values.network_resource_group_id
|
|
119
107
|
opex_resource_group_id = module.azure-{{displayName}}_core_values.opex_resource_group_id
|
|
120
108
|
|
|
@@ -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
|
-
});
|