Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
Signed-off-by: George Lemon <georgelemon@protonmail.com>
  • Loading branch information
georgelemon committed Mar 29, 2024
1 parent 5198270 commit 4fa7218
Show file tree
Hide file tree
Showing 7 changed files with 403 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ testresults/
/tests/*
!/tests/*.nim
!/tests/*.nims
.env
endpoints.md
22 changes: 22 additions & 0 deletions hetzner.nimble
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Package

version = "0.1.0"
author = "George Lemon"
description = "Asynchronous Nim client for interacting with Hetzner Cloud API"
license = "MIT"
srcDir = "src"


# Dependencies

requires "nim >= 2.0.2"
requires "jsony"

task build_action, "build /action":
exec "nim c -d:ssl -o:./bin/action src/hetzner/action.nim"

task build_certificate, "build /certficates":
exec "nim c -d:ssl -o:./bin/certificates src/hetzner/certificate.nim"

task build_network, "build /networks":
exec "nim c -d:ssl -o:./bin/networks src/hetzner/network.nim"
7 changes: 7 additions & 0 deletions src/hetzner.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# This is just an example to get you started. A typical library package
# exports the main API in this file. Note that you cannot rename this file
# but you can remove it if you wish.

proc add*(x, y: int): int =
## Adds two numbers together.
return x + y
103 changes: 103 additions & 0 deletions src/hetzner/action.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Asynchronous Nim client for
# interacting with the Hetzner Cloud API
#
# (c) 2023 George Lemon | MIT License
# Made by Humans from OpenPeeps
# https://github.com/openpeeps/hetzner-nim

import std/times
import pkg/jsony
import ./meta

## Actions show the results and progress of asynchronous requests to the API.
## Check Hetzner API Reference: https://docs.hetzner.cloud/#actions

type
ActionStatus* = enum
actionStatusRunning = "running"
actionStatusSuccess = "success"
actionStatusError = "error"

Action* = object
id: int64
command: string
status: ActionStatus
progress: int
started, finished: Time
error: ActionError
resources: seq[ActionResource]

ActionResourceType* = enum
actionResourceTypeServer = "server"
actionResourceTypeImage = "image"
actionResourceTypeISO = "iso"
actionResourceTypeFloatingIP = "floating_ip"
actionResourceTypeVolume = "volume"

ActionResource* = object
id: int64
`type`: ActionResourceType

ActionError* = ref object
code: string
message: string

Actions* = object
actions: seq[Action]
meta: Pagination

ActionClient* = ref object of HetznerClient


#
# `/actions`
#
# proc newActionClient*(): ActionClient =
# ## Create a new HttpClient for GET `/action`

proc sort*(client: ActionClient, filters: varargs[string]): ActionClient {.discardable.} =
## Sort actions by field and direction. Can be used multiple times.
## For more information, see [Sorting](https://docs.hetzner.cloud/#sorting)
assert client.uri == epActions
client.multiQuery["sort"] = filters.toSeq
result = client

proc status*(client: ActionClient, x: set[ActionStatus]): ActionClient {.discardable.} =
## Filter the actions by status. Can be used multiple times.
## The response will only contain actions matching the specified statuses
assert client.uri == epActions
client.multiQuery["status"] = x.toSeq.mapit($it)
result = client

proc page*(client: ActionClient, i: int64): ActionClient {.discardable.} =
## Maximum number of entries returned per page.
## For more information, see [Pagination](https://docs.hetzner.cloud/#pagination)
client.query["page"] = $i
result = client

proc perPage*(client: ActionClient, i: int64): ActionClient {.discardable.} =
## Page number to return. For more information
client.query["per_page"] = $i
result = client

proc get*(client: ActionClient): Future[Actions] {.async.} =
let res = await client.getHetzner()
let body = await res.body
fromJSON body, Actions

proc `$`*(certs: Actions): string =
## Serialize available Actions
toJSON certs

when isMainModule:
import pkg/dotenv
from std/os import getEnv
from std/macros import getProjectPath

dotenv.load(getProjectPath())

var hcloud = initHetzner(getEnv("hetznerApiKey"))
var client = newClient[ActionClient](hcloud, epActions)

let actions: Actions = waitFor client.get()
echo actions
59 changes: 59 additions & 0 deletions src/hetzner/certificate.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Asynchronous Nim client for
# interacting with the Hetzner Cloud API
#
# (c) 2023 George Lemon | MIT License
# Made by Humans from OpenPeeps
# https://github.com/openpeeps/hetzner-nim

import std/times
import pkg/jsony
import ./meta

type
CertificateStatus* = enum
certificateStatusIssuance
certificateStatusRenewal
certificateStatusError

CertificateType* = enum
certificateTypeUploaded = "uploaded"
certificateTypeManaged = "managed"

Certificate* = ref object
id: int64
name: string
labels: seq[string]
`type`: CertificateType
certificate: string
created: Time
not_valid_before, not_valid_after: Time
domain_names: seq[string]
fingerprint: string
status: CertificateStatus

Certificates* = ref object
certificates: seq[Certificate]
meta: Pagination

CertificateClient* = ref object of HetznerClient

proc get*(client: CertificateClient): Future[Certificates] {.async.} =
## Make a `GET` request to retrieve all `Certificates`
let res = await client.getHetzner()
let body = await res.body
fromJSON body, Certificates

proc `$`*(certs: Certificates): string =
## Serialize available `Certificates`
toJSON certs

when isMainModule:
import pkg/dotenv
from std/os import getEnv
from std/macros import getProjectPath

dotenv.load(getProjectPath())
var hcloud = initHetzner(getEnv("hetznerApiKey"))
var client = newClient[CertificateClient](hcloud, epCertificates)
let certs = waitFor client.get()
echo certs
108 changes: 108 additions & 0 deletions src/hetzner/meta.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Asynchronous Nim client for
# interacting with the Hetzner Cloud API
#
# (c) 2023 George Lemon | MIT License
# Made by Humans from OpenPeeps
# https://github.com/openpeeps/hetzner-nim

import std/[asyncdispatch, httpclient, tables,
strutils, sequtils, times]

import pkg/jsony

from std/httpcore import HttpMethod

export HttpMethod, asyncdispatch, httpclient,
tables, sequtils

type
HetznerEndpoint* = enum
# Action
epAction = "action"
epActions = "actions"
# Certifications
epCertificates = "certificates"
epCertificate = "certificates/{$1}"
# Datacenters
epDatacenters = "datacenters"
epDatacenter = "datacenter/{$1}"

epNetworks = "networks"
epNetwork = "networks/{$1}"

HetznerClient* = ref object of RootObj
uri*: HetznerEndpoint
httpClient*: AsyncHttpClient
query*: QueryTable
multiQuery*: MultiQueryTable

QueryTable* = OrderedTable[string, string]
MultiQueryTable* = OrderedTable[string, seq[string]]

Pagination* = object
page, per_page, previous_page,
next_page, last_page, total_entries: int

HetznerCloud* = ref object
apiKey: string

const baseHetznerUri = "https://api.hetzner.cloud/v1/"

proc initHetzner*(key: string): HetznerCloud =
## Initialize a new HetznerCloud
HetznerCloud(apiKey: key)

proc newClient*[T: HetznerClient](hcloud: HetznerCloud, endpoint: HetznerEndpoint): T =
new(result)
result.uri = endpoint
result.httpClient = newAsyncHttpClient()
result.httpClient.headers = newHttpHeaders({
"Authorization": "Bearer " & hcloud.apiKey
})

proc `$`*(query: QueryTable): string =
## Convert `query` QueryTable to string
if query.len > 0:
add result, "&"
add result, join(query.keys.toSeq.mapIt(it & "=" & query[it]), "&")

proc `$`*(query: MultiQueryTable): string =
## Convert `query` MultiQuerytable to string
if query.len > 0:
add result, "?"
var i = 0
let len = query.len - 1
for k, x in query:
if x.len == 1:
add result, k
add result, "=" & x[0]
else:
add result, join(x.mapIt(k & "=" & it), "&")
if i != len:
add result, "&"
inc i

#
# JSONY hooks
#
proc parseHook*(s: string, i: var int, v: var Time) =
var str: string
parseHook(s, i, str)
v = parseTime(str, "yyyy-MM-dd'T'hh:mm:sszzz", local())

proc dumpHook*(s: var string, v: Time) =
add s, '"'
add s, v.format("yyyy-MM-dd'T'hh:mm:sszzz", local())
add s, '"'

proc endpoint*(uri: HetznerEndpoint,
multiQuery: MultiQueryTable, query: QueryTable): string =
## Return the url string of an endpoint
result = baseHetznerUri & $uri & $multiQuery & $query

proc getHetzner*(client: HetznerClient): Future[AsyncResponse] {.async.} =
## Make a GET request using an instance of `HetznerClient`
let uri = client.uri.endpoint(client.multiQuery, client.query)
result = await client.httpClient.request(uri, HttpGet)
client.httpClient.close()

Loading

0 comments on commit 4fa7218

Please sign in to comment.