mirror of
https://github.com/apricote/hcloud-upload-image.git
synced 2026-01-13 21:31:03 +00:00
feat(cli): upload command
This commit is contained in:
parent
c4280aa898
commit
b6ae95f55b
10 changed files with 516 additions and 0 deletions
40
cmd/hcloud-image/cmd/cleanup.go
Normal file
40
cmd/hcloud-image/cmd/cleanup.go
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
Copyright © 2024 NAME HERE <EMAIL ADDRESS>
|
||||||
|
|
||||||
|
*/
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// cleanupCmd represents the cleanup command
|
||||||
|
var cleanupCmd = &cobra.Command{
|
||||||
|
Use: "cleanup",
|
||||||
|
Short: "A brief description of your command",
|
||||||
|
Long: `A longer description that spans multiple lines and likely contains examples
|
||||||
|
and usage of using your command. For example:
|
||||||
|
|
||||||
|
Cobra is a CLI library for Go that empowers applications.
|
||||||
|
This application is a tool to generate the needed files
|
||||||
|
to quickly create a Cobra application.`,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
fmt.Println("cleanup called")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(cleanupCmd)
|
||||||
|
|
||||||
|
// Here you will define your flags and configuration settings.
|
||||||
|
|
||||||
|
// Cobra supports Persistent Flags which will work for this command
|
||||||
|
// and all subcommands, e.g.:
|
||||||
|
// cleanupCmd.PersistentFlags().String("foo", "", "A help for foo")
|
||||||
|
|
||||||
|
// Cobra supports local flags which will only run when this command
|
||||||
|
// is called directly, e.g.:
|
||||||
|
// cleanupCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||||
|
}
|
||||||
40
cmd/hcloud-image/cmd/list.go
Normal file
40
cmd/hcloud-image/cmd/list.go
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
Copyright © 2024 NAME HERE <EMAIL ADDRESS>
|
||||||
|
|
||||||
|
*/
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// listCmd represents the list command
|
||||||
|
var listCmd = &cobra.Command{
|
||||||
|
Use: "list",
|
||||||
|
Short: "A brief description of your command",
|
||||||
|
Long: `A longer description that spans multiple lines and likely contains examples
|
||||||
|
and usage of using your command. For example:
|
||||||
|
|
||||||
|
Cobra is a CLI library for Go that empowers applications.
|
||||||
|
This application is a tool to generate the needed files
|
||||||
|
to quickly create a Cobra application.`,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
fmt.Println("list called")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(listCmd)
|
||||||
|
|
||||||
|
// Here you will define your flags and configuration settings.
|
||||||
|
|
||||||
|
// Cobra supports Persistent Flags which will work for this command
|
||||||
|
// and all subcommands, e.g.:
|
||||||
|
// listCmd.PersistentFlags().String("foo", "", "A help for foo")
|
||||||
|
|
||||||
|
// Cobra supports local flags which will only run when this command
|
||||||
|
// is called directly, e.g.:
|
||||||
|
// listCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||||
|
}
|
||||||
76
cmd/hcloud-image/cmd/root.go
Normal file
76
cmd/hcloud-image/cmd/root.go
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/hetznercloud/hcloud-go/v2/hcloud"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
hcloud_upload_image "github.com/apricote/hcloud-upload-image"
|
||||||
|
"github.com/apricote/hcloud-upload-image/util/contextlogger"
|
||||||
|
"github.com/apricote/hcloud-upload-image/util/control"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The pre-authenticated client. Set in the root command PersistentPreRun
|
||||||
|
var client hcloud_upload_image.SnapshotClient
|
||||||
|
|
||||||
|
// rootCmd represents the base command when called without any subcommands
|
||||||
|
var rootCmd = &cobra.Command{
|
||||||
|
Use: "hcloud-image",
|
||||||
|
Short: "A brief description of your application",
|
||||||
|
Long: `A longer description that spans multiple lines and likely contains
|
||||||
|
examples and usage of using your application. For example:
|
||||||
|
|
||||||
|
Cobra is a CLI library for Go that empowers applications.
|
||||||
|
This application is a tool to generate the needed files
|
||||||
|
to quickly create a Cobra application.`,
|
||||||
|
// Uncomment the following line if your bare application
|
||||||
|
// has an action associated with it:
|
||||||
|
// Run: func(cmd *cobra.Command, args []string) { },
|
||||||
|
|
||||||
|
PersistentPreRun: func(cmd *cobra.Command, _ []string) {
|
||||||
|
ctx := cmd.Context()
|
||||||
|
|
||||||
|
// Add logger to command context
|
||||||
|
logger := slog.Default()
|
||||||
|
ctx = contextlogger.New(ctx, logger)
|
||||||
|
cmd.SetContext(ctx)
|
||||||
|
|
||||||
|
client = newClient(ctx)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func newClient(ctx context.Context) hcloud_upload_image.SnapshotClient {
|
||||||
|
logger := contextlogger.From(ctx)
|
||||||
|
// Build hcloud-go client
|
||||||
|
if os.Getenv("HCLOUD_TOKEN") == "" {
|
||||||
|
logger.ErrorContext(ctx, "You need to set the HCLOUD_TOKEN environment variable to your Hetzner Cloud API Token.")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
hcloudClient := hcloud.NewClient(
|
||||||
|
hcloud.WithToken(os.Getenv("HCLOUD_TOKEN")),
|
||||||
|
hcloud.WithApplication("hcloud-image", ""),
|
||||||
|
hcloud.WithPollBackoffFunc(control.ExponentialBackoffWithLimit(2, 1*time.Second, 30*time.Second)),
|
||||||
|
// hcloud.WithDebugWriter(os.Stderr),
|
||||||
|
)
|
||||||
|
|
||||||
|
return hcloud_upload_image.New(hcloudClient)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Execute() {
|
||||||
|
err := rootCmd.Execute()
|
||||||
|
if err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.SetErrPrefix("\033[1;31mError:")
|
||||||
|
rootCmd.SetFlagErrorFunc(func(command *cobra.Command, err error) error {
|
||||||
|
return fmt.Errorf("fooo")
|
||||||
|
})
|
||||||
|
}
|
||||||
83
cmd/hcloud-image/cmd/upload.go
Normal file
83
cmd/hcloud-image/cmd/upload.go
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/hetznercloud/hcloud-go/v2/hcloud"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
hcloud_upload_image "github.com/apricote/hcloud-upload-image"
|
||||||
|
"github.com/apricote/hcloud-upload-image/util/contextlogger"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
uploadFlagImageURL = "image-url"
|
||||||
|
uploadFlagCompression = "compression"
|
||||||
|
uploadFlagArchitecture = "architecture"
|
||||||
|
uploadFlagDescription = "description"
|
||||||
|
uploadFlagLabels = "labels"
|
||||||
|
)
|
||||||
|
|
||||||
|
// uploadCmd represents the upload command
|
||||||
|
var uploadCmd = &cobra.Command{
|
||||||
|
Use: "upload",
|
||||||
|
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.`,
|
||||||
|
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
ctx := cmd.Context()
|
||||||
|
logger := contextlogger.From(ctx)
|
||||||
|
|
||||||
|
imageURLString, _ := cmd.Flags().GetString(uploadFlagImageURL)
|
||||||
|
imageCompression, _ := cmd.Flags().GetString(uploadFlagCompression)
|
||||||
|
architecture, _ := cmd.Flags().GetString(uploadFlagArchitecture)
|
||||||
|
description, _ := cmd.Flags().GetString(uploadFlagDescription)
|
||||||
|
labels, _ := cmd.Flags().GetStringToString(uploadFlagLabels)
|
||||||
|
|
||||||
|
imageURL, err := url.Parse(imageURLString)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to parse url from --%s=%q: %w", uploadFlagImageURL, imageURLString, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
image, err := client.Upload(ctx, hcloud_upload_image.UploadOptions{
|
||||||
|
ImageURL: imageURL,
|
||||||
|
ImageCompression: hcloud_upload_image.Compression(imageCompression),
|
||||||
|
Architecture: hcloud.Architecture(architecture),
|
||||||
|
Description: hcloud.Ptr(description),
|
||||||
|
Labels: labels,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to upload the image: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.InfoContext(ctx, "Successfully uploaded the image!", "image", image.ID)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(uploadCmd)
|
||||||
|
|
||||||
|
uploadCmd.Flags().String(uploadFlagImageURL, "", "Remote URL of the disk image that should be uploaded (required)")
|
||||||
|
_ = uploadCmd.MarkFlagRequired(uploadFlagImageURL)
|
||||||
|
|
||||||
|
uploadCmd.Flags().String(uploadFlagCompression, "", "Type of compression that was used on the disk image")
|
||||||
|
_ = uploadCmd.RegisterFlagCompletionFunc(
|
||||||
|
uploadFlagCompression,
|
||||||
|
cobra.FixedCompletions([]string{string(hcloud_upload_image.CompressionBZ2)}, cobra.ShellCompDirectiveNoFileComp),
|
||||||
|
)
|
||||||
|
|
||||||
|
uploadCmd.Flags().String(uploadFlagArchitecture, "", "CPU Architecture of the disk image. Choices: x86|arm")
|
||||||
|
_ = uploadCmd.RegisterFlagCompletionFunc(
|
||||||
|
uploadFlagArchitecture,
|
||||||
|
cobra.FixedCompletions([]string{string(hcloud.ArchitectureX86), string(hcloud.ArchitectureARM)}, cobra.ShellCompDirectiveNoFileComp),
|
||||||
|
)
|
||||||
|
_ = uploadCmd.MarkFlagRequired(uploadFlagArchitecture)
|
||||||
|
|
||||||
|
uploadCmd.Flags().String(uploadFlagDescription, "", "Description for the resulting Image")
|
||||||
|
|
||||||
|
uploadCmd.Flags().StringToString(uploadFlagLabels, map[string]string{}, "Labels for the resulting Image")
|
||||||
|
}
|
||||||
23
cmd/hcloud-image/go.mod
Normal file
23
cmd/hcloud-image/go.mod
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
module github.com/apricote/hcloud-upload-image/cmd/hcloud-image
|
||||||
|
|
||||||
|
go 1.22.2
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/hetznercloud/hcloud-go/v2 v2.7.3-0.20240430130644-7bb1a7b9ae5f
|
||||||
|
github.com/spf13/cobra v1.8.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
|
github.com/prometheus/client_golang v1.19.0 // indirect
|
||||||
|
github.com/prometheus/client_model v0.5.0 // indirect
|
||||||
|
github.com/prometheus/common v0.48.0 // indirect
|
||||||
|
github.com/prometheus/procfs v0.12.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
|
golang.org/x/net v0.24.0 // indirect
|
||||||
|
golang.org/x/sys v0.19.0 // indirect
|
||||||
|
golang.org/x/text v0.14.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.32.0 // indirect
|
||||||
|
)
|
||||||
26
cmd/hcloud-image/go.sum
Normal file
26
cmd/hcloud-image/go.sum
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/hetznercloud/hcloud-go/v2 v2.7.3-0.20240430130644-7bb1a7b9ae5f h1:c1ahn6OKXkSqwOfCoqyFrjVh14BEC9rD3ok0dehbCno=
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
|
||||||
|
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
|
||||||
|
github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
|
||||||
|
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||||
|
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||||
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
|
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||||
|
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||||
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
|
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
17
cmd/hcloud-image/main.go
Normal file
17
cmd/hcloud-image/main.go
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/apricote/hcloud-upload-image/cmd/hcloud-image/cmd"
|
||||||
|
"github.com/apricote/hcloud-upload-image/cmd/hcloud-image/util/ui"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
slog.SetDefault(slog.New(ui.NewHandler(os.Stdout, &ui.HandlerOptions{Level: slog.LevelDebug})))
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cmd.Execute()
|
||||||
|
}
|
||||||
155
cmd/hcloud-image/util/ui/slog_handler.go
Normal file
155
cmd/hcloud-image/util/ui/slog_handler.go
Normal file
|
|
@ -0,0 +1,155 @@
|
||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log/slog"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ansiClear = "\033[0m"
|
||||||
|
ansiBold = "\033[1m"
|
||||||
|
ansiBoldYellow = "\033[1;93m"
|
||||||
|
ansiBoldRed = "\033[1;31m"
|
||||||
|
ansiThinGray = "\033[2;37m"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Handler struct {
|
||||||
|
opts HandlerOptions
|
||||||
|
goas []groupOrAttrs
|
||||||
|
mu *sync.Mutex
|
||||||
|
out io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
type HandlerOptions struct {
|
||||||
|
Level slog.Leveler
|
||||||
|
}
|
||||||
|
|
||||||
|
// groupOrAttrs holds either a group name or a list of [slog.Attr].
|
||||||
|
type groupOrAttrs struct {
|
||||||
|
group string // group name if non-empty
|
||||||
|
attrs []slog.Attr // attrs if non-empty
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ slog.Handler = &Handler{}
|
||||||
|
|
||||||
|
func NewHandler(out io.Writer, opts *HandlerOptions) *Handler {
|
||||||
|
h := &Handler{
|
||||||
|
out: out,
|
||||||
|
mu: &sync.Mutex{},
|
||||||
|
}
|
||||||
|
if opts != nil {
|
||||||
|
h.opts = *opts
|
||||||
|
}
|
||||||
|
if h.opts.Level == nil {
|
||||||
|
h.opts.Level = slog.LevelInfo
|
||||||
|
}
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) Enabled(_ context.Context, level slog.Level) bool {
|
||||||
|
return level >= h.opts.Level.Level()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) Handle(_ context.Context, record slog.Record) error {
|
||||||
|
buf := make([]byte, 0, 512)
|
||||||
|
|
||||||
|
formattingPrefix := ""
|
||||||
|
|
||||||
|
switch record.Level {
|
||||||
|
case slog.LevelInfo:
|
||||||
|
formattingPrefix = ansiBold
|
||||||
|
case slog.LevelWarn:
|
||||||
|
// Bold + Yellow
|
||||||
|
formattingPrefix = ansiBoldYellow
|
||||||
|
case slog.LevelError:
|
||||||
|
// Bold + Red
|
||||||
|
formattingPrefix = ansiBoldRed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print main message in formatted text
|
||||||
|
buf = fmt.Appendf(buf, "%s%s%s", formattingPrefix, record.Message, ansiClear)
|
||||||
|
|
||||||
|
// Add attributes in thin gray
|
||||||
|
buf = fmt.Append(buf, ansiThinGray)
|
||||||
|
|
||||||
|
// Attributes from [WithGroup] and [WithAttrs] calls
|
||||||
|
goas := h.goas
|
||||||
|
if record.NumAttrs() == 0 {
|
||||||
|
for len(goas) > 0 && goas[len(goas)-1].group != "" {
|
||||||
|
goas = goas[:len(goas)-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
group := ""
|
||||||
|
for _, goa := range goas {
|
||||||
|
if goa.group != "" {
|
||||||
|
group = goa.group
|
||||||
|
} else {
|
||||||
|
for _, a := range goa.attrs {
|
||||||
|
buf = h.appendAttr(buf, group, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
record.Attrs(func(a slog.Attr) bool {
|
||||||
|
buf = h.appendAttr(buf, group, a)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
buf = fmt.Appendf(buf, "%s\n", ansiClear)
|
||||||
|
|
||||||
|
h.mu.Lock()
|
||||||
|
defer h.mu.Unlock()
|
||||||
|
_, err := h.out.Write(buf)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) appendAttr(buf []byte, group string, a slog.Attr) []byte {
|
||||||
|
a.Value = a.Value.Resolve()
|
||||||
|
if a.Equal(slog.Attr{}) {
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
if group != "" {
|
||||||
|
group += "."
|
||||||
|
}
|
||||||
|
|
||||||
|
switch a.Value.Kind() {
|
||||||
|
case slog.KindString:
|
||||||
|
buf = fmt.Appendf(buf, " %s%s=%q", group, a.Key, a.Value)
|
||||||
|
case slog.KindAny:
|
||||||
|
if err, ok := a.Value.Any().(error); ok {
|
||||||
|
buf = fmt.Appendf(buf, " %s%s=%q", group, a.Key, err.Error())
|
||||||
|
} else {
|
||||||
|
buf = fmt.Appendf(buf, " %s%s=%s", group, a.Key, a.Value)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
buf = fmt.Appendf(buf, " %s%s=%s", group, a.Key, a.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||||
|
if len(attrs) == 0 {
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
return h.withGroupOrAttrs(groupOrAttrs{attrs: attrs})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) WithGroup(name string) slog.Handler {
|
||||||
|
if name == "" {
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
return h.withGroupOrAttrs(groupOrAttrs{group: name})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) withGroupOrAttrs(goa groupOrAttrs) *Handler {
|
||||||
|
h2 := *h
|
||||||
|
h2.goas = make([]groupOrAttrs, len(h.goas)+1)
|
||||||
|
copy(h2.goas, h.goas)
|
||||||
|
h2.goas[len(h2.goas)-1] = goa
|
||||||
|
return &h2
|
||||||
|
}
|
||||||
6
go.work
Normal file
6
go.work
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
go 1.22.2
|
||||||
|
|
||||||
|
use (
|
||||||
|
.
|
||||||
|
./cmd/hcloud-image
|
||||||
|
)
|
||||||
50
go.work.sum
Normal file
50
go.work.sum
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY=
|
||||||
|
github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE=
|
||||||
|
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc=
|
||||||
|
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
|
||||||
|
github.com/dave/jennifer v1.6.0 h1:MQ/6emI2xM7wt0tJzJzyUik2Q3Tcn2eE0vtYgh4GPVI=
|
||||||
|
github.com/dave/jennifer v1.6.0/go.mod h1:AxTG893FiZKqxy3FP1kL80VMshSMuz2G+EgvszgGRnk=
|
||||||
|
github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU=
|
||||||
|
github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
|
||||||
|
github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA=
|
||||||
|
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||||
|
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||||
|
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
|
github.com/jessevdk/go-flags v1.4.1-0.20181029123624-5de817a9aa20 h1:dAOsPLhnBzIyxu0VvmnKjlNcIlgMK+erD6VRHDtweMI=
|
||||||
|
github.com/jessevdk/go-flags v1.4.1-0.20181029123624-5de817a9aa20/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
|
github.com/jmattheis/goverter v1.4.0 h1:SrboBYMpGkj1XSgFhWwqzdP024zIa1+58YzUm+0jcBE=
|
||||||
|
github.com/jmattheis/goverter v1.4.0/go.mod h1:iVIl/4qItWjWj2g3vjouGoYensJbRqDHpzlEVMHHFeY=
|
||||||
|
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
|
||||||
|
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||||
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
|
||||||
|
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
|
||||||
|
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
|
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||||
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
|
github.com/vburenin/ifacemaker v1.2.1 h1:3Vq8B/bfBgjWTkv+jDg4dVL1KHt3k1K4lO7XRxYA2sk=
|
||||||
|
github.com/vburenin/ifacemaker v1.2.1/go.mod h1:5WqrzX2aD7/hi+okBjcaEQJMg4lDGrpuEX3B8L4Wgrs=
|
||||||
|
github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc=
|
||||||
|
github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
|
||||||
|
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
||||||
|
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
|
golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ=
|
||||||
|
golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o=
|
||||||
|
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||||
|
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||||
|
golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
|
||||||
|
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||||
|
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
Loading…
Add table
Add a link
Reference in a new issue