@vaharoni/devops 1.1.1 → 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +196 -46
- package/dist/libs/init-generator.d.ts +41 -0
- package/dist/libs/init-generator.d.ts.map +1 -0
- package/dist/libs/init-generator.js +123 -0
- package/package.json +3 -1
- package/src/cli/init.ts +221 -48
- package/src/libs/init-generator.ts +165 -0
- package/src/target-templates/README.md +1 -0
- package/src/target-templates/cluster-resource-options/README.md +2 -0
- package/src/target-templates/{.devops → cluster-resource-options}/postgres/production/configurations/07-SGObjectStorage.yaml +1 -1
- package/src/target-templates/{.devops → cluster-resource-options}/postgres/production/configurations/08-SGScript.yaml +1 -1
- package/src/target-templates/{.devops → cluster-resource-options}/postgres/staging/configurations/07-SGObjectStorage.yaml +1 -1
- package/src/target-templates/{.devops → cluster-resource-options}/postgres/staging/configurations/08-SGScript.yaml +1 -1
- package/src/target-templates/infra-variants/README.md +2 -0
- package/src/target-templates/infra-variants/digitalocean/.devops/config/constants.yaml +18 -0
- package/src/target-templates/infra-variants/digitalocean/.github/workflows/k8s-build.yaml +73 -0
- package/src/target-templates/infra-variants/gcloud/.devops/config/constants.yaml +15 -0
- package/src/target-templates/infra-variants/gcloud/.devops/manifests/ingress.yaml.hb +22 -0
- package/src/target-templates/{.github → infra-variants/gcloud/.github}/workflows/k8s-build.yaml +2 -12
- package/src/target-templates/{.devops → infra-variants/hetzner/.devops}/config/constants.yaml +3 -3
- package/src/target-templates/{.devops → infra-variants/hetzner/.devops}/infra/hetzner/abandoned/harbor-values.yaml +2 -2
- package/src/target-templates/{.devops → infra-variants/hetzner/.devops}/infra/hetzner/abandoned/hcloud-config.yaml +1 -1
- package/src/target-templates/{.devops → infra-variants/hetzner/.devops}/infra/hetzner/harbor-cert.yaml +2 -2
- package/src/target-templates/{.devops → infra-variants/hetzner/.devops}/infra/hetzner/harbor-values.yaml +2 -2
- package/src/target-templates/{.devops → infra-variants/hetzner/.devops}/infra/hetzner/hcloud-config.yaml +1 -1
- package/src/target-templates/infra-variants/hetzner/.github/workflows/k8s-build.yaml +75 -0
- package/src/target-templates/lang-variants-common/README.md +4 -0
- package/src/target-templates/{.devops → lang-variants-common/python/.devops}/config/images.yaml +4 -4
- package/src/target-templates/{pyproject.toml → lang-variants-common/python/pyproject.toml} +1 -1
- package/src/target-templates/lang-variants-common/typescript/.devops/config/images.yaml +69 -0
- package/src/target-templates/lang-variants-common/typescript/.devops/manifests/_index.yaml +19 -0
- package/src/target-templates/lang-variants-common/typescript/.envrc +5 -0
- package/src/target-templates/{.github → lang-variants-common/typescript/.github}/actions/connect-to-infra@v1/action.yaml +2 -2
- package/src/target-templates/lang-variants-prisma/README.md +3 -0
- package/src/target-templates/config/.env.development +0 -1
- package/src/target-templates/config/.env.global +0 -4
- package/src/target-templates/config/.env.test +0 -1
- package/src/target-templates/libs/example-node-lib/bun.lock +0 -27
- /package/src/target-templates/{.devops/infra/test.yaml → cluster-resource-options/dns-test/dns-test.yaml} +0 -0
- /package/src/target-templates/{.devops → cluster-resource-options}/milvus/production/milvus-values.yaml +0 -0
- /package/src/target-templates/{.devops → cluster-resource-options}/milvus/staging/milvus-values.yaml +0 -0
- /package/src/target-templates/{.devops/infra → cluster-resource-options/monitoring-ingress}/monitoring-ingress.yaml +0 -0
- /package/src/target-templates/{.devops/postgres/DailyOperatorRestart.yaml → cluster-resource-options/postgres/daily-operator-restart.yaml} +0 -0
- /package/src/target-templates/{.devops → cluster-resource-options}/postgres/production/cluster/PodDisruptionBudget.yaml +0 -0
- /package/src/target-templates/{.devops → cluster-resource-options}/postgres/production/cluster/SGCluster.yaml +0 -0
- /package/src/target-templates/{.devops → cluster-resource-options}/postgres/production/cluster/StackGres-alerts.yaml +0 -0
- /package/src/target-templates/{.devops → cluster-resource-options}/postgres/production/configurations/06-SGDistributedLogs.yaml +0 -0
- /package/src/target-templates/{.devops/infra → cluster-resource-options/postgres}/stackgres-ui-ingress.yaml +0 -0
- /package/src/target-templates/{.devops → cluster-resource-options}/postgres/staging/cluster/SGCluster.yaml +0 -0
- /package/src/target-templates/{.devops → cluster-resource-options}/prefect/production/prefect-values.yaml +0 -0
- /package/src/target-templates/{.devops → cluster-resource-options}/prefect/staging/prefect-values.yaml +0 -0
- /package/src/target-templates/{.devops → cluster-resource-options}/redis/production/redis-values.yaml +0 -0
- /package/src/target-templates/{.devops → cluster-resource-options}/redis/staging/redis-values.yaml +0 -0
- /package/src/target-templates/{.devops → infra-variants/hetzner/.devops}/infra/hetzner/cert-manager.yaml +0 -0
- /package/src/target-templates/{.devops → infra-variants/hetzner/.devops}/infra/hetzner/ingress-nginx-annotations.yaml +0 -0
- /package/src/target-templates/{.devops → infra-variants/hetzner/.devops}/infra/hetzner/ingress-nginx-configmap.yaml +0 -0
- /package/src/target-templates/{.devops → infra-variants/hetzner/.devops}/infra/hetzner/retain-storage-class.yaml +0 -0
- /package/src/target-templates/{.devops → lang-variants-common/python/.devops}/docker-images/python-services/python-exec.sh +0 -0
- /package/src/target-templates/{.devops → lang-variants-common/python/.devops}/docker-images/python-services/python-run.sh +0 -0
- /package/src/target-templates/{.devops → lang-variants-common/python/.devops}/docker-images/python-services.Dockerfile +0 -0
- /package/src/target-templates/{.devops → lang-variants-common/python/.devops}/manifests/_index.yaml +0 -0
- /package/src/target-templates/{.devops → lang-variants-common/python/.devops}/manifests/prefect.yaml.hb +0 -0
- /package/src/target-templates/{applications → lang-variants-common/python/applications}/example-data-pipeline/pyproject.toml +0 -0
- /package/src/target-templates/{applications → lang-variants-common/python/applications}/example-data-pipeline/src/example_data_pipeline/main.py +0 -0
- /package/src/target-templates/{applications → lang-variants-common/python/applications}/example-python/pyproject.toml +0 -0
- /package/src/target-templates/{applications → lang-variants-common/python/applications}/example-python/src/example_python/__init__.py +0 -0
- /package/src/target-templates/{applications → lang-variants-common/python/applications}/example-python/src/example_python/main.py +0 -0
- /package/src/target-templates/{applications → lang-variants-common/python/applications}/example-python/src/example_python/scripts.py +0 -0
- /package/src/target-templates/{applications → lang-variants-common/python/applications}/example-python/tests/__init__.py +0 -0
- /package/src/target-templates/{devopspy → lang-variants-common/python/devopspy} +0 -0
- /package/src/target-templates/{libs → lang-variants-common/python/libs}/example-python-lib/pyproject.toml +0 -0
- /package/src/target-templates/{libs → lang-variants-common/python/libs}/example-python-lib/src/example_python_lib/__init__.py +0 -0
- /package/src/target-templates/{.devops → lang-variants-common/typescript/.devops}/docker-images/common/docker-common.sh +0 -0
- /package/src/target-templates/{.devops → lang-variants-common/typescript/.devops}/docker-images/node-services/node-exec.sh +0 -0
- /package/src/target-templates/{.devops → lang-variants-common/typescript/.devops}/docker-images/node-services/node-run.sh +0 -0
- /package/src/target-templates/{.devops → lang-variants-common/typescript/.devops}/docker-images/node-services.Dockerfile +0 -0
- /package/src/target-templates/{.devops → lang-variants-common/typescript/.devops}/env.example.yaml +0 -0
- /package/src/target-templates/{.devops → lang-variants-common/typescript/.devops}/manifests/cron-jobs.yaml.hb +0 -0
- /package/src/target-templates/{.devops → lang-variants-common/typescript/.devops}/manifests/db-migrate-job.yaml.hb +0 -0
- /package/src/target-templates/{.devops → lang-variants-common/typescript/.devops}/manifests/deployment-debug.yaml.hb +0 -0
- /package/src/target-templates/{.devops → lang-variants-common/typescript/.devops}/manifests/deployment-process.yaml.hb +0 -0
- /package/src/target-templates/{.devops → lang-variants-common/typescript/.devops}/manifests/deployment-web.yaml.hb +0 -0
- /package/src/target-templates/{.devops → lang-variants-common/typescript/.devops}/manifests/ingress.yaml.hb +0 -0
- /package/src/target-templates/{.devops → lang-variants-common/typescript/.devops}/manifests/service.yaml.hb +0 -0
- /package/src/target-templates/{.github → lang-variants-common/typescript/.github}/actions/build-image@v1/action.yaml +0 -0
- /package/src/target-templates/{.github → lang-variants-common/typescript/.github}/actions/connect-to-digital-ocean@v1/action.yaml +0 -0
- /package/src/target-templates/{.github → lang-variants-common/typescript/.github}/actions/connect-to-gke@v1/action.yaml +0 -0
- /package/src/target-templates/{.github → lang-variants-common/typescript/.github}/actions/connect-to-hetzner@v1/action.yaml +0 -0
- /package/src/target-templates/{.github → lang-variants-common/typescript/.github}/actions/db-migrate@v1/action.yaml +0 -0
- /package/src/target-templates/{.github → lang-variants-common/typescript/.github}/actions/deploy-image@v1/action.yaml +0 -0
- /package/src/target-templates/{.github → lang-variants-common/typescript/.github}/actions/setup-prereq@v1/action.yaml +0 -0
- /package/src/target-templates/{applications → lang-variants-common/typescript/applications}/example-node/index.ts +0 -0
- /package/src/target-templates/{applications → lang-variants-common/typescript/applications}/example-node/package.json +0 -0
- /package/src/target-templates/{applications → lang-variants-common/typescript/applications}/example-node/tsconfig.json +0 -0
- /package/src/target-templates/{applications → lang-variants-common/typescript/applications}/jobs/README.md +0 -0
- /package/src/target-templates/{applications → lang-variants-common/typescript/applications}/jobs/index.ts +0 -0
- /package/src/target-templates/{applications → lang-variants-common/typescript/applications}/jobs/package.json +0 -0
- /package/src/target-templates/{applications → lang-variants-common/typescript/applications}/jobs/tsconfig.json +0 -0
- /package/src/target-templates/{devops → lang-variants-common/typescript/devops} +0 -0
- /package/src/target-templates/{libs → lang-variants-common/typescript/libs}/example-node-lib/index.ts +0 -0
- /package/src/target-templates/{libs → lang-variants-common/typescript/libs}/example-node-lib/package.json +0 -0
- /package/src/target-templates/{libs → lang-variants-common/typescript/libs}/example-node-lib/tsconfig.json +0 -0
- /package/src/target-templates/{tmp → lang-variants-common/typescript/tmp}/.gitkeep +0 -0
- /package/src/target-templates/{tsconfig.json → lang-variants-common/typescript/tsconfig.json} +0 -0
- /package/src/target-templates/{db → lang-variants-prisma/python/db}/db/__init__.py +0 -0
- /package/src/target-templates/{db → lang-variants-prisma/python/db}/db/db_client_test.py +0 -0
- /package/src/target-templates/{db → lang-variants-prisma/python/db}/pyproject.toml +0 -0
- /package/src/target-templates/{db → lang-variants-prisma/typescript/db}/db-client-test.ts +0 -0
- /package/src/target-templates/{db → lang-variants-prisma/typescript/db}/db-client.ts +0 -0
- /package/src/target-templates/{db → lang-variants-prisma/typescript/db}/env.yaml +0 -0
- /package/src/target-templates/{db → lang-variants-prisma/typescript/db}/package.json +0 -0
- /package/src/target-templates/{db → lang-variants-prisma/typescript/db}/prisma/schema.prisma +0 -0
- /package/src/target-templates/{db → lang-variants-prisma/typescript/db}/prisma-setup-vitest.ts +0 -0
- /package/src/target-templates/{db → lang-variants-prisma/typescript/db}/tsconfig.json +0 -0
- /package/src/target-templates/{dml → lang-variants-prisma/typescript/dml}/package.json +0 -0
- /package/src/target-templates/{dml → lang-variants-prisma/typescript/dml}/tsconfig.json +0 -0
package/src/cli/init.ts
CHANGED
@@ -1,13 +1,9 @@
|
|
1
|
-
import
|
2
|
-
import
|
3
|
-
import fs from "fs-extra";
|
4
|
-
|
5
|
-
const __file__ = url.fileURLToPath(import.meta.url);
|
6
|
-
const __root__ = path.join(path.dirname(__file__), "../..");
|
7
|
-
const templatesDir = path.join(__root__, "src/target-templates");
|
8
|
-
const targetDir = process.cwd(); // User's current working directory
|
9
|
-
|
1
|
+
import inquirer from "inquirer";
|
2
|
+
import { InitGenerator, type InitGeneratorFileInfo } from "../libs/init-generator";
|
10
3
|
import { CLICommandParser, printUsageAndExit } from "./common";
|
4
|
+
import chalk from "chalk";
|
5
|
+
import fs from 'fs-extra';
|
6
|
+
import type { ConstFileSchema } from "../types";
|
11
7
|
|
12
8
|
const oneLiner =
|
13
9
|
"Initializes the devops utility by copying template files to the current folder";
|
@@ -25,56 +21,233 @@ EXAMPLES
|
|
25
21
|
|
26
22
|
async function run(cmdObj: CLICommandParser) {
|
27
23
|
if (cmdObj.help) printUsageAndExit(usage);
|
28
|
-
|
24
|
+
createFiles();
|
29
25
|
}
|
30
26
|
|
31
27
|
export default {
|
32
28
|
init: { oneLiner, keyExamples, run },
|
33
29
|
};
|
34
30
|
|
35
|
-
async function
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
31
|
+
async function createFiles() {
|
32
|
+
const tc = new InitGenerator();
|
33
|
+
const userChoices = await getUserChoices(tc.projectName);
|
34
|
+
|
35
|
+
// Language variants
|
36
|
+
tc.addCopiedFolder("lang-variants-common/typescript", ".");
|
37
|
+
if (userChoices.usePython) {
|
38
|
+
tc.addCopiedFolder("lang-variants-common/python", ".");
|
39
|
+
tc.enableSubtitution("pyproject.toml");
|
40
|
+
}
|
41
|
+
tc.enableSubtitution(".devops/config/images.yaml");
|
42
|
+
tc.setMessageGenerator(".envrc", envrcMessage);
|
43
|
+
|
44
|
+
// gitignore
|
45
|
+
const gitIgnore = gitIgnoreContent(userChoices.infraVariant, userChoices.usePython)
|
46
|
+
tc.addGeneratedFile(".gitignore", gitIgnore);
|
47
|
+
tc.setMessageGenerator(".gitignore", gitignoreMessageGen(gitIgnore));
|
48
|
+
|
49
|
+
// Infra variants
|
50
|
+
tc.addCopiedFolder(`infra-variants/${userChoices.infraVariant}`, ".");
|
51
|
+
tc.enableSubtitution(".devops/config/constants.yaml");
|
52
|
+
if (userChoices.infraVariant === "hetzner") {
|
53
|
+
tc.enableSubtitution(".devops/infra/hetzner/harbor-cert.yaml");
|
54
|
+
tc.enableSubtitution(".devops/infra/hetzner/harbor-values.yaml");
|
55
|
+
tc.enableSubtitution(".devops/infra/hetzner/hcloud-config.yaml");
|
50
56
|
}
|
51
|
-
}
|
52
57
|
|
53
|
-
|
54
|
-
|
58
|
+
// Prisma
|
59
|
+
if (userChoices.usePrisma) {
|
60
|
+
tc.addCopiedFolder("lang-variants-prisma/typescript", ".");
|
61
|
+
if (userChoices.usePython) {
|
62
|
+
tc.addCopiedFolder("lang-variants-prisma/python", ".");
|
63
|
+
}
|
64
|
+
}
|
55
65
|
|
56
|
-
|
66
|
+
// Cluster resources
|
67
|
+
const clusterResources = new Set(userChoices.clusterResources);
|
68
|
+
if (clusterResources.has("dns-test")) {
|
69
|
+
tc.addCopiedFolder("cluster-resource-options/dns-test", ".devops/infra/dns-test");
|
70
|
+
}
|
71
|
+
if (clusterResources.has("monitoring-ingress")) {
|
72
|
+
tc.addCopiedFolder("cluster-resource-options/monitoring-ingress", ".devops/infra/monitoring-ingress");
|
73
|
+
}
|
74
|
+
if (clusterResources.has("postgres")) {
|
75
|
+
tc.addCopiedFolder("cluster-resource-options/postgres", ".devops/infra/postgres");
|
76
|
+
// prettier-ignore
|
77
|
+
tc.enableSubtitution(".devops/infra/postgres/staging/configurations/07-SGObjectStorage.yaml");
|
78
|
+
// prettier-ignore
|
79
|
+
tc.enableSubtitution(".devops/infra/postgres/staging/configurations/08-SGScript.yaml");
|
80
|
+
// prettier-ignore
|
81
|
+
tc.enableSubtitution(".devops/infra/postgres/production/configurations/07-SGObjectStorage.yaml");
|
82
|
+
// prettier-ignore
|
83
|
+
tc.enableSubtitution(".devops/infra/postgres/production/configurations/08-SGScript.yaml");
|
84
|
+
}
|
85
|
+
if (clusterResources.has("redis")) {
|
86
|
+
tc.addCopiedFolder("cluster-resource-options/redis", ".devops/infra/redis");
|
87
|
+
}
|
88
|
+
if (clusterResources.has("milvus")) {
|
89
|
+
tc.addCopiedFolder("cluster-resource-options/milvus", ".devops/infra/milvus");
|
90
|
+
}
|
91
|
+
if (clusterResources.has("prefect") && userChoices.usePython) {
|
92
|
+
tc.addCopiedFolder("cluster-resource-options/prefect", ".devops/infra/prefect");
|
93
|
+
}
|
57
94
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
95
|
+
tc.run({
|
96
|
+
substitution: {
|
97
|
+
'PROJECT_NAME': userChoices.projectName,
|
98
|
+
'STAGING_DOMAIN': userChoices.stagingDomain,
|
99
|
+
'PRODUCTION_DOMAIN': userChoices.productionDomain,
|
100
|
+
'GCLOUD_PROJECT_ID': userChoices.gcloudProjectId,
|
101
|
+
'REGISTRY_IMAGE_PATH_PREFIX': userChoices.registryImagePathPrefix,
|
102
|
+
'REGISTRY_BASE_URL': userChoices.registryBaseUrl,
|
103
|
+
},
|
104
|
+
messages: [
|
105
|
+
packageJsonMessage(userChoices.usePrisma)
|
106
|
+
]
|
107
|
+
})
|
108
|
+
}
|
109
|
+
|
110
|
+
function packageJsonMessage(usePrisma: boolean) {
|
111
|
+
const prismaMessage = usePrisma
|
112
|
+
? `,
|
62
113
|
"db/**",
|
63
|
-
"dml/**"
|
64
|
-
|
114
|
+
"dml/**"`
|
115
|
+
: "";
|
65
116
|
|
66
|
-
|
67
|
-
|
117
|
+
return `add the following entry to the main ${chalk.blue("package.json")}:
|
118
|
+
${chalk.yellow(`"workspaces": [
|
119
|
+
"libs/**",
|
120
|
+
"applications/**"${prismaMessage}
|
121
|
+
],`)}`
|
122
|
+
}
|
123
|
+
|
124
|
+
function gitIgnoreContent(infraVariant: UserChoices["infraVariant"], usePython: boolean) {
|
125
|
+
const common = `**/.env*
|
68
126
|
config/kubeconfig
|
69
127
|
tmp/**
|
70
|
-
!tmp/**/.gitkeep
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
128
|
+
!tmp/**/.gitkeep`;
|
129
|
+
|
130
|
+
const gcloud = infraVariant === 'gcloud'
|
131
|
+
? 'config/gke_gcloud_auth_plugin_cache'
|
132
|
+
: null;
|
133
|
+
|
134
|
+
const python = usePython
|
135
|
+
? `venv/
|
136
|
+
**/__pycache__`
|
137
|
+
: null;
|
138
|
+
|
139
|
+
return [common, gcloud, python].filter(Boolean).join('\n');
|
140
|
+
}
|
141
|
+
|
142
|
+
function gitignoreMessageGen(content: string) {
|
143
|
+
return (exists: boolean) => {
|
144
|
+
if (!exists) return;
|
145
|
+
return `add the following to your ${chalk.blue(".gitignore")}:
|
146
|
+
${chalk.yellow(content)}`;
|
147
|
+
}
|
148
|
+
}
|
149
|
+
|
150
|
+
function envrcMessage(targetExists: boolean, fileInfo: InitGeneratorFileInfo) {
|
151
|
+
if (fileInfo.type !== 'copied') throw new Error(`envrcMessage() expects a copied file, got ${fileInfo.type}`);
|
152
|
+
if (targetExists) {
|
153
|
+
const content = fs.readFileSync(fileInfo.sourceAbs, 'utf-8');
|
154
|
+
return `add the following to your ${chalk.blue(".envrc")} and run ${chalk.yellow("direnv allow")}:
|
155
|
+
${chalk.yellow(content)}`;
|
156
|
+
} else {
|
157
|
+
return `Enable ${chalk.blue(".envrc")} by installing ${chalk.blue('direnv')} and running ${chalk.yellow("direnv allow")}`;
|
158
|
+
}
|
159
|
+
}
|
160
|
+
|
161
|
+
type UserChoices = {
|
162
|
+
projectName: string;
|
163
|
+
stagingDomain: string;
|
164
|
+
productionDomain: string;
|
165
|
+
infraVariant: ConstFileSchema["infra"];
|
166
|
+
gcloudProjectId?: string;
|
167
|
+
registryImagePathPrefix?: string;
|
168
|
+
registryBaseUrl?: string;
|
169
|
+
usePython: boolean;
|
170
|
+
usePrisma: boolean;
|
171
|
+
clusterResources: string[];
|
172
|
+
};
|
173
|
+
|
174
|
+
function getUserChoices(projectName: string | undefined): Promise<UserChoices> {
|
175
|
+
const defaultProjectName = projectName || "changeme";
|
176
|
+
|
177
|
+
return inquirer.prompt([
|
178
|
+
{
|
179
|
+
type: "input",
|
180
|
+
name: "projectName",
|
181
|
+
message: `Enter the project name (default: '${defaultProjectName}')`,
|
182
|
+
default: defaultProjectName,
|
183
|
+
},
|
184
|
+
{
|
185
|
+
type: "input",
|
186
|
+
name: "stagingDomain",
|
187
|
+
message: "Enter the staging domain (default: 'staging.com')",
|
188
|
+
default: "staging.com",
|
189
|
+
},
|
190
|
+
{
|
191
|
+
type: "input",
|
192
|
+
name: "productionDomain",
|
193
|
+
message: "Enter the production domain (default: 'production.com')",
|
194
|
+
default: "production.com",
|
195
|
+
},
|
196
|
+
{
|
197
|
+
type: "list",
|
198
|
+
name: "infraVariant",
|
199
|
+
message: "Where does your cluster run?",
|
200
|
+
choices: [
|
201
|
+
{ name: "Google Cloud", value: "gcloud" },
|
202
|
+
{ name: "Digital Ocean", value: "digitalocean" },
|
203
|
+
{ name: "Hetzner", value: "hetzner" },
|
204
|
+
],
|
205
|
+
},
|
206
|
+
{
|
207
|
+
type: "input",
|
208
|
+
name: "gcloudProjectId",
|
209
|
+
message: "Enter the GCP project ID (default: 'changeme')",
|
210
|
+
default: "changeme",
|
211
|
+
when: (answers) => answers.infraVariant === "gcloud",
|
212
|
+
},
|
213
|
+
{
|
214
|
+
type: "input",
|
215
|
+
name: "registryImagePathPrefix",
|
216
|
+
message: (answers) => `Enter your Digital Ocean container registry name (default: '${answers.projectName}')`,
|
217
|
+
default: (answers) => answers.projectName,
|
218
|
+
when: (answers) => answers.infraVariant === "digitalocean",
|
219
|
+
},
|
220
|
+
{
|
221
|
+
type: "input",
|
222
|
+
name: "registryBaseUrl",
|
223
|
+
message: (answers) => `Enter your registry base URL (default: 'registry.${answers.stagingDomain}')`,
|
224
|
+
default: (answers) => `registry.${answers.stagingDomain}`,
|
225
|
+
when: (answers) => answers.infraVariant === "hetzner",
|
226
|
+
},
|
227
|
+
{
|
228
|
+
type: "confirm",
|
229
|
+
name: "usePython",
|
230
|
+
message: "Add support for Python?",
|
231
|
+
default: true,
|
232
|
+
},
|
233
|
+
{
|
234
|
+
type: "confirm",
|
235
|
+
name: "usePrisma",
|
236
|
+
message: "Add support for Prisma?",
|
237
|
+
default: true,
|
238
|
+
},
|
239
|
+
{
|
240
|
+
type: "checkbox",
|
241
|
+
name: "clusterResources",
|
242
|
+
message: "Optional manifests and helm charts to add",
|
243
|
+
choices: (answers) => [
|
244
|
+
{ name: "Manifest to test DNS setup", value: "dns-test" },
|
245
|
+
{ name: "Manifest to setup ingress for graphana and prometheus", value: "monitoring-ingress" },
|
246
|
+
{ name: "Stackgres CRDs and manifests for Postgres", value: "postgres" },
|
247
|
+
{ name: "Redis Helm chart values", value: "redis" },
|
248
|
+
{ name: "Milvus helm chart values", value: "milvus" },
|
249
|
+
...(answers.usePython ? [{ name: "Prefect Helm chart values", value: "prefect" }] : [])
|
250
|
+
]
|
251
|
+
}
|
252
|
+
])
|
253
|
+
}
|
@@ -0,0 +1,165 @@
|
|
1
|
+
import url from "url";
|
2
|
+
import path from "path";
|
3
|
+
import fs from "fs-extra";
|
4
|
+
import chalk from "chalk";
|
5
|
+
import fg from 'fast-glob';
|
6
|
+
|
7
|
+
const __file__ = url.fileURLToPath(import.meta.url);
|
8
|
+
const __root__ = path.join(path.dirname(__file__), "../..");
|
9
|
+
const templatesDir = path.join(__root__, "src/target-templates");
|
10
|
+
const targetDir = process.cwd(); // User's current working directory
|
11
|
+
|
12
|
+
type MessageGeneratorFn = (targetExists: boolean, fileInfo: InitGeneratorFileInfo) => string | null | undefined;
|
13
|
+
|
14
|
+
type CommonFileInfo = {
|
15
|
+
targetRel: string;
|
16
|
+
targetAbs: string;
|
17
|
+
targetFolderAbs: string;
|
18
|
+
targetExists: boolean;
|
19
|
+
messageGenerator?: MessageGeneratorFn;
|
20
|
+
}
|
21
|
+
|
22
|
+
export type InitGeneratorCopiedFileInfo = CommonFileInfo &{
|
23
|
+
type: "copied";
|
24
|
+
sourceRel: string;
|
25
|
+
sourceAbs: string;
|
26
|
+
enableSubstitution?: boolean;
|
27
|
+
}
|
28
|
+
|
29
|
+
type InitGeneratorGeneratedFileInfo = CommonFileInfo & {
|
30
|
+
type: "generated";
|
31
|
+
content: string;
|
32
|
+
}
|
33
|
+
|
34
|
+
export type InitGeneratorFileInfo = InitGeneratorCopiedFileInfo | InitGeneratorGeneratedFileInfo;
|
35
|
+
|
36
|
+
export class InitGenerator {
|
37
|
+
projectName?: string;
|
38
|
+
|
39
|
+
/** The key is targetRel */
|
40
|
+
files: Record<string, InitGeneratorFileInfo> = {};
|
41
|
+
|
42
|
+
constructor() {
|
43
|
+
if (fs.existsSync("package.json")) {
|
44
|
+
const packageJson = fs.readJSONSync("package.json");
|
45
|
+
this.projectName = packageJson.name;
|
46
|
+
}
|
47
|
+
}
|
48
|
+
|
49
|
+
_ensureFileExists(targetRel: string) {
|
50
|
+
if (!this.files[targetRel]) {
|
51
|
+
throw new Error(`File for target "${targetRel}" not found.`);
|
52
|
+
}
|
53
|
+
}
|
54
|
+
|
55
|
+
enableSubtitution(targetRel: string) {
|
56
|
+
this._ensureFileExists(targetRel);
|
57
|
+
if (this.files[targetRel].type !== "copied") {
|
58
|
+
throw new Error(`File for target "${targetRel}" is not a copied file.`);
|
59
|
+
}
|
60
|
+
this.files[targetRel].enableSubstitution = true;
|
61
|
+
}
|
62
|
+
|
63
|
+
setMessageGenerator(targetRel: string, messageGen: MessageGeneratorFn) {
|
64
|
+
this._ensureFileExists(targetRel);
|
65
|
+
this.files[targetRel].messageGenerator = messageGen;
|
66
|
+
}
|
67
|
+
|
68
|
+
addGeneratedFile(targetRel: string, content: string) {
|
69
|
+
const targetAbs = path.join(targetDir, targetRel);
|
70
|
+
const targetFolderAbs = path.dirname(targetAbs);
|
71
|
+
const exists = fs.existsSync(targetAbs);
|
72
|
+
this.files[targetRel] = {
|
73
|
+
type: "generated",
|
74
|
+
targetRel,
|
75
|
+
targetAbs,
|
76
|
+
targetFolderAbs,
|
77
|
+
targetExists: exists,
|
78
|
+
content,
|
79
|
+
};
|
80
|
+
}
|
81
|
+
|
82
|
+
/**
|
83
|
+
* @param source relative path under the templates folder. All files and folders under `source` are copied directly under `target`.
|
84
|
+
* @param target relative path under the project root folder.
|
85
|
+
* If the target file exists already in `files`, it will be overridden.
|
86
|
+
*/
|
87
|
+
addCopiedFolder(source: string, target: string) {
|
88
|
+
const pathPrefix = path.join(templatesDir, source);
|
89
|
+
const glob = path.join(pathPrefix, '**/*');
|
90
|
+
fg.globSync(glob, { dot: true }).forEach((sourceAbs) => {
|
91
|
+
const sourceRel = path.relative(templatesDir, sourceAbs);
|
92
|
+
const pathUnderSource = path.relative(pathPrefix, sourceAbs);
|
93
|
+
const targetRel = path.join(target, pathUnderSource);
|
94
|
+
const targetAbs = path.join(targetDir, targetRel);
|
95
|
+
const targetFolderAbs = path.dirname(targetAbs);
|
96
|
+
const exists = fs.existsSync(targetAbs);
|
97
|
+
this.files[targetRel] = {
|
98
|
+
type: "copied",
|
99
|
+
sourceRel,
|
100
|
+
targetRel,
|
101
|
+
sourceAbs,
|
102
|
+
targetAbs,
|
103
|
+
targetFolderAbs,
|
104
|
+
targetExists: exists,
|
105
|
+
};
|
106
|
+
})
|
107
|
+
}
|
108
|
+
|
109
|
+
run({
|
110
|
+
substitution = {},
|
111
|
+
messages = [],
|
112
|
+
} : {
|
113
|
+
substitution?: Record<string, string | undefined>;
|
114
|
+
messages?: string[];
|
115
|
+
}) {
|
116
|
+
const fileMessages: string[] = [];
|
117
|
+
Object.values(this.files).forEach((fileInfo) => {
|
118
|
+
if (fileInfo.messageGenerator) {
|
119
|
+
const message = fileInfo.messageGenerator(fileInfo.targetExists, fileInfo);
|
120
|
+
if (message) {
|
121
|
+
fileMessages.push(message);
|
122
|
+
}
|
123
|
+
}
|
124
|
+
|
125
|
+
if (fileInfo.targetExists) {
|
126
|
+
console.log(`Skipped ${chalk.yellow(fileInfo.targetRel)} (exists)`);
|
127
|
+
return;
|
128
|
+
}
|
129
|
+
|
130
|
+
// Create or copy
|
131
|
+
if (!fs.existsSync(fileInfo.targetFolderAbs)) {
|
132
|
+
fs.mkdirSync(fileInfo.targetFolderAbs, { recursive: true });
|
133
|
+
}
|
134
|
+
|
135
|
+
if (fileInfo.type === 'generated') {
|
136
|
+
fs.writeFileSync(fileInfo.targetAbs, fileInfo.content, 'utf8');
|
137
|
+
} else if (fileInfo.enableSubstitution) {
|
138
|
+
const content = fs.readFileSync(fileInfo.sourceAbs, 'utf8');
|
139
|
+
const substitutedContent = content.replace(/\$([A-Z_]+)/g, (_, varName) => {
|
140
|
+
const value = substitution[varName];
|
141
|
+
if (!value) {
|
142
|
+
throw new Error(`${chalk.blue("TemplateCopier.run()")}: Variable ${chalk.yellow(varName)} is needed by ${chalk.yellow(fileInfo.targetRel)} but is undefined.`);
|
143
|
+
}
|
144
|
+
return value;
|
145
|
+
});
|
146
|
+
fs.writeFileSync(fileInfo.targetAbs, substitutedContent);
|
147
|
+
} else {
|
148
|
+
fs.copySync(fileInfo.sourceAbs, fileInfo.targetAbs, {
|
149
|
+
overwrite: false,
|
150
|
+
errorOnExist: false,
|
151
|
+
dereference: false,
|
152
|
+
});
|
153
|
+
}
|
154
|
+
console.log(`Created ${chalk.green(fileInfo.targetRel)}`);
|
155
|
+
});
|
156
|
+
|
157
|
+
const allMessages = [...messages, ...fileMessages];
|
158
|
+
if (!allMessages.length) return;
|
159
|
+
|
160
|
+
console.log(chalk.blue("\nNext steps:"));
|
161
|
+
allMessages.forEach((msg, i) => {
|
162
|
+
console.log(`${i + 1}. ${msg}\n`);
|
163
|
+
});
|
164
|
+
}
|
165
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
This folder contains the templates that are copied to the target project's folder based on answers the user provides during `./devops init`. Some of these answers may change the content of files, which is done by substituting variables prefixed by `$`. To avoid potential issues, such substitution is not pursued globally for all files in this folder. Files requiring substitution must be referenced by name in `./devops init` code.
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# These will be used when generating kubernetes entities
|
2
|
+
project-name: $PROJECT_NAME
|
3
|
+
|
4
|
+
# Supported: hetzner, digitalocean, or gcloud
|
5
|
+
infra: digitalocean
|
6
|
+
|
7
|
+
# Only relevant for Digital Ocean. Determines the number of versions to keep for each docker image.
|
8
|
+
image-versions-to-keep: 5
|
9
|
+
|
10
|
+
registry-base-url: registry.digitalocean.com
|
11
|
+
# What comes before <image-name>:<tag>. Can be empty.
|
12
|
+
registry-image-path-prefix: $REGISTRY_IMAGE_PATH_PREFIX
|
13
|
+
|
14
|
+
# production and staging are supported by default
|
15
|
+
extra-remote-environments: []
|
16
|
+
|
17
|
+
# development and test are supported by default
|
18
|
+
extra-local-environments: []
|
@@ -0,0 +1,73 @@
|
|
1
|
+
name: "Monorepo Build and Deploy"
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches:
|
6
|
+
- staging
|
7
|
+
- production
|
8
|
+
|
9
|
+
permissions:
|
10
|
+
contents: read
|
11
|
+
packages: read
|
12
|
+
|
13
|
+
jobs:
|
14
|
+
build_images:
|
15
|
+
runs-on: ubuntu-latest
|
16
|
+
strategy:
|
17
|
+
matrix:
|
18
|
+
include:
|
19
|
+
- image_name: main-node
|
20
|
+
- image_name: main-python
|
21
|
+
cache_path: /root/.cache/uv
|
22
|
+
steps:
|
23
|
+
# Fetch the last 50 commits so that devops affected works
|
24
|
+
- name: Checkout repo and history
|
25
|
+
uses: actions/checkout@v4
|
26
|
+
with:
|
27
|
+
fetch-depth: 50
|
28
|
+
|
29
|
+
- name: Setup prerequesites
|
30
|
+
uses: ./.github/actions/setup-prereq@v1
|
31
|
+
|
32
|
+
- name: Connect to infrastructure
|
33
|
+
uses: ./.github/actions/connect-to-infra@v1
|
34
|
+
with:
|
35
|
+
do_access_token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
|
36
|
+
do_cluster_name: ${{ secrets.DIGITALOCEAN_CLUSTER_NAME }}
|
37
|
+
|
38
|
+
- name: Build image
|
39
|
+
uses: ./.github/actions/build-image@v1
|
40
|
+
with:
|
41
|
+
image_name: ${{ matrix.image_name }}
|
42
|
+
cache_path: ${{ matrix.cache_path || '/root/.bun/install/cache' }}
|
43
|
+
|
44
|
+
db_migrate_and_deploy:
|
45
|
+
needs: [build_images]
|
46
|
+
runs-on: ubuntu-latest
|
47
|
+
steps:
|
48
|
+
# Fetch the last 50 commits so that devops affected works
|
49
|
+
- name: Checkout repo and history
|
50
|
+
uses: actions/checkout@v4
|
51
|
+
with:
|
52
|
+
fetch-depth: 50
|
53
|
+
|
54
|
+
- name: Setup prerequesites
|
55
|
+
uses: ./.github/actions/setup-prereq@v1
|
56
|
+
|
57
|
+
- name: Connect to infrastructure
|
58
|
+
uses: ./.github/actions/connect-to-infra@v1
|
59
|
+
with:
|
60
|
+
do_access_token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
|
61
|
+
do_cluster_name: ${{ secrets.DIGITALOCEAN_CLUSTER_NAME }}
|
62
|
+
|
63
|
+
- name: Run DB Migrate
|
64
|
+
uses: ./.github/actions/db-migrate@v1
|
65
|
+
|
66
|
+
# Repeat per image (it checks if the image is affected and deploys it if it is)
|
67
|
+
- name: Deploy main node
|
68
|
+
uses: ./.github/actions/deploy-image@v1
|
69
|
+
with: { "image_name": "main-node" }
|
70
|
+
|
71
|
+
- name: Deploy main python
|
72
|
+
uses: ./.github/actions/deploy-image@v1
|
73
|
+
with: { "image_name": "main-python" }
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# These will be used when generating kubernetes entities
|
2
|
+
project-name: $PROJECT_NAME
|
3
|
+
|
4
|
+
# Supported: hetzner, digitalocean, or gcloud
|
5
|
+
infra: gcloud
|
6
|
+
|
7
|
+
registry-base-url: gcr.io
|
8
|
+
# What comes before <image-name>:<tag>. Can be empty.
|
9
|
+
registry-image-path-prefix: $GCLOUD_PROJECT_ID
|
10
|
+
|
11
|
+
# production and staging are supported by default
|
12
|
+
extra-remote-environments: []
|
13
|
+
|
14
|
+
# development and test are supported by default
|
15
|
+
extra-local-environments: []
|
@@ -0,0 +1,22 @@
|
|
1
|
+
apiVersion: networking.k8s.io/v1
|
2
|
+
kind: Ingress
|
3
|
+
metadata:
|
4
|
+
name: {{app_name}}
|
5
|
+
namespace: {{namespace}}
|
6
|
+
labels:
|
7
|
+
app: {{app_name}}
|
8
|
+
env: {{monorepo_env}}
|
9
|
+
annotations:
|
10
|
+
kubernetes.io/ingress.class: "gce"
|
11
|
+
spec:
|
12
|
+
rules:
|
13
|
+
- host: {{subdomain}}.{{domain_name}}
|
14
|
+
http:
|
15
|
+
paths:
|
16
|
+
- path: /
|
17
|
+
pathType: Prefix
|
18
|
+
backend:
|
19
|
+
service:
|
20
|
+
name: {{service_name}}
|
21
|
+
port:
|
22
|
+
number: 80
|
package/src/target-templates/{.github → infra-variants/gcloud/.github}/workflows/k8s-build.yaml
RENAMED
@@ -32,15 +32,10 @@ jobs:
|
|
32
32
|
- name: Connect to infrastructure
|
33
33
|
uses: ./.github/actions/connect-to-infra@v1
|
34
34
|
with:
|
35
|
-
do_access_token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
|
36
|
-
do_cluster_name: ${{ secrets.DIGITALOCEAN_CLUSTER_NAME }}
|
37
|
-
hetzner_kubeconfig: ${{ secrets.HCLOUD_KUBECONFIG }}
|
38
|
-
harbor_user: ${{ secrets.HARBOR_USER }}
|
39
|
-
harbor_password: ${{ secrets.HARBOR_PASSWORD }}
|
40
35
|
gcloud_project_id: ${{ secrets.GCLOUD_PROJECT_ID }}
|
41
36
|
gcloud_zone: ${{ secrets.GCLOUD_ZONE }}
|
42
37
|
gcloud_cluster_name: ${{ secrets.GCLOUD_CLUSTER_NAME }}
|
43
|
-
|
38
|
+
gcloud_service_account_key: ${{ secrets.GCLOUD_SA_KEY }}
|
44
39
|
|
45
40
|
- name: Build image
|
46
41
|
uses: ./.github/actions/build-image@v1
|
@@ -64,15 +59,10 @@ jobs:
|
|
64
59
|
- name: Connect to infrastructure
|
65
60
|
uses: ./.github/actions/connect-to-infra@v1
|
66
61
|
with:
|
67
|
-
do_access_token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
|
68
|
-
do_cluster_name: ${{ secrets.DIGITALOCEAN_CLUSTER_NAME }}
|
69
|
-
hetzner_kubeconfig: ${{ secrets.HCLOUD_KUBECONFIG }}
|
70
|
-
harbor_user: ${{ secrets.HARBOR_USER }}
|
71
|
-
harbor_password: ${{ secrets.HARBOR_PASSWORD }}
|
72
62
|
gcloud_project_id: ${{ secrets.GCLOUD_PROJECT_ID }}
|
73
63
|
gcloud_zone: ${{ secrets.GCLOUD_ZONE }}
|
74
64
|
gcloud_cluster_name: ${{ secrets.GCLOUD_CLUSTER_NAME }}
|
75
|
-
|
65
|
+
gcloud_service_account_key: ${{ secrets.GCLOUD_SA_KEY }}
|
76
66
|
|
77
67
|
- name: Run DB Migrate
|
78
68
|
uses: ./.github/actions/db-migrate@v1
|