From patchwork Fri Apr 18 15:08:08 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sergey Trofimov X-Patchwork-Id: 41773 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 661DA27BC49; Fri, 18 Apr 2025 16:51:09 +0100 (BST) X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on mira.cbaines.net X-Spam-Level: X-Spam-Status: No, score=-6.4 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 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 1BDE427BC4B for ; Fri, 18 Apr 2025 16:51:08 +0100 (BST) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1u5nLD-00041C-Ow; Fri, 18 Apr 2025 11:09:19 -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 1u5nL1-0003wN-69 for guix-patches@gnu.org; Fri, 18 Apr 2025 11:09:13 -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 1u5nL0-0004xm-Le; Fri, 18 Apr 2025 11:09:06 -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=fRE96sqXU9z0Y6d7YAa4MN/MXV3kB/LQo5nDv77V4lU=; b=IXC29MRA0RdKQU9+69GpQ9fpV3DwLfbA8Pdlnsu6Z6KdzNdEHmbKllBIOudYPZH40dOmQOxcBexXTcS9cedfMmHB2XFaV0wZ1ijUcJ6FP5uJAHz60Ev+M/x/NNicdmcLi/T6KakeIUchPJrwm5hoIk3pbmnBERPCoDZEZ2NANC99+cnc1aaNQ3SBcwlS1CT4i7iB0Hw5xHkOB1eUzQ0v+4lmMhJWfCgsneM5fV5/975gO9S9zBSwjkN1osKkC8SivlyBgvMOb6HbvR11oLtysZQGo73lKRWWhzTgTl5mYtHx65FKgGNW+/GJ1FE3Mhs9i2Yor+Ki0ItHi8X7Oi/T7A==; Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1u5nKw-0000TN-U2; Fri, 18 Apr 2025 11:09:02 -0400 X-Loop: help-debbugs@gnu.org Subject: [bug#77019] [PATCH] 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, roman.scherer@burningswell.com, maxim.cournoyer@gmail.com, guix-patches@gnu.org Resent-Date: Fri, 18 Apr 2025 15:09: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?= , Roman Scherer , Maxim Cournoyer X-Debbugs-Original-Xcc: Sergey Trofimov , Ludovic =?utf-8?q?Court=C3=A8s?= , Roman Scherer , Maxim Cournoyer Received: via spool by 77019-submit@debbugs.gnu.org id=B77019.17449889361742 (code B ref 77019); Fri, 18 Apr 2025 15:09:02 +0000 Received: (at 77019) by debbugs.gnu.org; 18 Apr 2025 15:08:56 +0000 Received: from localhost ([127.0.0.1]:52464 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1u5nKk-0000RE-O0 for submit@debbugs.gnu.org; Fri, 18 Apr 2025 11:08:56 -0400 Received: from mail-ej1-x629.google.com ([2a00:1450:4864:20::629]:54321) by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.84_2) (envelope-from ) id 1u5nKa-0000NS-Cp for 77019@debbugs.gnu.org; Fri, 18 Apr 2025 11:08:48 -0400 Received: by mail-ej1-x629.google.com with SMTP id a640c23a62f3a-abbb12bea54so299364766b.0 for <77019@debbugs.gnu.org>; Fri, 18 Apr 2025 08:08:40 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=sarg.org.ru; s=google; t=1744988899; x=1745593699; 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=fRE96sqXU9z0Y6d7YAa4MN/MXV3kB/LQo5nDv77V4lU=; b=BtRGpvyL4arDWDqRJee/n7w1SkeyxsBnKV9QjWjlMjRe7xJPO+jL2364HCmiGw5zyJ 5bbhkESdUjxcCv3bFZ8N46tK7Go8Sctc1uMrs4u53OS9HaPVE+KHvWKhdepyn7ZK2U3+ WpXcmwZrBNcPGEPZNp9XDAKH6OyLpxO63LmgLHoFubmE28YV5BgwlKyZ7lzVMcVlDDIy Zc4fV0HvdsWnmwDRm6xKa/unHmDyDiflqYjtGo41to9WIqaFEZ+kNfk0kAZ8FX9EP0zz cbwv8WGAi1uy+9j7p8MEf2FtS1IDJYutleMZOXbaR50rpf6JNrjQaT6VgF+H91AoYLk2 Sd4Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1744988899; x=1745593699; 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=fRE96sqXU9z0Y6d7YAa4MN/MXV3kB/LQo5nDv77V4lU=; b=m4TYvvcziVZfdptQXPEroYMD92eNEdCtswJdLEGH33jzHgg74nPe0Bqw0BRW5NfN/k LksH9Kl35lsCWBnB/5qWFBo033HqcGiw+UACvfZoFe2LCJQVw4pkQIRbNHwG9Po9eV/k EdnHv9Dh198NO9UznlAGRQ0MRQAzEdYRUThjXCfaQzOwT9BUZ394jkP9I/BuzbUl/EGA SpJzmqUbmsDV38JTTELJdfsLB45xK+xowAd7Z5wKKFljJeKsFjpK8eHNkpQYyLxt9YUj uf1m2OERGN3yrPs1xRqB2+IKQHGiwK9uuR3aqpanU3E2vrEiA5x/GzB0vrUlt7OEdkTO KYmQ== X-Gm-Message-State: AOJu0YxarR0LblYUMn+jOOKz6hOo7etCrxBqCtPa25uMvbTNbkc/oKL3 BZ63t61ZpPYQBqz0fjf75oHoqSqA9w8rQSscGFAq0ZjFU6hzpiBlRxVDXg0mOPGsreNSbYl3nqc Zvok= X-Gm-Gg: ASbGnct1/+ShvWsK/StXqAgY7Y2dcySIMDYU3xav+F6bOTnlcrQoy9KXW5uJ1+Gje/t POJ//ItmrmLzfzgYdba2a/hgBG2g+E20xXxzYgy9d4+UMD0Hm9r6PR8xKLoBUchEZTu5X5Z3eJw BU7/cJvjEOnjogLCaUhMeC61VItPTFToI8eQjs3l7MxdRt1suZcb9HC3KFdOlIe7thIDAw0YEU/ /SZuzbeZRHdYrACisTZ6TcG4A6eduVMqJyRczcRSEkMizS/0grGvoltfyAL2Noag1Urw07tTq4V i+EZT3+/t/B4mjXCfPgJuuUxFolQ9g5Q4qRmng== X-Google-Smtp-Source: AGHT+IGfn11IInMUJzTQSD1YgTCdLcO73Yttjz4tneIsVHf7FzrGK6X2SgsHKiHl5wC1puUSderQbA== X-Received: by 2002:a17:907:3f94:b0:ac8:1798:a796 with SMTP id a640c23a62f3a-acb74dd5dd2mr299484966b.54.1744988898717; Fri, 18 Apr 2025 08:08:18 -0700 (PDT) Received: from localhost ([2a02:2454:a095:5600:a64e:31ff:fe38:fd6c]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-acb6eefcfa2sm130906866b.92.2025.04.18.08.08.17 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 18 Apr 2025 08:08:18 -0700 (PDT) Date: Fri, 18 Apr 2025 17:08:08 +0200 Message-ID: <0739a3a367114259a24b13f16d2673eb6d63b1f1.1744988608.git.sarg@sarg.org.ru> X-Mailer: git-send-email 2.49.0 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 (Invoking guix deploy): Document it. Change-Id: I44509cc98e041762dc483e876566e79bde85b26a --- doc/guix.texi | 6 ++++++ gnu/machine/hetzner.scm | 25 ++++++++++++++++++++++ gnu/machine/hetzner/http.scm | 37 +++++++++++++++++++++++++++------ tests/machine/hetzner/http.scm | 38 ++++++++++++++++++++++++++++++++++ 4 files changed, 100 insertions(+), 6 deletions(-) base-commit: a4a7ff0319c622cd08aa7461cc88cc6608fe62cb -- 2.49.0 diff --git a/doc/guix.texi b/doc/guix.texi index 070528667fa..ddd23c4ced0 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -46436,6 +46436,12 @@ Invoking guix deploy provisioning phase. If false, the server will be kept in order to debug any issues. +@item @code{ipv4} (default: @code{#t}) +@itemx @code{ipv6} (default: @code{#t}) +When false, no public IP address is 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 e8484e4d51e..ddac58dda0a 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 ; boolean | string + (default #t)) + (ipv6 hetzner-configuration-ipv6 ; boolean | string + (default #t)) (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)) + ipv6) #: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 51b4bff984f..bd12e8ee5eb 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,16 @@ (define-json-mapping (name hetzner-server-type-name) ; string (storage-type hetzner-server-type-storage-type "storage_type")) ; string +;; Reserved IP address. https://docs.hetzner.cloud/#primary-ips +(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 +600,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 +613,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 +678,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 618d9a4c94e..6c6d848a578 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)