Created via MCP
This commit is contained in:
172
undefined
Normal file
172
undefined
Normal file
@@ -0,0 +1,172 @@
|
||||
const express = require('express');
|
||||
const axios = require('axios');
|
||||
const https = require('https');
|
||||
|
||||
const app = express();
|
||||
const port = process.env.PORT || 3000;
|
||||
|
||||
const ARGOCD_URL = (process.env.ARGOCD_URL || 'https://argocd.thedevops.dev').replace(/\/$/, '');
|
||||
const ARGOCD_TOKEN = process.env.ARGOCD_TOKEN || '';
|
||||
|
||||
// Allow self-signed certs in homelab
|
||||
const httpsAgent = new https.Agent({ rejectUnauthorized: false });
|
||||
|
||||
const argoApi = axios.create({
|
||||
baseURL: `${ARGOCD_URL}/api/v1`,
|
||||
httpsAgent,
|
||||
headers: ARGOCD_TOKEN ? { Authorization: `Bearer ${ARGOCD_TOKEN}` } : {}
|
||||
});
|
||||
|
||||
app.use(express.json());
|
||||
app.use((req, res, next) => {
|
||||
res.header('Access-Control-Allow-Origin', '*');
|
||||
res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
||||
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
||||
if (req.method === 'OPTIONS') return res.sendStatus(200);
|
||||
next();
|
||||
});
|
||||
|
||||
app.get('/health', (req, res) => res.json({ status: 'healthy', service: 'mcp-argocd' }));
|
||||
|
||||
app.get('/', (req, res) => res.json({
|
||||
service: 'MCP ArgoCD Server',
|
||||
version: '1.0.0',
|
||||
argocd: ARGOCD_URL,
|
||||
endpoints: [
|
||||
'POST /api/apps/list - List all applications with sync/health status',
|
||||
'POST /api/apps/get - Get detailed app info (name required)',
|
||||
'POST /api/apps/sync - Trigger sync for an app (name required)',
|
||||
'POST /api/apps/out_of_sync - List all out-of-sync applications',
|
||||
'POST /api/apps/unhealthy - List all unhealthy applications',
|
||||
'POST /api/apps/history - Get deployment history for an app',
|
||||
'POST /api/apps/rollback - Rollback app to previous revision',
|
||||
'POST /api/clusters/list - List connected clusters',
|
||||
'POST /api/repos/list - List connected repositories',
|
||||
]
|
||||
}));
|
||||
|
||||
// List all apps
|
||||
app.post('/api/apps/list', async (req, res) => {
|
||||
try {
|
||||
const r = await argoApi.get('/applications');
|
||||
const apps = (r.data.items || []).map(a => ({
|
||||
name: a.metadata.name,
|
||||
namespace: a.spec.destination?.namespace,
|
||||
project: a.spec.project,
|
||||
syncStatus: a.status?.sync?.status,
|
||||
healthStatus: a.status?.health?.status,
|
||||
repo: a.spec.source?.repoURL,
|
||||
path: a.spec.source?.path,
|
||||
targetRevision: a.spec.source?.targetRevision,
|
||||
lastSyncedAt: a.status?.operationState?.finishedAt,
|
||||
}));
|
||||
res.json({ count: apps.length, apps });
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Get app details
|
||||
app.post('/api/apps/get', async (req, res) => {
|
||||
try {
|
||||
const { name } = req.body;
|
||||
if (!name) return res.status(400).json({ error: 'name is required' });
|
||||
const r = await argoApi.get(`/applications/${name}`);
|
||||
const a = r.data;
|
||||
res.json({
|
||||
name: a.metadata.name,
|
||||
project: a.spec.project,
|
||||
source: a.spec.source,
|
||||
destination: a.spec.destination,
|
||||
syncPolicy: a.spec.syncPolicy,
|
||||
syncStatus: a.status?.sync,
|
||||
healthStatus: a.status?.health,
|
||||
conditions: a.status?.conditions,
|
||||
resources: (a.status?.resources || []).map(r => ({
|
||||
kind: r.kind,
|
||||
name: r.name,
|
||||
namespace: r.namespace,
|
||||
status: r.status,
|
||||
health: r.health?.status
|
||||
}))
|
||||
});
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Sync app
|
||||
app.post('/api/apps/sync', async (req, res) => {
|
||||
try {
|
||||
const { name, prune, force } = req.body;
|
||||
if (!name) return res.status(400).json({ error: 'name is required' });
|
||||
const body = { prune: prune || false };
|
||||
if (force) body.strategy = { apply: { force: true } };
|
||||
const r = await argoApi.post(`/applications/${name}/sync`, body);
|
||||
res.json({
|
||||
message: `Sync triggered for ${name}`,
|
||||
phase: r.data.status?.operationState?.phase,
|
||||
startedAt: r.data.status?.operationState?.startedAt
|
||||
});
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Out-of-sync apps
|
||||
app.post('/api/apps/out_of_sync', async (req, res) => {
|
||||
try {
|
||||
const r = await argoApi.get('/applications');
|
||||
const apps = (r.data.items || [])
|
||||
.filter(a => a.status?.sync?.status !== 'Synced')
|
||||
.map(a => ({
|
||||
name: a.metadata.name,
|
||||
syncStatus: a.status?.sync?.status,
|
||||
healthStatus: a.status?.health?.status,
|
||||
namespace: a.spec.destination?.namespace,
|
||||
lastSyncedAt: a.status?.operationState?.finishedAt
|
||||
}));
|
||||
res.json({ count: apps.length, apps });
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Unhealthy apps
|
||||
app.post('/api/apps/unhealthy', async (req, res) => {
|
||||
try {
|
||||
const r = await argoApi.get('/applications');
|
||||
const apps = (r.data.items || [])
|
||||
.filter(a => !['Healthy', 'Progressing'].includes(a.status?.health?.status))
|
||||
.map(a => ({
|
||||
name: a.metadata.name,
|
||||
healthStatus: a.status?.health?.status,
|
||||
healthMessage: a.status?.health?.message,
|
||||
syncStatus: a.status?.sync?.status,
|
||||
namespace: a.spec.destination?.namespace,
|
||||
conditions: a.status?.conditions
|
||||
}));
|
||||
res.json({ count: apps.length, apps });
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Deployment history
|
||||
app.post('/api/apps/history', async (req, res) => {
|
||||
try {
|
||||
const { name, limit } = req.body;
|
||||
if (!name) return res.status(400).json({ error: 'name is required' });
|
||||
const r = await argoApi.get(`/applications/${name}/revisions`);
|
||||
const history = ((r.data.history || []).slice(-(limit || 10))).reverse().map(h => ({
|
||||
id: h.id,
|
||||
revision: h.revision,
|
||||
deployedAt: h.deployedAt,
|
||||
initiatedBy: h.initiatedBy
|
||||
}));
|
||||
res.json({ app: name, count: history.length, history });
|
||||
} catch (e) {
|
||||
// history endpoint may differ by ArgoCD version
|
||||
try {
|
||||
const r2 = await argoApi.get(`/applications/${name}`);
|
||||
const history = ((
|
||||
Reference in New Issue
Block a user