mirror of
https://github.com/apricote/hcloud-upload-image.git
synced 2026-02-07 02:07:05 +00:00
Compare commits
1 commit
56bd840010
...
3c1e41023b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c1e41023b |
17 changed files with 78 additions and 253 deletions
2
.github/release-please-manifest.json
vendored
2
.github/release-please-manifest.json
vendored
|
|
@ -1 +1 @@
|
|||
{".":"1.1.0","hcloudimages":"1.1.0"}
|
||||
{".":"1.0.0","hcloudimages":"1.0.0"}
|
||||
|
|
|
|||
8
.github/workflows/ci.yaml
vendored
8
.github/workflows/ci.yaml
vendored
|
|
@ -18,15 +18,15 @@ jobs:
|
|||
go-version-file: go.mod
|
||||
|
||||
- name: Run golangci-lint (CLI)
|
||||
uses: golangci/golangci-lint-action@v8
|
||||
uses: golangci/golangci-lint-action@v7
|
||||
with:
|
||||
version: v2.1.6 # renovate: datasource=github-releases depName=golangci/golangci-lint
|
||||
version: v2.1.5 # renovate: datasource=github-releases depName=golangci/golangci-lint
|
||||
args: --timeout 5m
|
||||
|
||||
- name: Run golangci-lint (Lib)
|
||||
uses: golangci/golangci-lint-action@v8
|
||||
uses: golangci/golangci-lint-action@v7
|
||||
with:
|
||||
version: v2.1.6 # renovate: datasource=github-releases depName=golangci/golangci-lint
|
||||
version: v2.1.5 # renovate: datasource=github-releases depName=golangci/golangci-lint
|
||||
args: --timeout 5m
|
||||
working-directory: hcloudimages
|
||||
|
||||
|
|
|
|||
2
.github/workflows/docs.yaml
vendored
2
.github/workflows/docs.yaml
vendored
|
|
@ -19,7 +19,7 @@ jobs:
|
|||
|
||||
- uses: ./.github/actions/setup-mdbook
|
||||
with:
|
||||
version: v0.4.49 # renovate: datasource=github-releases depName=rust-lang/mdbook
|
||||
version: v0.4.48 # renovate: datasource=github-releases depName=rust-lang/mdbook
|
||||
|
||||
- name: Build Book
|
||||
working-directory: docs
|
||||
|
|
|
|||
19
CHANGELOG.md
19
CHANGELOG.md
|
|
@ -1,24 +1,5 @@
|
|||
# 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)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ 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"
|
||||
|
|
@ -88,7 +89,7 @@ func initClient(cmd *cobra.Command, _ []string) {
|
|||
opts := []hcloud.ClientOption{
|
||||
hcloud.WithToken(os.Getenv("HCLOUD_TOKEN")),
|
||||
hcloud.WithApplication("hcloud-upload-image", version.Version),
|
||||
hcloud.WithPollOpts(hcloud.PollOpts{BackoffFunc: hcloud.ExponentialBackoffWithOpts(hcloud.ExponentialBackoffOpts{Multiplier: 2, Base: 1 * time.Second, Cap: 30 * time.Second})}),
|
||||
hcloud.WithPollOpts(hcloud.PollOpts{BackoffFunc: backoff.ExponentialBackoffWithLimit(2, 1*time.Second, 30*time.Second)}),
|
||||
}
|
||||
|
||||
if os.Getenv("HCLOUD_DEBUG") != "" || verbose >= 2 {
|
||||
|
|
|
|||
4
go.mod
4
go.mod
|
|
@ -2,10 +2,10 @@ module github.com/apricote/hcloud-upload-image
|
|||
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.24.3
|
||||
toolchain go1.24.2
|
||||
|
||||
require (
|
||||
github.com/apricote/hcloud-upload-image/hcloudimages v1.1.0
|
||||
github.com/apricote/hcloud-upload-image/hcloudimages v1.0.0
|
||||
github.com/hetznercloud/hcloud-go/v2 v2.21.0
|
||||
github.com/spf13/cobra v1.9.1
|
||||
)
|
||||
|
|
|
|||
4
go.sum
4
go.sum
|
|
@ -1,5 +1,5 @@
|
|||
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/apricote/hcloud-upload-image/hcloudimages v1.0.0 h1:Gq0SSuPCiZFApGQ3SIEEQqD8btD6tRwfOxr+cky7oTo=
|
||||
github.com/apricote/hcloud-upload-image/hcloudimages v1.0.0/go.mod h1:MwBmhSPlwS3S3ynOsFXPbco40Iv3udqhXKz2EXKSpVU=
|
||||
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=
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjH
|
|||
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/apricote/hcloud-upload-image/hcloudimages v1.0.0/go.mod h1:MwBmhSPlwS3S3ynOsFXPbco40Iv3udqhXKz2EXKSpVU=
|
||||
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=
|
||||
|
|
|
|||
|
|
@ -1,24 +1,5 @@
|
|||
# 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)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -16,10 +16,6 @@ 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
|
||||
|
|
|
|||
|
|
@ -316,7 +316,7 @@ func (s *Client) Upload(ctx context.Context, options UploadOptions) (*hcloud.Ima
|
|||
|
||||
err = control.Retry(
|
||||
contextlogger.New(ctx, logger.With("operation", "ssh")),
|
||||
100, // ~ 3 minutes
|
||||
10,
|
||||
func() error {
|
||||
var err error
|
||||
logger.DebugContext(ctx, "trying to connect to server", "ip", server.PublicNet.IPv4.IP)
|
||||
|
|
@ -329,42 +329,55 @@ func (s *Client) Upload(ctx context.Context, options UploadOptions) (*hcloud.Ima
|
|||
}
|
||||
defer func() { _ = sshClient.Close() }()
|
||||
|
||||
// 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)
|
||||
// 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())
|
||||
}
|
||||
|
||||
// 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
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
switch options.ImageFormat {
|
||||
case FormatRaw:
|
||||
cmd += " | dd of=/dev/sda bs=4M"
|
||||
case FormatQCOW2:
|
||||
cmd += " > image.qcow2 && qemu-img dd -f qcow2 -O raw if=image.qcow2 of=/dev/sda bs=4M"
|
||||
}
|
||||
|
||||
cmd += " && sync"
|
||||
|
||||
// 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 7: Finished writing image to disk")
|
||||
output, err := sshsession.Run(sshClient, cmd, options.ImageReader)
|
||||
logger.InfoContext(ctx, "# Step 6: 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)
|
||||
}
|
||||
|
||||
// 8. SSH On Server: Shutdown
|
||||
logger.InfoContext(ctx, "# Step 8: Shutting down server")
|
||||
// 7. SSH On Server: Shutdown
|
||||
logger.InfoContext(ctx, "# Step 7: 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)
|
||||
}
|
||||
|
||||
// 9. Create Image from Server
|
||||
logger.InfoContext(ctx, "# Step 9: Creating Image")
|
||||
// 8. Create Image from Server
|
||||
logger.InfoContext(ctx, "# Step 8: Creating Image")
|
||||
createImageResult, _, err := s.c.Server.CreateImage(ctx, server, &hcloud.ServerCreateImageOpts{
|
||||
Type: hcloud.ImageTypeSnapshot,
|
||||
Description: options.Description,
|
||||
|
|
@ -509,39 +522,3 @@ 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 | "
|
||||
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,110 +1,33 @@
|
|||
package hcloudimages
|
||||
package hcloudimages_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/hetznercloud/hcloud-go/v2/hcloud"
|
||||
|
||||
"github.com/apricote/hcloud-upload-image/hcloudimages"
|
||||
)
|
||||
|
||||
func mustParseURL(s string) *url.URL {
|
||||
u, err := url.Parse(s)
|
||||
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)
|
||||
}
|
||||
|
||||
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 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)
|
||||
}
|
||||
})
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
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)
|
||||
}
|
||||
|
|
@ -2,12 +2,12 @@ module github.com/apricote/hcloud-upload-image/hcloudimages
|
|||
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.24.3
|
||||
toolchain go1.24.2
|
||||
|
||||
require (
|
||||
github.com/hetznercloud/hcloud-go/v2 v2.21.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
golang.org/x/crypto v0.39.0
|
||||
golang.org/x/crypto v0.38.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
|
@ -23,7 +23,7 @@ require (
|
|||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
golang.org/x/net v0.38.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/text v0.26.0 // indirect
|
||||
golang.org/x/text v0.25.0 // indirect
|
||||
google.golang.org/protobuf v1.36.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
|
|||
|
|
@ -32,16 +32,16 @@ github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDN
|
|||
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.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||
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.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
|
||||
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
|
||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||
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=
|
||||
|
|
|
|||
|
|
@ -8,8 +8,7 @@ import (
|
|||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/hetznercloud/hcloud-go/v2/hcloud"
|
||||
|
||||
"github.com/apricote/hcloud-upload-image/hcloudimages/backoff"
|
||||
"github.com/apricote/hcloud-upload-image/hcloudimages/contextlogger"
|
||||
)
|
||||
|
||||
|
|
@ -19,7 +18,7 @@ func Retry(ctx context.Context, maxTries int, f func() error) error {
|
|||
|
||||
var err error
|
||||
|
||||
backoffFunc := hcloud.ExponentialBackoffWithOpts(hcloud.ExponentialBackoffOpts{Multiplier: 2, Base: 200 * time.Millisecond, Cap: 2 * time.Second})
|
||||
backoffFunc := backoff.ExponentialBackoffWithLimit(2, 200*time.Millisecond, 2*time.Second)
|
||||
|
||||
for try := 0; try < maxTries; try++ {
|
||||
if ctx.Err() != nil {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package version
|
|||
|
||||
var (
|
||||
// version is a semver version (https://semver.org).
|
||||
version = "1.1.0" // x-release-please-version
|
||||
version = "1.0.0" // x-release-please-version
|
||||
|
||||
// versionPrerelease is a semver version pre-release identifier (https://semver.org).
|
||||
//
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue