@backstage/backend-defaults 0.17.2-next.0 → 0.17.3-next.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.
- package/CHANGELOG.md +11 -0
- package/dist/alpha/entrypoints/actions/DefaultActionsService.cjs.js +4 -2
- package/dist/alpha/entrypoints/actions/DefaultActionsService.cjs.js.map +1 -1
- package/dist/alpha/entrypoints/actionsRegistry/DefaultActionsRegistryService.cjs.js +71 -43
- package/dist/alpha/entrypoints/actionsRegistry/DefaultActionsRegistryService.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/AzureBlobStorageUrlReader.cjs.js +8 -10
- package/dist/entrypoints/urlReader/lib/AzureBlobStorageUrlReader.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/GitlabUrlReader.cjs.js +4 -0
- package/dist/entrypoints/urlReader/lib/GitlabUrlReader.cjs.js.map +1 -1
- package/dist/package.json.cjs.js +1 -1
- package/dist/urlReader.d.ts +3 -2
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# @backstage/backend-defaults
|
|
2
2
|
|
|
3
|
+
## 0.17.3-next.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- b75158b: Adapted Azure-related tests for the Azure SDK upgrade to ESM-style exports. The `AzureBlobStorageUrlReader` now accepts an optional `createContainerClient` dependency for testability without needing to mock the `@azure/storage-blob` module.
|
|
8
|
+
- 0211390: Added a new `v2` invoke endpoint (`/.backstage/actions/v2/actions/:id/invoke`) that accepts a wrapped body format `{ input, secrets }` with secrets validation. The existing `v1` invoke endpoint remains unchanged for backward compatibility. Updated `DefaultActionsService` to use the `v2` endpoint. Updated `DefaultActionsRegistryService` to expose secrets schema in the actions list response and validate secrets on invocation.
|
|
9
|
+
- 34f21c3: Fix gitlabUrlReader issue with retrieving the repository archive tree
|
|
10
|
+
- Updated dependencies
|
|
11
|
+
- @backstage/integration@2.0.3-next.1
|
|
12
|
+
- @backstage/backend-plugin-api@1.9.2-next.1
|
|
13
|
+
|
|
3
14
|
## 0.17.2-next.0
|
|
4
15
|
|
|
5
16
|
### Patch Changes
|
|
@@ -47,15 +47,17 @@ class DefaultActionsService {
|
|
|
47
47
|
}
|
|
48
48
|
async invoke(opts) {
|
|
49
49
|
const pluginId = this.pluginIdFromActionId(opts.id);
|
|
50
|
+
const version = opts.secrets ? "v2" : "v1";
|
|
51
|
+
const body = version === "v2" ? JSON.stringify({ input: opts.input, secrets: opts.secrets }) : JSON.stringify(opts.input);
|
|
50
52
|
const response = await this.makeRequest({
|
|
51
|
-
path: `/.backstage/actions/
|
|
53
|
+
path: `/.backstage/actions/${version}/actions/${encodeURIComponent(
|
|
52
54
|
opts.id
|
|
53
55
|
)}/invoke`,
|
|
54
56
|
pluginId,
|
|
55
57
|
credentials: opts.credentials,
|
|
56
58
|
options: {
|
|
57
59
|
method: "POST",
|
|
58
|
-
body
|
|
60
|
+
body,
|
|
59
61
|
headers: {
|
|
60
62
|
"Content-Type": "application/json"
|
|
61
63
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DefaultActionsService.cjs.js","sources":["../../../../src/alpha/entrypoints/actions/DefaultActionsService.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n AuthService,\n BackstageCredentials,\n DiscoveryService,\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { ResponseError } from '@backstage/errors';\nimport { JsonObject } from '@backstage/types';\nimport {\n ActionsService,\n ActionsServiceAction,\n} from '@backstage/backend-plugin-api/alpha';\nimport { Minimatch } from 'minimatch';\nimport { Config } from '@backstage/config';\n\nexport class DefaultActionsService implements ActionsService {\n private readonly discovery: DiscoveryService;\n private readonly config: RootConfigService;\n private readonly logger: LoggerService;\n private readonly auth: AuthService;\n\n private constructor(\n discovery: DiscoveryService,\n config: RootConfigService,\n logger: LoggerService,\n auth: AuthService,\n ) {\n this.discovery = discovery;\n this.config = config;\n this.logger = logger;\n this.auth = auth;\n }\n\n static create({\n discovery,\n config,\n logger,\n auth,\n }: {\n discovery: DiscoveryService;\n config: RootConfigService;\n logger: LoggerService;\n auth: AuthService;\n }) {\n return new DefaultActionsService(discovery, config, logger, auth);\n }\n\n async list({ credentials }: { credentials: BackstageCredentials }) {\n const pluginSources =\n this.config.getOptionalStringArray('backend.actions.pluginSources') ?? [];\n\n const remoteActionsList = await Promise.all(\n pluginSources.map(async source => {\n try {\n const response = await this.makeRequest({\n path: `/.backstage/actions/v1/actions`,\n pluginId: source,\n credentials,\n });\n if (!response.ok) {\n throw await ResponseError.fromResponse(response);\n }\n const { actions } = (await response.json()) as {\n actions: ActionsServiceAction;\n };\n\n return actions;\n } catch (error) {\n this.logger.warn(`Failed to fetch actions from ${source}`, error);\n return [];\n }\n }),\n );\n\n return { actions: this.applyFilters(remoteActionsList.flat()) };\n }\n\n async invoke(opts: {\n id: string;\n input?: JsonObject;\n credentials: BackstageCredentials;\n }) {\n const pluginId = this.pluginIdFromActionId(opts.id);\n const response = await this.makeRequest({\n path: `/.backstage/actions/v1/actions/${encodeURIComponent(\n opts.id,\n )}/invoke`,\n pluginId,\n credentials: opts.credentials,\n options: {\n method: 'POST',\n body: JSON.stringify(opts.input),\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n });\n\n if (!response.ok) {\n throw await ResponseError.fromResponse(response);\n }\n\n const { output } = await response.json();\n return { output };\n }\n\n private async makeRequest(opts: {\n path: string;\n pluginId: string;\n options?: RequestInit;\n credentials: BackstageCredentials;\n }) {\n const { path, pluginId, credentials, options } = opts;\n const baseUrl = await this.discovery.getBaseUrl(pluginId);\n\n const { token } = await this.auth.getPluginRequestToken({\n onBehalfOf: credentials,\n targetPluginId: opts.pluginId,\n });\n\n return fetch(`${baseUrl}${path}`, {\n ...options,\n headers: {\n ...options?.headers,\n Authorization: `Bearer ${token}`,\n },\n });\n }\n\n private pluginIdFromActionId(id: string): string {\n const colonIndex = id.indexOf(':');\n if (colonIndex === -1) {\n throw new Error(`Invalid action id: ${id}`);\n }\n return id.substring(0, colonIndex);\n }\n\n private applyFilters(\n actions: ActionsServiceAction[],\n ): ActionsServiceAction[] {\n const filterConfig = this.config.getOptionalConfig(\n 'backend.actions.filter',\n );\n\n if (!filterConfig) {\n return actions;\n }\n\n const includeRules = this.parseFilterRules(\n filterConfig.getOptionalConfigArray('include') ?? [],\n );\n const excludeRules = this.parseFilterRules(\n filterConfig.getOptionalConfigArray('exclude') ?? [],\n );\n\n return actions.filter(action => {\n const excluded = excludeRules.some(rule =>\n this.matchesRule(action, rule),\n );\n\n if (excluded) {\n return false;\n }\n\n // If no include rules, include by default\n if (includeRules.length === 0) {\n return true;\n }\n\n // Must match at least one include rule\n return includeRules.some(rule => this.matchesRule(action, rule));\n });\n }\n\n private parseFilterRules(configArray: Array<Config>): Array<{\n idMatcher?: Minimatch;\n attributes?: Partial<\n Record<'destructive' | 'readOnly' | 'idempotent', boolean>\n >;\n }> {\n return configArray.map(ruleConfig => {\n const idPattern = ruleConfig.getOptionalString('id');\n const attributesConfig = ruleConfig.getOptionalConfig('attributes');\n\n const rule: {\n idMatcher?: Minimatch;\n attributes?: Partial<\n Record<'destructive' | 'readOnly' | 'idempotent', boolean>\n >;\n } = {};\n\n if (idPattern) {\n rule.idMatcher = new Minimatch(idPattern);\n }\n\n if (attributesConfig) {\n rule.attributes = {};\n for (const key of ['destructive', 'readOnly', 'idempotent'] as const) {\n const value = attributesConfig.getOptionalBoolean(key);\n if (value !== undefined) {\n rule.attributes[key] = value;\n }\n }\n }\n\n return rule;\n });\n }\n\n private matchesRule(\n action: ActionsServiceAction,\n rule: {\n idMatcher?: Minimatch;\n attributes?: Partial<\n Record<'destructive' | 'readOnly' | 'idempotent', boolean>\n >;\n },\n ): boolean {\n // If id pattern is specified, it must match\n if (rule.idMatcher && !rule.idMatcher.match(action.id)) {\n return false;\n }\n\n // If attributes are specified, all must match\n if (rule.attributes) {\n for (const [key, value] of Object.entries(rule.attributes)) {\n if (\n action.attributes[\n key as 'destructive' | 'readOnly' | 'idempotent'\n ] !== value\n ) {\n return false;\n }\n }\n }\n\n return true;\n }\n}\n"],"names":["ResponseError","Minimatch"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DefaultActionsService.cjs.js","sources":["../../../../src/alpha/entrypoints/actions/DefaultActionsService.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n AuthService,\n BackstageCredentials,\n DiscoveryService,\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { ResponseError } from '@backstage/errors';\nimport { JsonObject } from '@backstage/types';\nimport {\n ActionsService,\n ActionsServiceAction,\n} from '@backstage/backend-plugin-api/alpha';\nimport { Minimatch } from 'minimatch';\nimport { Config } from '@backstage/config';\n\nexport class DefaultActionsService implements ActionsService {\n private readonly discovery: DiscoveryService;\n private readonly config: RootConfigService;\n private readonly logger: LoggerService;\n private readonly auth: AuthService;\n\n private constructor(\n discovery: DiscoveryService,\n config: RootConfigService,\n logger: LoggerService,\n auth: AuthService,\n ) {\n this.discovery = discovery;\n this.config = config;\n this.logger = logger;\n this.auth = auth;\n }\n\n static create({\n discovery,\n config,\n logger,\n auth,\n }: {\n discovery: DiscoveryService;\n config: RootConfigService;\n logger: LoggerService;\n auth: AuthService;\n }) {\n return new DefaultActionsService(discovery, config, logger, auth);\n }\n\n async list({ credentials }: { credentials: BackstageCredentials }) {\n const pluginSources =\n this.config.getOptionalStringArray('backend.actions.pluginSources') ?? [];\n\n const remoteActionsList = await Promise.all(\n pluginSources.map(async source => {\n try {\n const response = await this.makeRequest({\n path: `/.backstage/actions/v1/actions`,\n pluginId: source,\n credentials,\n });\n if (!response.ok) {\n throw await ResponseError.fromResponse(response);\n }\n const { actions } = (await response.json()) as {\n actions: ActionsServiceAction;\n };\n\n return actions;\n } catch (error) {\n this.logger.warn(`Failed to fetch actions from ${source}`, error);\n return [];\n }\n }),\n );\n\n return { actions: this.applyFilters(remoteActionsList.flat()) };\n }\n\n async invoke(opts: {\n id: string;\n input?: JsonObject;\n secrets?: JsonObject;\n credentials: BackstageCredentials;\n }) {\n const pluginId = this.pluginIdFromActionId(opts.id);\n // Deprecated: remove v1 fallback once all registries support v2\n const version = opts.secrets ? 'v2' : 'v1';\n const body =\n version === 'v2'\n ? JSON.stringify({ input: opts.input, secrets: opts.secrets })\n : JSON.stringify(opts.input);\n\n const response = await this.makeRequest({\n path: `/.backstage/actions/${version}/actions/${encodeURIComponent(\n opts.id,\n )}/invoke`,\n pluginId,\n credentials: opts.credentials,\n options: {\n method: 'POST',\n body,\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n });\n\n if (!response.ok) {\n throw await ResponseError.fromResponse(response);\n }\n\n const { output } = await response.json();\n return { output };\n }\n\n private async makeRequest(opts: {\n path: string;\n pluginId: string;\n options?: RequestInit;\n credentials: BackstageCredentials;\n }) {\n const { path, pluginId, credentials, options } = opts;\n const baseUrl = await this.discovery.getBaseUrl(pluginId);\n\n const { token } = await this.auth.getPluginRequestToken({\n onBehalfOf: credentials,\n targetPluginId: opts.pluginId,\n });\n\n return fetch(`${baseUrl}${path}`, {\n ...options,\n headers: {\n ...options?.headers,\n Authorization: `Bearer ${token}`,\n },\n });\n }\n\n private pluginIdFromActionId(id: string): string {\n const colonIndex = id.indexOf(':');\n if (colonIndex === -1) {\n throw new Error(`Invalid action id: ${id}`);\n }\n return id.substring(0, colonIndex);\n }\n\n private applyFilters(\n actions: ActionsServiceAction[],\n ): ActionsServiceAction[] {\n const filterConfig = this.config.getOptionalConfig(\n 'backend.actions.filter',\n );\n\n if (!filterConfig) {\n return actions;\n }\n\n const includeRules = this.parseFilterRules(\n filterConfig.getOptionalConfigArray('include') ?? [],\n );\n const excludeRules = this.parseFilterRules(\n filterConfig.getOptionalConfigArray('exclude') ?? [],\n );\n\n return actions.filter(action => {\n const excluded = excludeRules.some(rule =>\n this.matchesRule(action, rule),\n );\n\n if (excluded) {\n return false;\n }\n\n // If no include rules, include by default\n if (includeRules.length === 0) {\n return true;\n }\n\n // Must match at least one include rule\n return includeRules.some(rule => this.matchesRule(action, rule));\n });\n }\n\n private parseFilterRules(configArray: Array<Config>): Array<{\n idMatcher?: Minimatch;\n attributes?: Partial<\n Record<'destructive' | 'readOnly' | 'idempotent', boolean>\n >;\n }> {\n return configArray.map(ruleConfig => {\n const idPattern = ruleConfig.getOptionalString('id');\n const attributesConfig = ruleConfig.getOptionalConfig('attributes');\n\n const rule: {\n idMatcher?: Minimatch;\n attributes?: Partial<\n Record<'destructive' | 'readOnly' | 'idempotent', boolean>\n >;\n } = {};\n\n if (idPattern) {\n rule.idMatcher = new Minimatch(idPattern);\n }\n\n if (attributesConfig) {\n rule.attributes = {};\n for (const key of ['destructive', 'readOnly', 'idempotent'] as const) {\n const value = attributesConfig.getOptionalBoolean(key);\n if (value !== undefined) {\n rule.attributes[key] = value;\n }\n }\n }\n\n return rule;\n });\n }\n\n private matchesRule(\n action: ActionsServiceAction,\n rule: {\n idMatcher?: Minimatch;\n attributes?: Partial<\n Record<'destructive' | 'readOnly' | 'idempotent', boolean>\n >;\n },\n ): boolean {\n // If id pattern is specified, it must match\n if (rule.idMatcher && !rule.idMatcher.match(action.id)) {\n return false;\n }\n\n // If attributes are specified, all must match\n if (rule.attributes) {\n for (const [key, value] of Object.entries(rule.attributes)) {\n if (\n action.attributes[\n key as 'destructive' | 'readOnly' | 'idempotent'\n ] !== value\n ) {\n return false;\n }\n }\n }\n\n return true;\n }\n}\n"],"names":["ResponseError","Minimatch"],"mappings":"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"}
|
|
@@ -72,56 +72,84 @@ class DefaultActionsRegistryService {
|
|
|
72
72
|
examples: action.examples,
|
|
73
73
|
schema: {
|
|
74
74
|
input: action.schema?.input ? zodToJsonSchema__default.default(action.schema.input(v3.z)) : zodToJsonSchema__default.default(v3.z.object({})),
|
|
75
|
-
output: action.schema?.output ? zodToJsonSchema__default.default(action.schema.output(v3.z)) : zodToJsonSchema__default.default(v3.z.object({}))
|
|
75
|
+
output: action.schema?.output ? zodToJsonSchema__default.default(action.schema.output(v3.z)) : zodToJsonSchema__default.default(v3.z.object({})),
|
|
76
|
+
...action.schema?.secrets && {
|
|
77
|
+
secrets: zodToJsonSchema__default.default(action.schema.secrets(v3.z))
|
|
78
|
+
}
|
|
76
79
|
}
|
|
77
80
|
}))
|
|
78
81
|
});
|
|
79
82
|
});
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
throw new errors.NotFoundError(
|
|
100
|
-
`Action "${req.params.actionId}" not found`
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
const input = action.schema?.input ? action.schema.input(v3.z).safeParse(req.body) : { success: true, data: void 0 };
|
|
105
|
-
if (!input.success) {
|
|
106
|
-
throw new errors.InputError(
|
|
107
|
-
`Invalid input to action "${req.params.actionId}"`,
|
|
108
|
-
input.error
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
const result = await action.action({
|
|
112
|
-
input: input.data,
|
|
113
|
-
credentials,
|
|
114
|
-
logger: this.logger
|
|
115
|
-
});
|
|
116
|
-
const output = action.schema?.output ? action.schema.output(v3.z).safeParse(result?.output) : { success: true, data: result?.output };
|
|
117
|
-
if (!output.success) {
|
|
118
|
-
throw new errors.InputError(
|
|
119
|
-
`Invalid output from action "${req.params.actionId}"`,
|
|
120
|
-
output.error
|
|
83
|
+
const invokeHandler = (opts) => async (req, res) => {
|
|
84
|
+
const credentials = await this.httpAuth.credentials(req);
|
|
85
|
+
if (this.auth.isPrincipal(credentials, "none")) {
|
|
86
|
+
throw new errors.NotAllowedError(
|
|
87
|
+
`Actions must be invoked by an authenticated principal, not an anonymous request`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
const action = this.actions.get(req.params.actionId);
|
|
91
|
+
if (!action) {
|
|
92
|
+
throw new errors.NotFoundError(`Action "${req.params.actionId}" not found`);
|
|
93
|
+
}
|
|
94
|
+
if (action.visibilityPermission) {
|
|
95
|
+
const [decision] = await this.permissions.authorize(
|
|
96
|
+
[{ permission: action.visibilityPermission }],
|
|
97
|
+
{ credentials }
|
|
98
|
+
);
|
|
99
|
+
if (decision.result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
|
|
100
|
+
throw new errors.NotFoundError(
|
|
101
|
+
`Action "${req.params.actionId}" not found`
|
|
121
102
|
);
|
|
122
103
|
}
|
|
123
|
-
res.json({ output: output.data });
|
|
124
104
|
}
|
|
105
|
+
const rawInput = opts.wrapped ? req.body.input : req.body;
|
|
106
|
+
const rawSecrets = opts.wrapped ? req.body.secrets : void 0;
|
|
107
|
+
const input = action.schema?.input ? action.schema.input(v3.z).safeParse(rawInput) : { success: true, data: void 0 };
|
|
108
|
+
if (!input.success) {
|
|
109
|
+
throw new errors.InputError(
|
|
110
|
+
`Invalid input to action "${req.params.actionId}"`,
|
|
111
|
+
input.error
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
if (action.schema?.secrets && !rawSecrets) {
|
|
115
|
+
throw new errors.InputError(
|
|
116
|
+
`Action "${req.params.actionId}" requires secrets but none were provided`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
if (!action.schema?.secrets && rawSecrets) {
|
|
120
|
+
throw new errors.InputError(
|
|
121
|
+
`Action "${req.params.actionId}" does not accept secrets`
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
const secrets = action.schema?.secrets ? action.schema.secrets(v3.z).safeParse(rawSecrets) : { success: true, data: void 0 };
|
|
125
|
+
if (!secrets.success) {
|
|
126
|
+
throw new errors.InputError(
|
|
127
|
+
`Invalid secrets for action "${req.params.actionId}"`,
|
|
128
|
+
secrets.error
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
const result = await action.action({
|
|
132
|
+
input: input.data,
|
|
133
|
+
secrets: secrets.data,
|
|
134
|
+
credentials,
|
|
135
|
+
logger: this.logger
|
|
136
|
+
});
|
|
137
|
+
const output = action.schema?.output ? action.schema.output(v3.z).safeParse(result?.output) : { success: true, data: result?.output };
|
|
138
|
+
if (!output.success) {
|
|
139
|
+
throw new errors.InputError(
|
|
140
|
+
`Invalid output from action "${req.params.actionId}"`,
|
|
141
|
+
output.error
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
res.json({ output: output.data });
|
|
145
|
+
};
|
|
146
|
+
router.post(
|
|
147
|
+
"/.backstage/actions/v1/actions/:actionId/invoke",
|
|
148
|
+
invokeHandler({ wrapped: false })
|
|
149
|
+
);
|
|
150
|
+
router.post(
|
|
151
|
+
"/.backstage/actions/v2/actions/:actionId/invoke",
|
|
152
|
+
invokeHandler({ wrapped: true })
|
|
125
153
|
);
|
|
126
154
|
return router;
|
|
127
155
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DefaultActionsRegistryService.cjs.js","sources":["../../../../src/alpha/entrypoints/actionsRegistry/DefaultActionsRegistryService.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AuthService,\n BackstageCredentials,\n HttpAuthService,\n LoggerService,\n PermissionsRegistryService,\n PermissionsService,\n PluginMetadataService,\n} from '@backstage/backend-plugin-api';\nimport PromiseRouter from 'express-promise-router';\nimport { Router, json } from 'express';\nimport { z, AnyZodObject } from 'zod/v3';\nimport zodToJsonSchema from 'zod-to-json-schema';\nimport {\n ActionsRegistryActionOptions,\n ActionsRegistryService,\n} from '@backstage/backend-plugin-api/alpha';\nimport { InputError, NotAllowedError, NotFoundError } from '@backstage/errors';\nimport { AuthorizeResult } from '@backstage/plugin-permission-common';\n\ntype ActionEntry = [string, ActionsRegistryActionOptions<any, any>];\n\nexport class DefaultActionsRegistryService implements ActionsRegistryService {\n private actions: Map<string, ActionsRegistryActionOptions<any, any>> =\n new Map();\n\n private readonly logger: LoggerService;\n private readonly httpAuth: HttpAuthService;\n private readonly auth: AuthService;\n private readonly metadata: PluginMetadataService;\n private readonly permissions: PermissionsService;\n private readonly permissionsRegistry: PermissionsRegistryService;\n\n private constructor(\n logger: LoggerService,\n httpAuth: HttpAuthService,\n auth: AuthService,\n metadata: PluginMetadataService,\n permissions: PermissionsService,\n permissionsRegistry: PermissionsRegistryService,\n ) {\n this.logger = logger;\n this.httpAuth = httpAuth;\n this.auth = auth;\n this.metadata = metadata;\n this.permissions = permissions;\n this.permissionsRegistry = permissionsRegistry;\n }\n\n static create({\n httpAuth,\n logger,\n auth,\n metadata,\n permissions,\n permissionsRegistry,\n }: {\n httpAuth: HttpAuthService;\n logger: LoggerService;\n auth: AuthService;\n metadata: PluginMetadataService;\n permissions: PermissionsService;\n permissionsRegistry: PermissionsRegistryService;\n }): DefaultActionsRegistryService {\n return new DefaultActionsRegistryService(\n logger,\n httpAuth,\n auth,\n metadata,\n permissions,\n permissionsRegistry,\n );\n }\n\n createRouter(): Router {\n const router = PromiseRouter();\n router.use('/.backstage/actions/', json());\n\n router.get('/.backstage/actions/v1/actions', async (req, res) => {\n const credentials = await this.httpAuth.credentials(req);\n const entries = Array.from(this.actions.entries());\n\n const allowedActions = await this.filterByPermissions(\n entries,\n credentials,\n );\n\n return res.json({\n actions: allowedActions.map(([id, action]) => ({\n id,\n name: action.name,\n title: action.title,\n description: action.description,\n pluginId: this.metadata.getId(),\n attributes: {\n // Inspired by the @modelcontextprotocol/sdk defaults for the hints.\n // https://github.com/modelcontextprotocol/typescript-sdk/blob/dd69efa1de8646bb6b195ff8d5f52e13739f4550/src/types.ts#L777-L812\n destructive: action.attributes?.destructive ?? true,\n idempotent: action.attributes?.idempotent ?? false,\n readOnly: action.attributes?.readOnly ?? false,\n },\n examples: action.examples,\n schema: {\n input: action.schema?.input\n ? zodToJsonSchema(action.schema.input(z))\n : zodToJsonSchema(z.object({})),\n output: action.schema?.output\n ? zodToJsonSchema(action.schema.output(z))\n : zodToJsonSchema(z.object({})),\n },\n })),\n });\n });\n\n router.post(\n '/.backstage/actions/v1/actions/:actionId/invoke',\n async (req, res) => {\n const credentials = await this.httpAuth.credentials(req);\n if (this.auth.isPrincipal(credentials, 'none')) {\n throw new NotAllowedError(\n `Actions must be invoked by an authenticated principal, not an anonymous request`,\n );\n }\n\n const action = this.actions.get(req.params.actionId);\n\n if (!action) {\n throw new NotFoundError(`Action \"${req.params.actionId}\" not found`);\n }\n\n if (action.visibilityPermission) {\n const [decision] = await this.permissions.authorize(\n [{ permission: action.visibilityPermission }],\n { credentials },\n );\n if (decision.result !== AuthorizeResult.ALLOW) {\n throw new NotFoundError(\n `Action \"${req.params.actionId}\" not found`,\n );\n }\n }\n\n const input = action.schema?.input\n ? action.schema.input(z).safeParse(req.body)\n : ({ success: true, data: undefined } as const);\n\n if (!input.success) {\n throw new InputError(\n `Invalid input to action \"${req.params.actionId}\"`,\n input.error,\n );\n }\n\n const result = await action.action({\n input: input.data,\n credentials,\n logger: this.logger,\n });\n\n const output = action.schema?.output\n ? action.schema.output(z).safeParse(result?.output)\n : ({ success: true, data: result?.output } as const);\n\n if (!output.success) {\n throw new InputError(\n `Invalid output from action \"${req.params.actionId}\"`,\n output.error,\n );\n }\n\n res.json({ output: output.data });\n },\n );\n return router;\n }\n\n register<\n TInputSchema extends AnyZodObject,\n TOutputSchema extends AnyZodObject,\n >(options: ActionsRegistryActionOptions<TInputSchema, TOutputSchema>): void {\n const id = `${this.metadata.getId()}:${options.name}`;\n\n if (this.actions.has(id)) {\n throw new Error(`Action with id \"${id}\" is already registered`);\n }\n\n if (options.visibilityPermission) {\n this.permissionsRegistry.addPermissions([options.visibilityPermission]);\n }\n\n this.actions.set(id, options);\n }\n\n private async filterByPermissions(\n entries: ActionEntry[],\n credentials: BackstageCredentials,\n ): Promise<ActionEntry[]> {\n const permissionedEntries = entries.filter(\n ([_, action]) => action.visibilityPermission,\n );\n\n if (permissionedEntries.length === 0) {\n return entries;\n }\n\n const decisions = await this.permissions.authorize(\n permissionedEntries.map(([_, action]) => ({\n permission: action.visibilityPermission!,\n })),\n { credentials },\n );\n\n const deniedIds = new Set(\n permissionedEntries\n .filter((_, index) => decisions[index].result !== AuthorizeResult.ALLOW)\n .map(([id]) => id),\n );\n\n return entries.filter(([id]) => !deniedIds.has(id));\n }\n}\n"],"names":["PromiseRouter","json","zodToJsonSchema","z","NotAllowedError","NotFoundError","AuthorizeResult","InputError"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DefaultActionsRegistryService.cjs.js","sources":["../../../../src/alpha/entrypoints/actionsRegistry/DefaultActionsRegistryService.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AuthService,\n BackstageCredentials,\n HttpAuthService,\n LoggerService,\n PermissionsRegistryService,\n PermissionsService,\n PluginMetadataService,\n} from '@backstage/backend-plugin-api';\nimport PromiseRouter from 'express-promise-router';\nimport { Router, json } from 'express';\nimport { z, AnyZodObject } from 'zod/v3';\nimport zodToJsonSchema from 'zod-to-json-schema';\nimport {\n ActionsRegistryActionOptions,\n ActionsRegistryService,\n} from '@backstage/backend-plugin-api/alpha';\nimport { InputError, NotAllowedError, NotFoundError } from '@backstage/errors';\nimport { AuthorizeResult } from '@backstage/plugin-permission-common';\n\ntype ActionEntry = [string, ActionsRegistryActionOptions<any, any, any>];\n\nexport class DefaultActionsRegistryService implements ActionsRegistryService {\n private actions: Map<string, ActionsRegistryActionOptions<any, any, any>> =\n new Map();\n\n private readonly logger: LoggerService;\n private readonly httpAuth: HttpAuthService;\n private readonly auth: AuthService;\n private readonly metadata: PluginMetadataService;\n private readonly permissions: PermissionsService;\n private readonly permissionsRegistry: PermissionsRegistryService;\n\n private constructor(\n logger: LoggerService,\n httpAuth: HttpAuthService,\n auth: AuthService,\n metadata: PluginMetadataService,\n permissions: PermissionsService,\n permissionsRegistry: PermissionsRegistryService,\n ) {\n this.logger = logger;\n this.httpAuth = httpAuth;\n this.auth = auth;\n this.metadata = metadata;\n this.permissions = permissions;\n this.permissionsRegistry = permissionsRegistry;\n }\n\n static create({\n httpAuth,\n logger,\n auth,\n metadata,\n permissions,\n permissionsRegistry,\n }: {\n httpAuth: HttpAuthService;\n logger: LoggerService;\n auth: AuthService;\n metadata: PluginMetadataService;\n permissions: PermissionsService;\n permissionsRegistry: PermissionsRegistryService;\n }): DefaultActionsRegistryService {\n return new DefaultActionsRegistryService(\n logger,\n httpAuth,\n auth,\n metadata,\n permissions,\n permissionsRegistry,\n );\n }\n\n createRouter(): Router {\n const router = PromiseRouter();\n router.use('/.backstage/actions/', json());\n\n router.get('/.backstage/actions/v1/actions', async (req, res) => {\n const credentials = await this.httpAuth.credentials(req);\n const entries = Array.from(this.actions.entries());\n\n const allowedActions = await this.filterByPermissions(\n entries,\n credentials,\n );\n\n return res.json({\n actions: allowedActions.map(([id, action]) => ({\n id,\n name: action.name,\n title: action.title,\n description: action.description,\n pluginId: this.metadata.getId(),\n attributes: {\n // Inspired by the @modelcontextprotocol/sdk defaults for the hints.\n // https://github.com/modelcontextprotocol/typescript-sdk/blob/dd69efa1de8646bb6b195ff8d5f52e13739f4550/src/types.ts#L777-L812\n destructive: action.attributes?.destructive ?? true,\n idempotent: action.attributes?.idempotent ?? false,\n readOnly: action.attributes?.readOnly ?? false,\n },\n examples: action.examples,\n schema: {\n input: action.schema?.input\n ? zodToJsonSchema(action.schema.input(z))\n : zodToJsonSchema(z.object({})),\n output: action.schema?.output\n ? zodToJsonSchema(action.schema.output(z))\n : zodToJsonSchema(z.object({})),\n ...(action.schema?.secrets && {\n secrets: zodToJsonSchema(action.schema.secrets(z)),\n }),\n },\n })),\n });\n });\n\n const invokeHandler =\n (opts: { wrapped: boolean }) =>\n async (\n req: import('express').Request,\n res: import('express').Response,\n ) => {\n const credentials = await this.httpAuth.credentials(req);\n if (this.auth.isPrincipal(credentials, 'none')) {\n throw new NotAllowedError(\n `Actions must be invoked by an authenticated principal, not an anonymous request`,\n );\n }\n\n const action = this.actions.get(req.params.actionId);\n\n if (!action) {\n throw new NotFoundError(`Action \"${req.params.actionId}\" not found`);\n }\n\n if (action.visibilityPermission) {\n const [decision] = await this.permissions.authorize(\n [{ permission: action.visibilityPermission }],\n { credentials },\n );\n if (decision.result !== AuthorizeResult.ALLOW) {\n throw new NotFoundError(\n `Action \"${req.params.actionId}\" not found`,\n );\n }\n }\n\n const rawInput = opts.wrapped ? req.body.input : req.body;\n const rawSecrets = opts.wrapped ? req.body.secrets : undefined;\n\n const input = action.schema?.input\n ? action.schema.input(z).safeParse(rawInput)\n : ({ success: true, data: undefined } as const);\n\n if (!input.success) {\n throw new InputError(\n `Invalid input to action \"${req.params.actionId}\"`,\n input.error,\n );\n }\n\n if (action.schema?.secrets && !rawSecrets) {\n throw new InputError(\n `Action \"${req.params.actionId}\" requires secrets but none were provided`,\n );\n }\n\n if (!action.schema?.secrets && rawSecrets) {\n throw new InputError(\n `Action \"${req.params.actionId}\" does not accept secrets`,\n );\n }\n\n const secrets = action.schema?.secrets\n ? action.schema.secrets(z).safeParse(rawSecrets)\n : ({ success: true, data: undefined } as const);\n\n if (!secrets.success) {\n throw new InputError(\n `Invalid secrets for action \"${req.params.actionId}\"`,\n secrets.error,\n );\n }\n\n const result = await action.action({\n input: input.data,\n secrets: secrets.data,\n credentials,\n logger: this.logger,\n });\n\n const output = action.schema?.output\n ? action.schema.output(z).safeParse(result?.output)\n : ({ success: true, data: result?.output } as const);\n\n if (!output.success) {\n throw new InputError(\n `Invalid output from action \"${req.params.actionId}\"`,\n output.error,\n );\n }\n\n res.json({ output: output.data });\n };\n\n // Deprecated: remove v1 invoke route once all callers have migrated to v2\n router.post(\n '/.backstage/actions/v1/actions/:actionId/invoke',\n invokeHandler({ wrapped: false }),\n );\n\n router.post(\n '/.backstage/actions/v2/actions/:actionId/invoke',\n invokeHandler({ wrapped: true }),\n );\n\n return router;\n }\n\n register<\n TInputSchema extends AnyZodObject,\n TOutputSchema extends AnyZodObject,\n TSecretsSchema extends AnyZodObject | undefined = undefined,\n >(\n options: ActionsRegistryActionOptions<\n TInputSchema,\n TOutputSchema,\n TSecretsSchema\n >,\n ): void {\n const id = `${this.metadata.getId()}:${options.name}`;\n\n if (this.actions.has(id)) {\n throw new Error(`Action with id \"${id}\" is already registered`);\n }\n\n if (options.visibilityPermission) {\n this.permissionsRegistry.addPermissions([options.visibilityPermission]);\n }\n\n this.actions.set(id, options);\n }\n\n private async filterByPermissions(\n entries: ActionEntry[],\n credentials: BackstageCredentials,\n ): Promise<ActionEntry[]> {\n const permissionedEntries = entries.filter(\n ([_, action]) => action.visibilityPermission,\n );\n\n if (permissionedEntries.length === 0) {\n return entries;\n }\n\n const decisions = await this.permissions.authorize(\n permissionedEntries.map(([_, action]) => ({\n permission: action.visibilityPermission!,\n })),\n { credentials },\n );\n\n const deniedIds = new Set(\n permissionedEntries\n .filter((_, index) => decisions[index].result !== AuthorizeResult.ALLOW)\n .map(([id]) => id),\n );\n\n return entries.filter(([id]) => !deniedIds.has(id));\n }\n}\n"],"names":["PromiseRouter","json","zodToJsonSchema","z","NotAllowedError","NotFoundError","AuthorizeResult","InputError"],"mappings":"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"}
|
|
@@ -35,16 +35,16 @@ class AzureBlobStorageUrlReader {
|
|
|
35
35
|
return { reader, predicate };
|
|
36
36
|
});
|
|
37
37
|
};
|
|
38
|
-
// private readonly blobServiceClient: BlobServiceClient;
|
|
39
|
-
credsManager;
|
|
40
38
|
integration;
|
|
41
39
|
deps;
|
|
42
40
|
constructor(credsManager, integration, deps) {
|
|
43
|
-
this.credsManager = credsManager;
|
|
44
41
|
this.integration = integration;
|
|
45
|
-
this.deps =
|
|
42
|
+
this.deps = {
|
|
43
|
+
...deps,
|
|
44
|
+
createContainerClient: deps.createContainerClient ?? this.#defaultCreateContainerClient.bind(this, credsManager)
|
|
45
|
+
};
|
|
46
46
|
}
|
|
47
|
-
async
|
|
47
|
+
async #defaultCreateContainerClient(credsManager, containerName) {
|
|
48
48
|
const accountName = this.integration.config.accountName;
|
|
49
49
|
const accountKey = this.integration.config.accountKey;
|
|
50
50
|
if (accountKey && accountName) {
|
|
@@ -55,9 +55,7 @@ class AzureBlobStorageUrlReader {
|
|
|
55
55
|
);
|
|
56
56
|
return blobServiceClient2.getContainerClient(containerName);
|
|
57
57
|
}
|
|
58
|
-
const credential = await
|
|
59
|
-
accountName
|
|
60
|
-
);
|
|
58
|
+
const credential = await credsManager.getCredentials(accountName);
|
|
61
59
|
let blobServiceClientUrl;
|
|
62
60
|
if (this.integration.config.endpoint) {
|
|
63
61
|
if (this.integration.config.sasToken) {
|
|
@@ -82,7 +80,7 @@ class AzureBlobStorageUrlReader {
|
|
|
82
80
|
const { etag, lastModifiedAfter } = options ?? {};
|
|
83
81
|
try {
|
|
84
82
|
const { path, container } = parseUrl(url);
|
|
85
|
-
const containerClient = await this.createContainerClient(container);
|
|
83
|
+
const containerClient = await this.deps.createContainerClient(container);
|
|
86
84
|
const blobClient = containerClient.getBlobClient(path);
|
|
87
85
|
const getBlobOptions = {
|
|
88
86
|
abortSignal: options?.signal,
|
|
@@ -116,7 +114,7 @@ class AzureBlobStorageUrlReader {
|
|
|
116
114
|
async readTree(url, options) {
|
|
117
115
|
try {
|
|
118
116
|
const { path, container } = parseUrl(url);
|
|
119
|
-
const containerClient = await this.createContainerClient(container);
|
|
117
|
+
const containerClient = await this.deps.createContainerClient(container);
|
|
120
118
|
const blobs = containerClient.listBlobsFlat({ prefix: path });
|
|
121
119
|
const responses = [];
|
|
122
120
|
for await (const blob of blobs) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AzureBlobStorageUrlReader.cjs.js","sources":["../../../../src/entrypoints/urlReader/lib/AzureBlobStorageUrlReader.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n BlobDownloadOptions,\n BlobServiceClient,\n ContainerClient,\n StorageSharedKeyCredential,\n} from '@azure/storage-blob';\nimport { ReaderFactory, ReadTreeResponseFactory } from './types';\nimport { toError, ForwardedError, NotModifiedError } from '@backstage/errors';\nimport { Readable } from 'node:stream';\nimport { relative } from 'node:path/posix';\nimport { ReadUrlResponseFactory } from './ReadUrlResponseFactory';\nimport {\n AzureBlobStorageIntegration,\n AzureCredentialsManager,\n DefaultAzureCredentialsManager,\n ScmIntegrations,\n} from '@backstage/integration';\nimport {\n UrlReaderService,\n UrlReaderServiceReadTreeOptions,\n UrlReaderServiceReadTreeResponse,\n UrlReaderServiceReadUrlOptions,\n UrlReaderServiceReadUrlResponse,\n UrlReaderServiceSearchOptions,\n UrlReaderServiceSearchResponse,\n} from '@backstage/backend-plugin-api';\n\nexport function parseUrl(url: string): { path: string; container: string } {\n const parsedUrl = new URL(url);\n const pathSegments = parsedUrl.pathname.split('/').filter(Boolean);\n\n if (pathSegments.length < 1) {\n throw new Error(`Invalid Azure Blob Storage URL format: ${url}`);\n }\n\n // First segment is the container name, rest is the blob path\n const container = pathSegments[0];\n const path = pathSegments.slice(1).join('/');\n\n return { path, container };\n}\n\n/**\n * Implements a {@link @backstage/backend-plugin-api#UrlReaderService} for Azure storage accounts urls.\n *\n * @public\n */\nexport class AzureBlobStorageUrlReader implements UrlReaderService {\n static factory: ReaderFactory = ({ config, treeResponseFactory }) => {\n const integrations = ScmIntegrations.fromConfig(config);\n\n const credsManager =\n DefaultAzureCredentialsManager.fromIntegrations(integrations);\n\n return integrations.azureBlobStorage.list().map(integrationConfig => {\n const reader = new AzureBlobStorageUrlReader(\n credsManager,\n integrationConfig,\n {\n treeResponseFactory,\n },\n );\n\n const predicate = (url: URL) =>\n url.host.endsWith(\n `${integrationConfig.config.accountName}.${integrationConfig.config.host}`,\n );\n return { reader, predicate };\n });\n };\n\n // private readonly blobServiceClient: BlobServiceClient;\n\n private readonly credsManager: AzureCredentialsManager;\n private readonly integration: AzureBlobStorageIntegration;\n private readonly deps: {\n treeResponseFactory: ReadTreeResponseFactory;\n };\n\n constructor(\n credsManager: AzureCredentialsManager,\n integration: AzureBlobStorageIntegration,\n deps: {\n treeResponseFactory: ReadTreeResponseFactory;\n },\n ) {\n this.credsManager = credsManager;\n this.integration = integration;\n this.deps = deps;\n }\n\n private async createContainerClient(\n containerName: string,\n ): Promise<ContainerClient> {\n const accountName = this.integration.config.accountName; // Use the account name from the integration config\n const accountKey = this.integration.config.accountKey; // Get the account key if it exists\n\n if (accountKey && accountName) {\n const creds = new StorageSharedKeyCredential(accountName, accountKey);\n const blobServiceClient = new BlobServiceClient(\n `https://${accountName}.${this.integration.config.host}`,\n creds,\n );\n return blobServiceClient.getContainerClient(containerName);\n }\n // Use the credentials manager to get the correct credentials\n const credential = await this.credsManager.getCredentials(\n accountName as string,\n );\n\n let blobServiceClientUrl: string;\n\n if (this.integration.config.endpoint) {\n if (this.integration.config.sasToken) {\n blobServiceClientUrl = `${this.integration.config.endpoint}?${this.integration.config.sasToken}`;\n } else {\n blobServiceClientUrl = `${this.integration.config.endpoint}`;\n }\n } else {\n blobServiceClientUrl = `https://${this.integration.config.accountName}.${this.integration.config.host}`;\n }\n\n const blobServiceClient = new BlobServiceClient(\n blobServiceClientUrl,\n credential,\n );\n return blobServiceClient.getContainerClient(containerName);\n }\n\n async read(url: string): Promise<Buffer> {\n const response = await this.readUrl(url);\n return response.buffer();\n }\n\n async readUrl(\n url: string,\n options?: UrlReaderServiceReadUrlOptions,\n ): Promise<UrlReaderServiceReadUrlResponse> {\n const { etag, lastModifiedAfter } = options ?? {};\n\n try {\n const { path, container } = parseUrl(url);\n\n const containerClient = await this.createContainerClient(container);\n const blobClient = containerClient.getBlobClient(path);\n\n const getBlobOptions: BlobDownloadOptions = {\n abortSignal: options?.signal,\n conditions: {\n ...(etag && { ifNoneMatch: etag }),\n ...(lastModifiedAfter && { ifModifiedSince: lastModifiedAfter }),\n },\n };\n\n const downloadBlockBlobResponse = await blobClient.download(\n 0,\n undefined,\n getBlobOptions,\n );\n\n return ReadUrlResponseFactory.fromReadable(\n downloadBlockBlobResponse.readableStreamBody as Readable,\n {\n etag: downloadBlockBlobResponse.etag,\n lastModifiedAt: downloadBlockBlobResponse.lastModified,\n },\n );\n } catch (e) {\n if (e.statusCode === 304) {\n throw new NotModifiedError();\n }\n\n throw new ForwardedError(\n 'Could not retrieve file from Azure Blob Storage',\n e,\n );\n }\n }\n\n async readTree(\n url: string,\n options?: UrlReaderServiceReadTreeOptions,\n ): Promise<UrlReaderServiceReadTreeResponse> {\n try {\n const { path, container } = parseUrl(url);\n\n const containerClient = await this.createContainerClient(container);\n const blobs = containerClient.listBlobsFlat({ prefix: path });\n\n const responses = [];\n\n for await (const blob of blobs) {\n const blobClient = containerClient.getBlobClient(blob.name);\n\n const downloadBlockBlobResponse = await blobClient.download(\n undefined,\n undefined,\n { abortSignal: options?.signal },\n );\n\n responses.push({\n data: Readable.from(\n downloadBlockBlobResponse.readableStreamBody as Readable,\n ),\n path: relative(path, blob.name),\n lastModifiedAt: blob.properties.lastModified,\n });\n }\n\n return this.deps.treeResponseFactory.fromReadableArray(responses);\n } catch (e) {\n throw new ForwardedError(\n 'Could not retrieve file tree from Azure Blob Storage',\n e,\n );\n }\n }\n\n async search(\n url: string,\n options?: UrlReaderServiceSearchOptions,\n ): Promise<UrlReaderServiceSearchResponse> {\n const { path } = parseUrl(url);\n\n if (path.match(/[*?]/)) {\n throw new Error(\n 'Glob search pattern not implemented for AzureBlobStorageUrlReader',\n );\n }\n\n try {\n const data = await this.readUrl(url, options);\n\n return {\n files: [\n {\n url: url,\n content: data.buffer,\n lastModifiedAt: data.lastModifiedAt,\n },\n ],\n etag: data.etag ?? '',\n };\n } catch (e) {\n throw toError(e);\n }\n }\n\n toString() {\n const accountName = this.integration.config.accountName;\n const accountKey = this.integration.config.accountKey;\n return `azureBlobStorage{accountName=${accountName},authed=${Boolean(\n accountKey,\n )}}`;\n }\n}\n"],"names":["ScmIntegrations","DefaultAzureCredentialsManager","StorageSharedKeyCredential","blobServiceClient","BlobServiceClient","ReadUrlResponseFactory","NotModifiedError","ForwardedError","Readable","relative","toError"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AzureBlobStorageUrlReader.cjs.js","sources":["../../../../src/entrypoints/urlReader/lib/AzureBlobStorageUrlReader.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n BlobDownloadOptions,\n BlobServiceClient,\n ContainerClient,\n StorageSharedKeyCredential,\n} from '@azure/storage-blob';\nimport { ReaderFactory, ReadTreeResponseFactory } from './types';\nimport { toError, ForwardedError, NotModifiedError } from '@backstage/errors';\nimport { Readable } from 'node:stream';\nimport { relative } from 'node:path/posix';\nimport { ReadUrlResponseFactory } from './ReadUrlResponseFactory';\nimport {\n AzureBlobStorageIntegration,\n AzureCredentialsManager,\n DefaultAzureCredentialsManager,\n ScmIntegrations,\n} from '@backstage/integration';\nimport {\n UrlReaderService,\n UrlReaderServiceReadTreeOptions,\n UrlReaderServiceReadTreeResponse,\n UrlReaderServiceReadUrlOptions,\n UrlReaderServiceReadUrlResponse,\n UrlReaderServiceSearchOptions,\n UrlReaderServiceSearchResponse,\n} from '@backstage/backend-plugin-api';\n\nexport function parseUrl(url: string): { path: string; container: string } {\n const parsedUrl = new URL(url);\n const pathSegments = parsedUrl.pathname.split('/').filter(Boolean);\n\n if (pathSegments.length < 1) {\n throw new Error(`Invalid Azure Blob Storage URL format: ${url}`);\n }\n\n // First segment is the container name, rest is the blob path\n const container = pathSegments[0];\n const path = pathSegments.slice(1).join('/');\n\n return { path, container };\n}\n\n/**\n * Implements a {@link @backstage/backend-plugin-api#UrlReaderService} for Azure storage accounts urls.\n *\n * @public\n */\nexport class AzureBlobStorageUrlReader implements UrlReaderService {\n static factory: ReaderFactory = ({ config, treeResponseFactory }) => {\n const integrations = ScmIntegrations.fromConfig(config);\n\n const credsManager =\n DefaultAzureCredentialsManager.fromIntegrations(integrations);\n\n return integrations.azureBlobStorage.list().map(integrationConfig => {\n const reader = new AzureBlobStorageUrlReader(\n credsManager,\n integrationConfig,\n {\n treeResponseFactory,\n },\n );\n\n const predicate = (url: URL) =>\n url.host.endsWith(\n `${integrationConfig.config.accountName}.${integrationConfig.config.host}`,\n );\n return { reader, predicate };\n });\n };\n\n private readonly integration: AzureBlobStorageIntegration;\n private readonly deps: {\n treeResponseFactory: ReadTreeResponseFactory;\n createContainerClient: (containerName: string) => Promise<ContainerClient>;\n };\n\n constructor(\n credsManager: AzureCredentialsManager,\n integration: AzureBlobStorageIntegration,\n deps: {\n treeResponseFactory: ReadTreeResponseFactory;\n createContainerClient?: (\n containerName: string,\n ) => Promise<ContainerClient>;\n },\n ) {\n this.integration = integration;\n this.deps = {\n ...deps,\n createContainerClient:\n deps.createContainerClient ??\n this.#defaultCreateContainerClient.bind(this, credsManager),\n };\n }\n\n async #defaultCreateContainerClient(\n credsManager: AzureCredentialsManager,\n containerName: string,\n ): Promise<ContainerClient> {\n const accountName = this.integration.config.accountName;\n const accountKey = this.integration.config.accountKey;\n\n if (accountKey && accountName) {\n const creds = new StorageSharedKeyCredential(accountName, accountKey);\n const blobServiceClient = new BlobServiceClient(\n `https://${accountName}.${this.integration.config.host}`,\n creds,\n );\n return blobServiceClient.getContainerClient(containerName);\n }\n\n const credential = await credsManager.getCredentials(accountName as string);\n\n let blobServiceClientUrl: string;\n\n if (this.integration.config.endpoint) {\n if (this.integration.config.sasToken) {\n blobServiceClientUrl = `${this.integration.config.endpoint}?${this.integration.config.sasToken}`;\n } else {\n blobServiceClientUrl = `${this.integration.config.endpoint}`;\n }\n } else {\n blobServiceClientUrl = `https://${this.integration.config.accountName}.${this.integration.config.host}`;\n }\n\n const blobServiceClient = new BlobServiceClient(\n blobServiceClientUrl,\n credential,\n );\n return blobServiceClient.getContainerClient(containerName);\n }\n\n async read(url: string): Promise<Buffer> {\n const response = await this.readUrl(url);\n return response.buffer();\n }\n\n async readUrl(\n url: string,\n options?: UrlReaderServiceReadUrlOptions,\n ): Promise<UrlReaderServiceReadUrlResponse> {\n const { etag, lastModifiedAfter } = options ?? {};\n\n try {\n const { path, container } = parseUrl(url);\n\n const containerClient = await this.deps.createContainerClient(container);\n const blobClient = containerClient.getBlobClient(path);\n\n const getBlobOptions: BlobDownloadOptions = {\n abortSignal: options?.signal,\n conditions: {\n ...(etag && { ifNoneMatch: etag }),\n ...(lastModifiedAfter && { ifModifiedSince: lastModifiedAfter }),\n },\n };\n\n const downloadBlockBlobResponse = await blobClient.download(\n 0,\n undefined,\n getBlobOptions,\n );\n\n return ReadUrlResponseFactory.fromReadable(\n downloadBlockBlobResponse.readableStreamBody as Readable,\n {\n etag: downloadBlockBlobResponse.etag,\n lastModifiedAt: downloadBlockBlobResponse.lastModified,\n },\n );\n } catch (e) {\n if (e.statusCode === 304) {\n throw new NotModifiedError();\n }\n\n throw new ForwardedError(\n 'Could not retrieve file from Azure Blob Storage',\n e,\n );\n }\n }\n\n async readTree(\n url: string,\n options?: UrlReaderServiceReadTreeOptions,\n ): Promise<UrlReaderServiceReadTreeResponse> {\n try {\n const { path, container } = parseUrl(url);\n\n const containerClient = await this.deps.createContainerClient(container);\n const blobs = containerClient.listBlobsFlat({ prefix: path });\n\n const responses = [];\n\n for await (const blob of blobs) {\n const blobClient = containerClient.getBlobClient(blob.name);\n\n const downloadBlockBlobResponse = await blobClient.download(\n undefined,\n undefined,\n { abortSignal: options?.signal },\n );\n\n responses.push({\n data: Readable.from(\n downloadBlockBlobResponse.readableStreamBody as Readable,\n ),\n path: relative(path, blob.name),\n lastModifiedAt: blob.properties.lastModified,\n });\n }\n\n return this.deps.treeResponseFactory.fromReadableArray(responses);\n } catch (e) {\n throw new ForwardedError(\n 'Could not retrieve file tree from Azure Blob Storage',\n e,\n );\n }\n }\n\n async search(\n url: string,\n options?: UrlReaderServiceSearchOptions,\n ): Promise<UrlReaderServiceSearchResponse> {\n const { path } = parseUrl(url);\n\n if (path.match(/[*?]/)) {\n throw new Error(\n 'Glob search pattern not implemented for AzureBlobStorageUrlReader',\n );\n }\n\n try {\n const data = await this.readUrl(url, options);\n\n return {\n files: [\n {\n url: url,\n content: data.buffer,\n lastModifiedAt: data.lastModifiedAt,\n },\n ],\n etag: data.etag ?? '',\n };\n } catch (e) {\n throw toError(e);\n }\n }\n\n toString() {\n const accountName = this.integration.config.accountName;\n const accountKey = this.integration.config.accountKey;\n return `azureBlobStorage{accountName=${accountName},authed=${Boolean(\n accountKey,\n )}}`;\n }\n}\n"],"names":["ScmIntegrations","DefaultAzureCredentialsManager","StorageSharedKeyCredential","blobServiceClient","BlobServiceClient","ReadUrlResponseFactory","NotModifiedError","ForwardedError","Readable","relative","toError"],"mappings":"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"}
|
|
@@ -140,6 +140,10 @@ class GitlabUrlReader {
|
|
|
140
140
|
)}/repository/archive?${archiveReqParams.toString()}`;
|
|
141
141
|
const archiveGitLabResponse = await this.integration.fetch(reqUrl, {
|
|
142
142
|
...integration.getGitLabRequestOptions(this.integration.config, token),
|
|
143
|
+
// The mode is set to 'same-origin' to overwrite the default 'cors' value.
|
|
144
|
+
// The repository/archive endpoint marks mode='cors' as a "hotlink" which will return 406 - Not Acceptable as a response
|
|
145
|
+
// More info on this issue can be found @ https://github.com/backstage/backstage/issues/34395
|
|
146
|
+
mode: "same-origin",
|
|
143
147
|
// TODO(freben): The signal cast is there because pre-3.x versions of
|
|
144
148
|
// node-fetch have a very slightly deviating AbortSignal type signature.
|
|
145
149
|
// The difference does not affect us in practice however. The cast can
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GitlabUrlReader.cjs.js","sources":["../../../../src/entrypoints/urlReader/lib/GitlabUrlReader.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n UrlReaderService,\n UrlReaderServiceReadTreeOptions,\n UrlReaderServiceReadTreeResponse,\n UrlReaderServiceReadUrlOptions,\n UrlReaderServiceReadUrlResponse,\n UrlReaderServiceSearchOptions,\n UrlReaderServiceSearchResponse,\n} from '@backstage/backend-plugin-api';\nimport { toError, NotFoundError, NotModifiedError } from '@backstage/errors';\nimport {\n getGitLabFileFetchUrl,\n getGitLabIntegrationRelativePath,\n getGitLabRequestOptions,\n GitLabIntegration,\n ScmIntegrations,\n} from '@backstage/integration';\nimport parseGitUrl from 'git-url-parse';\nimport { trimEnd, trimStart } from 'lodash';\nimport { Minimatch } from 'minimatch';\nimport { ReadUrlResponseFactory } from './ReadUrlResponseFactory';\nimport { ReaderFactory, ReadTreeResponseFactory } from './types';\n\n/**\n * Implements a {@link @backstage/backend-plugin-api#UrlReaderService} for files on GitLab.\n *\n * @public\n */\nexport class GitlabUrlReader implements UrlReaderService {\n static factory: ReaderFactory = ({ config, treeResponseFactory }) => {\n const integrations = ScmIntegrations.fromConfig(config);\n return integrations.gitlab.list().map(integration => {\n const reader = new GitlabUrlReader(integration, {\n treeResponseFactory,\n });\n const predicate = (url: URL) => url.host === integration.config.host;\n return { reader, predicate };\n });\n };\n\n private readonly integration: GitLabIntegration;\n private readonly deps: { treeResponseFactory: ReadTreeResponseFactory };\n\n constructor(\n integration: GitLabIntegration,\n deps: { treeResponseFactory: ReadTreeResponseFactory },\n ) {\n this.integration = integration;\n this.deps = deps;\n }\n\n async read(url: string): Promise<Buffer> {\n const response = await this.readUrl(url);\n return response.buffer();\n }\n\n async readUrl(\n url: string,\n options?: UrlReaderServiceReadUrlOptions,\n ): Promise<UrlReaderServiceReadUrlResponse> {\n const { etag, lastModifiedAfter, signal, token } = options ?? {};\n const isArtifact = url.includes('/-/jobs/artifacts/');\n const builtUrl = await this.getGitlabFetchUrl(url, token);\n\n let response: Response;\n try {\n response = await this.integration.fetch(builtUrl, {\n headers: {\n ...getGitLabRequestOptions(this.integration.config, token).headers,\n ...(etag && !isArtifact && { 'If-None-Match': etag }),\n ...(lastModifiedAfter &&\n !isArtifact && {\n 'If-Modified-Since': lastModifiedAfter.toUTCString(),\n }),\n },\n // TODO(freben): The signal cast is there because pre-3.x versions of\n // node-fetch have a very slightly deviating AbortSignal type signature.\n // The difference does not affect us in practice however. The cast can be\n // removed after we support ESM for CLI dependencies and migrate to\n // version 3 of node-fetch.\n // https://github.com/backstage/backstage/issues/8242\n ...(signal && { signal: signal as any }),\n });\n } catch (e) {\n throw new Error(`Unable to read ${url}, ${e}`);\n }\n\n if (response.status === 304) {\n throw new NotModifiedError();\n }\n\n if (response.ok) {\n return ReadUrlResponseFactory.fromResponse(response);\n }\n\n const message = `${url} could not be read as ${builtUrl}, ${response.status} ${response.statusText}`;\n if (response.status === 404) {\n throw new NotFoundError(message);\n }\n throw new Error(message);\n }\n\n async readTree(\n url: string,\n options?: UrlReaderServiceReadTreeOptions,\n ): Promise<UrlReaderServiceReadTreeResponse> {\n const { etag, signal, token } = options ?? {};\n const { ref, full_name, filepath } = parseGitUrl(url);\n\n let repoFullName = full_name;\n\n const relativePath = getGitLabIntegrationRelativePath(\n this.integration.config,\n );\n\n // Considering self hosted gitlab with relative\n // assuming '/gitlab' is the relative path\n // from: /gitlab/repo/project\n // to: repo/project\n if (relativePath) {\n const rectifiedRelativePath = `${trimStart(relativePath, '/')}/`;\n repoFullName = full_name.replace(rectifiedRelativePath, '');\n }\n\n // Use GitLab API to get the default branch\n // encodeURIComponent is required for GitLab API\n // https://docs.gitlab.com/ee/api/README.html#namespaced-path-encoding\n const projectGitlabResponse = await this.integration.fetch(\n new URL(\n `${this.integration.config.apiBaseUrl}/projects/${encodeURIComponent(\n repoFullName,\n )}`,\n ).toString(),\n getGitLabRequestOptions(this.integration.config, token),\n );\n if (!projectGitlabResponse.ok) {\n const msg = `Failed to read tree from ${url}, ${projectGitlabResponse.status} ${projectGitlabResponse.statusText}`;\n if (projectGitlabResponse.status === 404) {\n throw new NotFoundError(msg);\n }\n throw new Error(msg);\n }\n const projectGitlabResponseJson = await projectGitlabResponse.json();\n\n // ref is an empty string if no branch is set in provided url to readTree.\n const branch = ref || projectGitlabResponseJson.default_branch;\n\n // Fetch the latest commit that modifies the filepath in the provided or default branch\n // to compare against the provided sha.\n const commitsReqParams = new URLSearchParams();\n commitsReqParams.set('ref_name', branch);\n if (!!filepath) {\n commitsReqParams.set('path', filepath);\n }\n const commitsGitlabResponse = await this.integration.fetch(\n new URL(\n `${this.integration.config.apiBaseUrl}/projects/${encodeURIComponent(\n repoFullName,\n )}/repository/commits?${commitsReqParams.toString()}`,\n ).toString(),\n {\n ...getGitLabRequestOptions(this.integration.config, token),\n // TODO(freben): The signal cast is there because pre-3.x versions of\n // node-fetch have a very slightly deviating AbortSignal type signature.\n // The difference does not affect us in practice however. The cast can\n // be removed after we support ESM for CLI dependencies and migrate to\n // version 3 of node-fetch.\n // https://github.com/backstage/backstage/issues/8242\n ...(signal && { signal: signal as any }),\n },\n );\n if (!commitsGitlabResponse.ok) {\n const message = `Failed to read tree (branch) from ${url}, ${commitsGitlabResponse.status} ${commitsGitlabResponse.statusText}`;\n if (commitsGitlabResponse.status === 404) {\n throw new NotFoundError(message);\n }\n throw new Error(message);\n }\n\n const commitSha = (await commitsGitlabResponse.json())[0]?.id ?? '';\n if (etag && etag === commitSha) {\n throw new NotModifiedError();\n }\n\n const archiveReqParams = new URLSearchParams();\n archiveReqParams.set('sha', branch);\n if (!!filepath) {\n archiveReqParams.set('path', filepath);\n }\n // https://docs.gitlab.com/ee/api/repositories.html#get-file-archive\n const reqUrl = `${\n this.integration.config.apiBaseUrl\n }/projects/${encodeURIComponent(\n repoFullName,\n )}/repository/archive?${archiveReqParams.toString()}`;\n const archiveGitLabResponse = await this.integration.fetch(reqUrl, {\n ...getGitLabRequestOptions(this.integration.config, token),\n // TODO(freben): The signal cast is there because pre-3.x versions of\n // node-fetch have a very slightly deviating AbortSignal type signature.\n // The difference does not affect us in practice however. The cast can\n // be removed after we support ESM for CLI dependencies and migrate to\n // version 3 of node-fetch.\n // https://github.com/backstage/backstage/issues/8242\n ...(signal && { signal: signal as any }),\n });\n if (!archiveGitLabResponse.ok) {\n const message = `Failed to read tree (archive) from ${url}, ${reqUrl}, ${archiveGitLabResponse.status} ${archiveGitLabResponse.statusText}`;\n if (archiveGitLabResponse.status === 404) {\n throw new NotFoundError(message);\n }\n throw new Error(message);\n }\n\n return await this.deps.treeResponseFactory.fromTarArchive({\n response: archiveGitLabResponse,\n subpath: filepath,\n etag: commitSha,\n filter: options?.filter,\n });\n }\n\n async search(\n url: string,\n options?: UrlReaderServiceSearchOptions,\n ): Promise<UrlReaderServiceSearchResponse> {\n const { filepath } = parseGitUrl(url);\n\n // If it's a direct URL we use readUrl instead\n if (!filepath?.match(/[*?]/)) {\n try {\n const data = await this.readUrl(url, options);\n\n return {\n files: [\n {\n url: url,\n content: data.buffer,\n lastModifiedAt: data.lastModifiedAt,\n },\n ],\n etag: data.etag ?? '',\n };\n } catch (e) {\n const error = toError(e);\n if (error.name === 'NotFoundError') {\n return {\n files: [],\n etag: '',\n };\n }\n throw error;\n }\n }\n\n const staticPart = this.getStaticPart(filepath);\n const matcher = new Minimatch(filepath);\n const treeUrl = trimEnd(url.replace(filepath, staticPart), `/`);\n const pathPrefix = staticPart ? `${staticPart}/` : '';\n const tree = await this.readTree(treeUrl, {\n etag: options?.etag,\n signal: options?.signal,\n filter: path => matcher.match(`${pathPrefix}${path}`),\n });\n\n const files = await tree.files();\n return {\n etag: tree.etag,\n files: files.map(file => ({\n url: this.integration.resolveUrl({\n url: `/${pathPrefix}${file.path}`,\n base: url,\n }),\n content: file.content,\n lastModifiedAt: file.lastModifiedAt,\n })),\n };\n }\n\n /**\n * This function splits the input globPattern string into segments using the path separator /. It then iterates over\n * the segments from the end of the array towards the beginning, checking if the concatenated string up to that\n * segment matches the original globPattern using the minimatch function. If a match is found, it continues iterating.\n * If no match is found, it returns the concatenated string up to the current segment, which is the static part of the\n * glob pattern.\n *\n * E.g. `catalog/foo/*.yaml` will return `catalog/foo`.\n *\n * @param globPattern - the glob pattern\n */\n private getStaticPart(globPattern: string) {\n const segments = globPattern.split('/');\n const globIndex = segments.findIndex(segment => segment.match(/[*?]/));\n return globIndex === -1\n ? globPattern\n : segments.slice(0, globIndex).join('/');\n }\n\n toString() {\n const { host, token } = this.integration.config;\n return `gitlab{host=${host},authed=${Boolean(token)}}`;\n }\n\n private async getGitlabFetchUrl(\n target: string,\n token?: string,\n ): Promise<string> {\n // If the target is for a job artifact then go down that path\n const targetUrl = new URL(target);\n if (targetUrl.pathname.includes('/-/jobs/artifacts/')) {\n return this.getGitlabArtifactFetchUrl(targetUrl).then(value =>\n value.toString(),\n );\n }\n // Default to the optimized behavior - no API call needed for file URLs\n return getGitLabFileFetchUrl(target, this.integration.config, token);\n }\n\n // convert urls of the form:\n // https://example.com/<namespace>/<project>/-/jobs/artifacts/<ref>/raw/<path_to_file>?job=<job_name>\n // to urls of the form:\n // https://example.com/api/v4/projects/namespace%2Fproject/jobs/artifacts/:ref_name/raw/*artifact_path?job=<job_name>\n private getGitlabArtifactFetchUrl(target: URL): Promise<URL> {\n if (!target.pathname.includes('/-/jobs/artifacts/')) {\n throw new Error('Unable to process url as an GitLab artifact');\n }\n try {\n const [namespaceAndProject, ref] =\n target.pathname.split('/-/jobs/artifacts/');\n\n // Extract project path directly instead of making API call\n const relativePath = getGitLabIntegrationRelativePath(\n this.integration.config,\n );\n\n let projectPath = namespaceAndProject;\n // Check relative path exist and remove it\n if (relativePath) {\n projectPath = trimStart(projectPath, relativePath);\n }\n // Trim an initial / if it exists\n projectPath = projectPath.replace(/^\\//, '');\n\n const newUrl = new URL(target);\n newUrl.pathname = `${relativePath}/api/v4/projects/${encodeURIComponent(\n projectPath,\n )}/jobs/artifacts/${ref}`;\n return Promise.resolve(newUrl);\n } catch (e) {\n throw new Error(\n `Unable to translate GitLab artifact URL: ${target}, ${e}`,\n );\n }\n }\n}\n"],"names":["ScmIntegrations","getGitLabRequestOptions","NotModifiedError","ReadUrlResponseFactory","NotFoundError","parseGitUrl","getGitLabIntegrationRelativePath","trimStart","toError","Minimatch","trimEnd","getGitLabFileFetchUrl"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"GitlabUrlReader.cjs.js","sources":["../../../../src/entrypoints/urlReader/lib/GitlabUrlReader.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n UrlReaderService,\n UrlReaderServiceReadTreeOptions,\n UrlReaderServiceReadTreeResponse,\n UrlReaderServiceReadUrlOptions,\n UrlReaderServiceReadUrlResponse,\n UrlReaderServiceSearchOptions,\n UrlReaderServiceSearchResponse,\n} from '@backstage/backend-plugin-api';\nimport { toError, NotFoundError, NotModifiedError } from '@backstage/errors';\nimport {\n getGitLabFileFetchUrl,\n getGitLabIntegrationRelativePath,\n getGitLabRequestOptions,\n GitLabIntegration,\n ScmIntegrations,\n} from '@backstage/integration';\nimport parseGitUrl from 'git-url-parse';\nimport { trimEnd, trimStart } from 'lodash';\nimport { Minimatch } from 'minimatch';\nimport { ReadUrlResponseFactory } from './ReadUrlResponseFactory';\nimport { ReaderFactory, ReadTreeResponseFactory } from './types';\n\n/**\n * Implements a {@link @backstage/backend-plugin-api#UrlReaderService} for files on GitLab.\n *\n * @public\n */\nexport class GitlabUrlReader implements UrlReaderService {\n static factory: ReaderFactory = ({ config, treeResponseFactory }) => {\n const integrations = ScmIntegrations.fromConfig(config);\n return integrations.gitlab.list().map(integration => {\n const reader = new GitlabUrlReader(integration, {\n treeResponseFactory,\n });\n const predicate = (url: URL) => url.host === integration.config.host;\n return { reader, predicate };\n });\n };\n\n private readonly integration: GitLabIntegration;\n private readonly deps: { treeResponseFactory: ReadTreeResponseFactory };\n\n constructor(\n integration: GitLabIntegration,\n deps: { treeResponseFactory: ReadTreeResponseFactory },\n ) {\n this.integration = integration;\n this.deps = deps;\n }\n\n async read(url: string): Promise<Buffer> {\n const response = await this.readUrl(url);\n return response.buffer();\n }\n\n async readUrl(\n url: string,\n options?: UrlReaderServiceReadUrlOptions,\n ): Promise<UrlReaderServiceReadUrlResponse> {\n const { etag, lastModifiedAfter, signal, token } = options ?? {};\n const isArtifact = url.includes('/-/jobs/artifacts/');\n const builtUrl = await this.getGitlabFetchUrl(url, token);\n\n let response: Response;\n try {\n response = await this.integration.fetch(builtUrl, {\n headers: {\n ...getGitLabRequestOptions(this.integration.config, token).headers,\n ...(etag && !isArtifact && { 'If-None-Match': etag }),\n ...(lastModifiedAfter &&\n !isArtifact && {\n 'If-Modified-Since': lastModifiedAfter.toUTCString(),\n }),\n },\n // TODO(freben): The signal cast is there because pre-3.x versions of\n // node-fetch have a very slightly deviating AbortSignal type signature.\n // The difference does not affect us in practice however. The cast can be\n // removed after we support ESM for CLI dependencies and migrate to\n // version 3 of node-fetch.\n // https://github.com/backstage/backstage/issues/8242\n ...(signal && { signal: signal as any }),\n });\n } catch (e) {\n throw new Error(`Unable to read ${url}, ${e}`);\n }\n\n if (response.status === 304) {\n throw new NotModifiedError();\n }\n\n if (response.ok) {\n return ReadUrlResponseFactory.fromResponse(response);\n }\n\n const message = `${url} could not be read as ${builtUrl}, ${response.status} ${response.statusText}`;\n if (response.status === 404) {\n throw new NotFoundError(message);\n }\n throw new Error(message);\n }\n\n async readTree(\n url: string,\n options?: UrlReaderServiceReadTreeOptions,\n ): Promise<UrlReaderServiceReadTreeResponse> {\n const { etag, signal, token } = options ?? {};\n const { ref, full_name, filepath } = parseGitUrl(url);\n\n let repoFullName = full_name;\n\n const relativePath = getGitLabIntegrationRelativePath(\n this.integration.config,\n );\n\n // Considering self hosted gitlab with relative\n // assuming '/gitlab' is the relative path\n // from: /gitlab/repo/project\n // to: repo/project\n if (relativePath) {\n const rectifiedRelativePath = `${trimStart(relativePath, '/')}/`;\n repoFullName = full_name.replace(rectifiedRelativePath, '');\n }\n\n // Use GitLab API to get the default branch\n // encodeURIComponent is required for GitLab API\n // https://docs.gitlab.com/ee/api/README.html#namespaced-path-encoding\n const projectGitlabResponse = await this.integration.fetch(\n new URL(\n `${this.integration.config.apiBaseUrl}/projects/${encodeURIComponent(\n repoFullName,\n )}`,\n ).toString(),\n getGitLabRequestOptions(this.integration.config, token),\n );\n if (!projectGitlabResponse.ok) {\n const msg = `Failed to read tree from ${url}, ${projectGitlabResponse.status} ${projectGitlabResponse.statusText}`;\n if (projectGitlabResponse.status === 404) {\n throw new NotFoundError(msg);\n }\n throw new Error(msg);\n }\n const projectGitlabResponseJson = await projectGitlabResponse.json();\n\n // ref is an empty string if no branch is set in provided url to readTree.\n const branch = ref || projectGitlabResponseJson.default_branch;\n\n // Fetch the latest commit that modifies the filepath in the provided or default branch\n // to compare against the provided sha.\n const commitsReqParams = new URLSearchParams();\n commitsReqParams.set('ref_name', branch);\n if (!!filepath) {\n commitsReqParams.set('path', filepath);\n }\n const commitsGitlabResponse = await this.integration.fetch(\n new URL(\n `${this.integration.config.apiBaseUrl}/projects/${encodeURIComponent(\n repoFullName,\n )}/repository/commits?${commitsReqParams.toString()}`,\n ).toString(),\n {\n ...getGitLabRequestOptions(this.integration.config, token),\n // TODO(freben): The signal cast is there because pre-3.x versions of\n // node-fetch have a very slightly deviating AbortSignal type signature.\n // The difference does not affect us in practice however. The cast can\n // be removed after we support ESM for CLI dependencies and migrate to\n // version 3 of node-fetch.\n // https://github.com/backstage/backstage/issues/8242\n ...(signal && { signal: signal as any }),\n },\n );\n if (!commitsGitlabResponse.ok) {\n const message = `Failed to read tree (branch) from ${url}, ${commitsGitlabResponse.status} ${commitsGitlabResponse.statusText}`;\n if (commitsGitlabResponse.status === 404) {\n throw new NotFoundError(message);\n }\n throw new Error(message);\n }\n\n const commitSha = (await commitsGitlabResponse.json())[0]?.id ?? '';\n if (etag && etag === commitSha) {\n throw new NotModifiedError();\n }\n\n const archiveReqParams = new URLSearchParams();\n archiveReqParams.set('sha', branch);\n if (!!filepath) {\n archiveReqParams.set('path', filepath);\n }\n // https://docs.gitlab.com/ee/api/repositories.html#get-file-archive\n const reqUrl = `${\n this.integration.config.apiBaseUrl\n }/projects/${encodeURIComponent(\n repoFullName,\n )}/repository/archive?${archiveReqParams.toString()}`;\n const archiveGitLabResponse = await this.integration.fetch(reqUrl, {\n ...getGitLabRequestOptions(this.integration.config, token),\n // The mode is set to 'same-origin' to overwrite the default 'cors' value.\n // The repository/archive endpoint marks mode='cors' as a \"hotlink\" which will return 406 - Not Acceptable as a response\n // More info on this issue can be found @ https://github.com/backstage/backstage/issues/34395\n mode: 'same-origin',\n // TODO(freben): The signal cast is there because pre-3.x versions of\n // node-fetch have a very slightly deviating AbortSignal type signature.\n // The difference does not affect us in practice however. The cast can\n // be removed after we support ESM for CLI dependencies and migrate to\n // version 3 of node-fetch.\n // https://github.com/backstage/backstage/issues/8242\n ...(signal && { signal: signal as any }),\n });\n if (!archiveGitLabResponse.ok) {\n const message = `Failed to read tree (archive) from ${url}, ${reqUrl}, ${archiveGitLabResponse.status} ${archiveGitLabResponse.statusText}`;\n if (archiveGitLabResponse.status === 404) {\n throw new NotFoundError(message);\n }\n throw new Error(message);\n }\n\n return await this.deps.treeResponseFactory.fromTarArchive({\n response: archiveGitLabResponse,\n subpath: filepath,\n etag: commitSha,\n filter: options?.filter,\n });\n }\n\n async search(\n url: string,\n options?: UrlReaderServiceSearchOptions,\n ): Promise<UrlReaderServiceSearchResponse> {\n const { filepath } = parseGitUrl(url);\n\n // If it's a direct URL we use readUrl instead\n if (!filepath?.match(/[*?]/)) {\n try {\n const data = await this.readUrl(url, options);\n\n return {\n files: [\n {\n url: url,\n content: data.buffer,\n lastModifiedAt: data.lastModifiedAt,\n },\n ],\n etag: data.etag ?? '',\n };\n } catch (e) {\n const error = toError(e);\n if (error.name === 'NotFoundError') {\n return {\n files: [],\n etag: '',\n };\n }\n throw error;\n }\n }\n\n const staticPart = this.getStaticPart(filepath);\n const matcher = new Minimatch(filepath);\n const treeUrl = trimEnd(url.replace(filepath, staticPart), `/`);\n const pathPrefix = staticPart ? `${staticPart}/` : '';\n const tree = await this.readTree(treeUrl, {\n etag: options?.etag,\n signal: options?.signal,\n filter: path => matcher.match(`${pathPrefix}${path}`),\n });\n\n const files = await tree.files();\n return {\n etag: tree.etag,\n files: files.map(file => ({\n url: this.integration.resolveUrl({\n url: `/${pathPrefix}${file.path}`,\n base: url,\n }),\n content: file.content,\n lastModifiedAt: file.lastModifiedAt,\n })),\n };\n }\n\n /**\n * This function splits the input globPattern string into segments using the path separator /. It then iterates over\n * the segments from the end of the array towards the beginning, checking if the concatenated string up to that\n * segment matches the original globPattern using the minimatch function. If a match is found, it continues iterating.\n * If no match is found, it returns the concatenated string up to the current segment, which is the static part of the\n * glob pattern.\n *\n * E.g. `catalog/foo/*.yaml` will return `catalog/foo`.\n *\n * @param globPattern - the glob pattern\n */\n private getStaticPart(globPattern: string) {\n const segments = globPattern.split('/');\n const globIndex = segments.findIndex(segment => segment.match(/[*?]/));\n return globIndex === -1\n ? globPattern\n : segments.slice(0, globIndex).join('/');\n }\n\n toString() {\n const { host, token } = this.integration.config;\n return `gitlab{host=${host},authed=${Boolean(token)}}`;\n }\n\n private async getGitlabFetchUrl(\n target: string,\n token?: string,\n ): Promise<string> {\n // If the target is for a job artifact then go down that path\n const targetUrl = new URL(target);\n if (targetUrl.pathname.includes('/-/jobs/artifacts/')) {\n return this.getGitlabArtifactFetchUrl(targetUrl).then(value =>\n value.toString(),\n );\n }\n // Default to the optimized behavior - no API call needed for file URLs\n return getGitLabFileFetchUrl(target, this.integration.config, token);\n }\n\n // convert urls of the form:\n // https://example.com/<namespace>/<project>/-/jobs/artifacts/<ref>/raw/<path_to_file>?job=<job_name>\n // to urls of the form:\n // https://example.com/api/v4/projects/namespace%2Fproject/jobs/artifacts/:ref_name/raw/*artifact_path?job=<job_name>\n private getGitlabArtifactFetchUrl(target: URL): Promise<URL> {\n if (!target.pathname.includes('/-/jobs/artifacts/')) {\n throw new Error('Unable to process url as an GitLab artifact');\n }\n try {\n const [namespaceAndProject, ref] =\n target.pathname.split('/-/jobs/artifacts/');\n\n // Extract project path directly instead of making API call\n const relativePath = getGitLabIntegrationRelativePath(\n this.integration.config,\n );\n\n let projectPath = namespaceAndProject;\n // Check relative path exist and remove it\n if (relativePath) {\n projectPath = trimStart(projectPath, relativePath);\n }\n // Trim an initial / if it exists\n projectPath = projectPath.replace(/^\\//, '');\n\n const newUrl = new URL(target);\n newUrl.pathname = `${relativePath}/api/v4/projects/${encodeURIComponent(\n projectPath,\n )}/jobs/artifacts/${ref}`;\n return Promise.resolve(newUrl);\n } catch (e) {\n throw new Error(\n `Unable to translate GitLab artifact URL: ${target}, ${e}`,\n );\n }\n }\n}\n"],"names":["ScmIntegrations","getGitLabRequestOptions","NotModifiedError","ReadUrlResponseFactory","NotFoundError","parseGitUrl","getGitLabIntegrationRelativePath","trimStart","toError","Minimatch","trimEnd","getGitLabFileFetchUrl"],"mappings":"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"}
|
package/dist/package.json.cjs.js
CHANGED
package/dist/urlReader.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { RootConfigService, LoggerService, UrlReaderServiceReadTreeResponse, Url
|
|
|
3
3
|
import { AzureIntegration, AzureDevOpsCredentialsProvider, BitbucketCloudIntegration, BitbucketServerIntegration, GerritIntegration, GithubIntegration, GithubCredentialsProvider, GitLabIntegration, GiteaIntegration, HarnessIntegration, AwsS3Integration, AzureCredentialsManager, AzureBlobStorageIntegration } from '@backstage/integration';
|
|
4
4
|
import { Readable } from 'node:stream';
|
|
5
5
|
import { AwsCredentialsManager } from '@backstage/integration-aws-node';
|
|
6
|
+
import { ContainerClient } from '@azure/storage-blob';
|
|
6
7
|
import { Config } from '@backstage/config';
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -330,14 +331,14 @@ declare class AwsS3UrlReader implements UrlReaderService {
|
|
|
330
331
|
* @public
|
|
331
332
|
*/
|
|
332
333
|
declare class AzureBlobStorageUrlReader implements UrlReaderService {
|
|
334
|
+
#private;
|
|
333
335
|
static factory: ReaderFactory;
|
|
334
|
-
private readonly credsManager;
|
|
335
336
|
private readonly integration;
|
|
336
337
|
private readonly deps;
|
|
337
338
|
constructor(credsManager: AzureCredentialsManager, integration: AzureBlobStorageIntegration, deps: {
|
|
338
339
|
treeResponseFactory: ReadTreeResponseFactory;
|
|
340
|
+
createContainerClient?: (containerName: string) => Promise<ContainerClient>;
|
|
339
341
|
});
|
|
340
|
-
private createContainerClient;
|
|
341
342
|
read(url: string): Promise<Buffer>;
|
|
342
343
|
readUrl(url: string, options?: UrlReaderServiceReadUrlOptions): Promise<UrlReaderServiceReadUrlResponse>;
|
|
343
344
|
readTree(url: string, options?: UrlReaderServiceReadTreeOptions): Promise<UrlReaderServiceReadTreeResponse>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backstage/backend-defaults",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.3-next.1",
|
|
4
4
|
"description": "Backend defaults used by Backstage backend apps",
|
|
5
5
|
"backstage": {
|
|
6
6
|
"role": "node-library"
|
|
@@ -220,12 +220,12 @@
|
|
|
220
220
|
"@azure/storage-blob": "^12.5.0",
|
|
221
221
|
"@backstage/backend-app-api": "1.7.1-next.0",
|
|
222
222
|
"@backstage/backend-dev-utils": "0.1.7",
|
|
223
|
-
"@backstage/backend-plugin-api": "1.9.2-next.
|
|
223
|
+
"@backstage/backend-plugin-api": "1.9.2-next.1",
|
|
224
224
|
"@backstage/cli-node": "0.3.2",
|
|
225
225
|
"@backstage/config": "1.3.8",
|
|
226
226
|
"@backstage/config-loader": "1.10.11",
|
|
227
227
|
"@backstage/errors": "1.3.1",
|
|
228
|
-
"@backstage/integration": "2.0.3-next.
|
|
228
|
+
"@backstage/integration": "2.0.3-next.1",
|
|
229
229
|
"@backstage/integration-aws-node": "0.2.0",
|
|
230
230
|
"@backstage/plugin-auth-node": "0.7.2-next.0",
|
|
231
231
|
"@backstage/plugin-events-node": "0.4.23-next.0",
|
|
@@ -286,8 +286,8 @@
|
|
|
286
286
|
},
|
|
287
287
|
"devDependencies": {
|
|
288
288
|
"@aws-sdk/util-stream-node": "^3.350.0",
|
|
289
|
-
"@backstage/backend-test-utils": "1.11.4-next.
|
|
290
|
-
"@backstage/cli": "0.36.3-next.
|
|
289
|
+
"@backstage/backend-test-utils": "1.11.4-next.1",
|
|
290
|
+
"@backstage/cli": "0.36.3-next.1",
|
|
291
291
|
"@google-cloud/cloud-sql-connector": "^1.4.0",
|
|
292
292
|
"@types/archiver": "^7.0.0",
|
|
293
293
|
"@types/base64-stream": "^1.0.2",
|