diff --git a/.github/release-please-manifest.json b/.github/release-please-manifest.json index 35ef85ad..d439cae1 100644 --- a/.github/release-please-manifest.json +++ b/.github/release-please-manifest.json @@ -1 +1 @@ -{".":"2.8.0"} +{".":"2.9.0"} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 17a4610b..f614b31a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,9 +16,9 @@ jobs: with: go-version: "1.22" - - uses: golangci/golangci-lint-action@v5 + - uses: golangci/golangci-lint-action@v6 with: - version: v1.58.0 # renovate: datasource=github-releases depName=golangci/golangci-lint + version: v1.59.0 # renovate: datasource=github-releases depName=golangci/golangci-lint test: strategy: diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 04ad7c80..553d6989 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: google-github-actions/release-please-action@v4 + - uses: googleapis/release-please-action@v4 with: config-file: .github/release-please-config.json manifest-file: .github/release-please-manifest.json diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b7d67d68..4eeb2275 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -11,7 +11,7 @@ variables: test:golangci-lint: stage: test - image: golangci/golangci-lint:v1.58.0 + image: golangci/golangci-lint:v1.59.0 script: - golangci-lint run -v except: diff --git a/CHANGELOG.md b/CHANGELOG.md index adea8d98..e59ae5b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## [2.9.0](https://github.com/hetznercloud/hcloud-go/compare/v2.8.0...v2.9.0) (2024-05-29) + + +### Features + +* **exp:** add `AppendNextActions` function ([#440](https://github.com/hetznercloud/hcloud-go/issues/440)) ([b07d7ad](https://github.com/hetznercloud/hcloud-go/commit/b07d7adf0bb08dd372f1f6ff630d16af8cb1265c)) +* **exp:** add ssh key functions ([#441](https://github.com/hetznercloud/hcloud-go/issues/441)) ([d766e96](https://github.com/hetznercloud/hcloud-go/commit/d766e96e2220b069a0a8067a3249afe48cf68b2e)) + + +### Bug Fixes + +* **exp:** rename to `sshutils` package name ([#450](https://github.com/hetznercloud/hcloud-go/issues/450)) ([6d4100d](https://github.com/hetznercloud/hcloud-go/commit/6d4100dbceb9a43a66bad3ce91cee039198130c3)) + ## [2.8.0](https://github.com/hetznercloud/hcloud-go/compare/v2.7.2...v2.8.0) (2024-05-06) diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 00000000..8ac9f07f --- /dev/null +++ b/codecov.yml @@ -0,0 +1,2 @@ +ignore: + - "**/zz_*.go" diff --git a/go.mod b/go.mod index d23fbcdb..7f7cae6b 100644 --- a/go.mod +++ b/go.mod @@ -9,10 +9,11 @@ go 1.21 require ( github.com/google/go-cmp v0.6.0 github.com/jmattheis/goverter v1.4.0 - github.com/prometheus/client_golang v1.19.0 + github.com/prometheus/client_golang v1.19.1 github.com/stretchr/testify v1.9.0 github.com/vburenin/ifacemaker v1.2.1 - golang.org/x/net v0.24.0 + golang.org/x/crypto v0.23.0 + golang.org/x/net v0.25.0 ) require ( @@ -28,9 +29,9 @@ require ( github.com/prometheus/procfs v0.12.0 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect golang.org/x/mod v0.14.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect golang.org/x/tools v0.17.0 // indirect - google.golang.org/protobuf v1.32.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 4a96fba9..7c93752a 100644 --- a/go.sum +++ b/go.sum @@ -21,6 +21,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= +github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= +github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= @@ -33,20 +35,30 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/vburenin/ifacemaker v1.2.1 h1:3Vq8B/bfBgjWTkv+jDg4dVL1KHt3k1K4lO7XRxYA2sk= github.com/vburenin/ifacemaker v1.2.1/go.mod h1:5WqrzX2aD7/hi+okBjcaEQJMg4lDGrpuEX3B8L4Wgrs= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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 --git a/hcloud/exp/README.md b/hcloud/exp/README.md new file mode 100644 index 00000000..a9fe3155 --- /dev/null +++ b/hcloud/exp/README.md @@ -0,0 +1,8 @@ +# Experimental + +The `exp` namespace holds experimental features for the `hcloud-go` library. + +> [!CAUTION] +> Breaking changes may occur without notice. Do not use in production! + +When an API reaches a certain level of stability, it may be moved out of the `exp` namespace. diff --git a/hcloud/exp/actionutils/actions.go b/hcloud/exp/actionutils/actions.go new file mode 100644 index 00000000..aa08e5c9 --- /dev/null +++ b/hcloud/exp/actionutils/actions.go @@ -0,0 +1,11 @@ +package actionutils + +import "github.com/hetznercloud/hcloud-go/v2/hcloud" + +// AppendNextActions return the action and the next actions in a new slice. +func AppendNextActions(action *hcloud.Action, nextActions []*hcloud.Action) []*hcloud.Action { + all := make([]*hcloud.Action, 0, 1+len(nextActions)) + all = append(all, action) + all = append(all, nextActions...) + return all +} diff --git a/hcloud/exp/actionutils/actions_test.go b/hcloud/exp/actionutils/actions_test.go new file mode 100644 index 00000000..e31b743f --- /dev/null +++ b/hcloud/exp/actionutils/actions_test.go @@ -0,0 +1,18 @@ +package actionutils + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/hetznercloud/hcloud-go/v2/hcloud" +) + +func TestAppendNextActions(t *testing.T) { + action := &hcloud.Action{ID: 1} + nextActions := []*hcloud.Action{{ID: 2}, {ID: 3}} + + actions := AppendNextActions(action, nextActions) + + assert.Equal(t, []*hcloud.Action{{ID: 1}, {ID: 2}, {ID: 3}}, actions) +} diff --git a/hcloud/exp/doc.go b/hcloud/exp/doc.go new file mode 100644 index 00000000..96506ae2 --- /dev/null +++ b/hcloud/exp/doc.go @@ -0,0 +1,4 @@ +// Package exp is a namespace that holds experimental features for the `hcloud-go` library. +// +// Breaking changes may occur without notice. Do not use in production! +package exp diff --git a/hcloud/exp/kit/sshutils/ssh_key.go b/hcloud/exp/kit/sshutils/ssh_key.go new file mode 100644 index 00000000..01a23312 --- /dev/null +++ b/hcloud/exp/kit/sshutils/ssh_key.go @@ -0,0 +1,86 @@ +package sshutils + +import ( + "crypto" + "crypto/ed25519" + "encoding/pem" + "fmt" + + "golang.org/x/crypto/ssh" +) + +// GenerateKeyPair generates a new ed25519 ssh key pair, and returns the private key and +// the public key respectively. +func GenerateKeyPair() ([]byte, []byte, error) { + pub, priv, err := ed25519.GenerateKey(nil) + if err != nil { + return nil, nil, fmt.Errorf("could not generate key pair: %w", err) + } + + privBytes, err := encodePrivateKey(priv) + if err != nil { + return nil, nil, fmt.Errorf("could not encode private key: %w", err) + } + + pubBytes, err := encodePublicKey(pub) + if err != nil { + return nil, nil, fmt.Errorf("could not encode public key: %w", err) + } + + return privBytes, pubBytes, nil +} + +func encodePrivateKey(priv crypto.PrivateKey) ([]byte, error) { + privPem, err := ssh.MarshalPrivateKey(priv, "") + if err != nil { + return nil, err + } + + return pem.EncodeToMemory(privPem), nil +} + +func encodePublicKey(pub crypto.PublicKey) ([]byte, error) { + sshPub, err := ssh.NewPublicKey(pub) + if err != nil { + return nil, err + } + + return ssh.MarshalAuthorizedKey(sshPub), nil +} + +type privateKeyWithPublicKey interface { + crypto.PrivateKey + Public() crypto.PublicKey +} + +// GeneratePublicKey generate a public key from the provided private key. +func GeneratePublicKey(privBytes []byte) ([]byte, error) { + priv, err := ssh.ParseRawPrivateKey(privBytes) + if err != nil { + return nil, fmt.Errorf("could not decode private key: %w", err) + } + + key, ok := priv.(privateKeyWithPublicKey) + if !ok { + return nil, fmt.Errorf("private key doesn't export Public() crypto.PublicKey") + } + + pubBytes, err := encodePublicKey(key.Public()) + if err != nil { + return nil, fmt.Errorf("could not encode public key: %w", err) + } + + return pubBytes, nil +} + +// GetPublicKeyFingerprint generate the finger print for the provided public key. +func GetPublicKeyFingerprint(pubBytes []byte) (string, error) { + pub, _, _, _, err := ssh.ParseAuthorizedKey(pubBytes) + if err != nil { + return "", fmt.Errorf("could not decode public key: %w", err) + } + + fingerprint := ssh.FingerprintLegacyMD5(pub) + + return fingerprint, nil +} diff --git a/hcloud/exp/kit/sshutils/ssh_key_test.go b/hcloud/exp/kit/sshutils/ssh_key_test.go new file mode 100644 index 00000000..3e2413a0 --- /dev/null +++ b/hcloud/exp/kit/sshutils/ssh_key_test.go @@ -0,0 +1,54 @@ +package sshutils + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGenerateKeyPair(t *testing.T) { + privBytes, pubBytes, err := GenerateKeyPair() + assert.Nil(t, err) + + priv := string(privBytes) + pub := string(pubBytes) + + if !(strings.HasPrefix(priv, "-----BEGIN OPENSSH PRIVATE KEY-----\n") && + strings.HasSuffix(priv, "-----END OPENSSH PRIVATE KEY-----\n")) { + assert.Fail(t, "private key is invalid", priv) + } + + if !strings.HasPrefix(pub, "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA") { + assert.Fail(t, "public key is invalid", pub) + } +} + +func TestGeneratePublicKey(t *testing.T) { + privBytes, pubBytesOrig, err := GenerateKeyPair() + require.NoError(t, err) + + pubBytes, err := GeneratePublicKey(privBytes) + require.NoError(t, err) + + pub := string(pubBytes) + priv := string(privBytes) + + if !(strings.HasPrefix(priv, "-----BEGIN OPENSSH PRIVATE KEY-----\n") && + strings.HasSuffix(priv, "-----END OPENSSH PRIVATE KEY-----\n")) { + assert.Fail(t, "private key is invalid", priv) + } + + if !strings.HasPrefix(pub, "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA") { + assert.Fail(t, "public key is invalid", pub) + } + + assert.Equal(t, pubBytesOrig, pubBytes) +} + +func TestGetPublicKeyFingerprint(t *testing.T) { + fingerprint, err := GetPublicKeyFingerprint([]byte(`ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIccHCW76xx2rrPAUrjnuT6IjpEF1O+/U4IByVgv99Oi`)) + require.NoError(t, err) + assert.Equal(t, "77:79:69:b1:4d:c6:b6:45:6a:e9:52:29:04:3e:59:48", fingerprint) +} diff --git a/hcloud/hcloud.go b/hcloud/hcloud.go index 2c6745c2..4f10fdd0 100644 --- a/hcloud/hcloud.go +++ b/hcloud/hcloud.go @@ -2,4 +2,4 @@ package hcloud // Version is the library's version following Semantic Versioning. -const Version = "2.8.0" // x-release-please-version +const Version = "2.9.0" // x-release-please-version