Skip to content

Commit 82a729f

Browse files
authored
Implement vetu login credentials validation and introduce vetu {pull,push} --insecure command-line argument (#27)
* vetu login: validate credentials and support --no-validate * Chunker: fix NPE when Close()'ing a chunker with no Write()s made on it * Chunker: always emit at least one chunk on Close() This may happen when no Write()s were made. * Work around pierrec/lz4#212 This happens when attempting to compress a disk that is empty (0 bytes). * Hide progress bar when the size of disk to be pulled is zero (0 bytes) * vetu {pull,push}: support --insecure * dockerhosts: fix comments
1 parent d294ee4 commit 82a729f

File tree

10 files changed

+181
-49
lines changed

10 files changed

+181
-49
lines changed

go.mod

+5-5
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ require (
77
github.com/distribution/reference v0.5.0
88
github.com/docker/cli v24.0.6+incompatible
99
github.com/dustin/go-humanize v1.0.1
10-
github.com/google/uuid v1.3.1
10+
github.com/google/uuid v1.4.0
1111
github.com/gosuri/uitable v0.0.4
1212
github.com/hashicorp/go-version v1.6.0
1313
github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a
@@ -16,15 +16,15 @@ require (
1616
github.com/otiai10/copy v1.12.0
1717
github.com/pierrec/lz4/v4 v4.1.18
1818
github.com/projectcalico/libcalico-go v1.7.3
19-
github.com/regclient/regclient v0.5.2
19+
github.com/regclient/regclient v0.5.4
2020
github.com/samber/lo v1.38.1
2121
github.com/schollz/progressbar/v3 v3.13.1
2222
github.com/seancfoley/ipaddress-go v1.5.4
23-
github.com/spf13/cobra v1.7.0
23+
github.com/spf13/cobra v1.8.0
2424
github.com/stretchr/testify v1.8.4
2525
github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54
26-
golang.org/x/sys v0.12.0
27-
golang.org/x/term v0.12.0
26+
golang.org/x/sys v0.14.0
27+
golang.org/x/term v0.14.0
2828
gvisor.dev/gvisor v0.0.0-20230926030033-4af66e670562
2929
inet.af/tcpproxy v0.0.0-20221017015627-91f861402626
3030
)

go.sum

+11-11
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
github.com/armon/go-proxyproto v0.0.0-20210323213023-7e956b284f0a/go.mod h1:QmP9hvJ91BbJmGVGSbutW19IC0Q9phDCLGaomwTJbgU=
22
github.com/avast/retry-go/v4 v4.5.1 h1:AxIx0HGi4VZ3I02jr78j5lZ3M6x1E0Ivxa6b0pUUh7o=
33
github.com/avast/retry-go/v4 v4.5.1/go.mod h1:/sipNsvNB3RRuT5iNcb6h73nw3IBmXJ/H3XrCQYSOpc=
4-
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
4+
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
55
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
66
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
77
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -24,8 +24,8 @@ github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
2424
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
2525
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
2626
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
27-
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
28-
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
27+
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
28+
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
2929
github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY=
3030
github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo=
3131
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
@@ -77,8 +77,8 @@ github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7 h1:xoIK0ctDddBMnc74
7777
github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7/go.mod h1:YARuvh7BUWHNhzDq2OM5tzR2RiCcN2D7sapiKyCel/M=
7878
github.com/projectcalico/libcalico-go v1.7.3 h1:qcbxAhsq/5zqZqpHE24VqMHfmoBVdXZV0Kf82+5rbqU=
7979
github.com/projectcalico/libcalico-go v1.7.3/go.mod h1:0b/n/rPzNXjhn4ywFcEJuQdA/5olt9UxFIATz57xkbc=
80-
github.com/regclient/regclient v0.5.2 h1:8iLMsMIbI0/5iNwWUOHn463g3NSy9beLSpmFx/VfXAQ=
81-
github.com/regclient/regclient v0.5.2/go.mod h1:2iTgEKbcEEa2tJr3gWs0s1nkRm4V72XbPVDxmF4biX4=
80+
github.com/regclient/regclient v0.5.4 h1:oJLY/M18jo+Lzg+6KH29ZjzZ8XX5uIybAOTcAqP8hwU=
81+
github.com/regclient/regclient v0.5.4/go.mod h1:604ymXFhwbmWjyfGFp3uF91gfCXIcjWBxJhBg94s9cM=
8282
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
8383
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
8484
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
@@ -93,8 +93,8 @@ github.com/seancfoley/ipaddress-go v1.5.4 h1:ZdjewWC1J2y5ruQjWHwK6rA1tInWB6mz1ft
9393
github.com/seancfoley/ipaddress-go v1.5.4/go.mod h1:fpvVPC+Jso+YEhNcNiww8HQmBgKP8T4T6BTp1SLxxIo=
9494
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
9595
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
96-
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
97-
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
96+
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
97+
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
9898
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
9999
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
100100
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -122,11 +122,11 @@ golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBc
122122
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
123123
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
124124
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
125-
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
126-
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
125+
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
126+
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
127127
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
128-
golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU=
129-
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
128+
golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8=
129+
golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww=
130130
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
131131
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
132132
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

internal/chunker/chunker.go

+22-20
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ type Chunker struct {
2020
initializeWriter InitializeWriterFunc
2121

2222
// State
23-
chunks chan *Chunk
23+
chunks chan *Chunk
24+
emitted bool
2425

2526
// Per-chunk state
2627
buf *bytes.Buffer
@@ -38,7 +39,7 @@ type Chunk struct {
3839
UncompressedDigest digest.Digest
3940
}
4041

41-
func NewChunker(chunkSize int, initializeWriter InitializeWriterFunc) *Chunker {
42+
func NewChunker(chunkSize int, initializeWriter InitializeWriterFunc) (*Chunker, error) {
4243
chunker := &Chunker{
4344
// Settings
4445
chunkSize: chunkSize,
@@ -48,9 +49,11 @@ func NewChunker(chunkSize int, initializeWriter InitializeWriterFunc) *Chunker {
4849
chunks: make(chan *Chunk),
4950
}
5051

51-
chunker.resetPerChunkState()
52+
if err := chunker.resetPerChunkState(); err != nil {
53+
return nil, err
54+
}
5255

53-
return chunker
56+
return chunker, nil
5457
}
5558

5659
func (chunker *Chunker) Write(b []byte) (int, error) {
@@ -73,18 +76,11 @@ func (chunker *Chunker) Write(b []byte) (int, error) {
7376
UncompressedSize: chunker.uncompressedSize,
7477
UncompressedDigest: digest.NewDigest(digest.SHA256, chunker.uncompressedHash),
7578
}
79+
chunker.emitted = true
7680

77-
chunker.resetPerChunkState()
78-
}
79-
80-
// chunker.writer is nil resetPerChunkState(), so initialize it before using it
81-
if chunker.writer == nil {
82-
writer, err := chunker.initializeWriter(chunker.buf)
83-
if err != nil {
81+
if err := chunker.resetPerChunkState(); err != nil {
8482
return 0, err
8583
}
86-
87-
chunker.writer = writer
8884
}
8985

9086
// Update uncompressed chunk size
@@ -114,8 +110,9 @@ func (chunker *Chunker) Close() error {
114110
return err
115111
}
116112

117-
// Only emit a chunk if we have some data available
118-
if chunker.buf.Len() != 0 {
113+
// Only emit a last chunk if we have some data available
114+
// or there were no chunks emitted before
115+
if chunker.buf.Len() != 0 || !chunker.emitted {
119116
chunker.chunks <- &Chunk{
120117
Data: chunker.buf.Bytes(),
121118
UncompressedSize: chunker.uncompressedSize,
@@ -125,14 +122,19 @@ func (chunker *Chunker) Close() error {
125122

126123
close(chunker.chunks)
127124

128-
chunker.resetPerChunkState()
129-
130-
return nil
125+
return chunker.resetPerChunkState()
131126
}
132127

133-
func (chunker *Chunker) resetPerChunkState() {
128+
func (chunker *Chunker) resetPerChunkState() error {
134129
chunker.buf = &bytes.Buffer{}
135130
chunker.uncompressedSize = 0
136131
chunker.uncompressedHash = sha256.New()
137-
chunker.writer = nil
132+
133+
writer, err := chunker.initializeWriter(chunker.buf)
134+
if err != nil {
135+
return err
136+
}
137+
chunker.writer = writer
138+
139+
return nil
138140
}

internal/chunker/chunker_test.go

+32-1
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,10 @@ func TestSimple(t *testing.T) {
2727
})
2828
}
2929

30-
chunker := chunkerpkg.NewChunker(chunkSize, func(w io.Writer) (io.WriteCloser, error) {
30+
chunker, err := chunkerpkg.NewChunker(chunkSize, func(w io.Writer) (io.WriteCloser, error) {
3131
return WriteNopCloser(w), nil
3232
})
33+
require.NoError(t, err)
3334

3435
go func() {
3536
defer chunker.Close()
@@ -51,6 +52,36 @@ func TestSimple(t *testing.T) {
5152
require.Equal(t, expectedChunks, actualChunks)
5253
}
5354

55+
func TestNoWrites(t *testing.T) {
56+
const chunkSize = 1 * 1024 * 1024
57+
58+
// Create a chunker and close it right away without doing any Write()s
59+
chunker, err := chunkerpkg.NewChunker(chunkSize, func(w io.Writer) (io.WriteCloser, error) {
60+
return WriteNopCloser(w), nil
61+
})
62+
require.NoError(t, err)
63+
go func() {
64+
if err := chunker.Close(); err != nil {
65+
panic(err)
66+
}
67+
}()
68+
69+
// Ensure that exactly one empty chunk is emitted as a result of the above
70+
var actualChunks []*chunkerpkg.Chunk
71+
72+
for chunk := range chunker.Chunks() {
73+
actualChunks = append(actualChunks, chunk)
74+
}
75+
76+
require.Equal(t, []*chunkerpkg.Chunk{
77+
{
78+
Data: nil,
79+
UncompressedSize: 0,
80+
UncompressedDigest: digest.FromBytes([]byte{}),
81+
},
82+
}, actualChunks)
83+
}
84+
5485
type writeNopCloser struct {
5586
io.Writer
5687
}

internal/command/login/login.go

+28
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import (
55
"fmt"
66
dockerconfig "github.com/docker/cli/cli/config"
77
"github.com/docker/cli/cli/config/types"
8+
"github.com/regclient/regclient"
9+
"github.com/regclient/regclient/config"
10+
"github.com/regclient/regclient/types/ref"
811
"github.com/spf13/cobra"
912
"golang.org/x/term"
1013
"io"
@@ -15,6 +18,7 @@ import (
1518

1619
var username string
1720
var passwordStdin bool
21+
var noValidate bool
1822

1923
func NewCommand() *cobra.Command {
2024
cmd := &cobra.Command{
@@ -30,6 +34,8 @@ func NewCommand() *cobra.Command {
3034
cmd.Flags().BoolVar(&passwordStdin, "password-stdin", false,
3135
"receive the password from the standard input instead of asking it interactively "+
3236
"(requires --username)")
37+
cmd.Flags().BoolVar(&noValidate, "no-validate", false,
38+
"skip validation of the OCI registry's credentials before logging-in")
3339

3440
return cmd
3541
}
@@ -43,6 +49,28 @@ func runLogin(cmd *cobra.Command, args []string) error {
4349
return err
4450
}
4551

52+
if !noValidate {
53+
// Create an OCI registry client that only has the provided credentials
54+
client := regclient.New(regclient.WithConfigHost(config.Host{
55+
Name: registry,
56+
Hostname: registry,
57+
User: username,
58+
Pass: password,
59+
}))
60+
61+
// Create a reference that only has the Registry field set
62+
reference, err := ref.NewHost(registry)
63+
if err != nil {
64+
return err
65+
}
66+
67+
// Validate credentials
68+
_, err = client.Ping(cmd.Context(), reference)
69+
if err != nil {
70+
return err
71+
}
72+
}
73+
4674
// Store credentials
4775
configFile, err := dockerconfig.Load("")
4876
if err != nil {

internal/command/pull/pull.go

+15-5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package pull
33
import (
44
"errors"
55
"fmt"
6+
"github.com/cirruslabs/vetu/internal/dockerhosts"
67
"github.com/cirruslabs/vetu/internal/name/remotename"
78
"github.com/cirruslabs/vetu/internal/oci"
89
"github.com/cirruslabs/vetu/internal/storage/remote"
@@ -13,6 +14,7 @@ import (
1314
)
1415

1516
var concurrency uint8
17+
var insecure bool
1618

1719
func NewCommand() *cobra.Command {
1820
cmd := &cobra.Command{
@@ -24,6 +26,8 @@ func NewCommand() *cobra.Command {
2426

2527
cmd.Flags().Uint8Var(&concurrency, "concurrency", 4,
2628
"network concurrency to use when pulling a remote VM from the OCI-compatible registry")
29+
cmd.Flags().BoolVar(&insecure, "insecure", false,
30+
"connect to the OCI registry via insecure HTTP protocol")
2731

2832
return cmd
2933
}
@@ -43,21 +47,27 @@ func runPull(cmd *cobra.Command, args []string) error {
4347
return err
4448
}
4549

50+
// Convert remoteName to ref.Ref that is used in github.com/regclient/regclient
51+
reference, err := ref.New(remoteName.String())
52+
if err != nil {
53+
return err
54+
}
55+
4656
// Initialize a temporary directory to which we'll first pull the VM image
4757
vmDir, err := temporary.Create()
4858
if err != nil {
4959
return err
5060
}
5161

52-
// Initialize OCI registry client and convert remote name to a reference
53-
client := regclient.New(regclient.WithDockerCreds())
54-
55-
// Convert remoteName to ref.Ref that is used in github.com/regclient/regclient
56-
reference, err := ref.New(remoteName.String())
62+
// Load hosts from the Docker configuration file
63+
hosts, err := dockerhosts.Load(reference, insecure)
5764
if err != nil {
5865
return err
5966
}
6067

68+
// Initialize OCI registry client
69+
client := regclient.New(regclient.WithConfigHost(hosts...))
70+
6171
// Resolve the reference to a manifest
6272
fmt.Printf("pulling %s...\n", reference.CommonName())
6373

internal/command/push/push.go

+14-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package push
22

33
import (
4+
"github.com/cirruslabs/vetu/internal/dockerhosts"
45
"github.com/cirruslabs/vetu/internal/name/localname"
56
"github.com/cirruslabs/vetu/internal/name/remotename"
67
"github.com/cirruslabs/vetu/internal/oci"
@@ -12,6 +13,7 @@ import (
1213
)
1314

1415
var populateCache bool
16+
var insecure bool
1517

1618
func NewCommand() *cobra.Command {
1719
cmd := &cobra.Command{
@@ -23,6 +25,8 @@ func NewCommand() *cobra.Command {
2325

2426
cmd.Flags().BoolVar(&populateCache, "populate-cache", false, "cache pushed image locally, "+
2527
"increases disk usage, but saves time if you're going to pull the pushed images shortly thereafter")
28+
cmd.Flags().BoolVar(&insecure, "insecure", false,
29+
"connect to the OCI registry via insecure HTTP protocol")
2630

2731
return cmd
2832
}
@@ -48,15 +52,21 @@ func runPush(cmd *cobra.Command, args []string) error {
4852
return err
4953
}
5054

51-
// Initialize OCI registry client
52-
client := regclient.New(regclient.WithDockerCreds())
53-
54-
// Convert remoteName to ref.Ref that is used in github.com/regclient/regclient
55+
// Convert dstRemoteName to ref.Ref that is used in github.com/regclient/regclient
5556
reference, err := ref.New(dstRemoteName.String())
5657
if err != nil {
5758
return err
5859
}
5960

61+
// Load hosts from the Docker configuration file
62+
hosts, err := dockerhosts.Load(reference, insecure)
63+
if err != nil {
64+
return err
65+
}
66+
67+
// Initialize OCI registry client
68+
client := regclient.New(regclient.WithConfigHost(hosts...))
69+
6070
// Push the VM image
6171
digest, err := oci.PushVMDirectory(cmd.Context(), client, vmDir, reference)
6272
if err != nil {

0 commit comments

Comments
 (0)