@platforma-sdk/package-builder-lib 1.1.0
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/assets/conda-dockerfile.template +45 -0
- package/assets/python-dockerfile.template +22 -0
- package/dist/_virtual/_rolldown/runtime.cjs +33 -0
- package/dist/_virtual/_rolldown/runtime.js +13 -0
- package/dist/core/archive.cjs +52 -0
- package/dist/core/archive.cjs.map +1 -0
- package/dist/core/archive.js +47 -0
- package/dist/core/archive.js.map +1 -0
- package/dist/core/conda/builder.cjs +226 -0
- package/dist/core/conda/builder.cjs.map +1 -0
- package/dist/core/conda/builder.js +220 -0
- package/dist/core/conda/builder.js.map +1 -0
- package/dist/core/conda/validate-spec.cjs +76 -0
- package/dist/core/conda/validate-spec.cjs.map +1 -0
- package/dist/core/conda/validate-spec.js +73 -0
- package/dist/core/conda/validate-spec.js.map +1 -0
- package/dist/core/core.cjs +403 -0
- package/dist/core/core.cjs.map +1 -0
- package/dist/core/core.d.ts +82 -0
- package/dist/core/core.d.ts.map +1 -0
- package/dist/core/core.js +397 -0
- package/dist/core/core.js.map +1 -0
- package/dist/core/docker-conda.cjs +75 -0
- package/dist/core/docker-conda.cjs.map +1 -0
- package/dist/core/docker-conda.js +71 -0
- package/dist/core/docker-conda.js.map +1 -0
- package/dist/core/docker-python.cjs +147 -0
- package/dist/core/docker-python.cjs.map +1 -0
- package/dist/core/docker-python.js +144 -0
- package/dist/core/docker-python.js.map +1 -0
- package/dist/core/docker.cjs +223 -0
- package/dist/core/docker.cjs.map +1 -0
- package/dist/core/docker.js +207 -0
- package/dist/core/docker.js.map +1 -0
- package/dist/core/envs.cjs +58 -0
- package/dist/core/envs.cjs.map +1 -0
- package/dist/core/envs.d.ts +26 -0
- package/dist/core/envs.d.ts.map +1 -0
- package/dist/core/envs.js +51 -0
- package/dist/core/envs.js.map +1 -0
- package/dist/core/package-info.cjs +450 -0
- package/dist/core/package-info.cjs.map +1 -0
- package/dist/core/package-info.d.ts +57 -0
- package/dist/core/package-info.d.ts.map +1 -0
- package/dist/core/package-info.js +447 -0
- package/dist/core/package-info.js.map +1 -0
- package/dist/core/paths.cjs +15 -0
- package/dist/core/paths.cjs.map +1 -0
- package/dist/core/paths.js +14 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/resolver.cjs +99 -0
- package/dist/core/resolver.cjs.map +1 -0
- package/dist/core/resolver.js +95 -0
- package/dist/core/resolver.js.map +1 -0
- package/dist/core/schemas/artifacts.cjs +136 -0
- package/dist/core/schemas/artifacts.cjs.map +1 -0
- package/dist/core/schemas/artifacts.d.ts +280 -0
- package/dist/core/schemas/artifacts.d.ts.map +1 -0
- package/dist/core/schemas/artifacts.js +122 -0
- package/dist/core/schemas/artifacts.js.map +1 -0
- package/dist/core/schemas/entrypoints.cjs +43 -0
- package/dist/core/schemas/entrypoints.cjs.map +1 -0
- package/dist/core/schemas/entrypoints.d.ts +32 -0
- package/dist/core/schemas/entrypoints.d.ts.map +1 -0
- package/dist/core/schemas/entrypoints.js +41 -0
- package/dist/core/schemas/entrypoints.js.map +1 -0
- package/dist/core/schemas/sw-json.cjs +111 -0
- package/dist/core/schemas/sw-json.cjs.map +1 -0
- package/dist/core/schemas/sw-json.js +110 -0
- package/dist/core/schemas/sw-json.js.map +1 -0
- package/dist/core/storage.cjs +86 -0
- package/dist/core/storage.cjs.map +1 -0
- package/dist/core/storage.js +83 -0
- package/dist/core/storage.js.map +1 -0
- package/dist/core/sw-json-render.cjs +327 -0
- package/dist/core/sw-json-render.cjs.map +1 -0
- package/dist/core/sw-json-render.js +322 -0
- package/dist/core/sw-json-render.js.map +1 -0
- package/dist/core/util.cjs +313 -0
- package/dist/core/util.cjs.map +1 -0
- package/dist/core/util.d.ts +66 -0
- package/dist/core/util.d.ts.map +1 -0
- package/dist/core/util.js +281 -0
- package/dist/core/util.js.map +1 -0
- package/dist/defaults.cjs +54 -0
- package/dist/defaults.cjs.map +1 -0
- package/dist/defaults.d.ts +21 -0
- package/dist/defaults.d.ts.map +1 -0
- package/dist/defaults.js +37 -0
- package/dist/defaults.js.map +1 -0
- package/dist/index.cjs +31 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/package.json +56 -0
- package/src/__test__/Dockerfile +1 -0
- package/src/__test__/docker.test.ts +67 -0
- package/src/__test__/package.json +39 -0
- package/src/__test__/python-example/main.py +1 -0
- package/src/__test__/python-example/requirements.txt +0 -0
- package/src/__test__/vite.config.mts +7 -0
- package/src/core/__tests__/docker-conda.test.ts +145 -0
- package/src/core/__tests__/docker-entrypoint-generation.test.ts +119 -0
- package/src/core/__tests__/docker-python.test.ts +374 -0
- package/src/core/__tests__/publish-docker-images.test.ts +309 -0
- package/src/core/archive.ts +102 -0
- package/src/core/conda/builder.test.ts +71 -0
- package/src/core/conda/builder.ts +384 -0
- package/src/core/conda/validate-spec.test.ts +213 -0
- package/src/core/conda/validate-spec.ts +107 -0
- package/src/core/core.ts +835 -0
- package/src/core/docker-conda.ts +149 -0
- package/src/core/docker-python.ts +283 -0
- package/src/core/docker.ts +300 -0
- package/src/core/envs.ts +31 -0
- package/src/core/package-info.test.ts +82 -0
- package/src/core/package-info.ts +843 -0
- package/src/core/paths.ts +16 -0
- package/src/core/resolver.ts +195 -0
- package/src/core/schemas/artifacts.test.ts +72 -0
- package/src/core/schemas/artifacts.ts +279 -0
- package/src/core/schemas/entrypoints.test.ts +171 -0
- package/src/core/schemas/entrypoints.ts +150 -0
- package/src/core/schemas/sw-json.ts +216 -0
- package/src/core/schemas/test-artifacts.ts +362 -0
- package/src/core/storage.ts +125 -0
- package/src/core/sw-json-render.test.ts +215 -0
- package/src/core/sw-json-render.ts +682 -0
- package/src/core/util.ts +390 -0
- package/src/defaults.ts +38 -0
- package/src/index.ts +31 -0
- package/src/requirements.txt +1 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import type winston from "winston";
|
|
4
|
+
|
|
5
|
+
import * as defaults from "../defaults";
|
|
6
|
+
import * as paths from "./paths";
|
|
7
|
+
import * as util from "./util";
|
|
8
|
+
|
|
9
|
+
import type * as artifacts from "./schemas/artifacts";
|
|
10
|
+
import { micromambaDownloadUrl } from "./conda/builder";
|
|
11
|
+
import { validateCondaSpec } from "./conda/validate-spec";
|
|
12
|
+
import { dockerfileAutogenPath } from "./docker";
|
|
13
|
+
|
|
14
|
+
export const tmpSpecFile = "tmp-env-spec-88344b62d74b9307.yaml";
|
|
15
|
+
|
|
16
|
+
export interface CondaOptions {
|
|
17
|
+
baseImageTag: string;
|
|
18
|
+
micromambaVersion: string;
|
|
19
|
+
frozenSpecFile: string;
|
|
20
|
+
pkg: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface DockerOptions {
|
|
24
|
+
context: string;
|
|
25
|
+
dockerfile: string;
|
|
26
|
+
entrypoint: string[];
|
|
27
|
+
pkg: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface CondaDockerOptions extends CondaOptions, DockerOptions {}
|
|
31
|
+
|
|
32
|
+
function generateCondaDockerfileContent(options: CondaOptions, arch: util.ArchType): string {
|
|
33
|
+
// Read template from assets
|
|
34
|
+
const templatePath = paths.assets("conda-dockerfile.template");
|
|
35
|
+
const templateContent = fs.readFileSync(templatePath, "utf-8");
|
|
36
|
+
|
|
37
|
+
// Generate Dockerfile with dependencies
|
|
38
|
+
return templateContent
|
|
39
|
+
.replaceAll("${BASE_IMAGE_TAG}", options.baseImageTag)
|
|
40
|
+
.replaceAll("${MAMBA_ROOT_PREFIX}", path.posix.join(options.pkg, defaults.CONDA_DATA_LOCATION))
|
|
41
|
+
.replaceAll(
|
|
42
|
+
"${CONDA_PKGS_DIRS}",
|
|
43
|
+
path.posix.join(options.pkg, defaults.CONDA_DATA_LOCATION, "pkgs"),
|
|
44
|
+
)
|
|
45
|
+
.replaceAll(
|
|
46
|
+
"${MICROMAMBA_DOWNLOAD_URL}",
|
|
47
|
+
micromambaDownloadUrl(options.micromambaVersion, `linux-${arch}`),
|
|
48
|
+
)
|
|
49
|
+
.replaceAll("${PKG}", options.pkg)
|
|
50
|
+
.replaceAll("${TMP_SPEC_FILE}", tmpSpecFile)
|
|
51
|
+
.replaceAll("${FROZEN_SPEC_FILE}", options.frozenSpecFile);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function prepareDockerOptions(
|
|
55
|
+
logger: winston.Logger,
|
|
56
|
+
currentPackageRoot: string,
|
|
57
|
+
artifactID: string,
|
|
58
|
+
buildParams: artifacts.condaType,
|
|
59
|
+
arch: util.ArchType,
|
|
60
|
+
): DockerOptions {
|
|
61
|
+
logger.debug(
|
|
62
|
+
`Preparing Docker options for Conda package: ${buildParams.name} (id: ${artifactID})`,
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const options = getDefaultCondaOptions();
|
|
66
|
+
options.micromambaVersion = buildParams["micromamba-version"];
|
|
67
|
+
|
|
68
|
+
if (buildParams.pkg) {
|
|
69
|
+
options.pkg = buildParams.pkg;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const platform: util.PlatformType = `linux-${arch}`;
|
|
73
|
+
|
|
74
|
+
if (!buildParams.roots) {
|
|
75
|
+
throw util.CLIError(
|
|
76
|
+
'Cannot prepare Docker options: package root directory is not specified. Please ensure the "root" property is set in the build parameters.',
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
if (!buildParams.roots[platform]) {
|
|
80
|
+
throw util.CLIError(
|
|
81
|
+
`Cannot prepare Docker options: ${platform} root directory is not specified. Please ensure the 'roots' property is set for artifact.`,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const contextDir = path.resolve(currentPackageRoot, buildParams.roots[platform]);
|
|
86
|
+
if (!fs.existsSync(contextDir)) {
|
|
87
|
+
fs.mkdirSync(contextDir, { recursive: true });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Validate conda spec file before building
|
|
91
|
+
const srcSpecPath = path.join(currentPackageRoot, buildParams.spec);
|
|
92
|
+
logger.debug(`Validating conda spec file for forbidden channels...`);
|
|
93
|
+
try {
|
|
94
|
+
validateCondaSpec(logger, srcSpecPath);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
97
|
+
logger.error(errorMessage);
|
|
98
|
+
throw util.CLIError(
|
|
99
|
+
`Cannot prepare Docker options for conda package '${artifactID}': ${errorMessage}`,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Generate a temporary directory for the Dockerfile
|
|
104
|
+
const dockerfilePath = dockerfileAutogenPath(currentPackageRoot, artifactID);
|
|
105
|
+
const dockerfileDir = path.dirname(dockerfilePath);
|
|
106
|
+
fs.mkdirSync(dockerfileDir, { recursive: true });
|
|
107
|
+
logger.debug(`Created directory for autogenerated dockerfiles: ${dockerfileDir}`);
|
|
108
|
+
|
|
109
|
+
fs.copyFileSync(srcSpecPath, path.join(contextDir, tmpSpecFile));
|
|
110
|
+
logger.debug(`Copied spec file to context directory`);
|
|
111
|
+
|
|
112
|
+
const dockerfile = {
|
|
113
|
+
content: generateCondaDockerfileContent(options, arch),
|
|
114
|
+
path: dockerfilePath,
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
fs.writeFileSync(dockerfile.path, dockerfile.content);
|
|
118
|
+
logger.debug(`Written Dockerfile to: ${dockerfile.path}`);
|
|
119
|
+
|
|
120
|
+
const result: DockerOptions = {
|
|
121
|
+
dockerfile: dockerfile.path,
|
|
122
|
+
context: contextDir,
|
|
123
|
+
entrypoint: [],
|
|
124
|
+
pkg: options.pkg,
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
logger.debug(`Prepared Docker options: ${JSON.stringify(result)}`);
|
|
128
|
+
verifyDockerOptions(result);
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function getDefaultCondaOptions(): CondaOptions {
|
|
133
|
+
return {
|
|
134
|
+
baseImageTag: defaults.CONDA_DOCKER_BASE_IMAGE,
|
|
135
|
+
micromambaVersion: defaults.CONDA_MICROMAMBA_VERSION,
|
|
136
|
+
frozenSpecFile: defaults.CONDA_FROZEN_ENV_SPEC_FILE,
|
|
137
|
+
pkg: defaults.DOCKER_PLACEHOLDER_PKG,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function verifyDockerOptions(options: DockerOptions) {
|
|
142
|
+
if (!fs.existsSync(options.dockerfile)) {
|
|
143
|
+
throw util.CLIError(`Dockerfile '${options.dockerfile}' not found`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (!fs.existsSync(options.context)) {
|
|
147
|
+
throw util.CLIError(`Context '${options.context}' not found`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import type winston from "winston";
|
|
4
|
+
|
|
5
|
+
import * as defaults from "../defaults";
|
|
6
|
+
import * as paths from "./paths";
|
|
7
|
+
import * as util from "./util";
|
|
8
|
+
|
|
9
|
+
import type * as artifacts from "./schemas/artifacts";
|
|
10
|
+
import { resolveRunEnvironment } from "./resolver";
|
|
11
|
+
import { dockerfileAutogenPath } from "./docker";
|
|
12
|
+
|
|
13
|
+
const PYTHON_VERSION_PATTERNS = {
|
|
14
|
+
PY3_PREFIX: /^3\./,
|
|
15
|
+
VERSION_FORMAT:
|
|
16
|
+
/^3\.\d+(?:\.\d+)?(?:[-+_][a-zA-Z0-9]+|(?:a|b|rc)\d+|[a-zA-Z]+\d+(?:\.[a-zA-Z0-9]+)*)*$/,
|
|
17
|
+
DOCKER_SEPARATOR: /@.*$/,
|
|
18
|
+
DOCKER_UNSAFE_CHARS: /[^A-Za-z0-9_.-]+/g,
|
|
19
|
+
MULTIPLE_SEPARATORS: /[-.]{2,}/g,
|
|
20
|
+
EDGE_SEPARATORS: /^[-.]+|[-.]+$/g,
|
|
21
|
+
DOCKER_TAG_FORMAT: /^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$/,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export interface PythonOptions {
|
|
25
|
+
baseImageTag: string;
|
|
26
|
+
toolset: string;
|
|
27
|
+
requirements: string;
|
|
28
|
+
pkg: string;
|
|
29
|
+
envVars: string[]; // custom environment variables set for run environment
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface DockerOptions {
|
|
33
|
+
context: string;
|
|
34
|
+
dockerfile: string;
|
|
35
|
+
entrypoint: string[];
|
|
36
|
+
pkg: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface PythonDockerOptions extends PythonOptions, DockerOptions {}
|
|
40
|
+
|
|
41
|
+
function generatePythonDockerfileContent(options: PythonOptions): string {
|
|
42
|
+
// Read template from assets
|
|
43
|
+
const templatePath = paths.assets("python-dockerfile.template");
|
|
44
|
+
const templateContent = fs.readFileSync(templatePath, "utf-8");
|
|
45
|
+
|
|
46
|
+
const envVars = options.envVars.map((envVar) => `ENV ${envVar}`).join("\n");
|
|
47
|
+
|
|
48
|
+
// Generate Dockerfile with dependencies
|
|
49
|
+
return templateContent
|
|
50
|
+
.replace(/\$\{RUNENV_ENVS\}/g, envVars)
|
|
51
|
+
.replace(/\$\{BASE_IMAGE_TAG\}/g, options.baseImageTag)
|
|
52
|
+
.replace(/\$\{REQUIREMENTS_PATH\}/g, options.requirements)
|
|
53
|
+
.replace(/\$\{REQUIREMENTS_FILENAME\}/g, path.basename(options.requirements))
|
|
54
|
+
.replace(/\$\{TOOLSET\}/g, options.toolset)
|
|
55
|
+
.replace(/\$\{PKG\}/g, options.pkg);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function prepareDockerOptions(
|
|
59
|
+
logger: winston.Logger,
|
|
60
|
+
currentPackageRoot: string,
|
|
61
|
+
currentPackageName: string,
|
|
62
|
+
artifactID: string,
|
|
63
|
+
buildParams: artifacts.pythonType,
|
|
64
|
+
): DockerOptions {
|
|
65
|
+
logger.debug(
|
|
66
|
+
`Preparing Docker options for Python package: ${buildParams.name} (id: ${artifactID})`,
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const options = getDefaultPythonOptions();
|
|
70
|
+
|
|
71
|
+
const pythonInfo = getRunEnvironmentPythonInfo(
|
|
72
|
+
logger,
|
|
73
|
+
currentPackageRoot,
|
|
74
|
+
currentPackageName,
|
|
75
|
+
buildParams.environment,
|
|
76
|
+
);
|
|
77
|
+
if (pythonInfo.pythonVersion) {
|
|
78
|
+
logger.debug(`Extracted Python version from environment: ${pythonInfo.pythonVersion}`);
|
|
79
|
+
options.baseImageTag = pythonVersionToDockerTag(pythonInfo.pythonVersion);
|
|
80
|
+
} else {
|
|
81
|
+
logger.debug(`No Python version found in environment, using default: ${options.baseImageTag}`);
|
|
82
|
+
}
|
|
83
|
+
options.envVars = pythonInfo.envVars;
|
|
84
|
+
|
|
85
|
+
if (buildParams.dependencies) {
|
|
86
|
+
options.toolset = buildParams.dependencies.toolset;
|
|
87
|
+
options.requirements = buildParams.dependencies.requirements;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (buildParams.pkg) {
|
|
91
|
+
options.pkg = buildParams.pkg;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (!buildParams.root) {
|
|
95
|
+
throw util.CLIError(
|
|
96
|
+
'Cannot prepare Docker options: package root directory is not specified. Please ensure the "root" property is set in the build parameters.',
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
const contextDir = path.resolve(buildParams.root ?? currentPackageRoot);
|
|
100
|
+
|
|
101
|
+
options.requirements = path.join(contextDir, options.requirements);
|
|
102
|
+
const hasRequirements = fs.existsSync(options.requirements);
|
|
103
|
+
|
|
104
|
+
if (!hasRequirements) {
|
|
105
|
+
// If requirements.txt doesn't exist, create an empty one
|
|
106
|
+
fs.writeFileSync(options.requirements, "# No dependencies specified\n");
|
|
107
|
+
logger.warn(`Created empty requirements.txt at: ${options.requirements}`);
|
|
108
|
+
}
|
|
109
|
+
// If requirements.txt exists, use relative path for Docker COPY command
|
|
110
|
+
options.requirements = path.relative(contextDir, options.requirements);
|
|
111
|
+
|
|
112
|
+
// Generate a temporary directory for the Dockerfile
|
|
113
|
+
const dockerfilePath = dockerfileAutogenPath(currentPackageRoot, artifactID);
|
|
114
|
+
|
|
115
|
+
const dockerfileDir = path.dirname(dockerfilePath);
|
|
116
|
+
fs.mkdirSync(dockerfileDir, { recursive: true });
|
|
117
|
+
logger.debug(`Created temporary Docker directory: ${dockerfileDir}`);
|
|
118
|
+
|
|
119
|
+
const dockerfile = {
|
|
120
|
+
content: generatePythonDockerfileContent(options),
|
|
121
|
+
path: dockerfilePath,
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
fs.writeFileSync(dockerfile.path, dockerfile.content);
|
|
125
|
+
logger.debug(`Written Dockerfile to: ${dockerfile.path}`);
|
|
126
|
+
|
|
127
|
+
const result: DockerOptions = {
|
|
128
|
+
dockerfile: dockerfile.path,
|
|
129
|
+
context: contextDir,
|
|
130
|
+
entrypoint: [],
|
|
131
|
+
pkg: options.pkg,
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
logger.debug(`Prepared Docker options: ${JSON.stringify(result)}`);
|
|
135
|
+
verifyDockerOptions(result);
|
|
136
|
+
return result;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function normalizeDockerTag(candidate: string): string | undefined {
|
|
140
|
+
if (!candidate || typeof candidate !== "string") {
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// First remove Docker-specific parts like @sha256:...
|
|
145
|
+
let normalized = candidate.replace(PYTHON_VERSION_PATTERNS.DOCKER_SEPARATOR, "");
|
|
146
|
+
|
|
147
|
+
// For cases like "3.12.6:latest", extract only the version part
|
|
148
|
+
if (normalized.includes(":")) {
|
|
149
|
+
normalized = normalized.split(":")[0];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Clean up any remaining unsafe characters
|
|
153
|
+
normalized = normalized
|
|
154
|
+
.replace(PYTHON_VERSION_PATTERNS.DOCKER_UNSAFE_CHARS, "-")
|
|
155
|
+
.replace(PYTHON_VERSION_PATTERNS.MULTIPLE_SEPARATORS, "-")
|
|
156
|
+
.replace(PYTHON_VERSION_PATTERNS.EDGE_SEPARATORS, "");
|
|
157
|
+
|
|
158
|
+
return normalized || undefined;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function isValidPythonVersion(version: string): boolean {
|
|
162
|
+
return (
|
|
163
|
+
PYTHON_VERSION_PATTERNS.PY3_PREFIX.test(version) &&
|
|
164
|
+
PYTHON_VERSION_PATTERNS.VERSION_FORMAT.test(version)
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function isValidDockerTag(tag: string): boolean {
|
|
169
|
+
return PYTHON_VERSION_PATTERNS.DOCKER_TAG_FORMAT.test(tag);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function getRunEnvironmentPythonInfo(
|
|
173
|
+
logger: winston.Logger,
|
|
174
|
+
currentPackageRoot: string,
|
|
175
|
+
currentPackageName: string,
|
|
176
|
+
runEnvironmentID: artifacts.artifactIDString,
|
|
177
|
+
{ normalizeForDocker = true }: { normalizeForDocker?: boolean } = {},
|
|
178
|
+
): {
|
|
179
|
+
pythonVersion: string | undefined;
|
|
180
|
+
envVars: string[];
|
|
181
|
+
} {
|
|
182
|
+
if (!runEnvironmentID) {
|
|
183
|
+
return {
|
|
184
|
+
pythonVersion: undefined,
|
|
185
|
+
envVars: [],
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const environmentDescriptor = resolveRunEnvironment(
|
|
190
|
+
logger,
|
|
191
|
+
currentPackageRoot,
|
|
192
|
+
currentPackageName,
|
|
193
|
+
runEnvironmentID,
|
|
194
|
+
"python",
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
let pythonVersion: string | undefined = environmentDescriptor["python-version"];
|
|
198
|
+
const envVars = environmentDescriptor.envVars ?? [];
|
|
199
|
+
if (!pythonVersion) {
|
|
200
|
+
pythonVersion = undefined;
|
|
201
|
+
const trimmedInput = runEnvironmentID.trim();
|
|
202
|
+
if (!trimmedInput) {
|
|
203
|
+
return {
|
|
204
|
+
pythonVersion: undefined,
|
|
205
|
+
envVars,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const colonIndex = trimmedInput.indexOf(":");
|
|
210
|
+
if (colonIndex === -1 || colonIndex === trimmedInput.length - 1) {
|
|
211
|
+
return {
|
|
212
|
+
pythonVersion: undefined,
|
|
213
|
+
envVars,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
pythonVersion = trimmedInput.slice(colonIndex + 1);
|
|
218
|
+
if (pythonVersion.startsWith("python")) {
|
|
219
|
+
return {
|
|
220
|
+
pythonVersion: undefined,
|
|
221
|
+
envVars,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (!normalizeForDocker) {
|
|
227
|
+
return {
|
|
228
|
+
pythonVersion,
|
|
229
|
+
envVars,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const normalizedTag = normalizeDockerTag(pythonVersion);
|
|
234
|
+
if (!normalizedTag) {
|
|
235
|
+
return {
|
|
236
|
+
pythonVersion: undefined,
|
|
237
|
+
envVars,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (!isValidPythonVersion(normalizedTag) || !isValidDockerTag(normalizedTag)) {
|
|
242
|
+
return {
|
|
243
|
+
pythonVersion: undefined,
|
|
244
|
+
envVars,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return {
|
|
249
|
+
pythonVersion: normalizedTag,
|
|
250
|
+
envVars,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function getDefaultPythonOptions(): PythonOptions {
|
|
255
|
+
return {
|
|
256
|
+
baseImageTag: defaults.PYTHON_DOCKER_BASE_IMAGE,
|
|
257
|
+
toolset: defaults.PYTHON_TOOLSET,
|
|
258
|
+
requirements: defaults.PYTHON_REQUIREMENTS_FILE,
|
|
259
|
+
pkg: defaults.DOCKER_PLACEHOLDER_PKG,
|
|
260
|
+
envVars: [],
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function verifyDockerOptions(options: DockerOptions) {
|
|
265
|
+
if (!fs.existsSync(options.dockerfile)) {
|
|
266
|
+
throw util.CLIError(`Dockerfile '${options.dockerfile}' not found`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (!fs.existsSync(options.context)) {
|
|
270
|
+
throw util.CLIError(`Context '${options.context}' not found`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function pythonVersionToDockerTag(pythonVersion: string): string {
|
|
275
|
+
const parts = pythonVersion.split(".");
|
|
276
|
+
if (parts.length > 2) {
|
|
277
|
+
const major = parts[0];
|
|
278
|
+
const minor = parts[1];
|
|
279
|
+
return `python:${major}.${minor}-slim`;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return `python:${pythonVersion}-slim`;
|
|
283
|
+
}
|