github.com/SamarSidharth/kpt@v0.0.0-20231122062228-c7d747ae3ace/e2e/porch_test.go (about)

     1  // Copyright 2022 The kpt Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  //go:build porch
    16  
    17  package e2e
    18  
    19  import (
    20  	"bufio"
    21  	"bytes"
    22  	"errors"
    23  	"io/fs"
    24  	"os"
    25  	"os/exec"
    26  	"path/filepath"
    27  	"strings"
    28  	"testing"
    29  	"time"
    30  
    31  	"github.com/GoogleContainerTools/kpt/pkg/test/porch"
    32  	"github.com/google/go-cmp/cmp"
    33  	"sigs.k8s.io/kustomize/kyaml/yaml"
    34  )
    35  
    36  const (
    37  	updateGoldenFiles = "UPDATE_GOLDEN_FILES"
    38  	testGitNamespace  = "test-git-namespace"
    39  )
    40  
    41  func TestPorch(t *testing.T) {
    42  	abs, err := filepath.Abs(filepath.Join(".", "testdata", "porch"))
    43  	if err != nil {
    44  		t.Fatalf("Failed to get absolute path to testdata directory: %v", err)
    45  	}
    46  	runTests(t, abs)
    47  }
    48  
    49  func runUtilityCommand(t *testing.T, command string, args ...string) error {
    50  	cmd := exec.Command(command, args...)
    51  	t.Logf("running utility command %s %s", command, strings.Join(args, " "))
    52  	return cmd.Run()
    53  }
    54  
    55  func runTests(t *testing.T, path string) {
    56  	gitServerURL := startGitServer(t, path)
    57  	testCases := scanTestCases(t, path)
    58  
    59  	// remove any tmp files from previous test runs
    60  	err := runUtilityCommand(t, "rm", "-rf", "/tmp/porch-e2e")
    61  	if err != nil {
    62  		t.Fatalf("Failed to clean up older run: %v", err)
    63  	}
    64  	err = runUtilityCommand(t, "mkdir", "/tmp/porch-e2e")
    65  	if err != nil {
    66  		t.Fatalf("Failed to create temp directory: %v", err)
    67  	}
    68  
    69  	for _, tc := range testCases {
    70  		t.Run(tc.TestCase, func(t *testing.T) {
    71  			if tc.Skip != "" {
    72  				t.Skipf("Skipping test: %s", tc.Skip)
    73  			}
    74  			repoURL := gitServerURL + "/" + strings.ReplaceAll(tc.TestCase, "/", "-")
    75  			runTestCase(t, repoURL, tc)
    76  		})
    77  	}
    78  }
    79  
    80  func runTestCase(t *testing.T, repoURL string, tc porch.TestCaseConfig) {
    81  	porch.KubectlCreateNamespace(t, tc.TestCase)
    82  	t.Cleanup(func() {
    83  		porch.KubectlDeleteNamespace(t, tc.TestCase)
    84  	})
    85  
    86  	if tc.Repository != "" {
    87  		porch.RegisterRepository(t, repoURL, tc.TestCase, tc.Repository)
    88  	}
    89  
    90  	for i := range tc.Commands {
    91  		time.Sleep(1 * time.Second)
    92  		command := &tc.Commands[i]
    93  		cmd := exec.Command("kpt", command.Args...)
    94  
    95  		var stdout, stderr bytes.Buffer
    96  		if command.Stdin != "" {
    97  			cmd.Stdin = strings.NewReader(command.Stdin)
    98  		}
    99  		cmd.Stdout = &stdout
   100  		cmd.Stderr = &stderr
   101  
   102  		t.Logf("running command %v", strings.Join(cmd.Args, " "))
   103  		err := cmd.Run()
   104  
   105  		if command.Yaml {
   106  			reorderYamlStdout(t, &stdout)
   107  		}
   108  
   109  		cleanupStderr(t, &stderr)
   110  
   111  		if os.Getenv(updateGoldenFiles) != "" {
   112  			updateCommand(command, err, stdout.String(), stderr.String())
   113  		}
   114  
   115  		if got, want := exitCode(err), command.ExitCode; got != want {
   116  			t.Errorf("unexpected exit code from 'kpt %s'; got %d, want %d", strings.Join(command.Args, " "), got, want)
   117  		}
   118  		if got, want := stdout.String(), command.Stdout; got != want {
   119  			t.Errorf("unexpected stdout content from 'kpt %s'; (-want, +got) %s", strings.Join(command.Args, " "), cmp.Diff(want, got))
   120  		}
   121  		if got, want := stderr.String(), command.Stderr; got != want {
   122  			t.Errorf("unexpected stderr content from 'kpt %s'; (-want, +got) %s", strings.Join(command.Args, " "), cmp.Diff(want, got))
   123  		}
   124  
   125  		// hack here; but if the command registered a repo, give a few extra seconds for the repo to reach readiness
   126  		for _, arg := range command.Args {
   127  			if arg == "register" {
   128  				time.Sleep(5 * time.Second)
   129  			}
   130  		}
   131  	}
   132  
   133  	if os.Getenv(updateGoldenFiles) != "" {
   134  		porch.WriteTestCaseConfig(t, &tc)
   135  	}
   136  }
   137  
   138  // remove PASS lines from kpt fn eval, which includes a duration and will vary
   139  func cleanupStderr(t *testing.T, buf *bytes.Buffer) {
   140  	scanner := bufio.NewScanner(buf)
   141  	var newBuf bytes.Buffer
   142  	for scanner.Scan() {
   143  		line := scanner.Text()
   144  		if !strings.Contains(line, "[PASS]") {
   145  			newBuf.Write([]byte(line))
   146  			newBuf.Write([]byte("\n"))
   147  		}
   148  	}
   149  
   150  	buf.Reset()
   151  	if _, err := buf.Write(newBuf.Bytes()); err != nil {
   152  		t.Fatalf("Failed to update cleaned up stderr: %v", err)
   153  	}
   154  }
   155  
   156  func reorderYamlStdout(t *testing.T, buf *bytes.Buffer) {
   157  	if buf.Len() == 0 {
   158  		return
   159  	}
   160  
   161  	// strip out the resourceVersion:, creationTimestamp:
   162  	// because that will change with every run
   163  	scanner := bufio.NewScanner(buf)
   164  	var newBuf bytes.Buffer
   165  	for scanner.Scan() {
   166  		line := scanner.Text()
   167  		if !strings.Contains(line, "resourceVersion:") &&
   168  			!strings.Contains(line, "creationTimestamp:") {
   169  			newBuf.Write([]byte(line))
   170  			newBuf.Write([]byte("\n"))
   171  		}
   172  	}
   173  
   174  	var data interface{}
   175  	if err := yaml.Unmarshal(newBuf.Bytes(), &data); err != nil {
   176  		// not yaml.
   177  		return
   178  	}
   179  
   180  	var stable bytes.Buffer
   181  	encoder := yaml.NewEncoder(&stable)
   182  	encoder.SetIndent(2)
   183  	if err := encoder.Encode(data); err != nil {
   184  		t.Fatalf("Failed to re-encode yaml output: %v", err)
   185  	}
   186  	buf.Reset()
   187  	if _, err := buf.Write(stable.Bytes()); err != nil {
   188  		t.Fatalf("Failed to update reordered yaml output: %v", err)
   189  	}
   190  }
   191  
   192  func startGitServer(t *testing.T, path string) string {
   193  	gitServerURL := "http://git-server." + testGitNamespace + ".svc.cluster.local:8080"
   194  
   195  	gitServerImage := porch.GetGitServerImageName(t)
   196  	t.Logf("Git Image: %s", gitServerImage)
   197  
   198  	configFile := filepath.Join(path, "git-server.yaml")
   199  	configBytes, err := os.ReadFile(configFile)
   200  	if err != nil {
   201  		t.Fatalf("Failed to read git server config file %q: %v", configFile, err)
   202  	}
   203  	config := string(configBytes)
   204  	config = strings.ReplaceAll(config, "GIT_SERVER_IMAGE", gitServerImage)
   205  
   206  	t.Cleanup(func() {
   207  		porch.KubectlDeleteNamespace(t, testGitNamespace)
   208  	})
   209  
   210  	porch.KubectlApply(t, config)
   211  	porch.KubectlWaitForDeployment(t, testGitNamespace, "git-server")
   212  	porch.KubectlWaitForService(t, testGitNamespace, "git-server")
   213  	porch.KubectlWaitForGitDNS(t, gitServerURL)
   214  
   215  	return gitServerURL
   216  }
   217  
   218  func scanTestCases(t *testing.T, root string) []porch.TestCaseConfig {
   219  	testCases := []porch.TestCaseConfig{}
   220  
   221  	if err := filepath.Walk(root, func(path string, info fs.FileInfo, err error) error {
   222  		if err != nil {
   223  			return err
   224  		}
   225  		if !info.IsDir() {
   226  			return nil
   227  		}
   228  		if path == root {
   229  			return nil
   230  		}
   231  
   232  		tc := porch.ReadTestCaseConfig(t, info.Name(), path)
   233  		testCases = append(testCases, tc)
   234  
   235  		return nil
   236  	}); err != nil {
   237  		t.Fatalf("Failed to scan test cases: %v", err)
   238  	}
   239  
   240  	return testCases
   241  }
   242  
   243  func updateCommand(command *porch.Command, exit error, stdout, stderr string) {
   244  	command.ExitCode = exitCode(exit)
   245  	command.Stdout = stdout
   246  	command.Stderr = stderr
   247  }
   248  
   249  func exitCode(exit error) int {
   250  	var ee *exec.ExitError
   251  	if errors.As(exit, &ee) {
   252  		return ee.ExitCode()
   253  	}
   254  	return 0
   255  }