diff --git a/go.mod b/go.mod index 15fa75ce6..2def3ea04 100644 --- a/go.mod +++ b/go.mod @@ -50,6 +50,7 @@ require ( github.com/opencontainers/image-spec v1.0.1 // indirect github.com/opencontainers/runc v1.0.0-rc8 // indirect github.com/opencontainers/runtime-spec v1.0.1 + github.com/pin/tftp v2.1.0+incompatible github.com/prometheus/procfs v0.0.6 github.com/ryanuber/columnize v2.1.0+incompatible github.com/spf13/cobra v0.0.5 diff --git a/go.sum b/go.sum index a362a659e..50bc90e59 100644 --- a/go.sum +++ b/go.sum @@ -362,6 +362,8 @@ github.com/pborman/uuid v0.0.0-20150603214016-ca53cad383ca h1:dKRMHfduZ/ZqOHuYGk github.com/pborman/uuid v0.0.0-20150603214016-ca53cad383ca/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pin/tftp v2.1.0+incompatible h1:Yng4J7jv6lOc6IF4XoB5mnd3P7ZrF60XQq+my3FAMus= +github.com/pin/tftp v2.1.0+incompatible/go.mod h1:xVpZOMCXTy+A5QMjEVN0Glwa1sUvaJhFXbr/aAxuxGY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/hack/test/integration/dnsmasq/tftpboot/.gitkeep b/hack/test/integration/dnsmasq/tftpboot/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/hack/test/integration/docker-compose.yaml b/hack/test/integration/docker-compose.yaml index 27c4f1eef..17a6b4e5e 100644 --- a/hack/test/integration/docker-compose.yaml +++ b/hack/test/integration/docker-compose.yaml @@ -10,6 +10,7 @@ services: ipv4_address: 172.28.1.1 volumes: - ./dnsmasq/talos0.conf:/etc/dnsmasq.conf:Z + - ./dnsmasq/tftpboot:/var/lib/tftpboot cap_add: - NET_ADMIN restart: always diff --git a/hack/test/integration/libvirt.sh b/hack/test/integration/libvirt.sh index c1676d94d..551efebbd 100755 --- a/hack/test/integration/libvirt.sh +++ b/hack/test/integration/libvirt.sh @@ -53,7 +53,7 @@ function up { echo ${INSTALLER} cp $PWD/../../../build/initramfs.xz ./matchbox/assets/ cp $PWD/../../../build/vmlinuz ./matchbox/assets/ - cd matchbox/assets + cd ./matchbox/assets $PWD/../../../../../build/osctl-linux-amd64 config generate --install-image ${INSTALLER} integration-test https://kubernetes.talos.dev:6443 yq w -i init.yaml machine.install.extraKernelArgs[+] 'console=ttyS0' yq w -i init.yaml cluster.network.cni.name 'custom' diff --git a/pkg/download/download.go b/pkg/download/download.go index c4980b00e..8fb68587c 100644 --- a/pkg/download/download.go +++ b/pkg/download/download.go @@ -67,8 +67,9 @@ func Download(endpoint string, opts ...Option) (b []byte, err error) { opt(dlOpts) } - req, err := http.NewRequest(http.MethodGet, u.String(), nil) - if err != nil { + var req *http.Request + + if req, err = http.NewRequest(http.MethodGet, u.String(), nil); err != nil { return b, err } @@ -82,27 +83,26 @@ func Download(endpoint string, opts ...Option) (b []byte, err error) { return retry.ExpectedError(err) } - if dlOpts.Format == b64 { - var b64 []byte - - b64, err = base64.StdEncoding.DecodeString(string(b)) - if err != nil { - return err - } - - b = b64 - } - return nil }) if err != nil { return nil, fmt.Errorf("failed to download config from %q: %w", u.String(), err) } + if dlOpts.Format == b64 { + var b64 []byte + + b64, err = base64.StdEncoding.DecodeString(string(b)) + if err != nil { + return nil, err + } + + b = b64 + } + return b, nil } -// download handles the actual http request func download(req *http.Request) (data []byte, err error) { client := &http.Client{} @@ -124,3 +124,8 @@ func download(req *http.Request) (data []byte, err error) { return data, err } + +func init() { + transport := (http.DefaultTransport.(*http.Transport)) + transport.RegisterProtocol("tftp", NewTFTPTransport()) +} diff --git a/pkg/download/tftp.go b/pkg/download/tftp.go new file mode 100644 index 000000000..b5db34d5a --- /dev/null +++ b/pkg/download/tftp.go @@ -0,0 +1,66 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package download + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + + "github.com/pin/tftp" +) + +// NewTFTPTransport returns an http.RoundTripper capable of handling the TFTP +// protocol. +func NewTFTPTransport() http.RoundTripper { + return &tftpRoundTripper{} +} + +var _ http.RoundTripper = &tftpRoundTripper{} + +type tftpRoundTripper struct{} + +func (t *tftpRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + addr := req.URL.Host + + if req.URL.Port() == "" { + addr += ":69" + } + + c, err := tftp.NewClient(addr) + if err != nil { + return nil, err + } + + w, err := c.Receive(req.URL.Path, "octet") + if err != nil { + return nil, err + } + + buf := &bytes.Buffer{} + + written, err := w.WriteTo(buf) + if err != nil { + return nil, err + } + + if expected, ok := w.(tftp.IncomingTransfer).Size(); ok { + if written != expected { + return nil, fmt.Errorf("expected %d bytes, got %d", expected, written) + } + } + + return &http.Response{ + Status: "200 OK", + StatusCode: 200, + Proto: "TFTP/1.0", + ProtoMajor: 1, + ProtoMinor: 0, + Body: ioutil.NopCloser(buf), + ContentLength: -1, + Request: req, + }, nil +}