get.porter.sh/porter@v1.3.0/pkg/porter/helpers.go (about)

     1  package porter
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"math/rand"
     7  	"os"
     8  	"path/filepath"
     9  	"runtime"
    10  	"strings"
    11  	"testing"
    12  	"time"
    13  
    14  	"get.porter.sh/porter/pkg/build"
    15  	"get.porter.sh/porter/pkg/cache"
    16  	"get.porter.sh/porter/pkg/cnab"
    17  	cnabtooci "get.porter.sh/porter/pkg/cnab/cnab-to-oci"
    18  	cnabprovider "get.porter.sh/porter/pkg/cnab/provider"
    19  	"get.porter.sh/porter/pkg/config"
    20  	"get.porter.sh/porter/pkg/encoding"
    21  	"get.porter.sh/porter/pkg/manifest"
    22  	"get.porter.sh/porter/pkg/mixin"
    23  	"get.porter.sh/porter/pkg/plugins"
    24  	"get.porter.sh/porter/pkg/secrets"
    25  	"get.porter.sh/porter/pkg/signing"
    26  	"get.porter.sh/porter/pkg/storage"
    27  	"get.porter.sh/porter/pkg/tracing"
    28  	"get.porter.sh/porter/pkg/yaml"
    29  	"github.com/cnabio/cnab-go/bundle"
    30  	"github.com/stretchr/testify/require"
    31  )
    32  
    33  type TestPorter struct {
    34  	*Porter
    35  	TestConfig        *config.TestConfig
    36  	TestStore         storage.TestStore
    37  	TestInstallations *storage.TestInstallationProvider
    38  	TestCredentials   *storage.TestCredentialSetProvider
    39  	TestParameters    *storage.TestParameterSetProvider
    40  	TestCache         *cache.TestCache
    41  	TestRegistry      *cnabtooci.TestRegistry
    42  	TestSecrets       secrets.Store
    43  	TestSanitizer     *storage.Sanitizer
    44  
    45  	// original directory where the test was being executed
    46  	TestDir string
    47  
    48  	// directory where the integration test is being executed
    49  	BundleDir string
    50  
    51  	// root of the repository
    52  	// Helps us avoid hard coding relative paths from test directories, which easily break when tests are moved
    53  	RepoRoot string
    54  
    55  	// The root test context created by NewTestPorter
    56  	RootContext context.Context
    57  
    58  	// The root log span created by NewTestPorter
    59  	RootSpan tracing.TraceLogger
    60  }
    61  
    62  // NewTestPorter initializes a porter test client, with the output buffered, and an in-memory file system.
    63  func NewTestPorter(t *testing.T) *TestPorter {
    64  	tc := config.NewTestConfig(t)
    65  	testStore := storage.NewTestStore(tc)
    66  	testSecrets := secrets.NewTestSecretsProvider()
    67  	testSigner := signing.NewTestSigningProvider()
    68  	testCredentials := storage.NewTestCredentialProviderFor(t, testStore, testSecrets)
    69  	testParameters := storage.NewTestParameterProviderFor(t, testStore, testSecrets)
    70  	testCache := cache.NewTestCache(cache.New(tc.Config))
    71  	testInstallations := storage.NewTestInstallationProviderFor(t, testStore)
    72  	testRegistry := cnabtooci.NewTestRegistry()
    73  
    74  	p := NewFor(tc.Config, testStore, testSecrets, testSigner)
    75  	p.Config = tc.Config
    76  	p.Mixins = mixin.NewTestMixinProvider()
    77  	p.Plugins = plugins.NewTestPluginProvider()
    78  	p.Cache = testCache
    79  	p.builder = NewTestBuildProvider()
    80  	p.Installations = testInstallations
    81  	p.Credentials = testCredentials
    82  	p.Parameters = testParameters
    83  	p.Secrets = testSecrets
    84  	p.CNAB = cnabprovider.NewTestRuntimeFor(tc, testInstallations, testCredentials, testParameters, testSecrets)
    85  	p.Registry = testRegistry
    86  
    87  	tp := TestPorter{
    88  		Porter:            p,
    89  		TestConfig:        tc,
    90  		TestStore:         testStore,
    91  		TestSecrets:       testSecrets,
    92  		TestInstallations: testInstallations,
    93  		TestCredentials:   testCredentials,
    94  		TestParameters:    testParameters,
    95  		TestCache:         testCache,
    96  		TestRegistry:      testRegistry,
    97  		TestSanitizer:     storage.NewSanitizer(testParameters, testSecrets),
    98  		RepoRoot:          tc.TestContext.FindRepoRoot(),
    99  	}
   100  
   101  	// Start a tracing span for the test, so that we can capture logs
   102  	tp.RootContext, tp.RootSpan = p.StartRootSpan(context.Background(), t.Name())
   103  
   104  	return &tp
   105  }
   106  
   107  func (p *TestPorter) Close() error {
   108  	err := p.TestStore.Close()
   109  	p.TestConfig.Close()
   110  	p.RootSpan.EndSpan()
   111  	return err
   112  }
   113  
   114  func (p *TestPorter) SetupIntegrationTest() context.Context {
   115  	t := p.TestConfig.TestContext.T
   116  
   117  	// Undo changes above to make a unit test friendly Porter, so we hit the host
   118  	p.Porter = NewFor(p.Config, p.TestStore, p.TestSecrets, p.Signer)
   119  
   120  	// Run the test in a temp directory
   121  	ctx, testDir, _ := p.TestConfig.SetupIntegrationTest()
   122  	p.TestDir = testDir
   123  	p.CreateBundleDir()
   124  
   125  	// Write out a storage schema so that we don't trigger a migration check
   126  	err := p.Storage.WriteSchema(ctx)
   127  	require.NoError(t, err, "failed to set the storage schema")
   128  
   129  	// Load test credentials, with KUBECONFIG replaced properly
   130  	kubeconfig := filepath.Join(p.RepoRoot, "kind.config")
   131  	if runtime.GOOS == "windows" {
   132  		kubeconfig = strings.Replace(kubeconfig, `\`, `\\`, -1)
   133  	}
   134  	ciCredsPath := filepath.Join(p.RepoRoot, "build/testdata/credentials/ci.json")
   135  	ciCredsB, err := p.FileSystem.ReadFile(ciCredsPath)
   136  	require.NoError(t, err, "could not read test credentials %s", ciCredsPath)
   137  	// update the kubeconfig reference in the credentials to match what's on people's dev machine
   138  	ciCredsB = []byte(strings.Replace(string(ciCredsB), "KUBECONFIGPATH", kubeconfig, -1))
   139  	var testCreds storage.CredentialSet
   140  	err = encoding.UnmarshalJson(ciCredsB, &testCreds)
   141  	require.NoError(t, err, "could not unmarshal test credentials %s", ciCredsPath)
   142  	err = p.Credentials.UpsertCredentialSet(context.Background(), testCreds)
   143  	require.NoError(t, err, "could not save test credentials (ci)")
   144  
   145  	// Make a copy of the creds with a different name so that we can test out switching to different credential sets
   146  	testCreds.Name = "ci2"
   147  	err = p.Credentials.UpsertCredentialSet(context.Background(), testCreds)
   148  	require.NoError(t, err, "could not save test credentials (ci2)")
   149  
   150  	return ctx
   151  }
   152  
   153  func (p *TestPorter) AddTestFile(src string, dest string) {
   154  	if !filepath.IsAbs(src) {
   155  		src = filepath.Join(p.TestDir, src)
   156  	}
   157  
   158  	p.TestConfig.TestContext.AddTestFile(src, dest)
   159  }
   160  
   161  type TestDriver struct {
   162  	Name     string
   163  	Filepath string
   164  }
   165  
   166  func (p *TestPorter) AddTestDriver(driver TestDriver) string {
   167  	if !filepath.IsAbs(driver.Filepath) {
   168  		driver.Filepath = filepath.Join(p.TestDir, driver.Filepath)
   169  	}
   170  
   171  	return p.TestConfig.TestContext.AddTestDriver(driver.Filepath, driver.Name)
   172  }
   173  
   174  func (p *TestPorter) CreateBundleDir() string {
   175  	bundleDir, err := os.MkdirTemp("", "bundle")
   176  	require.NoError(p.T(), err)
   177  
   178  	p.BundleDir = bundleDir
   179  	p.Chdir(bundleDir)
   180  	p.TestConfig.TestContext.AddCleanupDir(p.BundleDir)
   181  
   182  	return bundleDir
   183  }
   184  
   185  func (p *TestPorter) T() *testing.T {
   186  	return p.TestConfig.TestContext.T
   187  }
   188  
   189  func (p *TestPorter) ReadBundle(path string) cnab.ExtendedBundle {
   190  	bunD, err := os.ReadFile(path)
   191  	require.NoError(p.T(), err, "ReadFile failed for %s", path)
   192  
   193  	bun, err := bundle.Unmarshal(bunD)
   194  	require.NoError(p.T(), err, "Unmarshal failed for bundle at %s", path)
   195  
   196  	return cnab.NewBundle(*bun)
   197  }
   198  
   199  func (p *TestPorter) RandomString(len int) string {
   200  	rand.New(rand.NewSource((time.Now().UnixNano())))
   201  	bytes := make([]byte, len)
   202  	for i := 0; i < len; i++ {
   203  		//A=97 and Z = 97+25
   204  		bytes[i] = byte(97 + rand.Intn(25))
   205  	}
   206  	return string(bytes)
   207  }
   208  
   209  // AddTestBundleDir into the test bundle directory and give it a unique name
   210  // to avoid collisions with other tests running in parallel.
   211  func (p *TestPorter) AddTestBundleDir(bundleDir string, generateUniqueName bool) string {
   212  	if !filepath.IsAbs(bundleDir) {
   213  		bundleDir = filepath.Join(p.TestDir, bundleDir)
   214  	}
   215  	p.TestConfig.TestContext.AddTestDirectory(bundleDir, p.BundleDir)
   216  
   217  	testManifest := filepath.Join(p.BundleDir, config.Name)
   218  	m, err := manifest.LoadManifestFrom(p.RootContext, p.Config, testManifest)
   219  	require.NoError(p.T(), err)
   220  
   221  	if !generateUniqueName {
   222  		return m.Name
   223  	}
   224  
   225  	e := yaml.NewEditor(p.FileSystem)
   226  	err = e.ReadFile(testManifest)
   227  	require.NoError(p.T(), err)
   228  
   229  	uniqueName := fmt.Sprintf("%s-%s", m.Name, p.RandomString(5))
   230  	err = e.SetValue("name", uniqueName)
   231  	require.NoError(p.T(), err)
   232  
   233  	err = e.WriteFile(testManifest)
   234  	require.NoError(p.T(), err)
   235  
   236  	return uniqueName
   237  }
   238  
   239  // CompareGoldenFile checks if the specified string matches the content of a golden test file.
   240  // When they are different and PORTER_UPDATE_TEST_FILES is true, the file is updated to match
   241  // the new test output.
   242  func (p *TestPorter) CompareGoldenFile(goldenFile string, got string) {
   243  	p.TestConfig.TestContext.CompareGoldenFile(goldenFile, got)
   244  }
   245  
   246  // CreateInstallation saves an installation record into claim store and store
   247  // sensitive parameters into secret store.
   248  func (p *TestPorter) SanitizeParameters(raw []secrets.SourceMap, recordID string, bun cnab.ExtendedBundle) []secrets.SourceMap {
   249  	strategies, err := p.Sanitizer.CleanParameters(context.Background(), raw, bun, recordID)
   250  	require.NoError(p.T(), err)
   251  
   252  	return strategies
   253  }
   254  
   255  func (p *TestPorter) CreateOutput(o storage.Output, bun cnab.ExtendedBundle) storage.Output {
   256  	return p.TestInstallations.CreateOutput(o, func(o *storage.Output) {
   257  		output, err := p.TestSanitizer.CleanOutput(context.Background(), *o, bun)
   258  		require.NoError(p.T(), err)
   259  		*o = output
   260  	})
   261  }
   262  
   263  type TestBuildProvider struct {
   264  }
   265  
   266  func NewTestBuildProvider() *TestBuildProvider {
   267  	return &TestBuildProvider{}
   268  }
   269  
   270  func (t *TestBuildProvider) BuildBundleImage(ctx context.Context, manifest *manifest.Manifest, opts build.BuildImageOptions) error {
   271  	return nil
   272  }
   273  
   274  func (t *TestBuildProvider) TagBundleImage(ctx context.Context, origTag, newTag string) error {
   275  	return nil
   276  }