package incus
import (
"context"
"crypto/sha256"
"fmt"
"io"
"net/http"
"net/url"
"os"
"os/exec"
"strings"
"time"
"github.com/lxc/incus/v6/shared/api"
"github.com/lxc/incus/v6/shared/subprocess"
"github.com/lxc/incus/v6/shared/util"
)
func (r *ProtocolSimpleStreams) GetImages() ([]api.Image, error) {
return r.ssClient.ListImages()
}
func (r *ProtocolSimpleStreams) GetImagesAllProjects() ([]api.Image, error) {
return r.GetImages()
}
func (r *ProtocolSimpleStreams) GetImageFingerprints() ([]string, error) {
images, err := r.ssClient.ListImages()
if err != nil {
return nil, err
}
fingerprints := []string{}
for _, img := range images {
fingerprints = append(fingerprints, img.Fingerprint)
}
return fingerprints, nil
}
func (r *ProtocolSimpleStreams) GetImagesWithFilter(filters []string) ([]api.Image, error) {
return nil, fmt.Errorf("GetImagesWithFilter is not supported by the simplestreams protocol")
}
func (r *ProtocolSimpleStreams) GetImage(fingerprint string) (*api.Image, string, error) {
image, err := r.ssClient.GetImage(fingerprint)
if err != nil {
return nil, "", fmt.Errorf("Failed getting image: %w", err)
}
return image, "", err
}
func (r *ProtocolSimpleStreams) GetImageFile(fingerprint string, req ImageFileRequest) (*ImageFileResponse, error) {
if req.MetaFile == nil && req.RootfsFile == nil {
return nil, fmt.Errorf("No file requested")
}
if util.PathExists("/dev/incus/sock") && os.Geteuid() == 0 {
unixURI := fmt.Sprintf("http://unix.socket/1.0/images/%s/export", url.PathEscape(fingerprint))
devIncusHTTP, err := unixHTTPClient(nil, "/dev/incus/sock")
if err == nil {
resp, err := incusDownloadImage(fingerprint, unixURI, r.httpUserAgent, devIncusHTTP.Do, req)
if err == nil {
return resp, nil
}
}
}
httpClient := *r.http
httpTransport := httpClient.Transport.(*http.Transport).Clone()
httpTransport.ResponseHeaderTimeout = 30 * time.Second
httpClient.Transport = httpTransport
files, err := r.ssClient.GetFiles(fingerprint)
if err != nil {
return nil, err
}
resp := ImageFileResponse{}
download := func(path string, filename string, hash string, target io.WriteSeeker) (int64, error) {
uri, err := url.JoinPath(fmt.Sprintf("http://%s", strings.TrimPrefix(r.httpHost, "https://")), path)
if err != nil {
return -1, err
}
size, err := util.DownloadFileHash(context.TODO(), &httpClient, r.httpUserAgent, req.ProgressHandler, req.Canceler, filename, uri, hash, sha256.New(), target)
if err != nil {
if err.Error() == "net/http: request canceled" {
return -1, err
}
uri, err := url.JoinPath(r.httpHost, path)
if err != nil {
return -1, err
}
size, err = util.DownloadFileHash(context.TODO(), &httpClient, r.httpUserAgent, req.ProgressHandler, req.Canceler, filename, uri, hash, sha256.New(), target)
if err != nil {
return -1, err
}
}
return size, nil
}
meta, ok := files["meta"]
if ok && req.MetaFile != nil {
size, err := download(meta.Path, "metadata", meta.Sha256, req.MetaFile)
if err != nil {
return nil, err
}
parts := strings.Split(meta.Path, "/")
resp.MetaName = parts[len(parts)-1]
resp.MetaSize = size
}
rootfs, ok := files["root"]
if ok && req.RootfsFile != nil {
downloaded := false
_, err := exec.LookPath("xdelta3")
if err == nil && req.DeltaSourceRetriever != nil {
for filename, file := range files {
_, srcFingerprint, prefixFound := strings.Cut(filename, "root.delta-")
if !prefixFound {
continue
}
srcPath := req.DeltaSourceRetriever(srcFingerprint, "rootfs")
if srcPath == "" {
continue
}
deltaFile, err := os.CreateTemp("", "incus_image_")
if err != nil {
return nil, err
}
defer func() { _ = deltaFile.Close() }()
defer func() { _ = os.Remove(deltaFile.Name()) }()
_, err = download(file.Path, "rootfs delta", file.Sha256, deltaFile)
if err != nil {
return nil, err
}
patchedFile, err := os.CreateTemp("", "incus_image_")
if err != nil {
return nil, err
}
defer func() { _ = patchedFile.Close() }()
defer func() { _ = os.Remove(patchedFile.Name()) }()
_, err = subprocess.RunCommand("xdelta3", "-f", "-d", "-s", srcPath, deltaFile.Name(), patchedFile.Name())
if err != nil {
return nil, err
}
size, err := io.Copy(req.RootfsFile, patchedFile)
if err != nil {
return nil, err
}
parts := strings.Split(rootfs.Path, "/")
resp.RootfsName = parts[len(parts)-1]
resp.RootfsSize = size
downloaded = true
}
}
if !downloaded {
size, err := download(rootfs.Path, "rootfs", rootfs.Sha256, req.RootfsFile)
if err != nil {
return nil, err
}
parts := strings.Split(rootfs.Path, "/")
resp.RootfsName = parts[len(parts)-1]
resp.RootfsSize = size
}
}
return &resp, nil
}
func (r *ProtocolSimpleStreams) GetImageSecret(fingerprint string) (string, error) {
return "", fmt.Errorf("Private images aren't supported by the simplestreams protocol")
}
func (r *ProtocolSimpleStreams) GetPrivateImage(fingerprint string, secret string) (*api.Image, string, error) {
return nil, "", fmt.Errorf("Private images aren't supported by the simplestreams protocol")
}
func (r *ProtocolSimpleStreams) GetPrivateImageFile(fingerprint string, secret string, req ImageFileRequest) (*ImageFileResponse, error) {
return nil, fmt.Errorf("Private images aren't supported by the simplestreams protocol")
}
func (r *ProtocolSimpleStreams) GetImageAliases() ([]api.ImageAliasesEntry, error) {
return r.ssClient.ListAliases()
}
func (r *ProtocolSimpleStreams) GetImageAliasNames() ([]string, error) {
aliases, err := r.ssClient.ListAliases()
if err != nil {
return nil, err
}
names := []string{}
for _, alias := range aliases {
names = append(names, alias.Name)
}
return names, nil
}
func (r *ProtocolSimpleStreams) GetImageAlias(name string) (*api.ImageAliasesEntry, string, error) {
alias, err := r.ssClient.GetAlias("container", name)
if err != nil {
alias, err = r.ssClient.GetAlias("virtual-machine", name)
if err != nil {
return nil, "", err
}
}
return alias, "", err
}
func (r *ProtocolSimpleStreams) GetImageAliasType(imageType string, name string) (*api.ImageAliasesEntry, string, error) {
if imageType == "" {
return r.GetImageAlias(name)
}
alias, err := r.ssClient.GetAlias(imageType, name)
if err != nil {
return nil, "", err
}
return alias, "", err
}
func (r *ProtocolSimpleStreams) GetImageAliasArchitectures(imageType string, name string) (map[string]*api.ImageAliasesEntry, error) {
if imageType == "" {
aliases, err := r.ssClient.GetAliasArchitectures("container", name)
if err != nil {
aliases, err = r.ssClient.GetAliasArchitectures("virtual-machine", name)
if err != nil {
return nil, err
}
}
return aliases, nil
}
return r.ssClient.GetAliasArchitectures(imageType, name)
}
func (r *ProtocolSimpleStreams) ExportImage(fingerprint string, image api.ImageExportPost) (Operation, error) {
return nil, fmt.Errorf("Exporting images is not supported by the simplestreams protocol")
}