mirror of
https://github.com/apricote/hcloud-upload-image.git
synced 2026-01-13 13:21:01 +00:00
Compare commits
101 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7a29b2bee | ||
| b440f8d7f6 | |||
| 8f7f0643b5 | |||
| 99691d54ab | |||
| d7ff673261 | |||
|
|
740dbb5c68 | ||
|
|
919461ddd0 | ||
|
|
0280f677e0 | ||
|
|
c4c86073ae | ||
|
|
f59aa2bb23 | ||
|
|
f78a5aa191 | ||
|
|
a73c1e4758 | ||
|
|
bafcd0c57b | ||
|
|
6a41192364 | ||
|
|
e9c4d29792 | ||
|
|
fcbc14aab6 | ||
| a9b16cf07c | |||
| d87db70674 | |||
| 5bbd840ea7 | |||
|
|
92e0397f7c | ||
| 5eba2d52fe | |||
| a20561944d | |||
|
|
43d0405d2b | ||
|
|
27916d2c94 | ||
|
|
04bfe9bcfa | ||
|
|
7537226258 | ||
|
|
37ebbce517 | ||
|
|
921d688fd4 | ||
|
|
fbd639ba59 | ||
|
|
b877383556 | ||
|
|
b5abcc149a | ||
|
|
892b806470 | ||
|
|
b64a100eff | ||
|
|
4167a5c27b | ||
|
|
3c0dd7bf51 | ||
|
|
cbcfd7d007 | ||
|
|
b2c2c8e979 | ||
|
|
bcf19efc64 | ||
|
|
8a03eb9e9b | ||
| b9af8855d5 | |||
| 6f949a2ba7 | |||
| 03f08da8a3 | |||
| fdfb284533 | |||
| 420dcf94c9 | |||
| 28bf5380f3 | |||
| 4ff1883fd0 | |||
| 7f10f7b253 | |||
| e490b9a7f3 | |||
|
|
b093e1eda8 | ||
|
|
f12b6b076b | ||
|
|
7cffed4ba7 | ||
|
|
19f1e085e9 | ||
| ba421d7dd8 | |||
| f481f20a33 | |||
| 9611d091a8 | |||
|
|
80f8291cac | ||
| 91df729f1c | |||
|
|
02e80b277f | ||
| d144b85e3d | |||
| c175ca0352 | |||
|
|
2df6fdd850 | ||
| 0cff95bab8 | |||
| e253df6fe9 | |||
|
|
4829843c2e | ||
|
|
cef79b943c | ||
|
|
df19de506c | ||
|
|
099ae2d061 | ||
| ac3e9dd7ec | |||
|
|
b556533208 | ||
|
|
91bcf7067f | ||
| f3fcb623fc | |||
| 021787a9c3 | |||
| b328867f6b | |||
|
|
d5ddab06f2 | ||
|
|
5032760cea | ||
|
|
83c9e043f9 | ||
|
|
020285e508 | ||
|
|
f8b953466b | ||
|
|
6cc128ffe9 | ||
|
|
baf35bb218 | ||
| 66dc5f70b6 | |||
|
|
489ece1798 | ||
| 4e24d83c7a | |||
|
|
b181eebd38 | ||
|
|
6d8d2a02df | ||
| 99d5396435 | |||
| a07856317d | |||
|
|
32a82ffc6f | ||
| ebe08b345c | |||
| d921510af3 | |||
| 42eeb00a07 | |||
|
|
f244acdfaf | ||
| 256989f4a3 | |||
|
|
8347e4ef16 | ||
|
|
63f17e9760 | ||
|
|
1f3c2afee9 | ||
|
|
13b1e26093 | ||
| 043c73cf7d | |||
| caaa5435bb | |||
| 393a292596 | |||
| bb2ca48200 |
43 changed files with 1133 additions and 303 deletions
16
.github/actions/setup-mdbook/action.yaml
vendored
Normal file
16
.github/actions/setup-mdbook/action.yaml
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
name: "Setup mdbook"
|
||||
inputs:
|
||||
version:
|
||||
description: "mdbook version"
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Setup mdbook
|
||||
shell: bash
|
||||
env:
|
||||
url: https://github.com/rust-lang/mdbook/releases/download/${{ inputs.version }}/mdbook-${{ inputs.version }}-x86_64-unknown-linux-gnu.tar.gz
|
||||
run: |
|
||||
mkdir mdbook
|
||||
curl -sSL "$url" | tar -xz --directory=./mdbook
|
||||
echo `pwd`/mdbook >> $GITHUB_PATH
|
||||
10
.github/release-please-config.json
vendored
10
.github/release-please-config.json
vendored
|
|
@ -2,11 +2,19 @@
|
|||
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
|
||||
"include-component-in-tag": false,
|
||||
"include-v-in-tag": true,
|
||||
"release-type": "go",
|
||||
"separate-pull-requests": true,
|
||||
"packages": {
|
||||
".": {
|
||||
"release-type": "go",
|
||||
"component": "cli",
|
||||
"package-name": "hcloud-upload-image",
|
||||
"extra-files": ["internal/version/version.go"]
|
||||
},
|
||||
"hcloudimages": {
|
||||
"component": "hcloudimages",
|
||||
"package-name": "hcloudimages",
|
||||
"include-component-in-tag": true,
|
||||
"tag-separator": "/"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
2
.github/release-please-manifest.json
vendored
2
.github/release-please-manifest.json
vendored
|
|
@ -1 +1 @@
|
|||
{".":"0.2.0"}
|
||||
{".":"1.3.0","hcloudimages":"1.3.0"}
|
||||
|
|
|
|||
42
.github/workflows/ci.yaml
vendored
42
.github/workflows/ci.yaml
vendored
|
|
@ -10,50 +10,48 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
- name: Run golangci-lint (CLI)
|
||||
uses: golangci/golangci-lint-action@v6
|
||||
uses: golangci/golangci-lint-action@v9
|
||||
with:
|
||||
version: v1.58.1 # renovate: datasource=github-releases depName=golangci/golangci-lint
|
||||
version: v2.7.2 # renovate: datasource=github-releases depName=golangci/golangci-lint
|
||||
args: --timeout 5m
|
||||
|
||||
- name: Run golangci-lint (Lib)
|
||||
uses: golangci/golangci-lint-action@v6
|
||||
uses: golangci/golangci-lint-action@v9
|
||||
with:
|
||||
version: v1.58.1 # renovate: datasource=github-releases depName=golangci/golangci-lint
|
||||
version: v2.7.2 # renovate: datasource=github-releases depName=golangci/golangci-lint
|
||||
args: --timeout 5m
|
||||
working-directory: hcloudimages
|
||||
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
- name: Run tests
|
||||
run: go test -v -race -coverpkg=./...,./hcloudimages/... ./... ./hcloudimages/...
|
||||
|
||||
|
||||
go-mod-tidy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
|
|
@ -65,3 +63,23 @@ jobs:
|
|||
|
||||
- if: failure()
|
||||
run: echo "::error::Check failed, please run 'go mod tidy' and commit the changes."
|
||||
|
||||
cli-help-pages:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
- name: Generate CLI help pages
|
||||
run: go run ./scripts/cli-help-pages.go
|
||||
|
||||
- name: Check uncommitted changes
|
||||
run: git diff --exit-code
|
||||
|
||||
- if: failure()
|
||||
run: echo "::error::Check failed, please run 'go run ./scripts/cli-help-pages.go' and commit the changes."
|
||||
|
|
|
|||
39
.github/workflows/docs.yaml
vendored
Normal file
39
.github/workflows/docs.yaml
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
name: docs
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write # To push a branch
|
||||
pages: write # To push to a GitHub Pages site
|
||||
id-token: write # To update the deployment status
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
|
||||
with:
|
||||
lfs: "true"
|
||||
|
||||
- uses: ./.github/actions/setup-mdbook
|
||||
with:
|
||||
version: v0.5.2 # renovate: datasource=github-releases depName=rust-lang/mdbook
|
||||
|
||||
- name: Build Book
|
||||
working-directory: docs
|
||||
run: mdbook build
|
||||
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4
|
||||
with:
|
||||
# Upload entire repository
|
||||
path: "docs/book"
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4
|
||||
2
.github/workflows/release-please.yaml
vendored
2
.github/workflows/release-please.yaml
vendored
|
|
@ -11,7 +11,7 @@ jobs:
|
|||
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: google-github-actions/release-please-action@v4
|
||||
- uses: googleapis/release-please-action@v4
|
||||
with:
|
||||
token: ${{ secrets.RELEASE_GH_TOKEN }}
|
||||
config-file: .github/release-please-config.json
|
||||
|
|
|
|||
19
.github/workflows/release.yaml
vendored
19
.github/workflows/release.yaml
vendored
|
|
@ -7,20 +7,31 @@ on:
|
|||
|
||||
jobs:
|
||||
release:
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@6862ffc5ab2cdb4405cf318a62a6f4c066e2298b
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v5
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
version: latest
|
||||
version: "~> v2"
|
||||
args: release --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.RELEASE_GH_TOKEN }}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
|
||||
|
||||
version: 1
|
||||
version: 2
|
||||
|
||||
before:
|
||||
hooks:
|
||||
- ./scripts/completions.sh
|
||||
|
||||
builds:
|
||||
- env:
|
||||
- id: default
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- linux
|
||||
|
|
@ -21,7 +22,7 @@ builds:
|
|||
- -X {{ .ModulePath }}/internal/version.versionPrerelease=
|
||||
|
||||
archives:
|
||||
- format: tar.gz
|
||||
- formats: [ "tar.gz" ]
|
||||
# this name template makes the OS and Arch compatible with the results of `uname`.
|
||||
name_template: >-
|
||||
{{ .ProjectName }}_
|
||||
|
|
@ -33,7 +34,7 @@ archives:
|
|||
# use zip for windows archives
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
formats: [ "zip" ]
|
||||
|
||||
files:
|
||||
- README.md
|
||||
|
|
@ -104,9 +105,23 @@ aurs:
|
|||
install -Dm644 "./completions/hcloud-upload-image.zsh" "${pkgdir}/usr/share/zsh/site-functions/_hcloud-upload-image"
|
||||
install -Dm644 "./completions/hcloud-upload-image.fish" "${pkgdir}/usr/share/fish/vendor_completions.d/hcloud-upload-image.fish"
|
||||
|
||||
kos:
|
||||
- id: container-images
|
||||
build: default
|
||||
repositories:
|
||||
- ghcr.io/apricote
|
||||
platforms:
|
||||
- linux/amd64
|
||||
- linux/arm64
|
||||
base_import_paths: true
|
||||
labels:
|
||||
org.opencontainers.image.source: https://github.com/apricote/hcloud-upload-image
|
||||
tags:
|
||||
- latest
|
||||
- "{{.Tag}}"
|
||||
|
||||
snapshot:
|
||||
name_template: "{{ .Version }}-dev+{{ .ShortCommit }}"
|
||||
version_template: "{{ .Version }}-dev+{{ .ShortCommit }}"
|
||||
|
||||
changelog:
|
||||
# Generated by release-please
|
||||
|
|
|
|||
72
CHANGELOG.md
72
CHANGELOG.md
|
|
@ -1,5 +1,77 @@
|
|||
# Changelog
|
||||
|
||||
## [1.3.0](https://github.com/apricote/hcloud-upload-image/compare/v1.2.0...v1.3.0) (2025-12-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add --location flag to specify datacenter region ([#141](https://github.com/apricote/hcloud-upload-image/issues/141)) ([fcbc14a](https://github.com/apricote/hcloud-upload-image/commit/fcbc14aab6d495d2c67d653f9ea1ff56a39a8c2f)), closes [#142](https://github.com/apricote/hcloud-upload-image/issues/142)
|
||||
|
||||
## [1.2.0](https://github.com/apricote/hcloud-upload-image/compare/v1.1.0...v1.2.0) (2025-11-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* change minimum required Go version to 1.24 ([#130](https://github.com/apricote/hcloud-upload-image/issues/130)) ([5eba2d5](https://github.com/apricote/hcloud-upload-image/commit/5eba2d52fe3aafb4fd0d93403548f4c32bc2b5ac))
|
||||
* support zstd compression ([#125](https://github.com/apricote/hcloud-upload-image/issues/125)) ([37ebbce](https://github.com/apricote/hcloud-upload-image/commit/37ebbce5179997ac216af274055fc34c777b01e6)), closes [#122](https://github.com/apricote/hcloud-upload-image/issues/122)
|
||||
* update default x86 server type to cx23 ([#129](https://github.com/apricote/hcloud-upload-image/issues/129)) ([a205619](https://github.com/apricote/hcloud-upload-image/commit/a20561944d0ba9485a6e10e99df15c56a688541d))
|
||||
|
||||
## [1.1.0](https://github.com/apricote/hcloud-upload-image/compare/v1.0.1...v1.1.0) (2025-05-10)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* smaller snapshots by zeroing disk first ([#101](https://github.com/apricote/hcloud-upload-image/issues/101)) ([fdfb284](https://github.com/apricote/hcloud-upload-image/commit/fdfb284533d3154806b0936c08015fd5cc64b0fb)), closes [#96](https://github.com/apricote/hcloud-upload-image/issues/96)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* upload from local image generates broken command ([#98](https://github.com/apricote/hcloud-upload-image/issues/98)) ([420dcf9](https://github.com/apricote/hcloud-upload-image/commit/420dcf94c965ee470602db6c9c23c777fda91222)), closes [#97](https://github.com/apricote/hcloud-upload-image/issues/97)
|
||||
|
||||
## [1.0.1](https://github.com/apricote/hcloud-upload-image/compare/v1.0.0...v1.0.1) (2025-05-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* timeout while waiting for SSH to become available ([#92](https://github.com/apricote/hcloud-upload-image/issues/92)) ([e490b9a](https://github.com/apricote/hcloud-upload-image/commit/e490b9a7f394e268fa1946ca51aa998c78c3d46a))
|
||||
|
||||
## [1.0.0](https://github.com/apricote/hcloud-upload-image/compare/v0.3.1...v1.0.0) (2025-05-04)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **deps:** require Go 1.23 ([#70](https://github.com/apricote/hcloud-upload-image/issues/70)) ([f3fcb62](https://github.com/apricote/hcloud-upload-image/commit/f3fcb623fc00095ab3806fa41dbcb7083c13c5df))
|
||||
* docs website ([#80](https://github.com/apricote/hcloud-upload-image/issues/80)) ([d144b85](https://github.com/apricote/hcloud-upload-image/commit/d144b85e3dfd933e8fbb09a0e6f5acacb4d05bea))
|
||||
* publish container image ([#82](https://github.com/apricote/hcloud-upload-image/issues/82)) ([91df729](https://github.com/apricote/hcloud-upload-image/commit/91df729f1cfd636355fc8338f47aefa4ab8b3b84))
|
||||
* upload qcow2 images ([#69](https://github.com/apricote/hcloud-upload-image/issues/69)) ([ac3e9dd](https://github.com/apricote/hcloud-upload-image/commit/ac3e9dd7ecd86d1538b6401c3073c7c078c40847))
|
||||
|
||||
## [0.3.1](https://github.com/apricote/hcloud-upload-image/compare/v0.3.0...v0.3.1) (2024-12-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **cli:** local install fails because of go.mod replace ([#47](https://github.com/apricote/hcloud-upload-image/issues/47)) ([66dc5f7](https://github.com/apricote/hcloud-upload-image/commit/66dc5f70b604ed3ee964576d74f94bdcea710c95))
|
||||
|
||||
## [0.3.0](https://github.com/apricote/hcloud-upload-image/compare/v0.2.1...v0.3.0) (2024-06-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* set server type explicitly ([#36](https://github.com/apricote/hcloud-upload-image/issues/36)) ([42eeb00](https://github.com/apricote/hcloud-upload-image/commit/42eeb00a0784e13a00a52cf15a8659b497d78d72)), closes [#30](https://github.com/apricote/hcloud-upload-image/issues/30)
|
||||
* update default x86 server type to cx22 ([#38](https://github.com/apricote/hcloud-upload-image/issues/38)) ([ebe08b3](https://github.com/apricote/hcloud-upload-image/commit/ebe08b345c8f31df73087b091fa39f5fdc195156))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* error early when the image write fails ([#34](https://github.com/apricote/hcloud-upload-image/issues/34)) ([256989f](https://github.com/apricote/hcloud-upload-image/commit/256989f4a37e7b124c0684aab0f34cf5e09559be)), closes [#33](https://github.com/apricote/hcloud-upload-image/issues/33)
|
||||
|
||||
## [0.2.1](https://github.com/apricote/hcloud-upload-image/compare/v0.2.0...v0.2.1) (2024-05-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **cli:** completion requires HCLOUD_TOKEN ([#19](https://github.com/apricote/hcloud-upload-image/issues/19)) ([bb2ca48](https://github.com/apricote/hcloud-upload-image/commit/bb2ca482000f5c780545edb9a03aa9f6bf93d906))
|
||||
|
||||
## [0.2.0](https://github.com/apricote/hcloud-upload-image/compare/v0.1.1...v0.2.0) (2024-05-09)
|
||||
|
||||
|
||||
|
|
|
|||
63
README.md
63
README.md
|
|
@ -1,29 +1,34 @@
|
|||
# `hcloud-upload-image`
|
||||
# hcloud-upload-image
|
||||
|
||||
<p align="center">
|
||||
Quickly upload any raw disk images into your <a href="https://hetzner.com/cloud" target="_blank">Hetzner Cloud</a> projects!
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://apricote.github.io/hcloud-upload-image" target="_blank"><img src="https://img.shields.io/badge/Documentation-brightgreen?style=flat-square" alt="Badge: Documentation"/></a>
|
||||
<a href="https://github.com/apricote/hcloud-upload-image/releases" target="_blank"><img src="https://img.shields.io/github/v/release/apricote/hcloud-upload-image?sort=semver&display_name=release&style=flat-square&color=green" alt="Badge: Stable Release"/></a>
|
||||
<img src="https://img.shields.io/badge/License-MIT-green?style=flat-square" alt="Badge: License MIT"/>
|
||||
</p>
|
||||
|
||||
Quickly upload any raw disk images into your [Hetzner Cloud](https://hetzner.com/cloud) projects!
|
||||
|
||||
## About
|
||||
|
||||
The [Hetzner Cloud API](https://docs.hetzner.cloud/) does not support uploading disk images directly, and it only
|
||||
provides a limited set of default images. The only option for custom disk images that users have is by taking a
|
||||
"snapshot" of an existing servers root disk. These can then be used to create new servers.
|
||||
The [Hetzner Cloud API](https://docs.hetzner.cloud/) does not support uploading disk images directly and only provides a limited set of default images. The only option for custom disk images is to take a snapshot of an existing server’s root disk. These snapshots can then be used to create new servers.
|
||||
|
||||
To create a completely custom disk image, users have to follow these steps:
|
||||
To create a completely custom disk image, users need to follow these steps:
|
||||
|
||||
1. Create server with the correct server type
|
||||
2. Enable rescue system for the server
|
||||
1. Create a server with the correct server type
|
||||
2. Enable the rescue system for the server
|
||||
3. Boot the server
|
||||
4. Download the disk image from within the rescue system
|
||||
5. Write disk image to servers root disk
|
||||
5. Write the disk image to the server’s root disk
|
||||
6. Shut down the server
|
||||
7. Take a snapshot of the servers root disk
|
||||
7. Take a snapshot of the server’s root disk
|
||||
8. Delete the server
|
||||
|
||||
This is an annoyingly long process. Many users have automated this with [Packer](https://www.packer.io/) &
|
||||
[`packer-plugin-hcloud`](https://github.com/hetznercloud/packer-plugin-hcloud/) before, but Packer offers a lot of
|
||||
additional complexity to wrap your head around.
|
||||
This is a frustratingly long process. Many users have automated it with [Packer](https://www.packer.io/) and [`packer-plugin-hcloud`](https://github.com/hetznercloud/packer-plugin-hcloud/), but Packer introduces additional complexity that can be difficult to manage.
|
||||
|
||||
This repository provides a simple CLI tool & Go library to do the above.
|
||||
This repository provides a simple CLI tool and Go library to streamline the process.
|
||||
|
||||
## Getting Started
|
||||
|
||||
|
|
@ -31,14 +36,34 @@ This repository provides a simple CLI tool & Go library to do the above.
|
|||
|
||||
#### Binary
|
||||
|
||||
> TODO
|
||||
We provide pre-built `deb`, `rpm` and `apk` packages. Alternatively we also provide the binaries directly.
|
||||
|
||||
Check out the [GitHub release artifacts](https://github.com/apricote/hcloud-upload-image/releases/latest) for all of these files and archives.
|
||||
|
||||
#### Arch Linux
|
||||
|
||||
You can get [`hcloud-upload-image-bin`](https://aur.archlinux.org/packages/hcloud-upload-image-bin) from the AUR.
|
||||
|
||||
Use your preferred wrapper to install:
|
||||
|
||||
```shell
|
||||
yay -S hcloud-upload-image-bin
|
||||
```
|
||||
|
||||
#### `go install`
|
||||
|
||||
If you already have a recent Go toolchain installed, you can build & install the binary from source:
|
||||
|
||||
```shell
|
||||
go install github.com/apricote/hcloud-upload-image
|
||||
go install github.com/apricote/hcloud-upload-image@latest
|
||||
```
|
||||
|
||||
#### Docker
|
||||
|
||||
There is a docker image published at `ghcr.io/apricote/hcloud-upload-image`.
|
||||
|
||||
```shell
|
||||
docker run --rm -e HCLOUD_TOKEN="<your token>" ghcr.io/apricote/hcloud-upload-image:latest <command>
|
||||
```
|
||||
|
||||
#### Usage
|
||||
|
|
@ -48,10 +73,11 @@ export HCLOUD_TOKEN="<your token>"
|
|||
hcloud-upload-image upload \
|
||||
--image-url "https://example.com/disk-image-x86.raw.bz2" \
|
||||
--architecture x86 \
|
||||
--compression bz2
|
||||
--compression bz2 \
|
||||
--location nbg1 # Optional: defaults to fsn1
|
||||
```
|
||||
|
||||
To learn more, you can use the embedded help output:
|
||||
To learn more, you can use the embedded help output or check out the [CLI help pages in this repository](docs/reference/cli/hcloud-upload-image.md).:
|
||||
|
||||
```shell
|
||||
hcloud-upload-image --help
|
||||
|
|
@ -98,6 +124,7 @@ func main() {
|
|||
ImageURL: imageURL,
|
||||
ImageCompression: hcloudimages.CompressionBZ2,
|
||||
Architecture: hcloud.ArchitectureX86,
|
||||
Location: &hcloud.Location{Name: "nbg1"}, // Optional: defaults to fsn1
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
|
|
|||
|
|
@ -18,12 +18,17 @@ that match the label "apricote.de/created-by=hcloud-upload-image".
|
|||
|
||||
If you want to see a preview of what would be removed, you can use the official hcloud CLI and run:
|
||||
|
||||
$ hcloud server list -l apricote.de/created-by=hcloud-upload-image
|
||||
$ hcloud ssh-key list -l apricote.de/created-by=hcloud-upload-image
|
||||
$ hcloud server list -l apricote.de/created-by=hcloud-upload-image
|
||||
$ hcloud ssh-key list -l apricote.de/created-by=hcloud-upload-image
|
||||
|
||||
This command does not handle any parallel executions of hcloud-upload-image
|
||||
and will remove in-use resources if called at the same time.`,
|
||||
DisableAutoGenTag: true,
|
||||
|
||||
GroupID: "primary",
|
||||
|
||||
PreRun: initClient,
|
||||
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
logger := contextlogger.From(ctx)
|
||||
|
|
@ -40,5 +45,5 @@ and will remove in-use resources if called at the same time.`,
|
|||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(cleanupCmd)
|
||||
RootCmd.AddCommand(cleanupCmd)
|
||||
}
|
||||
|
|
|
|||
38
cmd/root.go
38
cmd/root.go
|
|
@ -1,7 +1,6 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"os"
|
||||
"time"
|
||||
|
|
@ -10,7 +9,6 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/apricote/hcloud-upload-image/hcloudimages"
|
||||
"github.com/apricote/hcloud-upload-image/hcloudimages/backoff"
|
||||
"github.com/apricote/hcloud-upload-image/hcloudimages/contextlogger"
|
||||
"github.com/apricote/hcloud-upload-image/internal/ui"
|
||||
"github.com/apricote/hcloud-upload-image/internal/version"
|
||||
|
|
@ -29,11 +27,13 @@ var (
|
|||
// The pre-authenticated client. Set in the root command PersistentPreRun
|
||||
var client *hcloudimages.Client
|
||||
|
||||
// rootCmd represents the base command when called without any subcommands
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "hcloud-upload-image",
|
||||
Long: `Manage custom OS images on Hetzner Cloud.`,
|
||||
SilenceUsage: true,
|
||||
// RootCmd represents the base command when called without any subcommands
|
||||
var RootCmd = &cobra.Command{
|
||||
Use: "hcloud-upload-image",
|
||||
Short: `Manage custom OS images on Hetzner Cloud.`,
|
||||
Long: `Manage custom OS images on Hetzner Cloud.`,
|
||||
SilenceUsage: true,
|
||||
DisableAutoGenTag: true,
|
||||
|
||||
Version: version.Version,
|
||||
|
||||
|
|
@ -46,8 +46,6 @@ var rootCmd = &cobra.Command{
|
|||
logger := slog.Default()
|
||||
ctx = contextlogger.New(ctx, logger)
|
||||
cmd.SetContext(ctx)
|
||||
|
||||
client = newClient(ctx)
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -71,7 +69,15 @@ func initLogger() *slog.Logger {
|
|||
|
||||
}
|
||||
|
||||
func newClient(ctx context.Context) *hcloudimages.Client {
|
||||
func initClient(cmd *cobra.Command, _ []string) {
|
||||
if client != nil {
|
||||
// Only init if not set.
|
||||
// Theoretically this is not safe against data races and should use [sync.Once], but :shrug:
|
||||
return
|
||||
}
|
||||
|
||||
ctx := cmd.Context()
|
||||
|
||||
logger := contextlogger.From(ctx)
|
||||
// Build hcloud-go client
|
||||
if os.Getenv("HCLOUD_TOKEN") == "" {
|
||||
|
|
@ -82,29 +88,29 @@ func newClient(ctx context.Context) *hcloudimages.Client {
|
|||
opts := []hcloud.ClientOption{
|
||||
hcloud.WithToken(os.Getenv("HCLOUD_TOKEN")),
|
||||
hcloud.WithApplication("hcloud-upload-image", version.Version),
|
||||
hcloud.WithPollBackoffFunc(backoff.ExponentialBackoffWithLimit(2, 1*time.Second, 30*time.Second)),
|
||||
hcloud.WithPollOpts(hcloud.PollOpts{BackoffFunc: hcloud.ExponentialBackoffWithOpts(hcloud.ExponentialBackoffOpts{Multiplier: 2, Base: 1 * time.Second, Cap: 30 * time.Second})}),
|
||||
}
|
||||
|
||||
if os.Getenv("HCLOUD_DEBUG") != "" || verbose >= 2 {
|
||||
opts = append(opts, hcloud.WithDebugWriter(os.Stderr))
|
||||
}
|
||||
|
||||
return hcloudimages.NewClient(hcloud.NewClient(opts...))
|
||||
client = hcloudimages.NewClient(hcloud.NewClient(opts...))
|
||||
}
|
||||
|
||||
func Execute() {
|
||||
err := rootCmd.Execute()
|
||||
err := RootCmd.Execute()
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.SetErrPrefix("\033[1;31mError:")
|
||||
RootCmd.SetErrPrefix("\033[1;31mError:")
|
||||
|
||||
rootCmd.PersistentFlags().CountVarP(&verbose, flagVerbose, "v", "verbose debug output, can be specified up to 2 times")
|
||||
RootCmd.PersistentFlags().CountVarP(&verbose, flagVerbose, "v", "verbose debug output, can be specified up to 2 times")
|
||||
|
||||
rootCmd.AddGroup(&cobra.Group{
|
||||
RootCmd.AddGroup(&cobra.Group{
|
||||
ID: "primary",
|
||||
Title: "Primary Commands:",
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
|
|
@ -16,23 +18,31 @@ const (
|
|||
uploadFlagImageURL = "image-url"
|
||||
uploadFlagImagePath = "image-path"
|
||||
uploadFlagCompression = "compression"
|
||||
uploadFlagFormat = "format"
|
||||
uploadFlagArchitecture = "architecture"
|
||||
uploadFlagServerType = "server-type"
|
||||
uploadFlagDescription = "description"
|
||||
uploadFlagLabels = "labels"
|
||||
uploadFlagLocation = "location"
|
||||
)
|
||||
|
||||
//go:embed upload.md
|
||||
var longDescription string
|
||||
|
||||
// uploadCmd represents the upload command
|
||||
var uploadCmd = &cobra.Command{
|
||||
Use: "upload (--image-path=<local-path> | --image-url=<url>) --architecture=<x86|arm>",
|
||||
Short: "Upload the specified disk image into your Hetzner Cloud project.",
|
||||
Long: `This command implements a fake "upload", by going through a real server and snapshots.
|
||||
This does cost a bit of money for the server.`,
|
||||
Long: longDescription,
|
||||
Example: ` hcloud-upload-image upload --image-path /home/you/images/custom-linux-image-x86.bz2 --architecture x86 --compression bz2 --description "My super duper custom linux"
|
||||
hcloud-upload-image upload --image-url https://examples.com/image-arm.raw --architecture arm --labels foo=bar,version=latest
|
||||
`,
|
||||
hcloud-upload-image upload --image-url https://examples.com/image-x86.qcow2 --architecture x86 --format qcow2`,
|
||||
DisableAutoGenTag: true,
|
||||
|
||||
GroupID: "primary",
|
||||
|
||||
PreRun: initClient,
|
||||
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
logger := contextlogger.From(ctx)
|
||||
|
|
@ -40,13 +50,16 @@ This does cost a bit of money for the server.`,
|
|||
imageURLString, _ := cmd.Flags().GetString(uploadFlagImageURL)
|
||||
imagePathString, _ := cmd.Flags().GetString(uploadFlagImagePath)
|
||||
imageCompression, _ := cmd.Flags().GetString(uploadFlagCompression)
|
||||
imageFormat, _ := cmd.Flags().GetString(uploadFlagFormat)
|
||||
architecture, _ := cmd.Flags().GetString(uploadFlagArchitecture)
|
||||
serverType, _ := cmd.Flags().GetString(uploadFlagServerType)
|
||||
description, _ := cmd.Flags().GetString(uploadFlagDescription)
|
||||
labels, _ := cmd.Flags().GetStringToString(uploadFlagLabels)
|
||||
location, _ := cmd.Flags().GetString(uploadFlagLocation)
|
||||
|
||||
options := hcloudimages.UploadOptions{
|
||||
ImageCompression: hcloudimages.Compression(imageCompression),
|
||||
Architecture: hcloud.Architecture(architecture),
|
||||
ImageFormat: hcloudimages.Format(imageFormat),
|
||||
Description: hcloud.Ptr(description),
|
||||
Labels: labels,
|
||||
}
|
||||
|
|
@ -57,8 +70,26 @@ This does cost a bit of money for the server.`,
|
|||
return fmt.Errorf("unable to parse url from --%s=%q: %w", uploadFlagImageURL, imageURLString, err)
|
||||
}
|
||||
|
||||
// Check for image size
|
||||
resp, err := http.Head(imageURL.String())
|
||||
switch {
|
||||
case err != nil:
|
||||
logger.DebugContext(ctx, "failed to check for file size, error on request", "err", err)
|
||||
case resp.ContentLength == -1:
|
||||
logger.DebugContext(ctx, "failed to check for file size, server did not set the Content-Length", "err", err)
|
||||
default:
|
||||
options.ImageSize = resp.ContentLength
|
||||
}
|
||||
|
||||
options.ImageURL = imageURL
|
||||
} else if imagePathString != "" {
|
||||
stat, err := os.Stat(imagePathString)
|
||||
if err != nil {
|
||||
logger.DebugContext(ctx, "failed to check for file size, error on stat", "err", err)
|
||||
} else {
|
||||
options.ImageSize = stat.Size()
|
||||
}
|
||||
|
||||
imageFile, err := os.Open(imagePathString)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to read file from --%s=%q: %w", uploadFlagImagePath, imagePathString, err)
|
||||
|
|
@ -67,6 +98,16 @@ This does cost a bit of money for the server.`,
|
|||
options.ImageReader = imageFile
|
||||
}
|
||||
|
||||
if architecture != "" {
|
||||
options.Architecture = hcloud.Architecture(architecture)
|
||||
} else if serverType != "" {
|
||||
options.ServerType = &hcloud.ServerType{Name: serverType}
|
||||
}
|
||||
|
||||
if location != "" {
|
||||
options.Location = &hcloud.Location{Name: location}
|
||||
}
|
||||
|
||||
image, err := client.Upload(ctx, options)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to upload the image: %w", err)
|
||||
|
|
@ -79,17 +120,23 @@ This does cost a bit of money for the server.`,
|
|||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(uploadCmd)
|
||||
RootCmd.AddCommand(uploadCmd)
|
||||
|
||||
uploadCmd.Flags().String(uploadFlagImageURL, "", "Remote URL of the disk image that should be uploaded")
|
||||
uploadCmd.Flags().String(uploadFlagImagePath, "", "Local path to the disk image that should be uploaded")
|
||||
uploadCmd.MarkFlagsMutuallyExclusive(uploadFlagImageURL, uploadFlagImagePath)
|
||||
uploadCmd.MarkFlagsOneRequired(uploadFlagImageURL, uploadFlagImagePath)
|
||||
|
||||
uploadCmd.Flags().String(uploadFlagCompression, "", "Type of compression that was used on the disk image [choices: bz2, xz]")
|
||||
uploadCmd.Flags().String(uploadFlagCompression, "", "Type of compression that was used on the disk image [choices: bz2, xz, zstd]")
|
||||
_ = uploadCmd.RegisterFlagCompletionFunc(
|
||||
uploadFlagCompression,
|
||||
cobra.FixedCompletions([]string{string(hcloudimages.CompressionBZ2), string(hcloudimages.CompressionXZ)}, cobra.ShellCompDirectiveNoFileComp),
|
||||
cobra.FixedCompletions([]string{string(hcloudimages.CompressionBZ2), string(hcloudimages.CompressionXZ), string(hcloudimages.CompressionZSTD)}, cobra.ShellCompDirectiveNoFileComp),
|
||||
)
|
||||
|
||||
uploadCmd.Flags().String(uploadFlagFormat, "", "Format of the image. [default: raw, choices: qcow2]")
|
||||
_ = uploadCmd.RegisterFlagCompletionFunc(
|
||||
uploadFlagFormat,
|
||||
cobra.FixedCompletions([]string{string(hcloudimages.FormatQCOW2)}, cobra.ShellCompDirectiveNoFileComp),
|
||||
)
|
||||
|
||||
uploadCmd.Flags().String(uploadFlagArchitecture, "", "CPU architecture of the disk image [choices: x86, arm]")
|
||||
|
|
@ -97,9 +144,20 @@ func init() {
|
|||
uploadFlagArchitecture,
|
||||
cobra.FixedCompletions([]string{string(hcloud.ArchitectureX86), string(hcloud.ArchitectureARM)}, cobra.ShellCompDirectiveNoFileComp),
|
||||
)
|
||||
_ = uploadCmd.MarkFlagRequired(uploadFlagArchitecture)
|
||||
|
||||
uploadCmd.Flags().String(uploadFlagServerType, "", "Explicitly use this server type to generate the image. Mutually exclusive with --architecture.")
|
||||
|
||||
// Only one of them needs to be set
|
||||
uploadCmd.MarkFlagsOneRequired(uploadFlagArchitecture, uploadFlagServerType)
|
||||
uploadCmd.MarkFlagsMutuallyExclusive(uploadFlagArchitecture, uploadFlagServerType)
|
||||
|
||||
uploadCmd.Flags().String(uploadFlagDescription, "", "Description for the resulting image")
|
||||
|
||||
uploadCmd.Flags().StringToString(uploadFlagLabels, map[string]string{}, "Labels for the resulting image")
|
||||
|
||||
uploadCmd.Flags().String(uploadFlagLocation, "", "Datacenter location for the temporary server [default: fsn1, choices: fsn1, nbg1, hel1, ash, hil, sin]")
|
||||
_ = uploadCmd.RegisterFlagCompletionFunc(
|
||||
uploadFlagLocation,
|
||||
cobra.FixedCompletions([]string{"fsn1", "nbg1", "hel1", "ash", "hil", "sin"}, cobra.ShellCompDirectiveNoFileComp),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
12
cmd/upload.md
Normal file
12
cmd/upload.md
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
This command implements a fake "upload", by going through a real server and
|
||||
snapshots. This does cost a bit of money for the server.
|
||||
|
||||
#### Image Size
|
||||
|
||||
The image size for raw disk images is only limited by the servers root disk.
|
||||
|
||||
The image size for qcow2 images is limited to the rescue systems root disk.
|
||||
This is currently a memory-backed file system with **960 MB** of space. A qcow2
|
||||
image not be larger than this size, or the process will error. There is a
|
||||
warning being logged if hcloud-upload-image can detect that your file is larger
|
||||
than this size.
|
||||
2
docs/.gitignore
vendored
Normal file
2
docs/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
book
|
||||
https:
|
||||
22
docs/SUMMARY.md
Normal file
22
docs/SUMMARY.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# Summary
|
||||
|
||||
[Introduction](introduction.md)
|
||||
|
||||
# Guides
|
||||
|
||||
- [Uploading Images](guides/README.md)
|
||||
- [Fedora CoreOS ↗](https://docs.fedoraproject.org/en-US/fedora-coreos/provisioning-hetzner/#_creating_a_snapshot)
|
||||
- [Flatcar Container Linux ↗](https://www.flatcar.org/docs/latest/installing/cloud/hetzner/#building-the-snapshots-1)
|
||||
- [Talos Linux ↗](https://www.talos.dev/v1.10/talos-guides/install/cloud-platforms/hetzner/#hcloud-upload-image)
|
||||
|
||||
# Reference
|
||||
|
||||
- [CLI](reference/cli/hcloud-upload-image.md)
|
||||
- [`upload`](reference/cli/hcloud-upload-image_upload.md)
|
||||
- [`cleanup`](reference/cli/hcloud-upload-image_cleanup.md)
|
||||
- [Go Library](reference/go-library.md)
|
||||
|
||||
---
|
||||
|
||||
[Changelog CLI](changelog.md)
|
||||
[Changelog Go Library](changelog-hcloudimages.md)
|
||||
7
docs/book.toml
Normal file
7
docs/book.toml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
[book]
|
||||
language = "en"
|
||||
src = "."
|
||||
title = "hcloud-upload-image"
|
||||
|
||||
[output.html]
|
||||
git-repository-url = "https://github.com/apricote/hcloud-upload-image"
|
||||
3
docs/changelog-hcloudimages.md
Normal file
3
docs/changelog-hcloudimages.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Changelog Library
|
||||
|
||||
{{#include ../hcloudimages/CHANGELOG.md:2: }}
|
||||
3
docs/changelog.md
Normal file
3
docs/changelog.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Changelog CLI
|
||||
|
||||
{{#include ../CHANGELOG.md:2: }}
|
||||
7
docs/guides/README.md
Normal file
7
docs/guides/README.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# Uploading Images
|
||||
|
||||
Check out these docs from other projects to learn how to use `hcloud-upload-image`:
|
||||
|
||||
- [Fedora CoreOS ↗](https://docs.fedoraproject.org/en-US/fedora-coreos/provisioning-hetzner/#_creating_a_snapshot)
|
||||
- [Flatcar Container Linux ↗](https://www.flatcar.org/docs/latest/installing/cloud/hetzner/#building-the-snapshots-1)
|
||||
- [Talos Linux ↗](https://www.talos.dev/v1.10/talos-guides/install/cloud-platforms/hetzner/#hcloud-upload-image)
|
||||
3
docs/introduction.md
Normal file
3
docs/introduction.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Introduction
|
||||
|
||||
{{#include ../README.md:2:}}
|
||||
20
docs/reference/cli/hcloud-upload-image.md
Normal file
20
docs/reference/cli/hcloud-upload-image.md
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
## hcloud-upload-image
|
||||
|
||||
Manage custom OS images on Hetzner Cloud.
|
||||
|
||||
### Synopsis
|
||||
|
||||
Manage custom OS images on Hetzner Cloud.
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-h, --help help for hcloud-upload-image
|
||||
-v, --verbose count verbose debug output, can be specified up to 2 times
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [hcloud-upload-image cleanup](hcloud-upload-image_cleanup.md) - Remove any temporary resources that were left over
|
||||
* [hcloud-upload-image upload](hcloud-upload-image_upload.md) - Upload the specified disk image into your Hetzner Cloud project.
|
||||
|
||||
38
docs/reference/cli/hcloud-upload-image_cleanup.md
Normal file
38
docs/reference/cli/hcloud-upload-image_cleanup.md
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
## hcloud-upload-image cleanup
|
||||
|
||||
Remove any temporary resources that were left over
|
||||
|
||||
### Synopsis
|
||||
|
||||
If the upload fails at any point, there might still exist a server or
|
||||
ssh key in your Hetzner Cloud project. This command cleans up any resources
|
||||
that match the label "apricote.de/created-by=hcloud-upload-image".
|
||||
|
||||
If you want to see a preview of what would be removed, you can use the official hcloud CLI and run:
|
||||
|
||||
$ hcloud server list -l apricote.de/created-by=hcloud-upload-image
|
||||
$ hcloud ssh-key list -l apricote.de/created-by=hcloud-upload-image
|
||||
|
||||
This command does not handle any parallel executions of hcloud-upload-image
|
||||
and will remove in-use resources if called at the same time.
|
||||
|
||||
```
|
||||
hcloud-upload-image cleanup [flags]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-h, --help help for cleanup
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
-v, --verbose count verbose debug output, can be specified up to 2 times
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [hcloud-upload-image](hcloud-upload-image.md) - Manage custom OS images on Hetzner Cloud.
|
||||
|
||||
57
docs/reference/cli/hcloud-upload-image_upload.md
Normal file
57
docs/reference/cli/hcloud-upload-image_upload.md
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
## hcloud-upload-image upload
|
||||
|
||||
Upload the specified disk image into your Hetzner Cloud project.
|
||||
|
||||
### Synopsis
|
||||
|
||||
This command implements a fake "upload", by going through a real server and
|
||||
snapshots. This does cost a bit of money for the server.
|
||||
|
||||
#### Image Size
|
||||
|
||||
The image size for raw disk images is only limited by the servers root disk.
|
||||
|
||||
The image size for qcow2 images is limited to the rescue systems root disk.
|
||||
This is currently a memory-backed file system with **960 MB** of space. A qcow2
|
||||
image not be larger than this size, or the process will error. There is a
|
||||
warning being logged if hcloud-upload-image can detect that your file is larger
|
||||
than this size.
|
||||
|
||||
|
||||
```
|
||||
hcloud-upload-image upload (--image-path=<local-path> | --image-url=<url>) --architecture=<x86|arm> [flags]
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
```
|
||||
hcloud-upload-image upload --image-path /home/you/images/custom-linux-image-x86.bz2 --architecture x86 --compression bz2 --description "My super duper custom linux"
|
||||
hcloud-upload-image upload --image-url https://examples.com/image-arm.raw --architecture arm --labels foo=bar,version=latest
|
||||
hcloud-upload-image upload --image-url https://examples.com/image-x86.qcow2 --architecture x86 --format qcow2
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
--architecture string CPU architecture of the disk image [choices: x86, arm]
|
||||
--compression string Type of compression that was used on the disk image [choices: bz2, xz, zstd]
|
||||
--description string Description for the resulting image
|
||||
--format string Format of the image. [default: raw, choices: qcow2]
|
||||
-h, --help help for upload
|
||||
--image-path string Local path to the disk image that should be uploaded
|
||||
--image-url string Remote URL of the disk image that should be uploaded
|
||||
--labels stringToString Labels for the resulting image (default [])
|
||||
--location string Datacenter location for the temporary server [default: fsn1, choices: fsn1, nbg1, hel1, ash, hil, sin]
|
||||
--server-type string Explicitly use this server type to generate the image. Mutually exclusive with --architecture.
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
-v, --verbose count verbose debug output, can be specified up to 2 times
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [hcloud-upload-image](hcloud-upload-image.md) - Manage custom OS images on Hetzner Cloud.
|
||||
|
||||
3
docs/reference/go-library.md
Normal file
3
docs/reference/go-library.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Go Library
|
||||
|
||||
You can find the documentation at [pkg.go.dev/github.com/apricote/hcloud-upload-image/hcloudimages ↗](https://pkg.go.dev/github.com/apricote/hcloud-upload-image/hcloudimages).
|
||||
39
go.mod
39
go.mod
|
|
@ -1,27 +1,32 @@
|
|||
module github.com/apricote/hcloud-upload-image
|
||||
|
||||
go 1.22.2
|
||||
go 1.24.0
|
||||
|
||||
toolchain go1.25.5
|
||||
|
||||
require (
|
||||
github.com/apricote/hcloud-upload-image/hcloudimages v0.0.0
|
||||
github.com/hetznercloud/hcloud-go/v2 v2.8.0
|
||||
github.com/spf13/cobra v1.8.0
|
||||
github.com/apricote/hcloud-upload-image/hcloudimages v1.3.0
|
||||
github.com/hetznercloud/hcloud-go/v2 v2.34.0
|
||||
github.com/spf13/cobra v1.10.2
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/prometheus/client_golang v1.19.0 // indirect
|
||||
github.com/prometheus/client_model v0.5.0 // indirect
|
||||
github.com/prometheus/common v0.48.0 // indirect
|
||||
github.com/prometheus/procfs v0.12.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
golang.org/x/crypto v0.23.0 // indirect
|
||||
golang.org/x/net v0.24.0 // indirect
|
||||
golang.org/x/sys v0.20.0 // indirect
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/prometheus/client_golang v1.23.2 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.66.1 // indirect
|
||||
github.com/prometheus/procfs v0.16.1 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.9 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/crypto v0.46.0 // indirect
|
||||
golang.org/x/net v0.48.0 // indirect
|
||||
golang.org/x/sys v0.39.0 // indirect
|
||||
golang.org/x/text v0.32.0 // indirect
|
||||
google.golang.org/protobuf v1.36.8 // indirect
|
||||
)
|
||||
|
||||
replace github.com/apricote/hcloud-upload-image/hcloudimages => ./hcloudimages
|
||||
|
|
|
|||
90
go.sum
90
go.sum
|
|
@ -1,45 +1,69 @@
|
|||
github.com/apricote/hcloud-upload-image/hcloudimages v1.3.0 h1:FVIKGSqpxdkO4+t1N8a8xu/xxc+13vHy2QaTAWICuQo=
|
||||
github.com/apricote/hcloud-upload-image/hcloudimages v1.3.0/go.mod h1:NiCZ7xGoYNbWeK9L083leB7/g5oa7SAOZq405XkUSeQ=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/hetznercloud/hcloud-go/v2 v2.8.0 h1:vfbfL/JfV8dIZUX7ANHWEbKNqgFWsETqvt/EctvoFJ0=
|
||||
github.com/hetznercloud/hcloud-go/v2 v2.8.0/go.mod h1:jvpP3qAWMIZ3WQwQLYa97ia6t98iPCgsJNwRts+Jnrk=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/hetznercloud/hcloud-go/v2 v2.34.0 h1:mxasKipFPDPzni85xcMgwYci2PH8TalLgyPJoTlhWDA=
|
||||
github.com/hetznercloud/hcloud-go/v2 v2.34.0/go.mod h1:aF9x0XYNRRQ7N4gux/4cEJeGAHcggu7sX+BBA1rv8ks=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
|
||||
github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
|
||||
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
|
||||
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
|
||||
github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
|
||||
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
|
||||
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
||||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
|
||||
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
||||
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
|
||||
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
||||
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
||||
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
|
||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
||||
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
|
||||
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
|
||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
|
||||
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
|||
4
go.work
4
go.work
|
|
@ -1,4 +1,6 @@
|
|||
go 1.22.2
|
||||
go 1.24.0
|
||||
|
||||
toolchain go1.25.4
|
||||
|
||||
use (
|
||||
.
|
||||
|
|
|
|||
83
go.work.sum
83
go.work.sum
|
|
@ -1,7 +1,9 @@
|
|||
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
|
||||
github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY=
|
||||
github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE=
|
||||
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc=
|
||||
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
|
||||
github.com/apricote/hcloud-upload-image/hcloudimages v1.1.0/go.mod h1:iJ95BaLfISZBY9X8K2Y2A5a49dI0RLjAuq+4BqlOSgA=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
|
||||
github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
|
|
@ -21,52 +23,131 @@ github.com/dave/patsy v0.0.0-20210517141501-957256f50cba h1:1o36L4EKbZzazMk8iGC4
|
|||
github.com/dave/patsy v0.0.0-20210517141501-957256f50cba/go.mod h1:qfR88CgEGLoiqDaE+xxDCi5QA5v4vUoW0UCX2Nd5Tlc=
|
||||
github.com/dave/rebecca v0.9.1 h1:jxVfdOxRirbXL28vXMvUvJ1in3djwkVKXCq339qhBL0=
|
||||
github.com/dave/rebecca v0.9.1/go.mod h1:N6XYdMD/OKw3lkF3ywh8Z6wPGuwNFDNtWYEMFWEmXBA=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU=
|
||||
github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
|
||||
github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA=
|
||||
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||
github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/jessevdk/go-flags v1.4.1-0.20181029123624-5de817a9aa20 h1:dAOsPLhnBzIyxu0VvmnKjlNcIlgMK+erD6VRHDtweMI=
|
||||
github.com/jessevdk/go-flags v1.4.1-0.20181029123624-5de817a9aa20/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jessevdk/go-flags v1.6.1 h1:Cvu5U8UGrLay1rZfv/zP7iLpSHGUZ/Ou68T0iX1bBK4=
|
||||
github.com/jessevdk/go-flags v1.6.1/go.mod h1:Mk8T1hIAWpOiJiHa9rJASDK2UGWji0EuPGBnNLMooyc=
|
||||
github.com/jmattheis/goverter v1.4.0 h1:SrboBYMpGkj1XSgFhWwqzdP024zIa1+58YzUm+0jcBE=
|
||||
github.com/jmattheis/goverter v1.4.0/go.mod h1:iVIl/4qItWjWj2g3vjouGoYensJbRqDHpzlEVMHHFeY=
|
||||
github.com/jmattheis/goverter v1.5.1 h1:NdBYrF1V1EFQbAA1M/ZR4YVbQjxVl3L6Xupn7moF3LU=
|
||||
github.com/jmattheis/goverter v1.5.1/go.mod h1:iVIl/4qItWjWj2g3vjouGoYensJbRqDHpzlEVMHHFeY=
|
||||
github.com/jmattheis/goverter v1.8.0 h1:P8GQ/uJEzCwpNdm5vKxaAjDDMxTpsAJZxgrXegicAW8=
|
||||
github.com/jmattheis/goverter v1.8.0/go.mod h1:c8TVzpum2NThy2eJ/Wz3tyqRxzpElP2xDfoHOIDrNSQ=
|
||||
github.com/jmattheis/goverter v1.9.2 h1:pBjvkhJ0F3PKMqGyHPL0yqnbTe08jjZqt/Z9ZmNKtTQ=
|
||||
github.com/jmattheis/goverter v1.9.2/go.mod h1:1n3q6zf7j58tXcRWHbLFxK2Jk8WQVzr0d3nuaCcRqeg=
|
||||
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/vburenin/ifacemaker v1.2.1 h1:3Vq8B/bfBgjWTkv+jDg4dVL1KHt3k1K4lO7XRxYA2sk=
|
||||
github.com/vburenin/ifacemaker v1.2.1/go.mod h1:5WqrzX2aD7/hi+okBjcaEQJMg4lDGrpuEX3B8L4Wgrs=
|
||||
github.com/vburenin/ifacemaker v1.3.0 h1:X5//v/1tyORf5157wLATgP1wgquW3FUW91/OGHLRqGo=
|
||||
github.com/vburenin/ifacemaker v1.3.0/go.mod h1:SxTD9m+6uBQyhd0aohV7R4iirO+l9mEoTn4nSe67vMs=
|
||||
github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc=
|
||||
github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
|
||||
github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
|
||||
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
|
||||
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
|
||||
golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ=
|
||||
golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o=
|
||||
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
|
||||
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
|
||||
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
|
||||
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
||||
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2 h1:IRJeR9r1pYWsHKTRe/IInb7lYvbBVIqOgsX/u0mbOWY=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457 h1:zf5N6UOrA487eEFacMePxjXAJctxKmyjKUsjA11Uzuk=
|
||||
golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
|
||||
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE=
|
||||
golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg=
|
||||
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
|
||||
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
|
|
|
|||
63
hcloudimages/CHANGELOG.md
Normal file
63
hcloudimages/CHANGELOG.md
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
# Changelog
|
||||
|
||||
## [1.3.0](https://github.com/apricote/hcloud-upload-image/compare/hcloudimages/v1.2.0...hcloudimages/v1.3.0) (2025-12-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add --location flag to specify datacenter region ([#141](https://github.com/apricote/hcloud-upload-image/issues/141)) ([fcbc14a](https://github.com/apricote/hcloud-upload-image/commit/fcbc14aab6d495d2c67d653f9ea1ff56a39a8c2f)), closes [#142](https://github.com/apricote/hcloud-upload-image/issues/142)
|
||||
|
||||
## [1.2.0](https://github.com/apricote/hcloud-upload-image/compare/hcloudimages/v1.1.0...hcloudimages/v1.2.0) (2025-11-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* change minimum required Go version to 1.24 ([#130](https://github.com/apricote/hcloud-upload-image/issues/130)) ([5eba2d5](https://github.com/apricote/hcloud-upload-image/commit/5eba2d52fe3aafb4fd0d93403548f4c32bc2b5ac))
|
||||
* support zstd compression ([#125](https://github.com/apricote/hcloud-upload-image/issues/125)) ([37ebbce](https://github.com/apricote/hcloud-upload-image/commit/37ebbce5179997ac216af274055fc34c777b01e6)), closes [#122](https://github.com/apricote/hcloud-upload-image/issues/122)
|
||||
* update default x86 server type to cx23 ([#129](https://github.com/apricote/hcloud-upload-image/issues/129)) ([a205619](https://github.com/apricote/hcloud-upload-image/commit/a20561944d0ba9485a6e10e99df15c56a688541d))
|
||||
|
||||
## [1.1.0](https://github.com/apricote/hcloud-upload-image/compare/hcloudimages/v1.0.1...hcloudimages/v1.1.0) (2025-05-10)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* smaller snapshots by zeroing disk first ([#101](https://github.com/apricote/hcloud-upload-image/issues/101)) ([fdfb284](https://github.com/apricote/hcloud-upload-image/commit/fdfb284533d3154806b0936c08015fd5cc64b0fb)), closes [#96](https://github.com/apricote/hcloud-upload-image/issues/96)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* upload from local image generates broken command ([#98](https://github.com/apricote/hcloud-upload-image/issues/98)) ([420dcf9](https://github.com/apricote/hcloud-upload-image/commit/420dcf94c965ee470602db6c9c23c777fda91222)), closes [#97](https://github.com/apricote/hcloud-upload-image/issues/97)
|
||||
|
||||
## [1.0.1](https://github.com/apricote/hcloud-upload-image/compare/hcloudimages/v1.0.0...hcloudimages/v1.0.1) (2025-05-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* timeout while waiting for SSH to become available ([#92](https://github.com/apricote/hcloud-upload-image/issues/92)) ([e490b9a](https://github.com/apricote/hcloud-upload-image/commit/e490b9a7f394e268fa1946ca51aa998c78c3d46a))
|
||||
|
||||
## [1.0.0](https://github.com/apricote/hcloud-upload-image/compare/hcloudimages/v0.3.1...hcloudimages/v1.0.0) (2025-05-04)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* upload qcow2 images ([#69](https://github.com/apricote/hcloud-upload-image/issues/69)) ([ac3e9dd](https://github.com/apricote/hcloud-upload-image/commit/ac3e9dd7ecd86d1538b6401c3073c7c078c40847))
|
||||
|
||||
## [0.3.1](https://github.com/apricote/hcloud-upload-image/compare/hcloudimages/v0.3.0...hcloudimages/v0.3.1) (2024-12-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **cli:** local install fails because of go.mod replace ([#47](https://github.com/apricote/hcloud-upload-image/issues/47)) ([66dc5f7](https://github.com/apricote/hcloud-upload-image/commit/66dc5f70b604ed3ee964576d74f94bdcea710c95))
|
||||
|
||||
## [0.3.0](https://github.com/apricote/hcloud-upload-image/compare/hcloudimages/v0.2.0...hcloudimages/v0.3.0) (2024-06-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* set server type explicitly ([#36](https://github.com/apricote/hcloud-upload-image/issues/36)) ([42eeb00](https://github.com/apricote/hcloud-upload-image/commit/42eeb00a0784e13a00a52cf15a8659b497d78d72)), closes [#30](https://github.com/apricote/hcloud-upload-image/issues/30)
|
||||
* update default x86 server type to cx22 ([#38](https://github.com/apricote/hcloud-upload-image/issues/38)) ([ebe08b3](https://github.com/apricote/hcloud-upload-image/commit/ebe08b345c8f31df73087b091fa39f5fdc195156))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* error early when the image write fails ([#34](https://github.com/apricote/hcloud-upload-image/issues/34)) ([256989f](https://github.com/apricote/hcloud-upload-image/commit/256989f4a37e7b124c0684aab0f34cf5e09559be)), closes [#33](https://github.com/apricote/hcloud-upload-image/issues/33)
|
||||
|
|
@ -16,6 +16,10 @@ import (
|
|||
// It uses the formula:
|
||||
//
|
||||
// min(b^retries * d, limit)
|
||||
//
|
||||
// This function has a known overflow issue and should not be used anymore.
|
||||
//
|
||||
// Deprecated: Use BackoffFuncWithOpts from github.com/hetznercloud/hcloud-go/v2/hcloud instead.
|
||||
func ExponentialBackoffWithLimit(b float64, d time.Duration, limit time.Duration) hcloud.BackoffFunc {
|
||||
return func(retries int) time.Duration {
|
||||
current := time.Duration(math.Pow(b, float64(retries))) * d
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/hetznercloud/hcloud-go/v2/hcloud"
|
||||
"github.com/hetznercloud/hcloud-go/v2/hcloud/exp/kit/sshutil"
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
"github.com/apricote/hcloud-upload-image/hcloudimages/contextlogger"
|
||||
|
|
@ -17,7 +18,6 @@ import (
|
|||
"github.com/apricote/hcloud-upload-image/hcloudimages/internal/control"
|
||||
"github.com/apricote/hcloud-upload-image/hcloudimages/internal/labelutil"
|
||||
"github.com/apricote/hcloud-upload-image/hcloudimages/internal/randomid"
|
||||
"github.com/apricote/hcloud-upload-image/hcloudimages/internal/sshkey"
|
||||
"github.com/apricote/hcloud-upload-image/hcloudimages/internal/sshsession"
|
||||
)
|
||||
|
||||
|
|
@ -34,15 +34,19 @@ var (
|
|||
}
|
||||
|
||||
serverTypePerArchitecture = map[hcloud.Architecture]*hcloud.ServerType{
|
||||
hcloud.ArchitectureX86: {Name: "cx11"},
|
||||
hcloud.ArchitectureX86: {Name: "cx23"},
|
||||
hcloud.ArchitectureARM: {Name: "cax11"},
|
||||
}
|
||||
|
||||
defaultImage = &hcloud.Image{Name: "ubuntu-22.04"}
|
||||
defaultImage = &hcloud.Image{Name: "ubuntu-24.04"}
|
||||
defaultLocation = &hcloud.Location{Name: "fsn1"}
|
||||
defaultRescueType = hcloud.ServerRescueTypeLinux64
|
||||
|
||||
defaultSSHDialTimeout = 1 * time.Minute
|
||||
|
||||
// Size observed on x86, 2025-05-03, no idea if that changes.
|
||||
// Might be able to extends this to more of the available memory.
|
||||
rescueSystemRootDiskSizeMB int64 = 960
|
||||
)
|
||||
|
||||
type UploadOptions struct {
|
||||
|
|
@ -56,17 +60,30 @@ type UploadOptions struct {
|
|||
// set to anything else, the file will be decompressed before written to the disk.
|
||||
ImageCompression Compression
|
||||
|
||||
ImageFormat Format
|
||||
|
||||
// Can be optionally set to make the client validate that the image can be written to the server.
|
||||
ImageSize int64
|
||||
|
||||
// Possible future additions:
|
||||
// ImageSignatureVerification
|
||||
// ImageLocalPath
|
||||
// ImageType (RawDiskImage, ISO, qcow2, ...)
|
||||
|
||||
// Architecture should match the architecture of the Image. This decides if the Snapshot can later be
|
||||
// used with [hcloud.ArchitectureX86] or [hcloud.ArchitectureARM] servers.
|
||||
//
|
||||
// Internally this decides what server type is used for the temporary server.
|
||||
//
|
||||
// Optional if [UploadOptions.ServerType] is set.
|
||||
Architecture hcloud.Architecture
|
||||
|
||||
// ServerType can be optionally set to override the default server type for the architecture.
|
||||
// Situations where this makes sense:
|
||||
//
|
||||
// - Your image is larger than the root disk of the default server types.
|
||||
// - The default server type is no longer available, or not temporarily out of stock.
|
||||
ServerType *hcloud.ServerType
|
||||
|
||||
// Description is an optional description that the resulting image (snapshot) will have. There is no way to
|
||||
// select images by its description, you should use Labels if you need to identify your image later.
|
||||
Description *string
|
||||
|
|
@ -77,6 +94,10 @@ type UploadOptions struct {
|
|||
// We also always add a label `apricote.de/created-by=hcloud-image-upload` ([CreatedByLabel], [CreatedByValue]).
|
||||
Labels map[string]string
|
||||
|
||||
// Location is the datacenter location for the temporary server.
|
||||
// Defaults to fsn1 if not specified.
|
||||
Location *hcloud.Location
|
||||
|
||||
// DebugSkipResourceCleanup will skip the cleanup of the temporary SSH Key and Server.
|
||||
DebugSkipResourceCleanup bool
|
||||
}
|
||||
|
|
@ -87,9 +108,23 @@ const (
|
|||
CompressionNone Compression = ""
|
||||
CompressionBZ2 Compression = "bz2"
|
||||
CompressionXZ Compression = "xz"
|
||||
CompressionZSTD Compression = "zstd"
|
||||
|
||||
// Possible future additions:
|
||||
// zip,zstd
|
||||
// zip
|
||||
)
|
||||
|
||||
type Format string
|
||||
|
||||
const (
|
||||
FormatRaw Format = ""
|
||||
|
||||
// FormatQCOW2 allows to upload images in the qcow2 format directly.
|
||||
//
|
||||
// The qcow2 image must fit on the disk available in the rescue system. "qemu-img dd", which is used to convert
|
||||
// qcow2 to raw, requires a file as an input. If [UploadOption.ImageSize] is set and FormatQCOW2 is used, there is a
|
||||
// warning message displayed if there is a high probability of issues.
|
||||
FormatQCOW2 Format = "qcow2"
|
||||
)
|
||||
|
||||
// NewClient instantiates a new client. It requires a working [*hcloud.Client] to interact with the Hetzner Cloud API.
|
||||
|
|
@ -125,9 +160,22 @@ func (s *Client) Upload(ctx context.Context, options UploadOptions) (*hcloud.Ima
|
|||
resourceName := resourcePrefix + id
|
||||
labels := labelutil.Merge(DefaultLabels, options.Labels)
|
||||
|
||||
// 0. Validations
|
||||
if options.ImageFormat == FormatQCOW2 && options.ImageSize > 0 {
|
||||
if options.ImageSize > rescueSystemRootDiskSizeMB*1024*1024 {
|
||||
// Just a warning, because the size might change with time.
|
||||
// Alternatively one could add an override flag for the check and make this an error.
|
||||
logger.WarnContext(ctx,
|
||||
fmt.Sprintf("image must be smaller than %d MB (rescue system root disk) for qcow2", rescueSystemRootDiskSizeMB),
|
||||
"maximum-size", rescueSystemRootDiskSizeMB,
|
||||
"actual-size", options.ImageSize/(1024*1024),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 1. Create SSH Key
|
||||
logger.InfoContext(ctx, "# Step 1: Generating SSH Key")
|
||||
publicKey, privateKey, err := sshkey.GenerateKeyPair()
|
||||
privateKey, publicKey, err := sshutil.GenerateKeyPair()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate temporary ssh key pair: %w", err)
|
||||
}
|
||||
|
|
@ -159,14 +207,25 @@ func (s *Client) Upload(ctx context.Context, options UploadOptions) (*hcloud.Ima
|
|||
|
||||
// 2. Create Server
|
||||
logger.InfoContext(ctx, "# Step 2: Creating Server")
|
||||
serverType, ok := serverTypePerArchitecture[options.Architecture]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown architecture %q, valid options: %q, %q", options.Architecture, hcloud.ArchitectureX86, hcloud.ArchitectureARM)
|
||||
var serverType *hcloud.ServerType
|
||||
if options.ServerType != nil {
|
||||
serverType = options.ServerType
|
||||
} else {
|
||||
var ok bool
|
||||
serverType, ok = serverTypePerArchitecture[options.Architecture]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown architecture %q, valid options: %q, %q", options.Architecture, hcloud.ArchitectureX86, hcloud.ArchitectureARM)
|
||||
}
|
||||
}
|
||||
|
||||
location := defaultLocation
|
||||
if options.Location != nil {
|
||||
location = options.Location
|
||||
}
|
||||
|
||||
logger.DebugContext(ctx, "creating server with config",
|
||||
"image", defaultImage.Name,
|
||||
"location", defaultLocation.Name,
|
||||
"location", location.Name,
|
||||
"serverType", serverType.Name,
|
||||
)
|
||||
serverCreateResult, _, err := s.c.Server.Create(ctx, hcloud.ServerCreateOpts{
|
||||
|
|
@ -180,7 +239,7 @@ func (s *Client) Upload(ctx context.Context, options UploadOptions) (*hcloud.Ima
|
|||
StartAfterCreate: hcloud.Ptr(false),
|
||||
// Image will never be booted, we only boot into rescue system
|
||||
Image: defaultImage,
|
||||
Location: defaultLocation,
|
||||
Location: location,
|
||||
Labels: labels,
|
||||
})
|
||||
if err != nil {
|
||||
|
|
@ -267,7 +326,7 @@ func (s *Client) Upload(ctx context.Context, options UploadOptions) (*hcloud.Ima
|
|||
|
||||
err = control.Retry(
|
||||
contextlogger.New(ctx, logger.With("operation", "ssh")),
|
||||
10,
|
||||
100, // ~ 3 minutes
|
||||
func() error {
|
||||
var err error
|
||||
logger.DebugContext(ctx, "trying to connect to server", "ip", server.PublicNet.IPv4.IP)
|
||||
|
|
@ -278,46 +337,44 @@ func (s *Client) Upload(ctx context.Context, options UploadOptions) (*hcloud.Ima
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to ssh into temporary server: %w", err)
|
||||
}
|
||||
defer sshClient.Close()
|
||||
defer func() { _ = sshClient.Close() }()
|
||||
|
||||
// 6. SSH On Server: Download Image, Decompress, Write to Root Disk
|
||||
logger.InfoContext(ctx, "# Step 6: Downloading image and writing to disk")
|
||||
cmd := ""
|
||||
if options.ImageURL != nil {
|
||||
cmd += fmt.Sprintf("wget --no-verbose -O - %q | ", options.ImageURL.String())
|
||||
// 6. Wipe existing disk, to avoid storing any bytes from it in the snapshot
|
||||
logger.InfoContext(ctx, "# Step 6: Cleaning existing disk")
|
||||
|
||||
output, err := sshsession.Run(sshClient, "blkdiscard /dev/sda", nil)
|
||||
logger.DebugContext(ctx, string(output))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to clean existing disk: %w", err)
|
||||
}
|
||||
|
||||
if options.ImageCompression != CompressionNone {
|
||||
switch options.ImageCompression {
|
||||
case CompressionBZ2:
|
||||
cmd += "bzip2 -cd | "
|
||||
case CompressionXZ:
|
||||
cmd += "xz -cd | "
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown compression: %q", options.ImageCompression)
|
||||
}
|
||||
// 7. SSH On Server: Download Image, Decompress, Write to Root Disk
|
||||
logger.InfoContext(ctx, "# Step 7: Downloading image and writing to disk")
|
||||
|
||||
cmd, err := assembleCommand(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cmd += "dd of=/dev/sda bs=4M && sync"
|
||||
logger.DebugContext(ctx, "running download, decompress and write to disk command", "cmd", cmd)
|
||||
|
||||
output, err := sshsession.Run(sshClient, cmd, options.ImageReader)
|
||||
logger.InfoContext(ctx, "# Step 6: Finished writing image to disk")
|
||||
output, err = sshsession.Run(sshClient, cmd, options.ImageReader)
|
||||
logger.InfoContext(ctx, "# Step 7: Finished writing image to disk")
|
||||
logger.DebugContext(ctx, string(output))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to download and write the image: %w", err)
|
||||
}
|
||||
|
||||
// 7. SSH On Server: Shutdown
|
||||
logger.InfoContext(ctx, "# Step 7: Shutting down server")
|
||||
// 8. SSH On Server: Shutdown
|
||||
logger.InfoContext(ctx, "# Step 8: Shutting down server")
|
||||
_, err = sshsession.Run(sshClient, "shutdown now", nil)
|
||||
if err != nil {
|
||||
// TODO Verify if shutdown error, otherwise return
|
||||
logger.WarnContext(ctx, "shutdown returned error", "err", err)
|
||||
}
|
||||
|
||||
// 8. Create Image from Server
|
||||
logger.InfoContext(ctx, "# Step 8: Creating Image")
|
||||
// 9. Create Image from Server
|
||||
logger.InfoContext(ctx, "# Step 9: Creating Image")
|
||||
createImageResult, _, err := s.c.Server.CreateImage(ctx, server, &hcloud.ServerCreateImageOpts{
|
||||
Type: hcloud.ImageTypeSnapshot,
|
||||
Description: options.Description,
|
||||
|
|
@ -462,3 +519,41 @@ func (s *Client) cleanupTempSSHKeys(ctx context.Context, logger *slog.Logger, se
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func assembleCommand(options UploadOptions) (string, error) {
|
||||
// Make sure that we fail early, ie. if the image url does not work
|
||||
cmd := "set -euo pipefail && "
|
||||
|
||||
if options.ImageURL != nil {
|
||||
cmd += fmt.Sprintf("wget --no-verbose -O - %q | ", options.ImageURL.String())
|
||||
}
|
||||
|
||||
if options.ImageCompression != CompressionNone {
|
||||
switch options.ImageCompression {
|
||||
case CompressionBZ2:
|
||||
cmd += "bzip2 -cd | "
|
||||
case CompressionXZ:
|
||||
cmd += "xz -cd | "
|
||||
case CompressionZSTD:
|
||||
cmd += "zstd -cd | "
|
||||
default:
|
||||
return "", fmt.Errorf("unknown compression: %q", options.ImageCompression)
|
||||
}
|
||||
}
|
||||
|
||||
switch options.ImageFormat {
|
||||
case FormatRaw:
|
||||
cmd += "dd of=/dev/sda bs=4M"
|
||||
case FormatQCOW2:
|
||||
cmd += "tee image.qcow2 > /dev/null && qemu-img dd -f qcow2 -O raw if=image.qcow2 of=/dev/sda bs=4M"
|
||||
default:
|
||||
return "", fmt.Errorf("unknown format: %q", options.ImageFormat)
|
||||
}
|
||||
|
||||
cmd += " && sync"
|
||||
|
||||
// the pipefail does not work correctly without wrapping in bash.
|
||||
cmd = fmt.Sprintf("bash -c '%s'", cmd)
|
||||
|
||||
return cmd, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,33 +1,125 @@
|
|||
package hcloudimages_test
|
||||
package hcloudimages
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/hetznercloud/hcloud-go/v2/hcloud"
|
||||
|
||||
"github.com/apricote/hcloud-upload-image/hcloudimages"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func ExampleClient_Upload() {
|
||||
client := hcloudimages.NewClient(
|
||||
hcloud.NewClient(hcloud.WithToken("<your token>")),
|
||||
)
|
||||
|
||||
imageURL, err := url.Parse("https://example.com/disk-image.raw.bz2")
|
||||
func mustParseURL(s string) *url.URL {
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
image, err := client.Upload(context.TODO(), hcloudimages.UploadOptions{
|
||||
ImageURL: imageURL,
|
||||
ImageCompression: hcloudimages.CompressionBZ2,
|
||||
Architecture: hcloud.ArchitectureX86,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Uploaded Image: %d", image.ID)
|
||||
return u
|
||||
}
|
||||
|
||||
func TestAssembleCommand(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
options UploadOptions
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "local raw",
|
||||
options: UploadOptions{},
|
||||
want: "bash -c 'set -euo pipefail && dd of=/dev/sda bs=4M && sync'",
|
||||
},
|
||||
{
|
||||
name: "remote raw",
|
||||
options: UploadOptions{
|
||||
ImageURL: mustParseURL("https://example.com/image.xz"),
|
||||
},
|
||||
want: "bash -c 'set -euo pipefail && wget --no-verbose -O - \"https://example.com/image.xz\" | dd of=/dev/sda bs=4M && sync'",
|
||||
},
|
||||
{
|
||||
name: "local xz",
|
||||
options: UploadOptions{
|
||||
ImageCompression: CompressionXZ,
|
||||
},
|
||||
want: "bash -c 'set -euo pipefail && xz -cd | dd of=/dev/sda bs=4M && sync'",
|
||||
},
|
||||
{
|
||||
name: "remote xz",
|
||||
options: UploadOptions{
|
||||
ImageURL: mustParseURL("https://example.com/image.xz"),
|
||||
ImageCompression: CompressionXZ,
|
||||
},
|
||||
want: "bash -c 'set -euo pipefail && wget --no-verbose -O - \"https://example.com/image.xz\" | xz -cd | dd of=/dev/sda bs=4M && sync'",
|
||||
},
|
||||
{
|
||||
name: "local zstd",
|
||||
options: UploadOptions{
|
||||
ImageCompression: CompressionZSTD,
|
||||
},
|
||||
want: "bash -c 'set -euo pipefail && zstd -cd | dd of=/dev/sda bs=4M && sync'",
|
||||
},
|
||||
{
|
||||
name: "remote zstd",
|
||||
options: UploadOptions{
|
||||
ImageURL: mustParseURL("https://example.com/image.zst"),
|
||||
ImageCompression: CompressionZSTD,
|
||||
},
|
||||
want: "bash -c 'set -euo pipefail && wget --no-verbose -O - \"https://example.com/image.zst\" | zstd -cd | dd of=/dev/sda bs=4M && sync'",
|
||||
},
|
||||
{
|
||||
name: "local bz2",
|
||||
options: UploadOptions{
|
||||
ImageCompression: CompressionBZ2,
|
||||
},
|
||||
want: "bash -c 'set -euo pipefail && bzip2 -cd | dd of=/dev/sda bs=4M && sync'",
|
||||
},
|
||||
{
|
||||
name: "remote bz2",
|
||||
options: UploadOptions{
|
||||
ImageURL: mustParseURL("https://example.com/image.bz2"),
|
||||
ImageCompression: CompressionBZ2,
|
||||
},
|
||||
want: "bash -c 'set -euo pipefail && wget --no-verbose -O - \"https://example.com/image.bz2\" | bzip2 -cd | dd of=/dev/sda bs=4M && sync'",
|
||||
},
|
||||
{
|
||||
name: "local qcow2",
|
||||
options: UploadOptions{
|
||||
ImageFormat: FormatQCOW2,
|
||||
},
|
||||
want: "bash -c 'set -euo pipefail && tee image.qcow2 > /dev/null && qemu-img dd -f qcow2 -O raw if=image.qcow2 of=/dev/sda bs=4M && sync'",
|
||||
},
|
||||
{
|
||||
name: "remote qcow2",
|
||||
options: UploadOptions{
|
||||
ImageURL: mustParseURL("https://example.com/image.qcow2"),
|
||||
ImageFormat: FormatQCOW2,
|
||||
},
|
||||
want: "bash -c 'set -euo pipefail && wget --no-verbose -O - \"https://example.com/image.qcow2\" | tee image.qcow2 > /dev/null && qemu-img dd -f qcow2 -O raw if=image.qcow2 of=/dev/sda bs=4M && sync'",
|
||||
},
|
||||
|
||||
{
|
||||
name: "unknown compression",
|
||||
options: UploadOptions{
|
||||
ImageCompression: "noodle",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
|
||||
{
|
||||
name: "unknown format",
|
||||
options: UploadOptions{
|
||||
ImageFormat: "poodle",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := assembleCommand(tt.options)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("assembleCommand() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("assembleCommand() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
34
hcloudimages/doc_test.go
Normal file
34
hcloudimages/doc_test.go
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
package hcloudimages_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/hetznercloud/hcloud-go/v2/hcloud"
|
||||
|
||||
"github.com/apricote/hcloud-upload-image/hcloudimages"
|
||||
)
|
||||
|
||||
func ExampleClient_Upload() {
|
||||
client := hcloudimages.NewClient(
|
||||
hcloud.NewClient(hcloud.WithToken("<your token>")),
|
||||
)
|
||||
|
||||
imageURL, err := url.Parse("https://example.com/disk-image.raw.bz2")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
image, err := client.Upload(context.TODO(), hcloudimages.UploadOptions{
|
||||
ImageURL: imageURL,
|
||||
ImageCompression: hcloudimages.CompressionBZ2,
|
||||
Architecture: hcloud.ArchitectureX86,
|
||||
Location: &hcloud.Location{Name: "nbg1"}, // Optional: defaults to fsn1
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Uploaded Image: %d", image.ID)
|
||||
}
|
||||
|
|
@ -1,25 +1,29 @@
|
|||
module github.com/apricote/hcloud-upload-image/hcloudimages
|
||||
|
||||
go 1.22.2
|
||||
go 1.24.0
|
||||
|
||||
toolchain go1.25.5
|
||||
|
||||
require (
|
||||
github.com/hetznercloud/hcloud-go/v2 v2.8.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
golang.org/x/crypto v0.23.0
|
||||
github.com/hetznercloud/hcloud-go/v2 v2.34.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
golang.org/x/crypto v0.46.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_golang v1.19.0 // indirect
|
||||
github.com/prometheus/client_model v0.5.0 // indirect
|
||||
github.com/prometheus/common v0.48.0 // indirect
|
||||
github.com/prometheus/procfs v0.12.0 // indirect
|
||||
golang.org/x/net v0.24.0 // indirect
|
||||
golang.org/x/sys v0.20.0 // indirect
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
github.com/prometheus/client_golang v1.23.2 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.66.1 // indirect
|
||||
github.com/prometheus/procfs v0.16.1 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||
golang.org/x/net v0.48.0 // indirect
|
||||
golang.org/x/sys v0.39.0 // indirect
|
||||
golang.org/x/text v0.32.0 // indirect
|
||||
google.golang.org/protobuf v1.36.8 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,43 +1,53 @@
|
|||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/hetznercloud/hcloud-go/v2 v2.8.0 h1:vfbfL/JfV8dIZUX7ANHWEbKNqgFWsETqvt/EctvoFJ0=
|
||||
github.com/hetznercloud/hcloud-go/v2 v2.8.0/go.mod h1:jvpP3qAWMIZ3WQwQLYa97ia6t98iPCgsJNwRts+Jnrk=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/hetznercloud/hcloud-go/v2 v2.34.0 h1:mxasKipFPDPzni85xcMgwYci2PH8TalLgyPJoTlhWDA=
|
||||
github.com/hetznercloud/hcloud-go/v2 v2.34.0/go.mod h1:aF9x0XYNRRQ7N4gux/4cEJeGAHcggu7sX+BBA1rv8ks=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
|
||||
github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
|
||||
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
|
||||
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
|
||||
github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
|
||||
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
|
||||
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
||||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
|
||||
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
||||
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
|
||||
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
||||
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
||||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
|
||||
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
|
||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
|
||||
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ import (
|
|||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/apricote/hcloud-upload-image/hcloudimages/backoff"
|
||||
"github.com/hetznercloud/hcloud-go/v2/hcloud"
|
||||
|
||||
"github.com/apricote/hcloud-upload-image/hcloudimages/contextlogger"
|
||||
)
|
||||
|
||||
|
|
@ -18,7 +19,7 @@ func Retry(ctx context.Context, maxTries int, f func() error) error {
|
|||
|
||||
var err error
|
||||
|
||||
backoffFunc := backoff.ExponentialBackoffWithLimit(2, 1*time.Second, 30*time.Second)
|
||||
backoffFunc := hcloud.ExponentialBackoffWithOpts(hcloud.ExponentialBackoffOpts{Multiplier: 2, Base: 200 * time.Millisecond, Cap: 2 * time.Second})
|
||||
|
||||
for try := 0; try < maxTries; try++ {
|
||||
if ctx.Err() != nil {
|
||||
|
|
|
|||
|
|
@ -1,49 +0,0 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
// From https://gitlab.com/hetznercloud/fleeting-plugin-hetzner/-/blob/0f60204582289c243599f8ca0f5be4822789131d/internal/utils/ssh_key.go
|
||||
// Copyright (c) 2024 Hetzner Cloud GmbH
|
||||
|
||||
package sshkey
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"encoding/pem"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
func GenerateKeyPair() ([]byte, []byte, error) {
|
||||
pub, priv, err := ed25519.GenerateKey(nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
pubBytes, err := encodePublicKey(pub)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
privBytes, err := encodePrivateKey(priv)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return pubBytes, privBytes, nil
|
||||
}
|
||||
|
||||
func encodePublicKey(pub ed25519.PublicKey) ([]byte, error) {
|
||||
sshPub, err := ssh.NewPublicKey(pub)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ssh.MarshalAuthorizedKey(sshPub), nil
|
||||
}
|
||||
|
||||
func encodePrivateKey(priv ed25519.PrivateKey) ([]byte, error) {
|
||||
privPem, err := ssh.MarshalPrivateKey(priv, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pem.EncodeToMemory(privPem), nil
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
// From https://gitlab.com/hetznercloud/fleeting-plugin-hetzner/-/blob/0f60204582289c243599f8ca0f5be4822789131d/internal/utils/ssh_key_test.go
|
||||
// Copyright (c) 2024 Hetzner Cloud GmbH
|
||||
|
||||
package sshkey
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGenerateSSHKeyPair(t *testing.T) {
|
||||
pubBytes, privBytes, err := GenerateKeyPair()
|
||||
assert.Nil(t, err)
|
||||
|
||||
pub := string(pubBytes)
|
||||
priv := string(privBytes)
|
||||
|
||||
if !(strings.HasPrefix(priv, "-----BEGIN OPENSSH PRIVATE KEY-----\n") &&
|
||||
strings.HasSuffix(priv, "-----END OPENSSH PRIVATE KEY-----\n")) {
|
||||
assert.Fail(t, "private key is invalid", priv)
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(pub, "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA") {
|
||||
assert.Fail(t, "public key is invalid", pub)
|
||||
}
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@ func Run(client *ssh.Client, cmd string, stdin io.Reader) ([]byte, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer sess.Close()
|
||||
defer func() { _ = sess.Close() }()
|
||||
|
||||
if stdin != nil {
|
||||
sess.Stdin = stdin
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package version
|
|||
|
||||
var (
|
||||
// version is a semver version (https://semver.org).
|
||||
version = "0.2.0" // x-release-please-version
|
||||
version = "1.3.0" // x-release-please-version
|
||||
|
||||
// versionPrerelease is a semver version pre-release identifier (https://semver.org).
|
||||
//
|
||||
|
|
|
|||
|
|
@ -11,12 +11,18 @@
|
|||
"gomodTidy",
|
||||
"gomodUpdateImportPaths"
|
||||
],
|
||||
"goGetDirs": [
|
||||
"./...",
|
||||
"./hcloudimages/..."
|
||||
],
|
||||
"customManagers": [
|
||||
{
|
||||
"customType": "regex",
|
||||
"fileMatch": ["^\\.github\\/(?:workflows|actions)\\/.+\\.ya?ml$"],
|
||||
"managerFilePatterns": [
|
||||
"/^\\.github\\/(?:workflows|actions)\\/.+\\.ya?ml$/"
|
||||
],
|
||||
"matchStrings": [
|
||||
"(?:version|VERSION): (?<currentValue>.+) # renovate: datasource=(?<datasource>[a-z-]+) depName=(?<depName>.+)(?: lookupName=(?<lookupName>.+))?(?: versioning=(?<versioning>[a-z-]+))?"
|
||||
"(?:version|VERSION): (?<currentValue>.+) # renovate: datasource=(?<datasource>[a-z-]+) depName=(?<depName>.+)(?: packageName=(?<packageName>.+))?(?: versioning=(?<versioning>[a-z-]+))?"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
|||
35
scripts/cli-help-pages.go
Normal file
35
scripts/cli-help-pages.go
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra/doc"
|
||||
|
||||
"github.com/apricote/hcloud-upload-image/cmd"
|
||||
)
|
||||
|
||||
func run() error {
|
||||
// Define the directory where the docs will be generated
|
||||
dir := "docs/reference/cli"
|
||||
|
||||
// Ensure the directory exists
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return fmt.Errorf("error creating docs directory: %v", err)
|
||||
}
|
||||
|
||||
// Generate the docs
|
||||
if err := doc.GenMarkdownTree(cmd.RootCmd, dir); err != nil {
|
||||
return fmt.Errorf("error generating docs: %v", err)
|
||||
}
|
||||
|
||||
fmt.Println("Docs generated successfully in", dir)
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := run(); err != nil {
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue