mirror of
https://github.com/hanzoai/visor.git
synced 2026-09-24 17:04:14 +00:00
This ensures that sidecars must be installed from the same release. See issue #13718.
220 lines
6.8 KiB
Go
220 lines
6.8 KiB
Go
// Copyright 2026 The gVisor Authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package cmd
|
|
|
|
import (
|
|
"archive/tar"
|
|
"compress/bzip2"
|
|
"crypto/sha512"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"path"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
|
|
"gvisor.dev/gvisor/runsc/config"
|
|
"gvisor.dev/gvisor/runsc/gvisorbinaries"
|
|
"gvisor.dev/gvisor/runsc/version"
|
|
)
|
|
|
|
// releaseArches maps GOARCH to the architecture names used in release URLs.
|
|
var releaseArches = map[string]string{
|
|
"amd64": "x86_64",
|
|
"arm64": "aarch64",
|
|
}
|
|
|
|
func releaseTarballURL(ver, goarch string) (string, error) {
|
|
if !config.IsReleaseVersion(ver) {
|
|
return "", fmt.Errorf("cannot map version %q to a release download URL; please download sidecar binaries manually", ver)
|
|
}
|
|
arch, ok := releaseArches[goarch]
|
|
if !ok {
|
|
return "", fmt.Errorf("unknown architecture %q", goarch)
|
|
}
|
|
// The release date follows the first dash, whichever the prefix.
|
|
date := ver[strings.IndexByte(ver, '-')+1:]
|
|
return fmt.Sprintf("https://storage.googleapis.com/gvisor/releases/release/%s/%s/gvisor.tar.bz2", date, arch), nil
|
|
}
|
|
|
|
// fetch downloads url to dest using curl or wget, whichever exists.
|
|
// We do this because we cannot link net/http in `runsc`.
|
|
func fetch(url, dest string) error {
|
|
const timeoutSec = 300
|
|
var cmd *exec.Cmd
|
|
if curl, err := exec.LookPath("curl"); err == nil {
|
|
cmd = exec.Command(curl, "-fsSL", "--max-time", fmt.Sprintf("%d", timeoutSec), "-o", dest, url)
|
|
} else if wget, err := exec.LookPath("wget"); err == nil {
|
|
cmd = exec.Command(wget, "-q", "--timeout", fmt.Sprintf("%d", timeoutSec), "-O", dest, url)
|
|
} else {
|
|
return errors.New("cannot download sidecar binaries: neither curl nor wget found in $PATH")
|
|
}
|
|
if out, err := cmd.CombinedOutput(); err != nil {
|
|
return fmt.Errorf("cannot download (%s): %w: %s", strings.Join(cmd.Args, " "), err, out)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// verifySHA512 checks a file against the sha512 checksum file.
|
|
func verifySHA512(file, sumFile string) error {
|
|
sum, err := os.ReadFile(sumFile)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot read checksum file: %w", err)
|
|
}
|
|
fields := strings.Fields(string(sum))
|
|
if len(fields) == 0 {
|
|
return fmt.Errorf("checksum file %q is empty", sumFile)
|
|
}
|
|
f, err := os.Open(file)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot open tarball: %w", err)
|
|
}
|
|
defer f.Close()
|
|
h := sha512.New()
|
|
if _, err := io.Copy(h, f); err != nil {
|
|
return fmt.Errorf("cannot hash tarball: %w", err)
|
|
}
|
|
if got, want := hex.EncodeToString(h.Sum(nil)), fields[0]; !strings.EqualFold(got, want) {
|
|
return fmt.Errorf("SHA-512 mismatch for %q: got %s, expected %s", file, got, want)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// extractSidecars extracts `gvisor-bin/`.
|
|
func extractSidecars(tarball, dir string) error {
|
|
parent := filepath.Dir(dir)
|
|
tmpDir := filepath.Join(parent, ".gvisor-bin.tmp")
|
|
oldDir := filepath.Join(parent, ".gvisor-bin.old")
|
|
if err := os.RemoveAll(tmpDir); err != nil {
|
|
return fmt.Errorf("cannot remove stale directory: %w", err)
|
|
}
|
|
if err := os.RemoveAll(oldDir); err != nil {
|
|
return fmt.Errorf("cannot remove stale directory: %w", err)
|
|
}
|
|
if err := os.Mkdir(tmpDir, 0755); err != nil {
|
|
return fmt.Errorf("cannot create extraction directory: %w", err)
|
|
}
|
|
defer os.RemoveAll(tmpDir)
|
|
f, err := os.Open(tarball)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot open tarball: %w", err)
|
|
}
|
|
defer f.Close()
|
|
tr := tar.NewReader(bzip2.NewReader(f))
|
|
extracted := 0
|
|
for {
|
|
hdr, err := tr.Next()
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
if err != nil {
|
|
return fmt.Errorf("reading %q: %w", tarball, err)
|
|
}
|
|
if hdr.Typeflag != tar.TypeReg {
|
|
continue
|
|
}
|
|
name, ok := strings.CutPrefix(strings.TrimPrefix(path.Clean(hdr.Name), "/"), "gvisor-bin/")
|
|
if !ok || name == "" || strings.Contains(name, "/") {
|
|
continue
|
|
}
|
|
dst, err := os.OpenFile(filepath.Join(tmpDir, name), os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0755)
|
|
if err != nil {
|
|
return fmt.Errorf("extracting %q: %w", hdr.Name, err)
|
|
}
|
|
_, err = io.Copy(dst, tr)
|
|
if closeErr := dst.Close(); err == nil {
|
|
err = closeErr
|
|
}
|
|
if err != nil {
|
|
return fmt.Errorf("extracting %q: %w", hdr.Name, err)
|
|
}
|
|
extracted++
|
|
}
|
|
if extracted == 0 {
|
|
return fmt.Errorf("%q contains no gvisor-bin/ members", tarball)
|
|
}
|
|
// Swap the new directory into place.
|
|
if _, err := os.Stat(dir); err == nil {
|
|
if err := os.Rename(dir, oldDir); err != nil {
|
|
return fmt.Errorf("cannot move old sidecar directory aside: %w", err)
|
|
}
|
|
}
|
|
if err := os.Rename(tmpDir, dir); err != nil {
|
|
return fmt.Errorf("cannot move new sidecar directory into place: %w", err)
|
|
}
|
|
if err := os.RemoveAll(oldDir); err != nil {
|
|
return fmt.Errorf("cannot remove old sidecar directory: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// installSidecars ensures that all sidecar binaries are present.
|
|
func (i *Install) installSidecars() error {
|
|
var missing []string
|
|
for _, b := range gvisorbinaries.All {
|
|
if _, err := b.Path(); err != nil {
|
|
missing = append(missing, b.Name)
|
|
}
|
|
}
|
|
if len(missing) == 0 {
|
|
return nil
|
|
}
|
|
dir, err := gvisorbinaries.Dir()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !i.DownloadSidecars.Applies() {
|
|
return fmt.Errorf("sidecar binaries %v missing (expected under %q); not downloading them (--download-sidecars=%s, release build: %t)", missing, dir, i.DownloadSidecars, config.IsReleaseVersion(version.Version()))
|
|
}
|
|
url := i.SidecarURL
|
|
if url == "" {
|
|
if url, err = releaseTarballURL(version.Version(), runtime.GOARCH); err != nil {
|
|
return fmt.Errorf("missing sidecar binaries %v (expected under %q): %v", missing, dir, err)
|
|
}
|
|
}
|
|
// Download next to the final location to be on the same filesystem.
|
|
parent := filepath.Dir(dir)
|
|
stale, _ := filepath.Glob(filepath.Join(parent, ".runsc-sidecars.*"))
|
|
for _, d := range stale {
|
|
os.RemoveAll(d)
|
|
}
|
|
tmp, err := os.MkdirTemp(parent, ".runsc-sidecars.*")
|
|
if err != nil {
|
|
return fmt.Errorf("cannot create download directory: %w", err)
|
|
}
|
|
defer os.RemoveAll(tmp)
|
|
tarball := filepath.Join(tmp, "gvisor.tar.bz2")
|
|
if err := fetch(url, tarball); err != nil {
|
|
return err
|
|
}
|
|
sumFile := tarball + ".sha512"
|
|
if err := fetch(url+".sha512", sumFile); err != nil {
|
|
return err
|
|
}
|
|
if err := verifySHA512(tarball, sumFile); err != nil {
|
|
return err
|
|
}
|
|
if err := extractSidecars(tarball, dir); err != nil {
|
|
return err
|
|
}
|
|
log.Printf("Installed sidecar binaries from %s to %q. This is BEST-EFFORT FUNCTIONALITY which will STOP WORKING in a few weeks. Please see gvisor.dev/issue/13718", url, dir)
|
|
return nil
|
|
}
|