From 5bcc914fc8beaff5d2b086d70cda9cd63a4e2c0b Mon Sep 17 00:00:00 2001 From: Adam Mohammed Date: Sun, 2 Jun 2024 20:44:09 -0400 Subject: [PATCH] Beginning of PAPI Client --- metal-sdk.scm | 156 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100755 metal-sdk.scm diff --git a/metal-sdk.scm b/metal-sdk.scm new file mode 100755 index 0000000..d93269f --- /dev/null +++ b/metal-sdk.scm @@ -0,0 +1,156 @@ +(use-modules (web client) + (json) + (srfi srfi-1) + (srfi srfi-11)) + +(define (api-key) (getenv "API_KEY")) +(define (consumer-token) (getenv "CONSUMER_TOKEN")) +(define auth-header-key 'x-auth-token) +(define consumer-header-key 'x-consumer-token) +(define staff-header (cons 'x-packet-staff "true")) +(define base-url "https://api.equinix.com/metal/v1") +(define metal-prefix "/metal/v1") + +(define %default-headers + `((x-auth-token . ,(api-key)) + (CONTENT-TYPE . "application/json") + (ACCEPT . "application/json"))) + +(define http-debug? #f) + +(define (toggle-http-debug) + (set! http-debug? (not http-debug?))) + +(define (with-staff-headers thunk) + (let ((old-headers %default-headers)) + (dynamic-wind + (lambda () (set! %default-headers (append %default-headers + (list (cons 'X-CONSUMER-TOKEN (consumer-token)) + (cons 'X-PACKET-STAFF "true"))))) + thunk + (lambda () (set! %default-headers old-headers)))))q + +(define (headers) + %default-headers) + +(define (http-debug method path headers body) + (display (format #f "method: ~a\npath: ~a\nheaders: ~a\nbody: ~a\n" method path headers body))) + +(define (staff-headers) + (let ((consumer-token (getenv "CONSUMER_TOKEN"))) + (cons (car (headers)) + (list + (cons consumer-header-key consumer-token) + staff-header)))) + +(define (http-req method path headers body) + (let ((fixed-path (combine-path path))) + (when http-debug? (http-debug method path headers body)) + (let-values (((resp port) (http-get (combine-path path) #:headers headers + #:streaming? #t + #:decode-body? #f + #:body body))) + (handle-json port)))) + + +(define (handle-json port) + (with-input-from-port port + (lambda () + (let ((res (json->scm port))) + (close-port port) + res)))) + +(define (GET path headers) + (http-req 'GET path headers #f)) + +(define (POST path headers body) + (http-req 'POST path headers body)) + +(define (PUT path headers body) + (http-req 'PUT path headers body)) + +(define (DELETE path headers body) + (http-req 'DELETE path headers body)) + +(define (strip-prefix s prefix) + (substring s (string-length prefix))) + +(define (combine-path path) + (if (string-contains path metal-prefix) + (string-append base-url (strip-prefix path metal-prefix)) + (string-append base-url path))) + +(define (meta paginated-json) + (assoc-ref paginated-json "meta")) + +(define (meta->current-page meta) + (assoc-ref meta "current_page")) + +(define (meta->last-page meta) + (assoc-ref meta "last_page")) + +(define (meta->next meta) + (assoc-ref (assoc-ref meta "next") "href")) + +(define (build-project project-alist) + (list (assoc "id" project-alist) + (assoc "name" project-alist))) + +(define (build-device device-alist) + (list (assoc "id" device-alist) + (assoc "hostname" device-alist))) + +(define (list-all-projects) + (handle-pagination "/projects?exclude=devices,memberships,members" "projects" build-project)) + +(define (list-all-devices project) + (handle-pagination (format #f "/projects/~a/devices" (project->id project)) "devices" build-device)) + +(define (handle-pagination start-path array-key model-fn) + (let ((pagination (meta (GET start-path (headers)))) + (collection '())) + (define (process-page url) + (let ((full-page (GET url (headers)))) + (map (lambda (item) + (set! collection (cons (model-fn item) collection))) + (vector->list (assoc-ref full-page array-key))) + (if (meta->next (meta full-page)) + (process-page (meta->next (meta full-page))) + (reverse collection)))) + (process-page start-path))) + +(define (project->id project) + (assoc-ref project "id")) + +(define (project->organization project) + (let ((project (GET (format #f "/projects/~a" (project->id project)) (headers)))) + (let ((org (GET (assoc-ref (assoc-ref project "organization") "href") (headers)))) + (list (assoc "id" org) (assoc "name" org))))) + + + +(define (vlan->project vlan) + (project->id (assoc-ref vlan "project"))) + +(define (vlan->metro-code vlan) + (assoc-ref vlan "metro_code")) + +(define (vlan->description vlan) + (assoc-ref vlan "description")) + +(define (vlan-request-body vlan) + (scm->json-string + `(("metro" . ,(vlan->metro-code vlan)) + ("description" . ,(vlan->description vlan))))) + +(define (build-vlan vlan-alist) + (list (assoc "id" vlan-alist) + (assoc "description" vlan-alist) + (assoc "vxlan" vlan-alist) + (assoc "metro_code" vlan-alist))) + +(define (vlan->vxlan vlan) + (assoc-ref vlan "vxlan")) + +(define (list-all-vlans project) + (handle-pagination (format #f "/projects/~a/virtual-networks" (project->id project)) "virtual_networks" build-vlan))