github.com/turbot/steampipe@v1.7.0-rc.0.0.20240517123944-7cef272d4458/pkg/ociinstaller/steampipeimage.go (about)

     1  package ociinstaller
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"log"
     8  	"strings"
     9  
    10  	"github.com/containerd/containerd/remotes"
    11  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    12  	"github.com/turbot/steampipe/pkg/constants"
    13  )
    14  
    15  type SteampipeImage struct {
    16  	OCIDescriptor *ocispec.Descriptor
    17  	ImageRef      *SteampipeImageRef
    18  	Config        *config
    19  	Plugin        *PluginImage
    20  	Database      *DbImage
    21  	Fdw           *HubImage
    22  	Assets        *AssetsImage
    23  	resolver      *remotes.Resolver
    24  }
    25  
    26  type PluginImage struct {
    27  	BinaryFile         string
    28  	BinaryDigest       string
    29  	BinaryArchitecture string
    30  	DocsDir            string
    31  	ConfigFileDir      string
    32  	LicenseFile        string
    33  }
    34  
    35  type DbImage struct {
    36  	ArchiveDir  string
    37  	ReadmeFile  string
    38  	LicenseFile string
    39  }
    40  type HubImage struct {
    41  	BinaryFile  string
    42  	ReadmeFile  string
    43  	LicenseFile string
    44  	ControlFile string
    45  	SqlFile     string
    46  }
    47  type AssetsImage struct {
    48  	ReportUI string
    49  }
    50  
    51  func (o *ociDownloader) newSteampipeImage() *SteampipeImage {
    52  	SteampipeImage := &SteampipeImage{
    53  		resolver: &o.resolver,
    54  	}
    55  	o.Images = append(o.Images, SteampipeImage)
    56  	return SteampipeImage
    57  }
    58  
    59  type ImageType string
    60  
    61  const (
    62  	ImageTypeDatabase ImageType = "db"
    63  	ImageTypeFdw      ImageType = "fdw"
    64  	ImageTypeAssets   ImageType = "assets"
    65  	ImageTypePlugin   ImageType = "plugin"
    66  )
    67  
    68  func (o *ociDownloader) Download(ctx context.Context, ref *SteampipeImageRef, imageType ImageType, destDir string) (*SteampipeImage, error) {
    69  	var mediaTypes []string
    70  	Image := o.newSteampipeImage()
    71  	Image.ImageRef = ref
    72  	mediaType, err := MediaTypeForPlatform(imageType)
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  
    77  	mediaTypes = append(mediaTypes, mediaType...)
    78  	mediaTypes = append(mediaTypes, SharedMediaTypes(imageType)...)
    79  	mediaTypes = append(mediaTypes, ConfigMediaTypes()...)
    80  
    81  	log.Println("[TRACE] ociDownloader.Download:", "downloading", ref.ActualImageRef())
    82  
    83  	// Download the files
    84  	imageDesc, _, configBytes, layers, err := o.Pull(ctx, ref.ActualImageRef(), mediaTypes, destDir)
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  
    89  	Image.OCIDescriptor = imageDesc
    90  	Image.Config, err = newSteampipeImageConfig(configBytes)
    91  	if err != nil {
    92  		return nil, errors.New("invalid image - missing $config")
    93  	}
    94  
    95  	// Get the metadata
    96  	switch imageType {
    97  	case ImageTypeDatabase:
    98  		Image.Database, err = getDBImageData(layers)
    99  	case ImageTypeFdw:
   100  		Image.Fdw, err = getFdwImageData(layers)
   101  	case ImageTypePlugin:
   102  		Image.Plugin, err = getPluginImageData(layers)
   103  	case ImageTypeAssets:
   104  		Image.Assets, err = getAssetImageData(layers)
   105  
   106  	default:
   107  		return nil, errors.New("invalid Type - image types are: plugin, db, fdw")
   108  	}
   109  
   110  	if err != nil {
   111  		return nil, err
   112  	}
   113  	return Image, nil
   114  }
   115  
   116  func getAssetImageData(layers []ocispec.Descriptor) (*AssetsImage, error) {
   117  	var assetImage AssetsImage
   118  
   119  	// get the report dir
   120  	foundLayers := findLayersForMediaType(layers, MediaTypeAssetReportLayer)
   121  	if len(foundLayers) > 0 {
   122  		assetImage.ReportUI = foundLayers[0].Annotations["org.opencontainers.image.title"]
   123  	}
   124  
   125  	return &assetImage, nil
   126  }
   127  
   128  func getDBImageData(layers []ocispec.Descriptor) (*DbImage, error) {
   129  	res := &DbImage{}
   130  
   131  	// get the binary jar file
   132  	mediaType, err := MediaTypeForPlatform("db")
   133  	if err != nil {
   134  		return nil, err
   135  	}
   136  	foundLayers := findLayersForMediaType(layers, mediaType[0])
   137  	if len(foundLayers) != 1 {
   138  		return nil, fmt.Errorf("invalid Image - should contain 1 installation file per platform, found %d", len(foundLayers))
   139  	}
   140  	res.ArchiveDir = foundLayers[0].Annotations["org.opencontainers.image.title"]
   141  
   142  	// get the readme file info
   143  	foundLayers = findLayersForMediaType(layers, MediaTypeDbDocLayer)
   144  	if len(foundLayers) > 0 {
   145  		res.ReadmeFile = foundLayers[0].Annotations["org.opencontainers.image.title"]
   146  	}
   147  
   148  	// get the license file info
   149  	foundLayers = findLayersForMediaType(layers, MediaTypeDbLicenseLayer)
   150  	if len(foundLayers) > 0 {
   151  		res.LicenseFile = foundLayers[0].Annotations["org.opencontainers.image.title"]
   152  	}
   153  	return res, nil
   154  }
   155  
   156  func getFdwImageData(layers []ocispec.Descriptor) (*HubImage, error) {
   157  	res := &HubImage{}
   158  
   159  	// get the binary (steampipe-postgres-fdw.so) info
   160  	mediaType, err := MediaTypeForPlatform("fdw")
   161  	if err != nil {
   162  		return nil, err
   163  	}
   164  	foundLayers := findLayersForMediaType(layers, mediaType[0])
   165  	if len(foundLayers) != 1 {
   166  		return nil, fmt.Errorf("invalid image - image should contain 1 binary file per platform, found %d", len(foundLayers))
   167  	}
   168  	res.BinaryFile = foundLayers[0].Annotations["org.opencontainers.image.title"]
   169  
   170  	//sourcePath := filepath.Join(tempDir.Path, fileName)
   171  
   172  	// get the control file info
   173  	foundLayers = findLayersForMediaType(layers, MediaTypeFdwControlLayer)
   174  	if len(foundLayers) != 1 {
   175  		return nil, fmt.Errorf("invalid image - image should contain 1 control file, found %d", len(foundLayers))
   176  	}
   177  	res.ControlFile = foundLayers[0].Annotations["org.opencontainers.image.title"]
   178  
   179  	// get the sql file info
   180  	foundLayers = findLayersForMediaType(layers, MediaTypeFdwSqlLayer)
   181  	if len(foundLayers) != 1 {
   182  		return nil, fmt.Errorf("invalid image - image should contain 1 SQL file, found %d", len(foundLayers))
   183  	}
   184  	res.SqlFile = foundLayers[0].Annotations["org.opencontainers.image.title"]
   185  
   186  	// get the readme file info
   187  	foundLayers = findLayersForMediaType(layers, MediaTypeFdwDocLayer)
   188  	if len(foundLayers) > 0 {
   189  		res.ReadmeFile = foundLayers[0].Annotations["org.opencontainers.image.title"]
   190  	}
   191  
   192  	// get the license file info
   193  	foundLayers = findLayersForMediaType(layers, MediaTypeFdwLicenseLayer)
   194  	if len(foundLayers) > 0 {
   195  		res.LicenseFile = foundLayers[0].Annotations["org.opencontainers.image.title"]
   196  	}
   197  	return res, nil
   198  }
   199  
   200  func getPluginImageData(layers []ocispec.Descriptor) (*PluginImage, error) {
   201  	res := &PluginImage{}
   202  	var foundLayers []ocispec.Descriptor
   203  	// get the binary plugin file info
   204  	// iterate in order of mediatypes - as given by MediaTypeForPlatform (see function docs)
   205  	mediaTypes, err := MediaTypeForPlatform("plugin")
   206  	if err != nil {
   207  		return nil, err
   208  	}
   209  
   210  	for _, mediaType := range mediaTypes {
   211  		// find out the layer with the correct media type
   212  		foundLayers = findLayersForMediaType(layers, mediaType)
   213  		if len(foundLayers) == 1 {
   214  			// when found, assign and exit
   215  			res.BinaryFile = foundLayers[0].Annotations["org.opencontainers.image.title"]
   216  			res.BinaryDigest = string(foundLayers[0].Digest)
   217  			res.BinaryArchitecture = constants.ArchAMD64
   218  			if strings.Contains(mediaType, constants.ArchARM64) {
   219  				res.BinaryArchitecture = constants.ArchARM64
   220  			}
   221  			break
   222  		}
   223  		// loop over to the next one
   224  		log.Println("[TRACE] could not find data for", mediaType)
   225  		log.Println("[TRACE] falling back to the next one, if any")
   226  	}
   227  	if len(res.BinaryFile) == 0 {
   228  		return nil, fmt.Errorf("invalid image - should contain 1 binary file per platform, found %d", len(foundLayers))
   229  	}
   230  
   231  	// get the docs dir
   232  	foundLayers = findLayersForMediaType(layers, MediaTypePluginDocsLayer)
   233  	if len(foundLayers) > 0 {
   234  		res.DocsDir = foundLayers[0].Annotations["org.opencontainers.image.title"]
   235  	}
   236  
   237  	// get the .spc config / connections file dir
   238  	foundLayers = findLayersForMediaType(layers, MediaTypePluginSpcLayer)
   239  	if len(foundLayers) > 0 {
   240  		res.ConfigFileDir = foundLayers[0].Annotations["org.opencontainers.image.title"]
   241  	}
   242  
   243  	// get the license file info
   244  	foundLayers = findLayersForMediaType(layers, MediaTypePluginLicenseLayer)
   245  	if len(foundLayers) > 0 {
   246  		res.LicenseFile = foundLayers[0].Annotations["org.opencontainers.image.title"]
   247  	}
   248  
   249  	return res, nil
   250  }
   251  
   252  func findLayersForMediaType(layers []ocispec.Descriptor, mediaType string) []ocispec.Descriptor {
   253  	log.Println("[TRACE] looking for", mediaType)
   254  	var matchedLayers []ocispec.Descriptor
   255  
   256  	for _, layer := range layers {
   257  		if layer.MediaType == mediaType {
   258  			matchedLayers = append(matchedLayers, layer)
   259  		}
   260  	}
   261  	return matchedLayers
   262  }