get.porter.sh/porter@v1.3.0/tests/grpc/installation_test.go (about)

     1  package grpc
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"strconv"
     8  	"testing"
     9  
    10  	iGRPC "get.porter.sh/porter/gen/proto/go/porterapis/installation/v1alpha1"
    11  	pGRPC "get.porter.sh/porter/gen/proto/go/porterapis/porter/v1alpha1"
    12  	"get.porter.sh/porter/pkg/cnab"
    13  	"get.porter.sh/porter/pkg/porter"
    14  	"get.porter.sh/porter/pkg/storage"
    15  	"get.porter.sh/porter/tests"
    16  	"github.com/cnabio/cnab-go/bundle"
    17  	"github.com/cnabio/cnab-go/bundle/definition"
    18  	"github.com/stretchr/testify/assert"
    19  	"github.com/stretchr/testify/require"
    20  	"github.com/tidwall/gjson"
    21  	"google.golang.org/grpc"
    22  	"google.golang.org/grpc/credentials/insecure"
    23  	"google.golang.org/protobuf/encoding/protojson"
    24  )
    25  
    26  type testOutputOpt struct {
    27  	name         string
    28  	value        string
    29  	bundleOutput bundle.Output
    30  }
    31  type testInstallationOpts struct {
    32  	bundleDefs *map[string]*definition.Schema
    33  	outputs    *[]testOutputOpt
    34  }
    35  
    36  // TODO: add opts structure for different installation options
    37  func newTestInstallation(t *testing.T, namespace, name string, grpcSvr *TestPorterGRPCServer, opts testInstallationOpts) storage.Installation {
    38  	//Bundle Definition with required porter-state
    39  	bd := definition.Definitions{
    40  		"porter-state": &definition.Schema{
    41  			Type:    "string",
    42  			Comment: "porter-internal",
    43  		},
    44  	}
    45  	for name, schema := range *opts.bundleDefs {
    46  		bd[name] = schema
    47  	}
    48  	//Bundle Output with required porter-state
    49  	bo := map[string]bundle.Output{
    50  		"porter-state": {
    51  			Definition: "porter-state",
    52  			Path:       "/cnab/app/outputs/porter-state.tgz",
    53  		},
    54  	}
    55  	for _, out := range *opts.outputs {
    56  		bo[out.name] = out.bundleOutput
    57  	}
    58  	b := bundle.Bundle{
    59  		Definitions: bd,
    60  		Outputs:     bo,
    61  	}
    62  	extB := cnab.NewBundle(b)
    63  	storeInst := grpcSvr.TestPorter.TestInstallations.CreateInstallation(storage.NewInstallation(namespace, name), grpcSvr.TestPorter.TestInstallations.SetMutableInstallationValues, func(i *storage.Installation) {
    64  		i.Status.BundleVersion = "v0.1.0"
    65  		i.Status.ResultStatus = cnab.StatusSucceeded
    66  		i.Bundle.Repository = "test-bundle"
    67  		i.Bundle.Version = "v0.1.0"
    68  	})
    69  	c := grpcSvr.TestPorter.TestInstallations.CreateRun(storeInst.NewRun(cnab.ActionInstall, cnab.ExtendedBundle{}), func(sRun *storage.Run) {
    70  		sRun.Bundle = b
    71  		sRun.ParameterOverrides.Parameters = grpcSvr.TestPorter.SanitizeParameters(sRun.ParameterOverrides.Parameters, sRun.ID, extB)
    72  	})
    73  	sRes := grpcSvr.TestPorter.TestInstallations.CreateResult(c.NewResult(cnab.StatusSucceeded))
    74  	for _, out := range *opts.outputs {
    75  		grpcSvr.TestPorter.CreateOutput(sRes.NewOutput(out.name, []byte(out.value)), extB)
    76  	}
    77  	return storeInst
    78  }
    79  
    80  func TestInstall_installationMessage(t *testing.T) {
    81  	writeOnly := true
    82  	basicInstOpts := testInstallationOpts{
    83  		bundleDefs: &map[string]*definition.Schema{
    84  			"foo": {Type: "string", WriteOnly: &writeOnly},
    85  			"bar": {Type: "string", WriteOnly: &writeOnly},
    86  		},
    87  		outputs: &[]testOutputOpt{
    88  			{
    89  				name:         "foo",
    90  				value:        "foo-data",
    91  				bundleOutput: bundle.Output{Definition: "foo", Path: "/path/to/foo"},
    92  			},
    93  			{
    94  				name:         "bar",
    95  				value:        "bar-data",
    96  				bundleOutput: bundle.Output{Definition: "bar", Path: "/path/to/bar"},
    97  			},
    98  		},
    99  	}
   100  	tests := []struct {
   101  		testName      string
   102  		instName      string
   103  		instNamespace string ""
   104  		instOpts      testInstallationOpts
   105  	}{
   106  		{
   107  			testName: "basic installation",
   108  			instName: "test",
   109  			instOpts: basicInstOpts,
   110  		},
   111  		{
   112  			testName: "another installation",
   113  			instName: "another-test",
   114  			instOpts: basicInstOpts,
   115  		},
   116  	}
   117  	for _, test := range tests {
   118  		t.Run(test.testName, func(t *testing.T) {
   119  			//Server setup
   120  			grpcSvr, err := NewTestGRPCServer(t)
   121  			require.NoError(t, err)
   122  			server := grpcSvr.ListenAndServe()
   123  			defer server.Stop()
   124  
   125  			//Client setup
   126  			ctx := context.TODO()
   127  			client, err := grpc.NewClient("passthrough://bufnet", grpc.WithContextDialer(bufDialer), grpc.WithTransportCredentials(insecure.NewCredentials()))
   128  			require.NoError(t, err)
   129  			defer client.Close()
   130  			instClient := pGRPC.NewPorterClient(client)
   131  
   132  			inst := newTestInstallation(t, test.instNamespace, test.instName, grpcSvr, test.instOpts)
   133  
   134  			//Call ListInstallations
   135  			resp, err := instClient.ListInstallations(ctx, &iGRPC.ListInstallationsRequest{})
   136  			require.NoError(t, err)
   137  			assert.Len(t, resp.Installation, 1)
   138  			// Validation
   139  			validateInstallations(t, inst, resp.GetInstallation()[0])
   140  
   141  			//Call ListInstallationLatestOutputRequest
   142  			req := &iGRPC.ListInstallationLatestOutputRequest{Name: test.instName, Namespace: &test.instNamespace}
   143  			oresp, err := instClient.ListInstallationLatestOutputs(ctx, req)
   144  			require.NoError(t, err)
   145  			assert.Len(t, oresp.GetOutputs(), len(*test.instOpts.outputs))
   146  
   147  			oOpts := &porter.OutputListOptions{}
   148  			oOpts.Name = test.instName
   149  			oOpts.Namespace = test.instNamespace
   150  			oOpts.Format = "json"
   151  			dvs, err := grpcSvr.TestPorter.ListBundleOutputs(ctx, oOpts)
   152  			require.NoError(t, err)
   153  
   154  			//Validation
   155  			validateOutputs(t, dvs, oresp)
   156  		})
   157  	}
   158  }
   159  
   160  func validateInstallations(t *testing.T, expected storage.Installation, actual *iGRPC.Installation) {
   161  	assert.Equal(t, actual.Name, expected.Name)
   162  	bExpInst, err := json.Marshal(porter.NewDisplayInstallation(expected))
   163  	require.NoError(t, err)
   164  	bExpInst, err = tests.GRPCDisplayInstallationExpectedJSON(bExpInst)
   165  	require.NoError(t, err)
   166  	pjm := protojson.MarshalOptions{EmitUnpopulated: true}
   167  	bActInst, err := pjm.Marshal(actual)
   168  	require.NoError(t, err)
   169  	var pJson bytes.Buffer
   170  	require.NoError(t, json.Indent(&pJson, bActInst, "", "  "))
   171  	assert.JSONEq(t, string(bExpInst), string(bActInst))
   172  }
   173  
   174  func validateOutputs(t *testing.T, dvs porter.DisplayValues, actual *iGRPC.ListInstallationLatestOutputResponse) {
   175  	//Get expected json
   176  	bExpOuts, err := json.MarshalIndent(dvs, "", "  ")
   177  	require.NoError(t, err)
   178  	pjm := protojson.MarshalOptions{EmitUnpopulated: true, Multiline: true, Indent: "  "}
   179  	//Get actual json response
   180  	for i, gPV := range actual.GetOutputs() {
   181  		bActOut, err := pjm.Marshal(gPV)
   182  		require.NoError(t, err)
   183  		//TODO: make this not dependent on order
   184  		bExpOut := gjson.GetBytes(bExpOuts, strconv.Itoa(i)).String()
   185  		assert.JSONEq(t, bExpOut, string(bActOut))
   186  	}
   187  }