From patchwork Thu Mar 20 06:50:18 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sergey Trofimov X-Patchwork-Id: 40460 Return-Path: X-Original-To: patchwork@mira.cbaines.net Delivered-To: patchwork@mira.cbaines.net Received: by mira.cbaines.net (Postfix, from userid 113) id C283727BBE9; Thu, 20 Mar 2025 06:51:44 +0000 (GMT) X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on mira.cbaines.net X-Spam-Level: X-Spam-Status: No, score=-6.3 required=5.0 tests=BAYES_00,DKIM_INVALID, DKIM_SIGNED,MAILING_LIST_MULTI,RCVD_IN_DNSWL_BLOCKED, RCVD_IN_VALIDITY_CERTIFIED,RCVD_IN_VALIDITY_RPBL,RCVD_IN_VALIDITY_SAFE, SPF_HELO_PASS,URIBL_BLOCKED,URIBL_SBL_A autolearn=unavailable autolearn_force=no version=3.4.6 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mira.cbaines.net (Postfix) with ESMTPS id E9A2227BBE2 for ; Thu, 20 Mar 2025 06:51:41 +0000 (GMT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1tv9kF-00013I-UN; Thu, 20 Mar 2025 02:51:12 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1tv9kA-00012K-G9 for guix-patches@gnu.org; Thu, 20 Mar 2025 02:51:09 -0400 Received: from debbugs.gnu.org ([2001:470:142:5::43]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1tv9k7-0007xE-Vb; Thu, 20 Mar 2025 02:51:04 -0400 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=debbugs.gnu.org; s=debbugs-gnu-org; h=MIME-Version:Date:From:To:In-Reply-To:References:Subject; bh=Kw81YzkTPQHFS9zHq7GpiX0BN4jLBIUnXyxgxrW4Kgg=; b=gYNKlZ7U9OVPwR6XmBvAWM5SL3wZGtvyaNB2gqOh+N3nP3TDFLa/xBnAmFAX/ODksDTYkpy0qNcvwSMJsBi9gmwXNZl9L26xfUe9dsQP8LpVHvqIbHB2+jsQqaVPu9mvdfW1pVc6v7ZL0FPhajucxA24DS2iQgCCOPQM3YIZyIHwOu+2DUj+db6NUBcSe2bzdKcJt85ExYa+VLElj9WIyem15yvVrTSMGJZZH54lm4IHfP/tkbjQPUfV7pkv1CA9qz5OmhDUwdVy6zTRP5uETa85/EDKCOHvueJ+DVTZHQhZ4j+9L2alSnS7XRv3mJG0CEX5XcngIKVps6XaYpi+8Q==; Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1tv9k6-0001mj-4b; Thu, 20 Mar 2025 02:51:02 -0400 X-Loop: help-debbugs@gnu.org Subject: [bug#77019] [PATCH v1] machine: hetzner: Allow attaching existing public IPs. References: In-Reply-To: Resent-From: Sergey Trofimov Original-Sender: "Debbugs-submit" Resent-CC: sarg@sarg.org.ru, ludo@gnu.org, maxim.cournoyer@gmail.com, guix-patches@gnu.org Resent-Date: Thu, 20 Mar 2025 06:51:02 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 77019 X-GNU-PR-Package: guix-patches X-GNU-PR-Keywords: patch To: 77019@debbugs.gnu.org Cc: Sergey Trofimov , Sergey Trofimov , Ludovic =?utf-8?q?Court=C3=A8s?= , Maxim Cournoyer X-Debbugs-Original-Xcc: Sergey Trofimov , Ludovic =?utf-8?q?Court=C3=A8s?= , Maxim Cournoyer Received: via spool by 77019-submit@debbugs.gnu.org id=B77019.17424534346818 (code B ref 77019); Thu, 20 Mar 2025 06:51:02 +0000 Received: (at 77019) by debbugs.gnu.org; 20 Mar 2025 06:50:34 +0000 Received: from localhost ([127.0.0.1]:54978 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1tv9jd-0001lt-8v for submit@debbugs.gnu.org; Thu, 20 Mar 2025 02:50:33 -0400 Received: from mail-ej1-x631.google.com ([2a00:1450:4864:20::631]:44112) by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.84_2) (envelope-from ) id 1tv9jZ-0001lb-N8 for 77019@debbugs.gnu.org; Thu, 20 Mar 2025 02:50:31 -0400 Received: by mail-ej1-x631.google.com with SMTP id a640c23a62f3a-ac2ed007aacso108278066b.0 for <77019@debbugs.gnu.org>; Wed, 19 Mar 2025 23:50:29 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=sarg.org.ru; s=google; t=1742453423; x=1743058223; darn=debbugs.gnu.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to; bh=Kw81YzkTPQHFS9zHq7GpiX0BN4jLBIUnXyxgxrW4Kgg=; b=t7lwk7X54q6ZPtS53BdKr3srZFjkbtdLTssa6HPlMcjdW1fk4gc1s/9LciNoA3LVQp d3D4SzJtW/xAd4jN334P4RnNIhcg+O3wfzDKEy5NTngN3Gei4hLwB/Z8nlyCD1u4MZ7Z vS8BXUegCsqf5d2GY8Y11lzr5wFsjqU7sCfNPSbuQEG/OzW7bmpjpgVnBq59WuPYIMoQ qdlC1Ofjr1yx0h6RZXUwJYy1CmucMW907k0JjQuigr58k/1G/FD59XOdkJle4Oa7d57M csXwveqBP6I3y2mt7sNT4hBxvB7wQtenoRHyQQunslQkjqYGp8EtYL4aSibD8SchANr5 lvbg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1742453423; x=1743058223; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=Kw81YzkTPQHFS9zHq7GpiX0BN4jLBIUnXyxgxrW4Kgg=; b=Q+u5vbxAGiYvSSnjKgv2DN3itnWNVQTU0HHSqU8hypofrz/IN0d3M2CQRr96w7ukMS 4I8TgQ0eTkpIUaGtng3jLv8oY7SrnkKtNG3ZXXxbiLYRmhkf1pdNkpFXN4beDKgv/vAE N6/6sZF5fK+qthdcozd1W7BhEPXs6jzJP8YAEf0CFuyB6QqayL4wIS9xbWIzFF3ECd2f gZDBK2R8fC482uH25Q8bAtG7kZ1LpC/xb9iGG6zkXtgnptFK/n8ffnFMUZV3zmFURjEY 0kyHh1ikhfHCq5i/VS4KQJSqKhI+Imh1lv4jy/ywdBgDI/F/72c77y1hu+sDSIV/Fbok v4LQ== X-Gm-Message-State: AOJu0YxB4qaVzI1lkTx62qEizBN2WeDvJTdyl6NhDfCykHZQMGSR2XwW 3eo+R1EnyqEEAzGVGXHqoYtoiiTwz183onDFxbmrKMgXMtyT/Pej6XDXmUcag1LqguXhHI72ar0 zQhI= X-Gm-Gg: ASbGncvCW9xj7oolIUGypNZFX61ggZXXZuVVot6Ez01Rgs3uM91LvoiKYpS3mD73k4k EGmKrzSXx36ZJcQNnEefzmSmDFZxV2kni+jIULbAB4U5AbE0ND/AWZI3lb2z1tb3Q0ZhsddHAOk RZrsuj7JZFnymxlXeNeODgOFXwyVcolgHQhYsjhOY8DYijXfIPWRJYaTcK07hRp7fr0kdtnVpKC +KqtPJVpVS4bCHYxA7ZMr+s8MdfpEm57I041+SC4bz82apely8zXHaPYAzOA3PGHYgMuvkc8jkh 10H+zVZW9yocTcjLABFAEAR+lA4yAEB9hvq2n1MvtQ== X-Google-Smtp-Source: AGHT+IERm/7ba5lXQbpJbq0VogoTgjyCzlUtgBf+Pbv1sBA3i7hS7beqizMBCiuswLB7qBp0UnAtnQ== X-Received: by 2002:a17:907:6ea9:b0:ac2:dc00:b34d with SMTP id a640c23a62f3a-ac3b7ff500cmr588956066b.53.1742453422880; Wed, 19 Mar 2025 23:50:22 -0700 (PDT) Received: from localhost ([2a02:2454:a0a5:2400:a64e:31ff:fe38:fd6c]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-ac3b2b94da0sm240170066b.148.2025.03.19.23.50.22 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 19 Mar 2025 23:50:22 -0700 (PDT) Date: Thu, 20 Mar 2025 07:50:18 +0100 Message-ID: X-Mailer: git-send-email 2.48.1 MIME-Version: 1.0 X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-BeenThere: guix-patches@gnu.org List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-to: Sergey Trofimov X-ACL-Warn: , Sergey Trofimov via Guix-patches X-Patchwork-Original-From: Sergey Trofimov via Guix-patches via From: Sergey Trofimov Errors-To: guix-patches-bounces+patchwork=mira.cbaines.net@gnu.org Sender: guix-patches-bounces+patchwork=mira.cbaines.net@gnu.org X-getmail-retrieved-from-mailbox: Patches * gnu/machine/hetzner.scm (hetzner-configuration): Add ipv4 and ipv6 fields. Export accessors. * gnu/machine/hetzner/http.scm (hetnzer-api-primary-ips): New function. (): New json mapping. (hetzner-api-server-create): Pass IP addresses in request. * doc/guix.texi: Document it. --- doc/guix.texi | 10 +++++++++ gnu/machine/hetzner.scm | 25 ++++++++++++++++++++++ gnu/machine/hetzner/http.scm | 36 ++++++++++++++++++++++++++------ tests/machine/hetzner/http.scm | 38 ++++++++++++++++++++++++++++++++++ 4 files changed, 103 insertions(+), 6 deletions(-) base-commit: 77ff73a920759437639e8eb77601e51409fefefa prerequisite-patch-id: f9cc903b8048c8c6fde576fbf38ab110263020e3 prerequisite-patch-id: 220ddf11addf3a6c7ab3b349077bca6849241556 diff --git a/doc/guix.texi b/doc/guix.texi index e5894931ff..9352c56563 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -45962,6 +45962,16 @@ Invoking guix deploy provisioning phase. If false, the server will be kept in order to debug any issues. +@item @code{ipv4} (default: @code{'create}) +When false, no public IPv4 address is going to be attached. Specify the +name of an existing primary ip to attach it to the machine. Other values +would create a new address automatically. + +@item @code{ipv6} (default: @code{'create}) +When false, no public IPv6 address is going to be attached. Specify the +name of an existing primary ip to attach it to the machine. Other values +would create a new address automatically. + @item @code{labels} (default: @code{'()}) A user defined alist of key/value pairs attached to the SSH key and the server on the Hetzner API. Keys and values must be strings, diff --git a/gnu/machine/hetzner.scm b/gnu/machine/hetzner.scm index e8484e4d51..c1ccab54ae 100644 --- a/gnu/machine/hetzner.scm +++ b/gnu/machine/hetzner.scm @@ -73,6 +73,8 @@ (define-module (gnu machine hetzner) hetzner-configuration-authorize? hetzner-configuration-build-locally? hetzner-configuration-delete? + hetzner-configuration-ipv4 + hetzner-configuration-ipv6 hetzner-configuration-labels hetzner-configuration-location hetzner-configuration-server-type @@ -205,6 +207,10 @@ (define-record-type* hetzner-configuration (default "fsn1")) (server-type hetzner-configuration-server-type ; string (default "cx42")) + (ipv4 hetzner-configuration-ipv4 + (default 'create)) + (ipv6 hetzner-configuration-ipv6 + (default 'create)) (ssh-public-key hetzner-configuration-ssh-public-key ; public-key | string (thunked) (default (public-key-from-file (hetzner-configuration-ssh-key this-hetzner-configuration))) @@ -445,6 +451,17 @@ (define (hetzner-machine-server machine) (hetzner-configuration-api config) #:params `(("name" . ,(machine-display-name machine))))))) +(define (hetzner-resolve-ip api name) + "Find the NAME IP address on the Hetzner API." + (or + (find (lambda (primary-ip) + (equal? name (hetzner-primary-ip-name primary-ip))) + (hetzner-api-primary-ips api #:params `(("name" . ,name)))) + + (raise-exception + (formatted-message (G_ "primary ip '~a' does not exist.") + name)))) + (define (hetzner-machine-create-server machine) "Create the Hetzner server for MACHINE." (let* ((config (machine-configuration machine)) @@ -452,11 +469,19 @@ (define (hetzner-machine-create-server machine) (server-type (hetzner-configuration-server-type config))) (format #t "creating '~a' server for '~a'...\n" server-type name) (let* ((ssh-key (hetzner-machine-ssh-key machine)) + (ipv4 (hetzner-configuration-ipv4 config)) + (ipv6 (hetzner-configuration-ipv6 config)) (api (hetzner-configuration-api config)) (server (hetzner-api-server-create api (machine-display-name machine) (list ssh-key) + #:ipv4 (if (string? ipv4) + (hetzner-primary-ip-id (hetzner-resolve-ip api ipv4)) + ipv4) + #:ipv6 (if (string? ipv6) + (hetzner-primary-ip-id (hetzner-resolve-ip api ipv6)) + ipv4) #:labels (hetzner-configuration-labels config) #:location (hetzner-configuration-location config) #:server-type (hetzner-configuration-server-type config))) diff --git a/gnu/machine/hetzner/http.scm b/gnu/machine/hetzner/http.scm index 51b4bff984..33f501f53a 100644 --- a/gnu/machine/hetzner/http.scm +++ b/gnu/machine/hetzner/http.scm @@ -52,6 +52,7 @@ (define-module (gnu machine hetzner http) hetzner-api-actions hetzner-api-create-ssh-key hetzner-api-locations + hetzner-api-primary-ips hetzner-api-request-body hetzner-api-request-headers hetzner-api-request-method @@ -100,6 +101,13 @@ (define-module (gnu machine hetzner http) hetzner-location-name hetzner-location-network-zone hetzner-location? + hetzner-primary-ip + hetzner-primary-ip-created + hetzner-primary-ip-id + hetzner-primary-ip-ip + hetzner-primary-ip-labels + hetzner-primary-ip-name + hetzner-primary-ip-type hetzner-public-net hetzner-public-net-ipv4 hetzner-public-net-ipv6 @@ -144,6 +152,7 @@ (define-module (gnu machine hetzner http) make-hetzner-ipv6 make-hetzner-location make-hetzner-public-net + make-hetzner-primary-ip make-hetzner-resource make-hetzner-server make-hetzner-server-type @@ -296,6 +305,15 @@ (define-json-mapping (name hetzner-server-type-name) ; string (storage-type hetzner-server-type-storage-type "storage_type")) ; string +(define-json-mapping + make-hetzner-primary-ip hetzner-primary-ip? json->hetzner-primary-ip + (created hetzner-primary-ip-created "created" string->time) ; time + (id hetzner-primary-ip-id) ; integer + (ip hetzner-primary-ip-ip) ; string + (labels hetzner-primary-ip-labels) ; alist of string/string + (name hetzner-primary-ip-name) ; string + (type hetzner-primary-ip-type)) ; string + (define-json-mapping make-hetzner-ssh-key hetzner-ssh-key? json->hetzner-ssh-key (created hetzner-ssh-key-created "created" string->time) ; time @@ -581,12 +599,11 @@ (define* (hetzner-api-locations api . options) (define* (hetzner-api-server-create api name ssh-keys #:key - (enable-ipv4? #t) - (enable-ipv6? #t) + (ipv4 #f) + (ipv6 #f) (image %hetzner-default-server-image) (labels '()) (location %hetzner-default-server-location) - (public-net #f) (server-type %hetzner-default-server-type) (start-after-create? #f)) "Create a server with the Hetzner API." @@ -595,9 +612,11 @@ (define* (hetzner-api-server-create #:body `(("image" . ,image) ("labels" . ,labels) ("name" . ,name) - ("public_net" - . (("enable_ipv4" . ,enable-ipv4?) - ("enable_ipv6" . ,enable-ipv6?))) + ("public_net" . + (("enable_ipv4" . ,(and ipv4 #t)) + ("enable_ipv6" . ,(and ipv6 #t)) + ,@(if (integer? ipv4) `(("ipv4" . ,ipv4)) '()) + ,@(if (integer? ipv6) `(("ipv6" . ,ipv6)) '()))) ("location" . ,location) ("server_type" . ,server-type) ("ssh_keys" . ,(apply vector (map hetzner-ssh-key-id ssh-keys))) @@ -658,6 +677,11 @@ (define* (hetzner-api-ssh-keys api . options) (apply hetzner-api-list api "/ssh_keys" "ssh_keys" json->hetzner-ssh-key options)) +(define* (hetzner-api-primary-ips api . options) + "Get Primary IPs from the Hetzner API." + (apply hetzner-api-list api "/primary_ips" "primary_ips" + json->hetzner-primary-ip options)) + (define* (hetzner-api-server-types api . options) "Get server types from the Hetzner API." (apply hetzner-api-list api "/server_types" "server_types" diff --git a/tests/machine/hetzner/http.scm b/tests/machine/hetzner/http.scm index 618d9a4c94..6c6d848a57 100644 --- a/tests/machine/hetzner/http.scm +++ b/tests/machine/hetzner/http.scm @@ -239,6 +239,30 @@ (define server-x86-alist ("status" . "running") ("volumes" . #()))) +(define primary-ip + (make-hetzner-primary-ip + #(55 2 19 28 9 123 6 300 -1 0 #f) + 42 + "131.232.99.1" + '() + "static-ip" + "ipv4")) + +(define primary-ip-alist + `(("created" . "2023-10-28T19:02:55+00:00") + ("id" . 42) + ("labels") + ("name" . "static-ip") + ("blocked" . #f) + ("ip" . "131.232.99.1") + ("datacenter") + ("dns_ptr") + ("protection" . (("delete" . #f))) + ("type" . "ipv4") + ("auto_delete" . #t) + ("assignee_type" . "server") + ("assignee_id" . 17))) + (define ssh-key-root (make-hetzner-ssh-key #(55 2 19 28 9 123 6 300 -1 0 #f) @@ -512,6 +536,20 @@ (define-syntax-rule (with-cleanup-api (api-sym api-init) body ...) ("ssh_keys" . #(,ssh-key-root-alist))))))) (hetzner-api-ssh-keys (hetzner-api)))) +(test-equal "hetzner-api-primary-ips-unit" + (list primary-ip) + (mock ((gnu machine hetzner http) hetzner-api-request-send + (lambda* (request #:key expected) + (assert (equal? 'GET (hetzner-api-request-method request))) + (assert (equal? "https://api.hetzner.cloud/v1/primary_ips" + (hetzner-api-request-url request))) + (assert (unspecified? (hetzner-api-request-body request))) + (assert (equal? '(("page" . 1)) (hetzner-api-request-params request))) + (hetzner-api-response + (body `(("meta" . ,meta-page-alist) + ("primary_ips" . #(,primary-ip-alist))))))) + (hetzner-api-primary-ips (hetzner-api)))) + ;; Integration tests (test-skip %when-no-token)