Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 96 additions & 3 deletions create-a-container/routers/containers.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,44 @@ router.get('/new', requireAuth, async (req, res) => {
});
});

// Helper to detect API bearer requests
function isApiRequest(req) {
const auth = req.get('authorization') || '';
const parts = auth.split(' ');
return parts.length === 2 && parts[0] === 'Bearer' && parts[1] === process.env.API_KEY;
}

// GET /sites/:siteId/containers - List all containers for the logged-in user in this site
router.get('/', requireAuth, async (req, res) => {
router.get('/', async (req, res) => {
// If called by API clients using Bearer token, return JSON instead of HTML
if (isApiRequest(req)) {
try {
const siteId = parseInt(req.params.siteId, 10);
const site = await Site.findByPk(siteId);
if (!site) return res.status(404).json([]);

// Limit search to nodes within this site
const nodes = await Node.findAll({ where: { siteId }, attributes: ['id'] });
const nodeIds = nodes.map(n => n.id);

const { hostname } = req.query;
const where = {};
if (hostname) where.hostname = hostname;
where.nodeId = nodeIds;

const containers = await Container.findAll({ where, include: [{ association: 'node', attributes: ['id', 'name'] }] });
const out = containers.map(c => ({ id: c.id, hostname: c.hostname, ipv4Address: c.ipv4Address, macAddress: c.macAddress, node: c.node ? { id: c.node.id, name: c.node.name } : null, createdAt: c.createdAt }));
return res.json(out);
} catch (err) {
console.error('API GET /sites/:siteId/containers error:', err);
return res.status(500).json({ error: 'Internal server error' });
}
}

// Browser path: require authentication and render HTML
await new Promise(resolve => requireAuth(req, res, resolve));
if (res.headersSent) return; // requireAuth already handled redirect

const siteId = parseInt(req.params.siteId, 10);

const site = await Site.findByPk(siteId);
Expand Down Expand Up @@ -203,12 +239,41 @@ router.post('/', async (req, res) => {
// Validate site exists
const site = await Site.findByPk(siteId);
if (!site) {
if (isApiRequest(req)) return res.status(404).json({ error: 'Site not found' });
req.flash('error', 'Site not found');
return res.redirect('/sites');
}

// TODO: build the container async in a Job
try {
// If API client (Bearer token), perform a lightweight create and return JSON
if (isApiRequest(req)) {
try {
const { hostname, ipv4Address, macAddress, nodeName, containerId } = req.body;
if (!hostname) return res.status(400).json({ error: 'hostname required' });
// attempt to associate node if provided
let node = null;
if (nodeName) node = await Node.findOne({ where: { name: nodeName, siteId } });

let existing = await Container.findOne({ where: { hostname } });
if (existing) return res.status(200).json({ containerId: existing.id, message: 'Already exists' });

const created = await Container.create({
hostname,
username: req.body.username || 'api',
nodeId: node ? node.id : null,
containerId: containerId || null,
macAddress: macAddress || null,
ipv4Address: ipv4Address || null
});

return res.status(201).json({ containerId: created.id, message: 'Created' });
} catch (err) {
console.error('API POST /sites/:siteId/containers error:', err);
return res.status(500).json({ error: 'Internal server error' });
}
}

const { hostname, template, services } = req.body;
const [ nodeName, templateVmid ] = template.split(',');
const node = await Node.findOne({ where: { name: nodeName, siteId } });
Expand Down Expand Up @@ -378,9 +443,25 @@ router.post('/', async (req, res) => {
});

// PUT /sites/:siteId/containers/:id - Update container services
router.put('/:id', requireAuth, async (req, res) => {
router.put('/:id', async (req, res) => {
const siteId = parseInt(req.params.siteId, 10);
const containerId = parseInt(req.params.id, 10);
// API clients may update container metadata via Bearer token
if (isApiRequest(req)) {
try {
const container = await Container.findByPk(containerId);
if (!container) return res.status(404).json({ error: 'Not found' });
await container.update({
ipv4Address: req.body.ipv4Address ?? container.ipv4Address,
macAddress: req.body.macAddress ?? container.macAddress,
osRelease: req.body.osRelease ?? container.osRelease
});
return res.status(200).json({ message: 'Updated' });
} catch (err) {
console.error('API PUT /sites/:siteId/containers/:id error:', err);
return res.status(500).json({ error: 'Internal server error' });
}
}

const site = await Site.findByPk(siteId);
if (!site) {
Expand Down Expand Up @@ -513,9 +594,21 @@ router.put('/:id', requireAuth, async (req, res) => {
});

// DELETE /sites/:siteId/containers/:id - Delete a container
router.delete('/:id', requireAuth, async (req, res) => {
router.delete('/:id', async (req, res) => {
const siteId = parseInt(req.params.siteId, 10);
const containerId = parseInt(req.params.id, 10);
// If API request, perform lightweight delete and return JSON/204
if (isApiRequest(req)) {
try {
const container = await Container.findByPk(containerId);
if (!container) return res.status(404).json({ error: 'Not found' });
await container.destroy();
return res.status(204).send();
} catch (err) {
console.error('API DELETE /sites/:siteId/containers/:id error:', err);
return res.status(500).json({ error: 'Internal server error' });
}
}

// Validate site exists
const site = await Site.findByPk(siteId);
Expand Down
4 changes: 4 additions & 0 deletions create-a-container/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,16 @@ async function main() {
});

// --- Mount Routers ---
// Mount top-level API endpoints (used by automation / API clients)
const apiContainersRouter = require('./routers/api_containers');
const loginRouter = require('./routers/login');
const registerRouter = require('./routers/register');
const usersRouter = require('./routers/users');
const groupsRouter = require('./routers/groups');
const sitesRouter = require('./routers/sites'); // Includes nested nodes and containers routers
const jobsRouter = require('./routers/jobs');
// expose API endpoints before HTML routes so they respond at top-level
app.use('/', apiContainersRouter);
app.use('/jobs', jobsRouter);
app.use('/login', loginRouter);
app.use('/register', registerRouter);
Expand Down