duoops 0.0.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/LICENSE +22 -0
- package/README.md +181 -0
- package/bin/dev.cmd +3 -0
- package/bin/dev.js +7 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +8 -0
- package/dist/commands/act.d.ts +12 -0
- package/dist/commands/act.js +61 -0
- package/dist/commands/ask.d.ts +8 -0
- package/dist/commands/ask.js +22 -0
- package/dist/commands/init.d.ts +5 -0
- package/dist/commands/init.js +97 -0
- package/dist/commands/job/logs.d.ts +13 -0
- package/dist/commands/job/logs.js +26 -0
- package/dist/commands/measure/calculate.d.ts +19 -0
- package/dist/commands/measure/calculate.js +208 -0
- package/dist/commands/measure/component.d.ts +5 -0
- package/dist/commands/measure/component.js +23 -0
- package/dist/commands/measure/seed.d.ts +5 -0
- package/dist/commands/measure/seed.js +62 -0
- package/dist/commands/pipelines/list.d.ts +14 -0
- package/dist/commands/pipelines/list.js +62 -0
- package/dist/commands/pipelines/show.d.ts +13 -0
- package/dist/commands/pipelines/show.js +68 -0
- package/dist/commands/portal.d.ts +8 -0
- package/dist/commands/portal.js +139 -0
- package/dist/commands/undo.d.ts +5 -0
- package/dist/commands/undo.js +35 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/ai/agent.d.ts +6 -0
- package/dist/lib/ai/agent.js +139 -0
- package/dist/lib/ai/model.d.ts +2 -0
- package/dist/lib/ai/model.js +22 -0
- package/dist/lib/ai/tools/editing.d.ts +3 -0
- package/dist/lib/ai/tools/editing.js +61 -0
- package/dist/lib/ai/tools/filesystem.d.ts +4 -0
- package/dist/lib/ai/tools/filesystem.js +44 -0
- package/dist/lib/ai/tools/gitlab.d.ts +4 -0
- package/dist/lib/ai/tools/gitlab.js +81 -0
- package/dist/lib/ai/tools/measure.d.ts +3 -0
- package/dist/lib/ai/tools/measure.js +26 -0
- package/dist/lib/config.d.ts +18 -0
- package/dist/lib/config.js +72 -0
- package/dist/lib/gitlab/client.d.ts +6 -0
- package/dist/lib/gitlab/client.js +18 -0
- package/dist/lib/gitlab/index.d.ts +6 -0
- package/dist/lib/gitlab/index.js +49 -0
- package/dist/lib/gitlab/provider.d.ts +14 -0
- package/dist/lib/gitlab/provider.js +72 -0
- package/dist/lib/gitlab/types.d.ts +34 -0
- package/dist/lib/gitlab/types.js +5 -0
- package/dist/lib/integrations/bigquery-sink.d.ts +12 -0
- package/dist/lib/integrations/bigquery-sink.js +47 -0
- package/dist/lib/logger.d.ts +2 -0
- package/dist/lib/logger.js +11 -0
- package/dist/lib/measure/bigquery-service.d.ts +2 -0
- package/dist/lib/measure/bigquery-service.js +54 -0
- package/dist/lib/measure/carbon-calculator.d.ts +13 -0
- package/dist/lib/measure/carbon-calculator.js +125 -0
- package/dist/lib/measure/cli-utils.d.ts +2 -0
- package/dist/lib/measure/cli-utils.js +107 -0
- package/dist/lib/measure/intensity-provider.d.ts +6 -0
- package/dist/lib/measure/intensity-provider.js +34 -0
- package/dist/lib/measure/power-profile-repository.d.ts +19 -0
- package/dist/lib/measure/power-profile-repository.js +129 -0
- package/dist/lib/measure/types.d.ts +137 -0
- package/dist/lib/measure/types.js +1 -0
- package/dist/lib/measure/zone-mapper.d.ts +16 -0
- package/dist/lib/measure/zone-mapper.js +104 -0
- package/dist/lib/state.d.ts +4 -0
- package/dist/lib/state.js +21 -0
- package/dist/portal/assets/index-BP8FwWqA.css +1 -0
- package/dist/portal/assets/index-MU6EBerh.js +188 -0
- package/dist/portal/duoops.svg +4 -0
- package/dist/portal/index.html +24 -0
- package/dist/portal/vite.svg +1 -0
- package/oclif.manifest.json +415 -0
- package/package.json +103 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Younes Laaroussi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# DuoOps
|
|
2
|
+
|
|
3
|
+
> **Toolset for Explainable and Sustainable CI on GitLab.**
|
|
4
|
+
|
|
5
|
+
DuoOps is a developer-focused CLI and Web Portal designed to make GitLab CI/CD pipelines more transparent, debuggable, and environmentally sustainable. It leverages AI agents to explain failures and provides tools to measure and visualize the carbon footprint of your CI jobs.
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+

|
|
9
|
+
|
|
10
|
+
## 🌟 Key Features
|
|
11
|
+
|
|
12
|
+
* **🤖 AI-Powered Troubleshooting**: Ask questions about your pipelines in plain English. The AI agent (powered by GitLab Duo or Google Vertex AI) analyzes logs, identifies root causes, and suggests fixes.
|
|
13
|
+
* **🔍 Deep Pipeline Inspection**: View pipeline status, job details, and logs directly from your terminal.
|
|
14
|
+
* **🌱 Carbon Measurement**: Calculate the carbon emissions of your CI jobs based on CPU/RAM usage timeseries. Track your environmental impact over time.
|
|
15
|
+
* **📊 Web Portal**: A local React-based dashboard to visualize pipeline metrics and chat with the AI assistant in a rich interface.
|
|
16
|
+
* **🧠 Intelligent Summarization**: Automatically summarizes long job logs using sub-agents to provide concise failure analysis without token limit issues.
|
|
17
|
+
|
|
18
|
+
## 🚀 Quick Start
|
|
19
|
+
|
|
20
|
+
### Prerequisites
|
|
21
|
+
|
|
22
|
+
* **Node.js** >= 18.0.0
|
|
23
|
+
* **GitLab Account**: A Personal Access Token with `api` scope.
|
|
24
|
+
* **(Optional) Google Cloud SDK**: Required only for BigQuery metrics.
|
|
25
|
+
* Install the `gcloud` CLI.
|
|
26
|
+
* Authenticate: `gcloud auth application-default login`
|
|
27
|
+
|
|
28
|
+
### Installation
|
|
29
|
+
|
|
30
|
+
#### Install from npm (recommended)
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# Install the published CLI globally
|
|
34
|
+
npm install -g duoops
|
|
35
|
+
|
|
36
|
+
# Verify the binary is available
|
|
37
|
+
duoops --help
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
#### Install from source (development)
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# Clone the repository
|
|
44
|
+
git clone https://github.com/youneslaaroussi/duoops.git
|
|
45
|
+
cd duoops
|
|
46
|
+
|
|
47
|
+
# Install dependencies
|
|
48
|
+
pnpm install
|
|
49
|
+
|
|
50
|
+
# Build the project
|
|
51
|
+
pnpm build
|
|
52
|
+
|
|
53
|
+
# Link the CLI globally
|
|
54
|
+
npm link
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Configuration
|
|
58
|
+
|
|
59
|
+
Run the interactive initialization command to set up your credentials:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
duoops init
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
This will prompt you for:
|
|
66
|
+
* GitLab URL (default: `https://gitlab.com`)
|
|
67
|
+
* GitLab Personal Access Token
|
|
68
|
+
* (Optional) BigQuery settings for sustainability tracking
|
|
69
|
+
|
|
70
|
+
Alternatively, you can configure DuoOps using environment variables:
|
|
71
|
+
|
|
72
|
+
| Variable | Description |
|
|
73
|
+
| :--- | :--- |
|
|
74
|
+
| `GITLAB_TOKEN` | Your GitLab Personal Access Token |
|
|
75
|
+
| `GITLAB_URL` | GitLab instance URL (default: `https://gitlab.com`) |
|
|
76
|
+
| `DUOOPS_AI_PROVIDER` | AI Provider: `gitlab` (default) or `vertex` |
|
|
77
|
+
| `GCP_PROJECT_ID` | Google Cloud Project ID (required for Vertex AI) |
|
|
78
|
+
| `GCP_LOCATION` | Google Cloud Location (default: `us-central1`) |
|
|
79
|
+
|
|
80
|
+
## 📖 Usage
|
|
81
|
+
|
|
82
|
+
### 1. Inspect Pipelines
|
|
83
|
+
|
|
84
|
+
List recent pipelines for a project:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
duoops pipelines list <project-id-or-path>
|
|
88
|
+
# Example: duoops pipelines list my-group/my-project
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### 2. Analyze Jobs & Logs
|
|
92
|
+
|
|
93
|
+
Get details and logs for a specific job:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
duoops job show <project-id> <job-id>
|
|
97
|
+
# Example: duoops job show 12345 987654
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 3. Ask the AI Agent
|
|
101
|
+
|
|
102
|
+
Troubleshoot failures or ask general CI questions:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
duoops ask "Why did the last pipeline fail?"
|
|
106
|
+
duoops ask "How do I optimize my .gitlab-ci.yml for faster builds?"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### 4. Measure Carbon Footprint
|
|
110
|
+
|
|
111
|
+
Calculate emissions for a job using CPU/RAM usage data (JSON timeseries):
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
duoops measure calculate \
|
|
115
|
+
--provider gcp \
|
|
116
|
+
--machine e2-standard-4 \
|
|
117
|
+
--region us-central1 \
|
|
118
|
+
--cpu-timeseries ./data/cpu.json \
|
|
119
|
+
--ram-used-timeseries ./data/ram.json \
|
|
120
|
+
--out-md report.md
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
This will output a report to the console and save a Markdown summary to `report.md`.
|
|
124
|
+
|
|
125
|
+
**Persist from CI:** To save results to BigQuery when the measure component runs in GitLab CI, set these CI/CD variables (masked where appropriate):
|
|
126
|
+
|
|
127
|
+
| Variable | Description |
|
|
128
|
+
| :--- | :--- |
|
|
129
|
+
| `DUOOPS_BQ_DATASET` | BigQuery dataset name |
|
|
130
|
+
| `DUOOPS_BQ_TABLE` | BigQuery table name |
|
|
131
|
+
| `GCP_PROJECT_ID` | Google Cloud project ID |
|
|
132
|
+
|
|
133
|
+
The same GCP service account used for the measure component (e.g. `GCP_SA_KEY_BASE64`) is used for BigQuery; the component sets `GOOGLE_APPLICATION_CREDENTIALS` so the BigQuery client can authenticate. Ensure the table exists (e.g. run `duoops init` locally with BigQuery enabled to create it).
|
|
134
|
+
|
|
135
|
+
#### Act on recent data
|
|
136
|
+
|
|
137
|
+
Run `duoops act [project-id] [--limit N]` to fetch recent CI carbon metrics from BigQuery and get the agent's recommendations (e.g. set a budget, optimize jobs, suggest .gitlab-ci changes). Project ID can be omitted if set via `duoops init`.
|
|
138
|
+
|
|
139
|
+
### 5. Launch the Web Portal
|
|
140
|
+
|
|
141
|
+
Start the local dashboard to visualize metrics and chat:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
duoops portal
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Open your browser to `http://localhost:3000`.
|
|
148
|
+
|
|
149
|
+
## 🛠️ Development
|
|
150
|
+
|
|
151
|
+
### Project Structure
|
|
152
|
+
|
|
153
|
+
* `bin/`: CLI entry point.
|
|
154
|
+
* `src/commands/`: Oclif command implementations.
|
|
155
|
+
* `src/lib/`: Core logic (AI agents, GitLab client, Measurement tools).
|
|
156
|
+
* `portal/`: React frontend application.
|
|
157
|
+
|
|
158
|
+
### Running Tests
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
pnpm test
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Building for Production
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
pnpm build
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
This compiles the TypeScript CLI and builds the React frontend, copying assets to the `dist/` directory.
|
|
171
|
+
|
|
172
|
+
## 📦 Publishing to npm
|
|
173
|
+
|
|
174
|
+
1. Update `package.json` with the release version you want to publish.
|
|
175
|
+
2. Run the checks: `pnpm test` (this runs lint afterwards) to ensure the CLI and portal build cleanly.
|
|
176
|
+
3. Inspect the publish payload locally with `pnpm pack` (this runs the `prepack` script, which now builds the CLI, portal, and Oclif manifest automatically). Untar the generated `.tgz` if you want to double-check the contents.
|
|
177
|
+
4. When you're satisfied, publish the package: `pnpm publish --access public`.
|
|
178
|
+
|
|
179
|
+
## 📄 License
|
|
180
|
+
|
|
181
|
+
MIT
|
package/bin/dev.cmd
ADDED
package/bin/dev.js
ADDED
package/bin/run.cmd
ADDED
package/bin/run.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class Act extends Command {
|
|
3
|
+
static args: {
|
|
4
|
+
'project-id': import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
limit: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import { runAgent } from '../lib/ai/agent.js';
|
|
3
|
+
import { configManager } from '../lib/config.js';
|
|
4
|
+
import { fetchCarbonMetrics } from '../lib/measure/bigquery-service.js';
|
|
5
|
+
export default class Act extends Command {
|
|
6
|
+
static args = {
|
|
7
|
+
'project-id': Args.string({
|
|
8
|
+
description: 'GitLab project ID (or use default from duoops init)',
|
|
9
|
+
required: false,
|
|
10
|
+
}),
|
|
11
|
+
};
|
|
12
|
+
static description = 'Fetch recent CI carbon metrics from BigQuery and get the agent\'s recommendations';
|
|
13
|
+
static examples = [
|
|
14
|
+
`<%= config.bin %> <%= command.id %>`,
|
|
15
|
+
`<%= config.bin %> <%= command.id %> 12345 --limit 20`,
|
|
16
|
+
];
|
|
17
|
+
static flags = {
|
|
18
|
+
limit: Flags.integer({
|
|
19
|
+
char: 'n',
|
|
20
|
+
default: 20,
|
|
21
|
+
description: 'Number of recent job records to fetch',
|
|
22
|
+
}),
|
|
23
|
+
};
|
|
24
|
+
async run() {
|
|
25
|
+
const { args, flags } = await this.parse(Act);
|
|
26
|
+
const config = configManager.get();
|
|
27
|
+
const projectId = args['project-id'] ?? config.defaultProjectId;
|
|
28
|
+
if (!projectId) {
|
|
29
|
+
this.error('Project ID is required. Pass it as an argument or run "duoops init" and set a default project.');
|
|
30
|
+
}
|
|
31
|
+
this.log('Fetching recent carbon metrics...');
|
|
32
|
+
let metrics;
|
|
33
|
+
try {
|
|
34
|
+
metrics = await fetchCarbonMetrics(projectId, flags.limit);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
38
|
+
this.error(message.includes('BigQuery') ? message : `Failed to fetch metrics: ${message}`);
|
|
39
|
+
}
|
|
40
|
+
if (metrics.length === 0) {
|
|
41
|
+
this.log('No carbon metrics found for this project. Run the measure component in CI with BigQuery env vars set to persist data.');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const dataBlob = JSON.stringify(metrics, null, 2);
|
|
45
|
+
const prompt = `Here are the last ${metrics.length} CI job carbon metrics for GitLab project ${projectId} (from BigQuery):
|
|
46
|
+
|
|
47
|
+
\`\`\`json
|
|
48
|
+
${dataBlob}
|
|
49
|
+
\`\`\`
|
|
50
|
+
|
|
51
|
+
Analyze this data and recommend concrete actions. For example: set an emission budget, add or tune the measure component in .gitlab-ci.yml, optimize the heaviest jobs, suggest region or machine type changes, or link to specific job logs. Be concise and actionable.`;
|
|
52
|
+
this.log('Thinking...');
|
|
53
|
+
try {
|
|
54
|
+
const response = await runAgent(prompt);
|
|
55
|
+
this.log('\n' + response);
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
this.error(error instanceof Error ? error.message : String(error));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Args, Command } from '@oclif/core';
|
|
2
|
+
import { runAgent } from '../lib/ai/agent.js';
|
|
3
|
+
export default class Ask extends Command {
|
|
4
|
+
static args = {
|
|
5
|
+
question: Args.string({
|
|
6
|
+
description: 'The question to ask the agent',
|
|
7
|
+
required: true,
|
|
8
|
+
}),
|
|
9
|
+
};
|
|
10
|
+
static description = 'Ask questions about your CI/CD pipelines, logs, and sustainability';
|
|
11
|
+
async run() {
|
|
12
|
+
const { args } = await this.parse(Ask);
|
|
13
|
+
this.log('Thinking...');
|
|
14
|
+
try {
|
|
15
|
+
const response = await runAgent(args.question);
|
|
16
|
+
this.log('\n' + response);
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
this.error(error instanceof Error ? error.message : String(error));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { BigQuery } from '@google-cloud/bigquery';
|
|
2
|
+
import { confirm, input, password } from '@inquirer/prompts';
|
|
3
|
+
import { Command } from '@oclif/core';
|
|
4
|
+
import { configManager } from '../lib/config.js';
|
|
5
|
+
export default class Init extends Command {
|
|
6
|
+
static description = 'Initialize the configuration for DuoOps';
|
|
7
|
+
async run() {
|
|
8
|
+
this.log('Welcome to DuoOps! Let\'s set up your configuration.');
|
|
9
|
+
this.log('You will need a GitLab Personal Access Token with the "api" scope.');
|
|
10
|
+
this.log('You can generate one at: https://gitlab.com/-/user_settings/personal_access_tokens');
|
|
11
|
+
this.log('');
|
|
12
|
+
const gitlabUrl = await input({
|
|
13
|
+
default: 'https://gitlab.com',
|
|
14
|
+
message: 'GitLab URL',
|
|
15
|
+
});
|
|
16
|
+
const gitlabToken = await password({
|
|
17
|
+
mask: '*',
|
|
18
|
+
message: 'GitLab Personal Access Token',
|
|
19
|
+
});
|
|
20
|
+
const enableMeasure = await confirm({
|
|
21
|
+
message: 'Enable Measure/Sustainability Tracking (BigQuery)?',
|
|
22
|
+
});
|
|
23
|
+
let bigqueryDataset;
|
|
24
|
+
let bigqueryTable;
|
|
25
|
+
let googleProjectId;
|
|
26
|
+
if (enableMeasure) {
|
|
27
|
+
this.log('Note: You must have Google Cloud SDK installed and authenticated (gcloud auth application-default login) or a service account key.');
|
|
28
|
+
googleProjectId = await input({
|
|
29
|
+
message: 'Google Cloud Project ID',
|
|
30
|
+
});
|
|
31
|
+
bigqueryDataset = await input({
|
|
32
|
+
default: 'measure_data',
|
|
33
|
+
message: 'BigQuery Dataset Name',
|
|
34
|
+
});
|
|
35
|
+
bigqueryTable = await input({
|
|
36
|
+
default: 'emissions',
|
|
37
|
+
message: 'BigQuery Table Name',
|
|
38
|
+
});
|
|
39
|
+
const shouldCreate = await confirm({
|
|
40
|
+
message: `Attempt to create BigQuery Dataset (${bigqueryDataset}) and Table (${bigqueryTable}) if missing?`,
|
|
41
|
+
});
|
|
42
|
+
if (shouldCreate) {
|
|
43
|
+
try {
|
|
44
|
+
this.log('Checking BigQuery resources...');
|
|
45
|
+
const bigquery = new BigQuery({ projectId: googleProjectId });
|
|
46
|
+
// 1. Create Dataset
|
|
47
|
+
const dataset = bigquery.dataset(bigqueryDataset);
|
|
48
|
+
const [datasetExists] = await dataset.exists();
|
|
49
|
+
if (datasetExists) {
|
|
50
|
+
this.log(`Dataset '${bigqueryDataset}' already exists.`);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
this.log(`Creating dataset '${bigqueryDataset}'...`);
|
|
54
|
+
await dataset.create({ location: 'US' }); // Default location
|
|
55
|
+
this.log('Dataset created.');
|
|
56
|
+
}
|
|
57
|
+
// 2. Create Table with Schema
|
|
58
|
+
const table = dataset.table(bigqueryTable);
|
|
59
|
+
const [tableExists] = await table.exists();
|
|
60
|
+
if (tableExists) {
|
|
61
|
+
this.log(`Table '${bigqueryTable}' already exists.`);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
this.log(`Creating table '${bigqueryTable}'...`);
|
|
65
|
+
const schema = [
|
|
66
|
+
{ name: 'ingested_at', type: 'TIMESTAMP' },
|
|
67
|
+
{ name: 'gitlab_project_id', type: 'INTEGER' },
|
|
68
|
+
{ name: 'gitlab_job_id', type: 'INTEGER' },
|
|
69
|
+
{ name: 'gitlab_job_name', type: 'STRING' },
|
|
70
|
+
{ name: 'gitlab_user_name', type: 'STRING' },
|
|
71
|
+
{ name: 'machine_type', type: 'STRING' },
|
|
72
|
+
{ name: 'region', type: 'STRING' },
|
|
73
|
+
{ name: 'runtime_seconds', type: 'FLOAT' },
|
|
74
|
+
{ name: 'cpu_utilization_avg', type: 'FLOAT' },
|
|
75
|
+
{ name: 'ram_utilization_avg', type: 'FLOAT' },
|
|
76
|
+
{ name: 'energy_kwh', type: 'FLOAT' },
|
|
77
|
+
{ name: 'total_emissions_g', type: 'FLOAT' }
|
|
78
|
+
];
|
|
79
|
+
await table.create({ schema });
|
|
80
|
+
this.log('Table created successfully.');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
this.warn(`Failed to create BigQuery resources: ${error instanceof Error ? error.message : String(error)}`);
|
|
85
|
+
this.warn('You may need to create them manually or check your permissions.');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
configManager.set({
|
|
90
|
+
gitlabToken,
|
|
91
|
+
gitlabUrl,
|
|
92
|
+
measure: enableMeasure ? { bigqueryDataset, bigqueryTable, googleProjectId } : undefined,
|
|
93
|
+
});
|
|
94
|
+
this.log('Configuration saved successfully!');
|
|
95
|
+
this.log(`You can now use DuoOps commands. Try running '${this.config.bin} pipelines:list <project-id>'`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class JobLogs extends Command {
|
|
3
|
+
static args: {
|
|
4
|
+
project: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
|
+
job_id: import("@oclif/core/interfaces").Arg<number, {
|
|
6
|
+
max?: number;
|
|
7
|
+
min?: number;
|
|
8
|
+
}>;
|
|
9
|
+
};
|
|
10
|
+
static description: string;
|
|
11
|
+
static examples: string[];
|
|
12
|
+
run(): Promise<void>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Args, Command } from '@oclif/core';
|
|
2
|
+
import { getPipelineProvider } from '../../lib/gitlab/index.js';
|
|
3
|
+
export default class JobLogs extends Command {
|
|
4
|
+
static args = {
|
|
5
|
+
/* eslint-disable perfectionist/sort-objects -- project must be first for CLI arg order */
|
|
6
|
+
project: Args.string({
|
|
7
|
+
description: 'Project ID or path (e.g. group/project)',
|
|
8
|
+
required: true,
|
|
9
|
+
}),
|
|
10
|
+
job_id: Args.integer({
|
|
11
|
+
description: 'Job ID',
|
|
12
|
+
required: true,
|
|
13
|
+
}),
|
|
14
|
+
/* eslint-enable perfectionist/sort-objects */
|
|
15
|
+
};
|
|
16
|
+
static description = 'Get job log trace';
|
|
17
|
+
static examples = [
|
|
18
|
+
`<%= config.bin %> <%= command.id %> group/my-project 12345`,
|
|
19
|
+
];
|
|
20
|
+
async run() {
|
|
21
|
+
const { args } = await this.parse(JobLogs);
|
|
22
|
+
const provider = getPipelineProvider();
|
|
23
|
+
const trace = await provider.getJobTrace(args.project, args.job_id);
|
|
24
|
+
this.log(trace);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class CarbonCalculate extends Command {
|
|
3
|
+
static args: {};
|
|
4
|
+
static description: string;
|
|
5
|
+
static examples: string[];
|
|
6
|
+
static flags: {
|
|
7
|
+
budget: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
'cpu-timeseries': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
'fail-on-budget': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
machine: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
'out-json': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
'out-md': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
provider: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
'ram-size-timeseries': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
'ram-used-timeseries': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
+
region: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
17
|
+
};
|
|
18
|
+
run(): Promise<void>;
|
|
19
|
+
}
|