Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package hcloud-upload-image for openSUSE:Factory checked in at 2025-05-12 16:47:56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/hcloud-upload-image (Old) and /work/SRC/openSUSE:Factory/.hcloud-upload-image.new.30101 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "hcloud-upload-image" Mon May 12 16:47:56 2025 rev:3 rq:1276488 version:1.1.0 Changes: -------- --- /work/SRC/openSUSE:Factory/hcloud-upload-image/hcloud-upload-image.changes 2024-12-09 21:12:32.035381634 +0100 +++ /work/SRC/openSUSE:Factory/.hcloud-upload-image.new.30101/hcloud-upload-image.changes 2025-05-12 16:51:24.887060098 +0200 @@ -1,0 +2,93 @@ +Sat May 10 14:46:53 UTC 2025 - Johannes Kastl <opensuse_buildserv...@ojkastl.de> + +- Update to version 1.1.0: + * Highlights + - Smaller Snapshots + The root disk is now zeroed before the intend image is + uploaded. This makes sure, that no bytes from the original + image are stored in the compressed snapshot. Previously a + very small uploaded image could not be smaller than ~0.42Gi, + as that was the (compressed) size of image that the server + was started with. Starting a server from a custom image is + slower the larger the image is, so by having smaller images + the servers can start faster. + As one example: The test Talos Linux 1.10.1 x86 image was + 0.42Gi before, and now only takes up 0.2Gi. + This only benefits images that are smaller than 0.42Gi + compressed. + * Features + - smaller snapshots by zeroing disk first (#101) (fdfb284), + closes #96 + * Bug Fixes + - upload from local image generates broken command (#98) + (420dcf9), closes #97 + +------------------------------------------------------------------- +Sat May 10 09:02:35 UTC 2025 - Johannes Kastl <opensuse_buildserv...@ojkastl.de> + +- Update to version 1.0.1: + * Bug Fixes + - timeout while waiting for SSH to become available (#92) + (e490b9a) + * Dependencies + - chore(deps): update module + github.com/apricote/hcloud-upload-image/hcloudimages to + v1.0.1 (#95) + - chore(deps): update golangci/golangci-lint-action action to + v8 (#86) + - chore(deps): update dependency golangci/golangci-lint to + v2.1.6 (#85) + - chore(deps): update dependency rust-lang/mdbook to v0.4.49 + (#87) + - chore(deps): update dependency go to v1.24.3 (#91) + +------------------------------------------------------------------- +Sat May 10 08:49:27 UTC 2025 - Johannes Kastl <opensuse_buildserv...@ojkastl.de> + +- Update to version 1.0.0: + * Highlights + - Upload qcow2 images + qcow2 images with a max size of 960 MB can now be uploaded. + hcloud-upload-image upload --format=qcow2 + --architecture=x86 + --image-url=https://download.opensuse.org/tumbleweed/appliances/openSUSE-MicroOS.x86_64-OpenStack-Cloud.qcow2 + These will be converted to a raw disk image on the temporary + server with qemu-img dd. + - Container image + There is now a pre-built container image available at + ghcr.io/apricote/hcloud-upload-image that you can use to run + the command instead of installing it. + - Docs Website + You can now enjoy the same old docs in a new dress published + to a website: https://apricote.github.io/hcloud-upload-image/ + * Features + - deps: require Go 1.23 (#70) (f3fcb62) + - docs website (#80) (d144b85) + - publish container image (#82) (91df729) + - upload qcow2 images (#69) (ac3e9dd) + * Dependencies + - chore(deps): update module + github.com/apricote/hcloud-upload-image/hcloudimages to + v1.0.0 (#84) + - chore(deps): update docker/login-action digest to 6d4b68b + (#83) + - chore(deps): update dependency rust-lang/mdbook to v0.4.48 + (#81) + - chore(deps): update module golang.org/x/crypto to v0.37.0 + (#72) + - chore(renovate): make sure to bump dependencies in lib too + (#74) + - chore(deps): update dependency golangci/golangci-lint to v2 + (#66) + - chore(config): migrate renovate config (#73) + - chore(deps): update module golang.org/x/net to v0.38.0 + [security] (#63) + - chore(deps): update module + github.com/hetznercloud/hcloud-go/v2 to v2.21.0 (#62) + - chore(deps): update dependency golangci/golangci-lint to + v1.64.8 (#64) + - chore(deps): update module github.com/spf13/cobra to v1.9.1 + (#65) + - feat(deps): require Go 1.23 (#70) + +------------------------------------------------------------------- Old: ---- hcloud-upload-image-0.3.1.obscpio New: ---- hcloud-upload-image-1.1.0.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ hcloud-upload-image.spec ++++++ --- /var/tmp/diff_new_pack.9Zo9Y9/_old 2025-05-12 16:51:25.507086212 +0200 +++ /var/tmp/diff_new_pack.9Zo9Y9/_new 2025-05-12 16:51:25.507086212 +0200 @@ -1,7 +1,7 @@ # # spec file for package hcloud-upload-image # -# Copyright (c) 2024 SUSE LLC +# Copyright (c) 2025 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -17,7 +17,7 @@ Name: hcloud-upload-image -Version: 0.3.1 +Version: 1.1.0 Release: 0 Summary: Quickly upload any raw disk images into your Hetzner Cloud projects License: MIT @@ -26,8 +26,8 @@ Source1: vendor.tar.gz BuildRequires: bash-completion BuildRequires: fish -BuildRequires: go >= 1.22 BuildRequires: zsh +BuildRequires: golang(API) >= 1.24 %description Quickly upload any raw disk images into your Hetzner Cloud projects! ++++++ _service ++++++ --- /var/tmp/diff_new_pack.9Zo9Y9/_old 2025-05-12 16:51:25.555088234 +0200 +++ /var/tmp/diff_new_pack.9Zo9Y9/_new 2025-05-12 16:51:25.555088234 +0200 @@ -5,7 +5,7 @@ <param name="exclude">.git</param> <param name="exclude">go.work</param> <param name="exclude">go.work.sum</param> - <param name="revision">v0.3.1</param> + <param name="revision">v1.1.0</param> <param name="match-tag">v*</param> <param name="versionformat">@PARENT_TAG@</param> <param name="versionrewrite-pattern">v(.*)</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.9Zo9Y9/_old 2025-05-12 16:51:25.575089077 +0200 +++ /var/tmp/diff_new_pack.9Zo9Y9/_new 2025-05-12 16:51:25.579089245 +0200 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/apricote/hcloud-upload-image/</param> - <param name="changesrevision">b328867f6b9b965dcd581cb6f6907dae364faa7a</param></service></servicedata> + <param name="changesrevision">b9af8855d5c0c1e40ddfaa670296731b2ed0c53e</param></service></servicedata> (No newline at EOF) ++++++ hcloud-upload-image-0.3.1.obscpio -> hcloud-upload-image-1.1.0.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hcloud-upload-image-0.3.1/.github/release-please-config.json new/hcloud-upload-image-1.1.0/.github/release-please-config.json --- old/hcloud-upload-image-0.3.1/.github/release-please-config.json 2024-12-07 01:57:59.000000000 +0100 +++ new/hcloud-upload-image-1.1.0/.github/release-please-config.json 1970-01-01 01:00:00.000000000 +0100 @@ -1,29 +0,0 @@ -{ - "$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", - "group-pull-request-title-pattern": "chore(${branch}): release ${version}", - "packages": { - ".": { - "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": "/" - } - }, - "plugins": [ - { - "type": "linked-versions", - "groupName": "repo", - "components": [ - "cli", "hcloudimages" - ] - } - ] -} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hcloud-upload-image-0.3.1/.github/release-please-manifest.json new/hcloud-upload-image-1.1.0/.github/release-please-manifest.json --- old/hcloud-upload-image-0.3.1/.github/release-please-manifest.json 2024-12-07 01:57:59.000000000 +0100 +++ new/hcloud-upload-image-1.1.0/.github/release-please-manifest.json 1970-01-01 01:00:00.000000000 +0100 @@ -1 +0,0 @@ -{".":"0.3.1","hcloudimages":"0.3.1"} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hcloud-upload-image-0.3.1/.github/workflows/ci.yaml new/hcloud-upload-image-1.1.0/.github/workflows/ci.yaml --- old/hcloud-upload-image-0.3.1/.github/workflows/ci.yaml 2024-12-07 01:57:59.000000000 +0100 +++ new/hcloud-upload-image-1.1.0/.github/workflows/ci.yaml 1970-01-01 01:00:00.000000000 +0100 @@ -1,85 +0,0 @@ -name: ci - -on: - push: - branches: [main] - pull_request: - -jobs: - lint: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version-file: go.mod - - - name: Run golangci-lint (CLI) - uses: golangci/golangci-lint-action@v6 - with: - version: v1.62.2 # renovate: datasource=github-releases depName=golangci/golangci-lint - args: --timeout 5m - - - name: Run golangci-lint (Lib) - uses: golangci/golangci-lint-action@v6 - with: - version: v1.62.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 - - - name: Set up Go - uses: actions/setup-go@v5 - 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 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version-file: go.mod - - - name: Run go mod tidy - run: go mod tidy - - - name: Check uncommitted changes - run: git diff --exit-code - - - 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@v4 - - - name: Set up Go - uses: actions/setup-go@v5 - 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." diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hcloud-upload-image-0.3.1/.github/workflows/release-please.yaml new/hcloud-upload-image-1.1.0/.github/workflows/release-please.yaml --- old/hcloud-upload-image-0.3.1/.github/workflows/release-please.yaml 2024-12-07 01:57:59.000000000 +0100 +++ new/hcloud-upload-image-1.1.0/.github/workflows/release-please.yaml 1970-01-01 01:00:00.000000000 +0100 @@ -1,18 +0,0 @@ -name: release-please - -on: - push: - branches: [main] - -jobs: - release-please: - # Do not run on forks. - if: github.repository == 'apricote/hcloud-upload-image' - - runs-on: ubuntu-latest - steps: - - uses: googleapis/release-please-action@v4 - with: - token: ${{ secrets.RELEASE_GH_TOKEN }} - config-file: .github/release-please-config.json - manifest-file: .github/release-please-manifest.json diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hcloud-upload-image-0.3.1/.github/workflows/release.yaml new/hcloud-upload-image-1.1.0/.github/workflows/release.yaml --- old/hcloud-upload-image-0.3.1/.github/workflows/release.yaml 2024-12-07 01:57:59.000000000 +0100 +++ new/hcloud-upload-image-1.1.0/.github/workflows/release.yaml 1970-01-01 01:00:00.000000000 +0100 @@ -1,27 +0,0 @@ -name: release - -on: - push: - tags: - - "v*.*.*" - -jobs: - release: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version-file: go.mod - - - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v6 - with: - version: "~> v2" - args: release --clean - env: - GITHUB_TOKEN: ${{ secrets.RELEASE_GH_TOKEN }} - AUR_SSH_KEY: ${{ secrets.RELEASE_AUR_SSH_KEY }} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hcloud-upload-image-0.3.1/.gitignore new/hcloud-upload-image-1.1.0/.gitignore --- old/hcloud-upload-image-0.3.1/.gitignore 2024-12-07 01:57:59.000000000 +0100 +++ new/hcloud-upload-image-1.1.0/.gitignore 1970-01-01 01:00:00.000000000 +0100 @@ -1,3 +0,0 @@ - -dist/ -completions/ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hcloud-upload-image-0.3.1/.goreleaser.yaml new/hcloud-upload-image-1.1.0/.goreleaser.yaml --- old/hcloud-upload-image-0.3.1/.goreleaser.yaml 2024-12-07 01:57:59.000000000 +0100 +++ new/hcloud-upload-image-1.1.0/.goreleaser.yaml 2025-05-10 14:27:47.000000000 +0200 @@ -7,7 +7,8 @@ - ./scripts/completions.sh builds: - - env: + - id: default + env: - CGO_ENABLED=0 goos: - linux @@ -21,7 +22,7 @@ - -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 @@ # use zip for windows archives format_overrides: - goos: windows - format: zip + formats: [ "zip" ] files: - README.md @@ -104,9 +105,23 @@ 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 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hcloud-upload-image-0.3.1/CHANGELOG.md new/hcloud-upload-image-1.1.0/CHANGELOG.md --- old/hcloud-upload-image-0.3.1/CHANGELOG.md 2024-12-07 01:57:59.000000000 +0100 +++ new/hcloud-upload-image-1.1.0/CHANGELOG.md 2025-05-10 14:27:47.000000000 +0200 @@ -1,5 +1,34 @@ # Changelog +## [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) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hcloud-upload-image-0.3.1/README.md new/hcloud-upload-image-1.1.0/README.md --- old/hcloud-upload-image-0.3.1/README.md 2024-12-07 01:57:59.000000000 +0100 +++ new/hcloud-upload-image-1.1.0/README.md 2025-05-10 14:27:47.000000000 +0200 @@ -1,6 +1,15 @@ # `hcloud-upload-image` -Quickly upload any raw disk images into your [Hetzner Cloud](https://hetzner.com/cloud) projects! +<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> + ## About @@ -53,6 +62,14 @@ 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 ```shell @@ -63,7 +80,7 @@ --compression bz2 ``` -To learn more, you can use the embedded help output or check out the [CLI help pages in this repository](docs/cli/hcloud-upload-image.md).: +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 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hcloud-upload-image-0.3.1/cmd/root.go new/hcloud-upload-image-1.1.0/cmd/root.go --- old/hcloud-upload-image-0.3.1/cmd/root.go 2024-12-07 01:57:59.000000000 +0100 +++ new/hcloud-upload-image-1.1.0/cmd/root.go 2025-05-10 14:27:47.000000000 +0200 @@ -9,7 +9,6 @@ "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" @@ -89,7 +88,7 @@ opts := []hcloud.ClientOption{ hcloud.WithToken(os.Getenv("HCLOUD_TOKEN")), hcloud.WithApplication("hcloud-upload-image", version.Version), - hcloud.WithPollOpts(hcloud.PollOpts{BackoffFunc: 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 { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hcloud-upload-image-0.3.1/cmd/upload.go new/hcloud-upload-image-1.1.0/cmd/upload.go --- old/hcloud-upload-image-0.3.1/cmd/upload.go 2024-12-07 01:57:59.000000000 +0100 +++ new/hcloud-upload-image-1.1.0/cmd/upload.go 2025-05-10 14:27:47.000000000 +0200 @@ -1,7 +1,9 @@ package cmd import ( + _ "embed" "fmt" + "net/http" "net/url" "os" @@ -16,20 +18,24 @@ uploadFlagImageURL = "image-url" uploadFlagImagePath = "image-path" uploadFlagCompression = "compression" + uploadFlagFormat = "format" uploadFlagArchitecture = "architecture" uploadFlagServerType = "server-type" uploadFlagDescription = "description" uploadFlagLabels = "labels" ) +//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-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", @@ -43,6 +49,7 @@ 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) @@ -50,6 +57,7 @@ options := hcloudimages.UploadOptions{ ImageCompression: hcloudimages.Compression(imageCompression), + ImageFormat: hcloudimages.Format(imageFormat), Description: hcloud.Ptr(description), Labels: labels, } @@ -60,8 +68,26 @@ 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) @@ -101,6 +127,12 @@ cobra.FixedCompletions([]string{string(hcloudimages.CompressionBZ2), string(hcloudimages.CompressionXZ)}, cobra.ShellCompDirectiveNoFileComp), ) + uploadCmd.Flags().String(uploadFlagFormat, "", "Format of the image. [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]") _ = uploadCmd.RegisterFlagCompletionFunc( uploadFlagArchitecture, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hcloud-upload-image-0.3.1/cmd/upload.md new/hcloud-upload-image-1.1.0/cmd/upload.md --- old/hcloud-upload-image-0.3.1/cmd/upload.md 1970-01-01 01:00:00.000000000 +0100 +++ new/hcloud-upload-image-1.1.0/cmd/upload.md 2025-05-10 14:27:47.000000000 +0200 @@ -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. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hcloud-upload-image-0.3.1/docs/.gitignore new/hcloud-upload-image-1.1.0/docs/.gitignore --- old/hcloud-upload-image-0.3.1/docs/.gitignore 1970-01-01 01:00:00.000000000 +0100 +++ new/hcloud-upload-image-1.1.0/docs/.gitignore 2025-05-10 14:27:47.000000000 +0200 @@ -0,0 +1,2 @@ +book +https: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hcloud-upload-image-0.3.1/docs/SUMMARY.md new/hcloud-upload-image-1.1.0/docs/SUMMARY.md --- old/hcloud-upload-image-0.3.1/docs/SUMMARY.md 1970-01-01 01:00:00.000000000 +0100 +++ new/hcloud-upload-image-1.1.0/docs/SUMMARY.md 2025-05-10 14:27:47.000000000 +0200 @@ -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) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hcloud-upload-image-0.3.1/docs/book.toml new/hcloud-upload-image-1.1.0/docs/book.toml --- old/hcloud-upload-image-0.3.1/docs/book.toml 1970-01-01 01:00:00.000000000 +0100 +++ new/hcloud-upload-image-1.1.0/docs/book.toml 2025-05-10 14:27:47.000000000 +0200 @@ -0,0 +1,8 @@ +[book] +language = "en" +multilingual = false +src = "." +title = "hcloud-upload-image" + +[output.html] +git-repository-url = "https://github.com/apricote/hcloud-upload-image" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hcloud-upload-image-0.3.1/docs/changelog-hcloudimages.md new/hcloud-upload-image-1.1.0/docs/changelog-hcloudimages.md --- old/hcloud-upload-image-0.3.1/docs/changelog-hcloudimages.md 1970-01-01 01:00:00.000000000 +0100 +++ new/hcloud-upload-image-1.1.0/docs/changelog-hcloudimages.md 2025-05-10 14:27:47.000000000 +0200 @@ -0,0 +1,3 @@ +# Changelog Library + +{{#include ../hcloudimages/CHANGELOG.md:2: }} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hcloud-upload-image-0.3.1/docs/changelog.md new/hcloud-upload-image-1.1.0/docs/changelog.md --- old/hcloud-upload-image-0.3.1/docs/changelog.md 1970-01-01 01:00:00.000000000 +0100 +++ new/hcloud-upload-image-1.1.0/docs/changelog.md 2025-05-10 14:27:47.000000000 +0200 @@ -0,0 +1,3 @@ +# Changelog CLI + +{{#include ../CHANGELOG.md:2: }} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hcloud-upload-image-0.3.1/docs/cli/hcloud-upload-image.md new/hcloud-upload-image-1.1.0/docs/cli/hcloud-upload-image.md --- old/hcloud-upload-image-0.3.1/docs/cli/hcloud-upload-image.md 2024-12-07 01:57:59.000000000 +0100 +++ new/hcloud-upload-image-1.1.0/docs/cli/hcloud-upload-image.md 1970-01-01 01:00:00.000000000 +0100 @@ -1,20 +0,0 @@ -## 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. - diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hcloud-upload-image-0.3.1/docs/cli/hcloud-upload-image_cleanup.md new/hcloud-upload-image-1.1.0/docs/cli/hcloud-upload-image_cleanup.md --- old/hcloud-upload-image-0.3.1/docs/cli/hcloud-upload-image_cleanup.md 2024-12-07 01:57:59.000000000 +0100 +++ new/hcloud-upload-image-1.1.0/docs/cli/hcloud-upload-image_cleanup.md 1970-01-01 01:00:00.000000000 +0100 @@ -1,38 +0,0 @@ -## 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. - diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hcloud-upload-image-0.3.1/docs/cli/hcloud-upload-image_upload.md new/hcloud-upload-image-1.1.0/docs/cli/hcloud-upload-image_upload.md --- old/hcloud-upload-image-0.3.1/docs/cli/hcloud-upload-image_upload.md 2024-12-07 01:57:59.000000000 +0100 +++ new/hcloud-upload-image-1.1.0/docs/cli/hcloud-upload-image_upload.md 1970-01-01 01:00:00.000000000 +0100 @@ -1,43 +0,0 @@ -## 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. - -``` -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 -``` - -### 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] - --description string Description for the resulting image - -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 []) - --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. - diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hcloud-upload-image-0.3.1/docs/guides/README.md new/hcloud-upload-image-1.1.0/docs/guides/README.md --- old/hcloud-upload-image-0.3.1/docs/guides/README.md 1970-01-01 01:00:00.000000000 +0100 +++ new/hcloud-upload-image-1.1.0/docs/guides/README.md 2025-05-10 14:27:47.000000000 +0200 @@ -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) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hcloud-upload-image-0.3.1/docs/introduction.md new/hcloud-upload-image-1.1.0/docs/introduction.md --- old/hcloud-upload-image-0.3.1/docs/introduction.md 1970-01-01 01:00:00.000000000 +0100 +++ new/hcloud-upload-image-1.1.0/docs/introduction.md 2025-05-10 14:27:47.000000000 +0200 @@ -0,0 +1,3 @@ +# Introduction + +{{#include ../README.md:2:}} \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hcloud-upload-image-0.3.1/docs/reference/cli/hcloud-upload-image.md new/hcloud-upload-image-1.1.0/docs/reference/cli/hcloud-upload-image.md --- old/hcloud-upload-image-0.3.1/docs/reference/cli/hcloud-upload-image.md 1970-01-01 01:00:00.000000000 +0100 +++ new/hcloud-upload-image-1.1.0/docs/reference/cli/hcloud-upload-image.md 2025-05-10 14:27:47.000000000 +0200 @@ -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. + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hcloud-upload-image-0.3.1/docs/reference/cli/hcloud-upload-image_cleanup.md new/hcloud-upload-image-1.1.0/docs/reference/cli/hcloud-upload-image_cleanup.md --- old/hcloud-upload-image-0.3.1/docs/reference/cli/hcloud-upload-image_cleanup.md 1970-01-01 01:00:00.000000000 +0100 +++ new/hcloud-upload-image-1.1.0/docs/reference/cli/hcloud-upload-image_cleanup.md 2025-05-10 14:27:47.000000000 +0200 @@ -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. + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hcloud-upload-image-0.3.1/docs/reference/cli/hcloud-upload-image_upload.md new/hcloud-upload-image-1.1.0/docs/reference/cli/hcloud-upload-image_upload.md --- old/hcloud-upload-image-0.3.1/docs/reference/cli/hcloud-upload-image_upload.md 1970-01-01 01:00:00.000000000 +0100 +++ new/hcloud-upload-image-1.1.0/docs/reference/cli/hcloud-upload-image_upload.md 2025-05-10 14:27:47.000000000 +0200 @@ -0,0 +1,56 @@ +## 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] + --description string Description for the resulting image + --format string Format of the image. [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 []) + --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. + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hcloud-upload-image-0.3.1/docs/reference/go-library.md new/hcloud-upload-image-1.1.0/docs/reference/go-library.md --- old/hcloud-upload-image-0.3.1/docs/reference/go-library.md 1970-01-01 01:00:00.000000000 +0100 +++ new/hcloud-upload-image-1.1.0/docs/reference/go-library.md 2025-05-10 14:27:47.000000000 +0200 @@ -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). diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hcloud-upload-image-0.3.1/go.mod new/hcloud-upload-image-1.1.0/go.mod --- old/hcloud-upload-image-0.3.1/go.mod 2024-12-07 01:57:59.000000000 +0100 +++ new/hcloud-upload-image-1.1.0/go.mod 2025-05-10 14:27:47.000000000 +0200 @@ -1,31 +1,32 @@ module github.com/apricote/hcloud-upload-image -go 1.22.2 +go 1.23.0 + +toolchain go1.24.3 require ( - github.com/apricote/hcloud-upload-image/hcloudimages v0.3.0 - github.com/hetznercloud/hcloud-go/v2 v2.17.0 - github.com/spf13/cobra v1.8.1 + github.com/apricote/hcloud-upload-image/hcloudimages v1.1.0 + github.com/hetznercloud/hcloud-go/v2 v2.21.0 + github.com/spf13/cobra v1.9.1 ) require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/klauspost/compress v1.17.9 // indirect + github.com/klauspost/compress v1.17.11 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/prometheus/client_golang v1.20.5 // indirect + github.com/prometheus/client_golang v1.21.1 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/common v0.62.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect - github.com/stretchr/testify v1.10.0 // indirect - golang.org/x/crypto v0.30.0 // indirect - golang.org/x/net v0.30.0 // indirect - golang.org/x/sys v0.28.0 // indirect - golang.org/x/text v0.21.0 // indirect - google.golang.org/protobuf v1.34.2 // indirect + github.com/spf13/pflag v1.0.6 // indirect + golang.org/x/crypto v0.37.0 // indirect + golang.org/x/net v0.38.0 // indirect + golang.org/x/sys v0.32.0 // indirect + golang.org/x/text v0.24.0 // indirect + google.golang.org/protobuf v1.36.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hcloud-upload-image-0.3.1/go.sum new/hcloud-upload-image-1.1.0/go.sum --- old/hcloud-upload-image-0.3.1/go.sum 2024-12-07 01:57:59.000000000 +0100 +++ new/hcloud-upload-image-1.1.0/go.sum 2025-05-10 14:27:47.000000000 +0200 @@ -1,21 +1,21 @@ -github.com/apricote/hcloud-upload-image/hcloudimages v0.3.0 h1:POoU0nKeTq3fw7Ok/BTwJ/I3CTKhcChtn8L+Ecpba5U= -github.com/apricote/hcloud-upload-image/hcloudimages v0.3.0/go.mod h1:9W0XQVhzb9x69UBXXR0nxF0taC6s6fl0/nemDGUVrfI= +github.com/apricote/hcloud-upload-image/hcloudimages v1.1.0 h1:1guW0IO2/070qbaP6zzNJJ8XsGLKPpxyE1W6fyf7MPc= +github.com/apricote/hcloud-upload-image/hcloudimages v1.1.0/go.mod h1:iJ95BaLfISZBY9X8K2Y2A5a49dI0RLjAuq+4BqlOSgA= 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.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.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +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.17.0 h1:ge0w2piey9SV6XGyU/wQ6HBR24QyMbJ3wLzezplqR68= -github.com/hetznercloud/hcloud-go/v2 v2.17.0/go.mod h1:zfyZ4Orx+mPpYDzWAxXR7DHGL50nnlZ5Edzgs1o6f/s= +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.21.0 h1:wUpQT+fgAxIcdMtFvuCJ78ziqc/VARubpOQPQyj4Q84= +github.com/hetznercloud/hcloud-go/v2 v2.21.0/go.mod h1:WSM7w+9tT86sJTNcF8a/oHljC3HUmQfcLxYsgx6PpSc= 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.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= 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= @@ -26,36 +26,36 @@ 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.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= -github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk= +github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= -github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= +github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= 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.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -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/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY= -golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= -golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= +golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= +google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 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= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hcloud-upload-image-0.3.1/hcloudimages/CHANGELOG.md new/hcloud-upload-image-1.1.0/hcloudimages/CHANGELOG.md --- old/hcloud-upload-image-0.3.1/hcloudimages/CHANGELOG.md 2024-12-07 01:57:59.000000000 +0100 +++ new/hcloud-upload-image-1.1.0/hcloudimages/CHANGELOG.md 2025-05-10 14:27:47.000000000 +0200 @@ -1,5 +1,31 @@ # Changelog +## [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) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hcloud-upload-image-0.3.1/hcloudimages/backoff/backoff.go new/hcloud-upload-image-1.1.0/hcloudimages/backoff/backoff.go --- old/hcloud-upload-image-0.3.1/hcloudimages/backoff/backoff.go 2024-12-07 01:57:59.000000000 +0100 +++ new/hcloud-upload-image-1.1.0/hcloudimages/backoff/backoff.go 2025-05-10 14:27:47.000000000 +0200 @@ -16,6 +16,10 @@ // 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 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hcloud-upload-image-0.3.1/hcloudimages/client.go new/hcloud-upload-image-1.1.0/hcloudimages/client.go --- old/hcloud-upload-image-0.3.1/hcloudimages/client.go 2024-12-07 01:57:59.000000000 +0100 +++ new/hcloud-upload-image-1.1.0/hcloudimages/client.go 2025-05-10 14:27:47.000000000 +0200 @@ -43,6 +43,10 @@ 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,10 +60,14 @@ // 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. @@ -101,6 +109,19 @@ // zip,zstd ) +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. func NewClient(c *hcloud.Client) *Client { return &Client{ @@ -134,6 +155,19 @@ 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") privateKey, publicKey, err := sshutil.GenerateKeyPair() @@ -282,7 +316,7 @@ 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) @@ -293,50 +327,44 @@ 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") - 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) - } + 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) } - cmd += "dd of=/dev/sda bs=4M && sync" + // 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 + } - // Make sure that we fail early, ie. if the image url does not work. - // the pipefail does not work correctly without wrapping in bash. - cmd = fmt.Sprintf("bash -c 'set -euo pipefail && %s'", cmd) 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, @@ -481,3 +509,39 @@ 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 | " + 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 +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hcloud-upload-image-0.3.1/hcloudimages/client_test.go new/hcloud-upload-image-1.1.0/hcloudimages/client_test.go --- old/hcloud-upload-image-0.3.1/hcloudimages/client_test.go 2024-12-07 01:57:59.000000000 +0100 +++ new/hcloud-upload-image-1.1.0/hcloudimages/client_test.go 2025-05-10 14:27:47.000000000 +0200 @@ -1,33 +1,110 @@ -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) - } + return u +} - fmt.Printf("Uploaded Image: %d", image.ID) +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 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: CompressionXZ, + }, + want: "bash -c 'set -euo pipefail && wget --no-verbose -O - \"https://example.com/image.bz2\" | xz -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) + } + }) + } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hcloud-upload-image-0.3.1/hcloudimages/doc_test.go new/hcloud-upload-image-1.1.0/hcloudimages/doc_test.go --- old/hcloud-upload-image-0.3.1/hcloudimages/doc_test.go 1970-01-01 01:00:00.000000000 +0100 +++ new/hcloud-upload-image-1.1.0/hcloudimages/doc_test.go 2025-05-10 14:27:47.000000000 +0200 @@ -0,0 +1,33 @@ +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, + }) + if err != nil { + panic(err) + } + + fmt.Printf("Uploaded Image: %d", image.ID) +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hcloud-upload-image-0.3.1/hcloudimages/go.mod new/hcloud-upload-image-1.1.0/hcloudimages/go.mod --- old/hcloud-upload-image-0.3.1/hcloudimages/go.mod 2024-12-07 01:57:59.000000000 +0100 +++ new/hcloud-upload-image-1.1.0/hcloudimages/go.mod 2025-05-10 14:27:47.000000000 +0200 @@ -1,27 +1,29 @@ module github.com/apricote/hcloud-upload-image/hcloudimages -go 1.22.2 +go 1.23.0 + +toolchain go1.24.3 require ( - github.com/hetznercloud/hcloud-go/v2 v2.17.0 + github.com/hetznercloud/hcloud-go/v2 v2.21.0 github.com/stretchr/testify v1.10.0 - golang.org/x/crypto v0.30.0 + golang.org/x/crypto v0.37.0 ) require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/klauspost/compress v1.17.9 // indirect + github.com/klauspost/compress v1.17.11 // 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.20.5 // indirect + github.com/prometheus/client_golang v1.21.1 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/common v0.62.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect - golang.org/x/net v0.30.0 // indirect - golang.org/x/sys v0.28.0 // indirect - golang.org/x/text v0.21.0 // indirect - google.golang.org/protobuf v1.34.2 // indirect + golang.org/x/net v0.38.0 // indirect + golang.org/x/sys v0.32.0 // indirect + golang.org/x/text v0.24.0 // indirect + google.golang.org/protobuf v1.36.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hcloud-upload-image-0.3.1/hcloudimages/go.sum new/hcloud-upload-image-1.1.0/hcloudimages/go.sum --- old/hcloud-upload-image-0.3.1/hcloudimages/go.sum 2024-12-07 01:57:59.000000000 +0100 +++ new/hcloud-upload-image-1.1.0/hcloudimages/go.sum 2025-05-10 14:27:47.000000000 +0200 @@ -4,12 +4,12 @@ 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.17.0 h1:ge0w2piey9SV6XGyU/wQ6HBR24QyMbJ3wLzezplqR68= -github.com/hetznercloud/hcloud-go/v2 v2.17.0/go.mod h1:zfyZ4Orx+mPpYDzWAxXR7DHGL50nnlZ5Edzgs1o6f/s= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +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.21.0 h1:wUpQT+fgAxIcdMtFvuCJ78ziqc/VARubpOQPQyj4Q84= +github.com/hetznercloud/hcloud-go/v2 v2.21.0/go.mod h1:WSM7w+9tT86sJTNcF8a/oHljC3HUmQfcLxYsgx6PpSc= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= 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= @@ -20,30 +20,30 @@ 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.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= -github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk= +github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= -github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= +github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= 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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY= -golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= -golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= +golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= +google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 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= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hcloud-upload-image-0.3.1/hcloudimages/internal/control/retry.go new/hcloud-upload-image-1.1.0/hcloudimages/internal/control/retry.go --- old/hcloud-upload-image-0.3.1/hcloudimages/internal/control/retry.go 2024-12-07 01:57:59.000000000 +0100 +++ new/hcloud-upload-image-1.1.0/hcloudimages/internal/control/retry.go 2025-05-10 14:27:47.000000000 +0200 @@ -8,7 +8,8 @@ "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 @@ 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 { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hcloud-upload-image-0.3.1/hcloudimages/internal/sshsession/session.go new/hcloud-upload-image-1.1.0/hcloudimages/internal/sshsession/session.go --- old/hcloud-upload-image-0.3.1/hcloudimages/internal/sshsession/session.go 2024-12-07 01:57:59.000000000 +0100 +++ new/hcloud-upload-image-1.1.0/hcloudimages/internal/sshsession/session.go 2025-05-10 14:27:47.000000000 +0200 @@ -12,7 +12,7 @@ if err != nil { return nil, err } - defer sess.Close() + defer func() { _ = sess.Close() }() if stdin != nil { sess.Stdin = stdin diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hcloud-upload-image-0.3.1/internal/version/version.go new/hcloud-upload-image-1.1.0/internal/version/version.go --- old/hcloud-upload-image-0.3.1/internal/version/version.go 2024-12-07 01:57:59.000000000 +0100 +++ new/hcloud-upload-image-1.1.0/internal/version/version.go 2025-05-10 14:27:47.000000000 +0200 @@ -2,7 +2,7 @@ var ( // version is a semver version (https://semver.org). - version = "0.3.1" // x-release-please-version + version = "1.1.0" // x-release-please-version // versionPrerelease is a semver version pre-release identifier (https://semver.org). // diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hcloud-upload-image-0.3.1/renovate.json new/hcloud-upload-image-1.1.0/renovate.json --- old/hcloud-upload-image-0.3.1/renovate.json 2024-12-07 01:57:59.000000000 +0100 +++ new/hcloud-upload-image-1.1.0/renovate.json 2025-05-10 14:27:47.000000000 +0200 @@ -11,12 +11,15 @@ "gomodTidy", "gomodUpdateImportPaths" ], + "goGetDirs": ["./...", "./hcloudimages/..."], "customManagers": [ { "customType": "regex", - "fileMatch": ["^\\.github\\/(?:workflows|actions)\\/.+\\.ya?ml$"], + "fileMatch": [ + "^\\.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-]+))?" ] } ] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hcloud-upload-image-0.3.1/scripts/cli-help-pages.go new/hcloud-upload-image-1.1.0/scripts/cli-help-pages.go --- old/hcloud-upload-image-0.3.1/scripts/cli-help-pages.go 2024-12-07 01:57:59.000000000 +0100 +++ new/hcloud-upload-image-1.1.0/scripts/cli-help-pages.go 2025-05-10 14:27:47.000000000 +0200 @@ -11,7 +11,7 @@ func run() error { // Define the directory where the docs will be generated - dir := "docs/cli" + dir := "docs/reference/cli" // Ensure the directory exists if err := os.MkdirAll(dir, 0755); err != nil { ++++++ hcloud-upload-image.obsinfo ++++++ --- /var/tmp/diff_new_pack.9Zo9Y9/_old 2025-05-12 16:51:25.711094805 +0200 +++ /var/tmp/diff_new_pack.9Zo9Y9/_new 2025-05-12 16:51:25.715094973 +0200 @@ -1,5 +1,5 @@ name: hcloud-upload-image -version: 0.3.1 -mtime: 1733533079 -commit: b328867f6b9b965dcd581cb6f6907dae364faa7a +version: 1.1.0 +mtime: 1746880067 +commit: b9af8855d5c0c1e40ddfaa670296731b2ed0c53e ++++++ vendor.tar.gz ++++++ ++++ 19544 lines of diff (skipped)