github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/pkg/machine/fcos.go (about)

     1  //go:build amd64 || arm64
     2  // +build amd64 arm64
     3  
     4  package machine
     5  
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"net/http"
    11  	url2 "net/url"
    12  	"os"
    13  	"path/filepath"
    14  	"runtime"
    15  	"strings"
    16  
    17  	"github.com/coreos/stream-metadata-go/fedoracoreos"
    18  	"github.com/coreos/stream-metadata-go/release"
    19  	"github.com/coreos/stream-metadata-go/stream"
    20  	"github.com/pkg/errors"
    21  
    22  	digest "github.com/opencontainers/go-digest"
    23  	"github.com/sirupsen/logrus"
    24  )
    25  
    26  // These should eventually be moved into machine/qemu as
    27  // they are specific to running qemu
    28  var (
    29  	artifact = "qemu"
    30  	Format   = "qcow2.xz"
    31  )
    32  
    33  const (
    34  	// Used for testing the latest podman in fcos
    35  	// special builds
    36  	podmanTesting     = "podman-testing"
    37  	PodmanTestingHost = "fedorapeople.org"
    38  	PodmanTestingURL  = "groups/podman/testing"
    39  )
    40  
    41  type FcosDownload struct {
    42  	Download
    43  }
    44  
    45  func NewFcosDownloader(vmType, vmName, imageStream string) (DistributionDownload, error) {
    46  	info, err := GetFCOSDownload(imageStream)
    47  	if err != nil {
    48  		return nil, err
    49  	}
    50  	urlSplit := strings.Split(info.Location, "/")
    51  	imageName := urlSplit[len(urlSplit)-1]
    52  	url, err := url2.Parse(info.Location)
    53  	if err != nil {
    54  		return nil, err
    55  	}
    56  
    57  	dataDir, err := GetDataDir(vmType)
    58  	if err != nil {
    59  		return nil, err
    60  	}
    61  
    62  	fcd := FcosDownload{
    63  		Download: Download{
    64  			Arch:      getFcosArch(),
    65  			Artifact:  artifact,
    66  			Format:    Format,
    67  			ImageName: imageName,
    68  			LocalPath: filepath.Join(dataDir, imageName),
    69  			Sha256sum: info.Sha256Sum,
    70  			URL:       url,
    71  			VMName:    vmName,
    72  		},
    73  	}
    74  	fcd.Download.LocalUncompressedFile = fcd.getLocalUncompressedName()
    75  	return fcd, nil
    76  }
    77  
    78  func (f FcosDownload) Get() *Download {
    79  	return &f.Download
    80  }
    81  
    82  type FcosDownloadInfo struct {
    83  	CompressionType string
    84  	Location        string
    85  	Release         string
    86  	Sha256Sum       string
    87  }
    88  
    89  func (f FcosDownload) HasUsableCache() (bool, error) {
    90  	//	 check the sha of the local image if it exists
    91  	//  get the sha of the remote image
    92  	// == dont bother to pull
    93  	if _, err := os.Stat(f.LocalPath); os.IsNotExist(err) {
    94  		return false, nil
    95  	}
    96  	fd, err := os.Open(f.LocalPath)
    97  	if err != nil {
    98  		return false, err
    99  	}
   100  	defer func() {
   101  		if err := fd.Close(); err != nil {
   102  			logrus.Error(err)
   103  		}
   104  	}()
   105  	sum, err := digest.SHA256.FromReader(fd)
   106  	if err != nil {
   107  		return false, err
   108  	}
   109  	return sum.Encoded() == f.Sha256sum, nil
   110  }
   111  
   112  func getFcosArch() string {
   113  	var arch string
   114  	// TODO fill in more architectures
   115  	switch runtime.GOARCH {
   116  	case "arm64":
   117  		arch = "aarch64"
   118  	default:
   119  		arch = "x86_64"
   120  	}
   121  	return arch
   122  }
   123  
   124  // getStreamURL is a wrapper for the fcos.GetStream URL
   125  // so that we can inject a special stream and url for
   126  // testing podman before it merges into fcos builds
   127  func getStreamURL(streamType string) url2.URL {
   128  	// For the podmanTesting stream type, we point to
   129  	// a custom url on fedorapeople.org
   130  	if streamType == podmanTesting {
   131  		return url2.URL{
   132  			Scheme: "https",
   133  			Host:   PodmanTestingHost,
   134  			Path:   fmt.Sprintf("%s/%s.json", PodmanTestingURL, "podman4"),
   135  		}
   136  	}
   137  	return fedoracoreos.GetStreamURL(streamType)
   138  }
   139  
   140  // This should get Exported and stay put as it will apply to all fcos downloads
   141  // getFCOS parses fedoraCoreOS's stream and returns the image download URL and the release version
   142  func GetFCOSDownload(imageStream string) (*FcosDownloadInfo, error) { //nolint:staticcheck
   143  	var (
   144  		fcosstable stream.Stream
   145  		altMeta    release.Release
   146  		streamType string
   147  	)
   148  
   149  	switch imageStream {
   150  	case "podman-testing":
   151  		streamType = "podman-testing"
   152  	case "testing", "":
   153  		streamType = fedoracoreos.StreamTesting
   154  	case "next":
   155  		streamType = fedoracoreos.StreamNext
   156  	case "stable":
   157  		streamType = fedoracoreos.StreamStable
   158  	default:
   159  		return nil, errors.Errorf("invalid stream %s: valid streams are `testing` and `stable`", imageStream)
   160  	}
   161  	streamurl := getStreamURL(streamType)
   162  	resp, err := http.Get(streamurl.String())
   163  	if err != nil {
   164  		return nil, err
   165  	}
   166  	body, err := ioutil.ReadAll(resp.Body)
   167  	if err != nil {
   168  		return nil, err
   169  	}
   170  	defer func() {
   171  		if err := resp.Body.Close(); err != nil {
   172  			logrus.Error(err)
   173  		}
   174  	}()
   175  	if imageStream == podmanTesting {
   176  		if err := json.Unmarshal(body, &altMeta); err != nil {
   177  			return nil, err
   178  		}
   179  
   180  		arches, ok := altMeta.Architectures[getFcosArch()]
   181  		if !ok {
   182  			return nil, fmt.Errorf("unable to pull VM image: no targetArch in stream")
   183  		}
   184  		qcow2, ok := arches.Media.Qemu.Artifacts["qcow2.xz"]
   185  		if !ok {
   186  			return nil, fmt.Errorf("unable to pull VM image: no qcow2.xz format in stream")
   187  		}
   188  		disk := qcow2.Disk
   189  
   190  		return &FcosDownloadInfo{
   191  			Location:        disk.Location,
   192  			Sha256Sum:       disk.Sha256,
   193  			CompressionType: "xz",
   194  		}, nil
   195  	}
   196  
   197  	if err := json.Unmarshal(body, &fcosstable); err != nil {
   198  		return nil, err
   199  	}
   200  	arch, ok := fcosstable.Architectures[getFcosArch()]
   201  	if !ok {
   202  		return nil, fmt.Errorf("unable to pull VM image: no targetArch in stream")
   203  	}
   204  	artifacts := arch.Artifacts
   205  	if artifacts == nil {
   206  		return nil, fmt.Errorf("unable to pull VM image: no artifact in stream")
   207  	}
   208  	qemu, ok := artifacts[artifact]
   209  	if !ok {
   210  		return nil, fmt.Errorf("unable to pull VM image: no qemu artifact in stream")
   211  	}
   212  	formats := qemu.Formats
   213  	if formats == nil {
   214  		return nil, fmt.Errorf("unable to pull VM image: no formats in stream")
   215  	}
   216  	qcow, ok := formats[Format]
   217  	if !ok {
   218  		return nil, fmt.Errorf("unable to pull VM image: no qcow2.xz format in stream")
   219  	}
   220  	disk := qcow.Disk
   221  	if disk == nil {
   222  		return nil, fmt.Errorf("unable to pull VM image: no disk in stream")
   223  	}
   224  	return &FcosDownloadInfo{
   225  		Location:        disk.Location,
   226  		Release:         qemu.Release,
   227  		Sha256Sum:       disk.Sha256,
   228  		CompressionType: "xz",
   229  	}, nil
   230  }