baseUrl = rtrim($baseUrl, '/'); $this->token = $token; } private function headers() { $h = [ 'Content-Type: application/json', 'Accept: application/json', ]; if ($this->token) { $h[] = 'Authorization: Token ' . $this->token; } return $h; } private function request($method, $endpoint, $body = null) { $url = $this->baseUrl . '/api/' . ltrim($endpoint, '/'); $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => $this->headers(), CURLOPT_FOLLOWLOCATION => true, CURLOPT_CUSTOMREQUEST => strtoupper($method), ]); if ($body !== null) { curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body)); } $resp = curl_exec($ch); $info = curl_getinfo($ch); curl_close($ch); if ($info['http_code'] >= 400) { throw new Exception("NetBox API {$method} {$endpoint} failed (HTTP {$info['http_code']}): {$resp}"); } return json_decode($resp, true); } /** * Get list endpoint (handles pagination for small result sets). */ public function getList($endpoint) { $results = []; $url = $endpoint . '?limit=100'; while ($url) { $page = $this->request('get', $url); foreach (($page['results'] ?? []) as $item) { $results[] = $item; } $url = $page['next'] ?? null; // Normalize next URL — NetBox returns absolute URLs if ($url && strpos($url, 'http') === 0) { $url = str_replace($this->baseUrl . '/api/', '', $url); } } return $results; } public function post($endpoint, $data) { return $this->request('post', $endpoint, $data); } /** * Find a tag by name; returns the tag array or null. */ public function findTag($name) { $tags = $this->getList('/extras/tags/'); foreach ($tags as $tag) { if ($tag['name'] === $name) { return $tag; } } return null; } /** * Find a cluster by name; returns the cluster array or null. */ public function findCluster($name) { $clusters = $this->getList('/virtualization/clusters/'); foreach ($clusters as $c) { if ($c['name'] === $name) { return $c; } } return null; } /** * Create a Virtual Machine in NetBox. * * NetBox virtualization VM API uses: * - vcpus: float (e.g. 2.0) * - memory: int in MB (e.g. 4096) * - disk: int in MB (e.g. 51200) * - cluster: required (int id) * - tags: array of tag names to look up */ public function createVirtualMachine(array $params) { $payload = [ 'name' => $params['name'], 'cluster' => $params['cluster_id'], 'status' => $params['status'] ?? 'active', 'vcpus' => (float) $params['vcpus'], 'memory' => (int) ($params['ram_gb'] * 1024), // GB -> MB 'disk' => (int) ($params['disk_gb'] * 1024), // GB -> MB 'description' => $params['description'] ?? '', ]; // Custom fields: usernames and sudo_usernames $cf = []; if (!empty($params['usernames'])) { $cf['usernames'] = $params['usernames']; } if (!empty($params['sudo_usernames'])) { $cf['sudo_usernames'] = $params['sudo_usernames']; } if (!empty($cf)) { $payload['custom_fields'] = $cf; } // Tag lookup for domain — tags must be numeric IDs in the NetBox API if (!empty($params['domain'])) { $tag = $this->findTag($params['domain']); if ($tag) { $payload['tags'] = [$tag['id']]; } else { error_log("NetBox: tag '{$params['domain']}' not found — creating VM without domain tag."); } } $result = $this->post('/virtualization/virtual-machines/', $payload); return $result; } }