Skip to content

Commit 0bb005e

Browse files
authored
Merge pull request #17 from oslabs-beta/lorenc-ci
implement pipelineCommit to commit yaml files
2 parents f203e10 + 759d6b4 commit 0bb005e

File tree

4 files changed

+232
-67
lines changed

4 files changed

+232
-67
lines changed

server/routes/pipelineCommit.js

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { Router } from 'express';
2+
import { requireSession } from '../lib/requireSession.js';
3+
import { getGithubAccessTokenForUser } from '../lib/github-token.js';
4+
import { upsertWorkflowFile } from '../tools/github_adapter.js';
5+
6+
const router = Router();
7+
8+
/**
9+
* POST /mcp/v1/pipeline_commit
10+
* Body:
11+
* {
12+
* "repoFullName": "owner/repo",
13+
* "branch": "main",
14+
* "yaml": "name: CI/CD Pipeline ...",
15+
* "path": ".github/workflows/ci.yml"
16+
* }
17+
*/
18+
router.post('/pipeline_commit', requireSession, async (req, res) => {
19+
try {
20+
const { repoFullName, branch, yaml, path } = req.body || {};
21+
if (!repoFullName || !yaml) {
22+
return res
23+
.status(400)
24+
.json({ error: 'repoFullName and yaml are required' });
25+
}
26+
27+
const userId = req.user?.user_id;
28+
if (!userId)
29+
return res.status(401).json({ error: 'User session missing or invalid' });
30+
31+
const token = await getGithubAccessTokenForUser(userId);
32+
if (!token)
33+
return res.status(401).json({ error: 'Missing GitHub token for user' });
34+
35+
const [owner, repo] = repoFullName.split('/');
36+
const workflowPath = path || '.github/workflows/ci.yml';
37+
const branchName = branch || 'main';
38+
39+
console.log(
40+
`[pipeline_commit] Committing workflow to ${repoFullName}:${workflowPath}`
41+
);
42+
43+
const result = await upsertWorkflowFile({
44+
token,
45+
owner,
46+
repo,
47+
path: workflowPath,
48+
content: yaml,
49+
branch: branchName,
50+
message: 'Add CI workflow via OSP',
51+
});
52+
53+
return res.status(201).json({
54+
ok: true,
55+
message: 'Workflow committed successfully',
56+
data: result,
57+
});
58+
} catch (err) {
59+
console.error('[pipeline_commit] error:', err);
60+
const status = err.status || 500;
61+
return res
62+
.status(status)
63+
.json({ error: err.message, details: err.details || undefined });
64+
}
65+
});
66+
67+
export default router;

server/server.js

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,19 @@ import cors from 'cors';
44
import helmet from 'helmet';
55
import morgan from 'morgan';
66
import { healthCheck } from './db.js';
7-
import githubAuthRouter from './routes/auth.github.js';
8-
import userRouter from './routes/usersRoutes.js';
97
import mcpRoutes from './routes/mcp.js';
108
import agentRoutes from './routes/agent.js';
11-
import cookieParser from 'cookie-parser';
9+
import githubAuthRouter from './routes/auth.github.js';
1210
import deploymentsRouter from './routes/deployments.js';
11+
import authRoutes from './routes/authRoutes.js';
12+
import userRouter from './routes/usersRoutes.js';
13+
import cookieParser from 'cookie-parser';
1314
import authAws from './routes/auth.aws.js';
1415
import authGoogle from './routes/auth.google.js';
1516
import { z } from 'zod';
1617
import { query } from './db.js';
17-
import jenkinsRouter from "./routes/jenkins.js";
18+
import jenkinsRouter from './routes/jenkins.js';
19+
import pipelineCommitRouter from './routes/pipelineCommit.js';
1820
// app.use(authRoutes);
1921

2022
const app = express();
@@ -137,21 +139,19 @@ app.get('/connections', async (_req, res) => {
137139
// -- Agent entry point
138140
app.use('/deployments', deploymentsRouter);
139141
app.use('/agent', agentRoutes);
142+
app.use('/mcp/v1', pipelineCommitRouter);
140143
app.use('/mcp/v1', mcpRoutes);
141144

142145
// Mount GitHub OAuth routes at /auth/github
143146
app.use('/auth/github', githubAuthRouter);
144-
145-
147+
app.use(authRoutes);
146148
// Mount AWS SSO routes
147149
app.use('/auth/aws', authAws);
148150

149151
// Mount Google OAuth routes
150152
app.use('/auth/google', authGoogle);
151153

152154
app.use('/jenkins', jenkinsRouter);
153-
// // Mount GitHub OAuth routes at /auth/github
154-
// app.use('/auth/github', githubAuthRouter);
155155

156156
// --- Global Error Handler ---
157157
app.use((err, req, res, next) => {
@@ -163,7 +163,5 @@ app.use((err, req, res, next) => {
163163
});
164164
});
165165

166-
167-
168166
const port = process.env.PORT || 4000;
169167
app.listen(port, () => console.log(`API on http://localhost:${port}`));

server/tools/github_adapter.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,3 +255,69 @@ export async function dispatchWorkflow({
255255
} catch {}
256256
throw err;
257257
}
258+
259+
// Helper function to create and update a workflow file in a repo
260+
export async function upsertWorkflowFile({
261+
token,
262+
owner,
263+
repo,
264+
path,
265+
content,
266+
branch,
267+
message = 'Add CI workflow via AutoDeploy',
268+
}) {
269+
const baseUrl = `https://api.github.com/repos/${owner}/${repo}/contents/${encodeURIComponent(
270+
path
271+
)}`;
272+
273+
const headers = {
274+
Authorization: `Bearer ${token}`,
275+
Accept: 'application/vnd.github+json',
276+
'User-Agent': 'OSP-CI-Builder',
277+
'Content-Type': 'application/json',
278+
};
279+
280+
//check if the file exists to get the SHA
281+
let sha = undefined;
282+
const getRes = await fetch(`${baseUrl}?ref=${encodeURIComponent(branch)}`, {
283+
headers,
284+
});
285+
286+
if (getRes.status === 200) {
287+
const existing = await getRes.json();
288+
sha = existing.sha;
289+
} else if (getRes.status === 400) {
290+
const text = await getRes.text().catch(() => '');
291+
throw new Error(
292+
`Get existing workflow failed: ${getRes.status} ${getRes.statusText} ${text}`
293+
);
294+
}
295+
296+
//Encode content as base64 character data
297+
const encoded = Buffer.from(content, 'utf-8').toString('base64');
298+
299+
//PUT to creat/update the file
300+
const body = {
301+
message,
302+
content: encoded,
303+
branch,
304+
};
305+
306+
if (sha) body.sha = sha;
307+
308+
const putRes = await fetch(baseUrl, {
309+
method: 'PUT',
310+
headers,
311+
body: JSON.stringify(body),
312+
});
313+
314+
if (!putRes.ok) {
315+
const text = await putRes.text().catch(() => '');
316+
throw new Error(
317+
`Upsert workflow file failed: ${putRes.status} ${putRes.statusText} ${text}`
318+
);
319+
}
320+
321+
const data = await putRes.json();
322+
return data;
323+
}

0 commit comments

Comments
 (0)