github.com/dean7474/operator-registry@v1.21.1-0.20220418203638-d4717f98c2e5/test/e2e/opm_test.go (about)

     1  package e2e_test
     2  
     3  import (
     4  	"context"
     5  	"database/sql"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"os"
     9  	"os/exec"
    10  	"path/filepath"
    11  	"reflect"
    12  
    13  	. "github.com/onsi/ginkgo"
    14  	. "github.com/onsi/gomega"
    15  	"github.com/otiai10/copy"
    16  	"github.com/sirupsen/logrus"
    17  	"k8s.io/apimachinery/pkg/util/rand"
    18  
    19  	"github.com/operator-framework/operator-registry/pkg/containertools"
    20  	"github.com/operator-framework/operator-registry/pkg/image"
    21  	"github.com/operator-framework/operator-registry/pkg/image/containerdregistry"
    22  	"github.com/operator-framework/operator-registry/pkg/image/execregistry"
    23  	"github.com/operator-framework/operator-registry/pkg/lib/bundle"
    24  	"github.com/operator-framework/operator-registry/pkg/lib/indexer"
    25  	lregistry "github.com/operator-framework/operator-registry/pkg/lib/registry"
    26  	"github.com/operator-framework/operator-registry/pkg/registry"
    27  	"github.com/operator-framework/operator-registry/pkg/sqlite"
    28  )
    29  
    30  var (
    31  	packageName    = "prometheus"
    32  	channels       = "preview"
    33  	defaultChannel = "preview"
    34  
    35  	bundlePath1 = "manifests/prometheus/0.14.0"
    36  	bundlePath2 = "manifests/prometheus/0.15.0"
    37  	bundlePath3 = "manifests/prometheus/0.22.2"
    38  
    39  	bundleTag1 = rand.String(6)
    40  	bundleTag2 = rand.String(6)
    41  	bundleTag3 = rand.String(6)
    42  	indexTag1  = rand.String(6)
    43  	indexTag2  = rand.String(6)
    44  	indexTag3  = rand.String(6)
    45  
    46  	bundleImage = dockerHost + "/olmtest/e2e-bundle"
    47  	indexImage  = dockerHost + "/olmtest/e2e-index"
    48  	indexImage1 = dockerHost + "/olmtest/e2e-index:" + indexTag1
    49  	indexImage2 = dockerHost + "/olmtest/e2e-index:" + indexTag2
    50  	indexImage3 = dockerHost + "/olmtest/e2e-index:" + indexTag3
    51  
    52  	// publishedIndex is an index used to check for regressions in opm's behavior.
    53  	publishedIndex = os.Getenv("PUBLISHED_INDEX")
    54  )
    55  
    56  type bundleLocation struct {
    57  	image, path string
    58  }
    59  
    60  type bundleLocations []bundleLocation
    61  
    62  func (bl bundleLocations) images() []string {
    63  	images := make([]string, len(bl))
    64  	for i, b := range bl {
    65  		images[i] = b.image
    66  	}
    67  
    68  	return images
    69  }
    70  
    71  func inTemporaryBuildContext(f func() error) (rerr error) {
    72  	td, err := ioutil.TempDir(".", "opm-")
    73  	if err != nil {
    74  		return err
    75  	}
    76  	err = copy.Copy("../../manifests", filepath.Join(td, "manifests"))
    77  	if err != nil {
    78  		return err
    79  	}
    80  	wd, err := os.Getwd()
    81  	if err != nil {
    82  		return err
    83  	}
    84  	err = os.Chdir(td)
    85  	if err != nil {
    86  		return err
    87  	}
    88  	defer func() {
    89  		err := os.Chdir(wd)
    90  		if rerr == nil {
    91  			rerr = err
    92  		}
    93  	}()
    94  	return f()
    95  }
    96  
    97  func buildIndexWith(containerTool, fromIndexImage, toIndexImage string, bundleImages []string, mode registry.Mode, overwriteLatest bool) error {
    98  	logger := logrus.WithFields(logrus.Fields{"bundles": bundleImages})
    99  	indexAdder := indexer.NewIndexAdder(containertools.NewContainerTool(containerTool, containertools.NoneTool), containertools.NewContainerTool(containerTool, containertools.NoneTool), logger)
   100  
   101  	request := indexer.AddToIndexRequest{
   102  		Generate:          false,
   103  		FromIndex:         fromIndexImage,
   104  		BinarySourceImage: "",
   105  		OutDockerfile:     "",
   106  		Tag:               toIndexImage,
   107  		Mode:              mode,
   108  		Bundles:           bundleImages,
   109  		Permissive:        false,
   110  		Overwrite:         overwriteLatest,
   111  		SkipTLSVerify:     *skipTLSForRegistry,
   112  		PlainHTTP:         *useHTTPforRegistry,
   113  	}
   114  
   115  	return indexAdder.AddToIndex(request)
   116  }
   117  
   118  func buildFromIndexWith(containerTool string) error {
   119  	bundles := []string{
   120  		bundleImage + ":" + bundleTag3,
   121  	}
   122  	logger := logrus.WithFields(logrus.Fields{"bundles": bundles})
   123  	indexAdder := indexer.NewIndexAdder(containertools.NewContainerTool(containerTool, containertools.NoneTool), containertools.NewContainerTool(containerTool, containertools.NoneTool), logger)
   124  
   125  	request := indexer.AddToIndexRequest{
   126  		Generate:          false,
   127  		FromIndex:         indexImage1,
   128  		BinarySourceImage: "",
   129  		OutDockerfile:     "",
   130  		Tag:               indexImage2,
   131  		Bundles:           bundles,
   132  		Permissive:        false,
   133  	}
   134  
   135  	return indexAdder.AddToIndex(request)
   136  }
   137  
   138  // TODO(djzager): make this more complete than what should be a simple no-op
   139  func pruneIndexWith(containerTool string) error {
   140  	logger := logrus.WithFields(logrus.Fields{"packages": packageName})
   141  	indexAdder := indexer.NewIndexPruner(containertools.NewContainerTool(containerTool, containertools.NoneTool), logger)
   142  
   143  	request := indexer.PruneFromIndexRequest{
   144  		Generate:          false,
   145  		FromIndex:         indexImage2,
   146  		BinarySourceImage: "",
   147  		OutDockerfile:     "",
   148  		Tag:               indexImage3,
   149  		Packages:          []string{packageName},
   150  		Permissive:        false,
   151  	}
   152  
   153  	return indexAdder.PruneFromIndex(request)
   154  }
   155  
   156  func pushWith(containerTool, image string) error {
   157  	dockerpush := exec.Command(containerTool, "push", image)
   158  	dockerpush.Stderr = GinkgoWriter
   159  	dockerpush.Stdout = GinkgoWriter
   160  	return dockerpush.Run()
   161  }
   162  
   163  func exportPackageWith(containerTool string) error {
   164  	packages := []string{packageName}
   165  	logger := logrus.WithFields(logrus.Fields{"package": packages})
   166  	indexExporter := indexer.NewIndexExporter(containertools.NewContainerTool(containerTool, containertools.NoneTool), logger)
   167  
   168  	request := indexer.ExportFromIndexRequest{
   169  		Index:         indexImage2,
   170  		Packages:      packages,
   171  		DownloadPath:  "downloaded",
   172  		ContainerTool: containertools.NewContainerTool(containerTool, containertools.NoneTool),
   173  		SkipTLSVerify: *skipTLSForRegistry,
   174  		PlainHTTP:     *useHTTPforRegistry,
   175  	}
   176  
   177  	return indexExporter.ExportFromIndex(request)
   178  }
   179  
   180  func exportIndexImageWith(containerTool string) error {
   181  
   182  	logger := logrus.NewEntry(logrus.New())
   183  	indexExporter := indexer.NewIndexExporter(containertools.NewContainerTool(containerTool, containertools.NoneTool), logger)
   184  
   185  	request := indexer.ExportFromIndexRequest{
   186  		Index:         indexImage2,
   187  		Packages:      []string{},
   188  		DownloadPath:  "downloaded",
   189  		ContainerTool: containertools.NewContainerTool(containerTool, containertools.NoneTool),
   190  		SkipTLSVerify: *skipTLSForRegistry,
   191  		PlainHTTP:     *useHTTPforRegistry,
   192  	}
   193  
   194  	return indexExporter.ExportFromIndex(request)
   195  }
   196  
   197  func initialize() error {
   198  	tmpDB, err := ioutil.TempFile("./", "index_tmp.db")
   199  	if err != nil {
   200  		return err
   201  	}
   202  	defer os.Remove(tmpDB.Name())
   203  
   204  	db, err := sqlite.Open(tmpDB.Name())
   205  	if err != nil {
   206  		return err
   207  	}
   208  	defer db.Close()
   209  
   210  	dbLoader, err := sqlite.NewSQLLiteLoader(db)
   211  	if err != nil {
   212  		return err
   213  	}
   214  	if err := dbLoader.Migrate(context.TODO()); err != nil {
   215  		return err
   216  	}
   217  
   218  	loader := sqlite.NewSQLLoaderForDirectory(dbLoader, "downloaded")
   219  	return loader.Populate()
   220  }
   221  
   222  var _ = Describe("opm", func() {
   223  	IncludeSharedSpecs := func(containerTool string) {
   224  		It("builds and validates a bundle image", func() {
   225  			By("building bundle")
   226  			img := bundleImage + ":" + bundleTag3
   227  			err := inTemporaryBuildContext(func() error {
   228  				return bundle.BuildFunc(bundlePath3, "", img, containerTool, packageName, channels, defaultChannel, false)
   229  			})
   230  			Expect(err).NotTo(HaveOccurred())
   231  
   232  			By("pushing bundle")
   233  			Expect(pushWith(containerTool, img)).To(Succeed())
   234  
   235  			By("pulling bundle")
   236  			logger := logrus.WithFields(logrus.Fields{"image": img})
   237  			tool := containertools.NewContainerTool(containerTool, containertools.NoneTool)
   238  			var registry image.Registry
   239  			switch tool {
   240  			case containertools.PodmanTool, containertools.DockerTool:
   241  				registry, err = execregistry.NewRegistry(tool, logger)
   242  			case containertools.NoneTool:
   243  				registry, err = containerdregistry.NewRegistry(containerdregistry.WithLog(logger))
   244  			default:
   245  				err = fmt.Errorf("unrecognized container-tool option: %s", containerTool)
   246  			}
   247  			Expect(err).NotTo(HaveOccurred())
   248  
   249  			unpackDir, err := ioutil.TempDir(".", bundleTag3)
   250  			Expect(err).NotTo(HaveOccurred())
   251  			validator := bundle.NewImageValidator(registry, logger)
   252  			Expect(validator.PullBundleImage(img, unpackDir)).To(Succeed())
   253  
   254  			By("validating bundle format")
   255  			Expect(validator.ValidateBundleFormat(unpackDir)).To(Succeed())
   256  
   257  			By("validating bundle content")
   258  			manifestsDir := filepath.Join(unpackDir, bundle.ManifestsDir)
   259  			Expect(validator.ValidateBundleContent(manifestsDir)).To(Succeed())
   260  			Expect(os.RemoveAll(unpackDir)).To(Succeed())
   261  		})
   262  
   263  		It("builds and manipulates bundle and index images", func() {
   264  			By("building bundles")
   265  			bundles := bundleLocations{
   266  				{bundleImage + ":" + bundleTag1, bundlePath1},
   267  				{bundleImage + ":" + bundleTag2, bundlePath2},
   268  				{bundleImage + ":" + bundleTag3, bundlePath3},
   269  			}
   270  			var err error
   271  			for _, b := range bundles {
   272  				err = inTemporaryBuildContext(func() error {
   273  					return bundle.BuildFunc(b.path, "", b.image, containerTool, packageName, channels, defaultChannel, false)
   274  				})
   275  				Expect(err).NotTo(HaveOccurred())
   276  			}
   277  
   278  			By("pushing bundles")
   279  			for _, b := range bundles {
   280  				Expect(pushWith(containerTool, b.image)).NotTo(HaveOccurred())
   281  			}
   282  
   283  			By("building an index")
   284  			err = buildIndexWith(containerTool, "", indexImage1, bundles[:2].images(), registry.ReplacesMode, false)
   285  			Expect(err).NotTo(HaveOccurred())
   286  
   287  			By("pushing an index")
   288  			err = pushWith(containerTool, indexImage1)
   289  			Expect(err).NotTo(HaveOccurred())
   290  
   291  			By("building from an index")
   292  			err = buildFromIndexWith(containerTool)
   293  			Expect(err).NotTo(HaveOccurred())
   294  
   295  			By("pushing an index")
   296  			err = pushWith(containerTool, indexImage2)
   297  			Expect(err).NotTo(HaveOccurred())
   298  
   299  			By("pruning an index")
   300  			err = pruneIndexWith(containerTool)
   301  			Expect(err).NotTo(HaveOccurred())
   302  
   303  			By("pushing an index")
   304  			err = pushWith(containerTool, indexImage3)
   305  			Expect(err).NotTo(HaveOccurred())
   306  
   307  			By("exporting a package from an index to disk")
   308  			err = exportPackageWith(containerTool)
   309  			Expect(err).NotTo(HaveOccurred())
   310  
   311  			By("loading manifests from a directory")
   312  			err = initialize()
   313  			Expect(err).NotTo(HaveOccurred())
   314  
   315  			// clean and try again with containerd
   316  			err = os.RemoveAll("downloaded")
   317  			Expect(err).NotTo(HaveOccurred())
   318  
   319  			By("exporting a package from an index to disk with containerd")
   320  			err = exportPackageWith(containertools.NoneTool.String())
   321  			Expect(err).NotTo(HaveOccurred())
   322  
   323  			By("loading manifests from a containerd-extracted directory")
   324  			err = initialize()
   325  			Expect(err).NotTo(HaveOccurred())
   326  
   327  			// clean containerd-extracted directory
   328  			err = os.RemoveAll("downloaded")
   329  			Expect(err).NotTo(HaveOccurred())
   330  
   331  			By("exporting an entire index to disk")
   332  			err = exportIndexImageWith(containerTool)
   333  			Expect(err).NotTo(HaveOccurred())
   334  
   335  			By("loading manifests from a directory")
   336  			err = initialize()
   337  			Expect(err).NotTo(HaveOccurred())
   338  		})
   339  
   340  		It("build bundles and index via inference", func() {
   341  
   342  			bundles := bundleLocations{
   343  				{bundleImage + ":" + rand.String(6), "./testdata/aqua/0.0.1"},
   344  				{bundleImage + ":" + rand.String(6), "./testdata/aqua/0.0.2"},
   345  				{bundleImage + ":" + rand.String(6), "./testdata/aqua/1.0.0"},
   346  				{bundleImage + ":" + rand.String(6), "./testdata/aqua/1.0.1"},
   347  			}
   348  
   349  			By("building bundles")
   350  			for _, b := range bundles {
   351  				td, err := ioutil.TempDir(".", "opm-")
   352  				Expect(err).NotTo(HaveOccurred())
   353  				defer os.RemoveAll(td)
   354  
   355  				err = bundle.BuildFunc(b.path, td, b.image, containerTool, "", "", "", true)
   356  				Expect(err).NotTo(HaveOccurred())
   357  			}
   358  
   359  			By("pushing bundles")
   360  			for _, b := range bundles {
   361  				Expect(pushWith(containerTool, b.image)).NotTo(HaveOccurred())
   362  			}
   363  
   364  			By("building an index")
   365  			indexImage := indexImage + ":" + rand.String(6)
   366  			err := buildIndexWith(containerTool, "", indexImage, bundles.images(), registry.ReplacesMode, false)
   367  			Expect(err).NotTo(HaveOccurred())
   368  		})
   369  		It("build index without bundles", func() {
   370  			indexImage := indexImage + ":" + rand.String(6)
   371  			By("building an index")
   372  			err := buildIndexWith(containerTool, "", indexImage, []string{}, registry.ReplacesMode, true)
   373  			Expect(err).NotTo(HaveOccurred())
   374  		})
   375  
   376  		PIt("can overwrite existing bundles in an index", func() {
   377  			// TODO fix regression overwriting existing bundles in an index
   378  			bundles := bundleLocations{
   379  				{bundleImage + ":" + rand.String(6), "./testdata/aqua/0.0.1"},
   380  				{bundleImage + ":" + rand.String(6), "./testdata/aqua/0.0.2"},
   381  				{bundleImage + ":" + rand.String(6), "./testdata/aqua/1.0.0"},
   382  				{bundleImage + ":" + rand.String(6), "./testdata/aqua/1.0.1"},
   383  				{bundleImage + ":" + rand.String(6), "./testdata/aqua/1.0.1-overwrite"},
   384  			}
   385  
   386  			for _, b := range bundles {
   387  				td, err := ioutil.TempDir(".", "opm-")
   388  				Expect(err).NotTo(HaveOccurred())
   389  				defer os.RemoveAll(td)
   390  
   391  				err = bundle.BuildFunc(b.path, td, b.image, containerTool, "", "", "", true)
   392  				Expect(err).NotTo(HaveOccurred())
   393  			}
   394  
   395  			By("pushing bundles")
   396  			for _, b := range bundles {
   397  				Expect(pushWith(containerTool, b.image)).NotTo(HaveOccurred())
   398  			}
   399  
   400  			indexImage := indexImage + ":" + rand.String(6)
   401  			By("adding net-new bundles to an index")
   402  			err := buildIndexWith(containerTool, "", indexImage, bundles[:4].images(), registry.ReplacesMode, true) // 0.0.1, 0.0.2, 1.0.0, 1.0.1
   403  			Expect(err).NotTo(HaveOccurred())
   404  			Expect(pushWith(containerTool, indexImage)).NotTo(HaveOccurred())
   405  
   406  			By("failing to overwrite a non-latest bundle")
   407  			nextIndex := indexImage + "-next"
   408  			err = buildIndexWith(containerTool, indexImage, nextIndex, bundles[1:2].images(), registry.ReplacesMode, true) // 0.0.2
   409  			Expect(err).To(HaveOccurred())
   410  
   411  			By("failing to overwrite in a non-replace mode")
   412  			err = buildIndexWith(containerTool, indexImage, nextIndex, bundles[4:].images(), registry.SemVerMode, true) // 1.0.1-overwrite
   413  			Expect(err).To(HaveOccurred())
   414  
   415  			By("overwriting the latest bundle in an index")
   416  			err = buildIndexWith(containerTool, indexImage, nextIndex, bundles[4:].images(), registry.ReplacesMode, true) // 1.0.1-overwrite
   417  			Expect(err).NotTo(HaveOccurred())
   418  		})
   419  
   420  		It("doesn't change published content on overwrite", func() {
   421  			if publishedIndex == "" {
   422  				Skip("Set the PUBLISHED_INDEX environment variable to enable this test")
   423  			}
   424  
   425  			logger := logrus.NewEntry(logrus.StandardLogger())
   426  			logger.Logger.SetLevel(logrus.WarnLevel)
   427  			tool := containertools.NewContainerTool(containerTool, containertools.NoneTool)
   428  			imageIndexer := indexer.ImageIndexer{
   429  				PullTool: tool,
   430  				Logger:   logger,
   431  			}
   432  			dbFile, err := imageIndexer.ExtractDatabase(".", publishedIndex, "", *skipTLSForRegistry, *useHTTPforRegistry)
   433  			Expect(err).NotTo(HaveOccurred(), "error extracting registry db")
   434  
   435  			db, err := sql.Open("sqlite3", fmt.Sprintf("file:%s", dbFile))
   436  			Expect(err).NotTo(HaveOccurred(), "Error reading db file")
   437  
   438  			querier := sqlite.NewSQLLiteQuerierFromDb(db)
   439  
   440  			graphLoader, err := sqlite.NewSQLGraphLoaderFromDB(db)
   441  			Expect(err).NotTo(HaveOccurred(), "Error reading db file")
   442  
   443  			packages, err := querier.ListPackages(context.TODO())
   444  			Expect(err).NotTo(HaveOccurred(), "Error listing packages")
   445  
   446  			var errs []error
   447  
   448  			adder := lregistry.NewRegistryAdder(logger)
   449  			for _, pkg := range packages {
   450  				existing, err := graphLoader.Generate(pkg)
   451  				Expect(err).NotTo(HaveOccurred(), "Error generating graph for package %s", pkg, existing)
   452  
   453  				for name, ch := range existing.Channels {
   454  					replacement, err := querier.GetBundleThatReplaces(context.TODO(), ch.Head.CsvName, pkg, name)
   455  					if err != nil && err.Error() != fmt.Errorf("no entry found for %s %s", pkg, name).Error() {
   456  						errs = append(errs, err)
   457  						continue
   458  					}
   459  
   460  					if replacement != nil {
   461  						continue
   462  					}
   463  
   464  					request := lregistry.AddToRegistryRequest{
   465  						Permissive:    false,
   466  						SkipTLSVerify: *skipTLSForRegistry,
   467  						PlainHTTP:     *useHTTPforRegistry,
   468  						InputDatabase: dbFile,
   469  						Bundles:       []string{ch.Head.BundlePath},
   470  						Mode:          registry.ReplacesMode,
   471  						ContainerTool: tool,
   472  						Overwrite:     true,
   473  					}
   474  
   475  					err = adder.AddToRegistry(request)
   476  					if err != nil {
   477  						errs = append(errs, fmt.Errorf("Error overwriting bundles for package %s: %s", pkg, err))
   478  					}
   479  				}
   480  
   481  				overwritten, err := graphLoader.Generate(pkg)
   482  				Expect(err).NotTo(HaveOccurred(), "Error generating graph for package %s", pkg)
   483  
   484  				if !reflect.DeepEqual(existing, overwritten) {
   485  					errs = append(errs, fmt.Errorf("the update graph has changed during overwrite-latest for package: %s\nWas\n%s\nIs now\n%s", pkg, existing, overwritten))
   486  				}
   487  			}
   488  
   489  			Expect(errs).To(BeEmpty(), fmt.Sprintf("%s", errs))
   490  		})
   491  	}
   492  
   493  	Context("using docker", func() {
   494  		cmd := exec.Command("docker")
   495  		cmd.Stderr = os.Stderr
   496  		if err := cmd.Run(); err != nil {
   497  			GinkgoT().Logf("container tool docker not found - skipping docker-based opm e2e tests: %v", err)
   498  			return
   499  		}
   500  		IncludeSharedSpecs("docker")
   501  	})
   502  
   503  	Context("using podman", func() {
   504  		cmd := exec.Command("podman", "info")
   505  		cmd.Stderr = os.Stderr
   506  		if err := cmd.Run(); err != nil {
   507  			GinkgoT().Logf("container tool podman not found - skipping podman-based opm e2e tests: %v", err)
   508  			return
   509  		}
   510  		IncludeSharedSpecs("podman")
   511  	})
   512  })