github.com/kubeshop/testkube@v1.17.23/cmd/kubectl-testkube/commands/crds/tests_crds.go (about)

     1  package crds
     2  
     3  import (
     4  	"fmt"
     5  	"io/fs"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  
    10  	"github.com/spf13/cobra"
    11  
    12  	"github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/common"
    13  	"github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/common/validator"
    14  	"github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/tests"
    15  	"github.com/kubeshop/testkube/contrib/executor/postman/pkg/postman"
    16  	"github.com/kubeshop/testkube/pkg/api/v1/client"
    17  	"github.com/kubeshop/testkube/pkg/api/v1/testkube"
    18  	"github.com/kubeshop/testkube/pkg/crd"
    19  	"github.com/kubeshop/testkube/pkg/ui"
    20  	"github.com/kubeshop/testkube/pkg/utils"
    21  )
    22  
    23  var (
    24  	uri   string
    25  	flags tests.CreateCommonFlags
    26  )
    27  
    28  // NewCRDTestsCmd is command to generate test CRDs
    29  func NewCRDTestsCmd() *cobra.Command {
    30  	cmd := &cobra.Command{
    31  		Use:     "tests-crds <manifestDirectory>",
    32  		Aliases: []string{"test-crd", "test-crds", "tests-crd"},
    33  		Short:   "Generate tests CRD file based on directory",
    34  		Long:    `Generate tests manifest based on directory (e.g. for ArgoCD sync based on tests files)`,
    35  		Args:    validator.ManifestsDirectory,
    36  		Run: func(cmd *cobra.Command, args []string) {
    37  			var (
    38  				testContentType, file, name string
    39  				crdOnly                     bool
    40  			)
    41  			cmd.Flags().StringVar(&testContentType, "test-content-type", "string", "")
    42  			cmd.Flags().BoolVar(&crdOnly, "crd-only", true, "generate only CRD")
    43  			cmd.Flags().StringVar(&uri, "uri", "", "")
    44  			cmd.Flags().StringVar(&file, "file", "", "")
    45  			cmd.Flags().StringVar(&name, "name", "", "")
    46  			if flags.ExecutorType == postman.PostmanCollectionType {
    47  				processPostmanFiles(cmd, args)
    48  			} else {
    49  				dir := args[0]
    50  				err := filepath.Walk(dir, func(path string, info fs.FileInfo, err error) error {
    51  					if err != nil {
    52  						return nil
    53  					}
    54  
    55  					if info.IsDir() {
    56  						return nil
    57  					}
    58  					cmd.Flags().Set("file", path)
    59  					cmd.Flags().Set("name", utils.SanitizeName(filepath.Base(path)))
    60  					options, err := tests.NewUpsertTestOptionsFromFlags(cmd)
    61  					if err != nil {
    62  						fmt.Println("# getting test options for file", path, err.Error())
    63  						fmt.Println("---")
    64  						return nil
    65  					}
    66  					(*testkube.TestUpsertRequest)(&options).QuoteTestTextFields()
    67  					data, err := crd.ExecuteTemplate(crd.TemplateTest, options)
    68  					if err != nil {
    69  						fmt.Println("# executing crd template for file", err.Error())
    70  						fmt.Println("---")
    71  						return nil
    72  					}
    73  
    74  					fmt.Println(data)
    75  					fmt.Println("---")
    76  					return nil
    77  				})
    78  
    79  				ui.ExitOnError("getting directory content", err)
    80  			}
    81  		},
    82  	}
    83  
    84  	tests.AddCreateFlags(cmd, &flags)
    85  	return cmd
    86  }
    87  
    88  // ErrTypeNotDetected is not detcted test type error
    89  var ErrTypeNotDetected = fmt.Errorf("type not detected")
    90  
    91  // processPostmanFiles processes postman files
    92  func processPostmanFiles(cmd *cobra.Command, args []string) error {
    93  	detector := postman.Detector{}
    94  	dir := args[0]
    95  
    96  	detectedTests := make(map[string]client.UpsertTestOptions, 0)
    97  	testEnvs := make(map[string]map[string]string, 0)
    98  	testSecretEnvs := make(map[string]map[string]string, 0)
    99  
   100  	var preRunScript, postRunScript []byte
   101  
   102  	err := filepath.Walk(dir, func(path string, info fs.FileInfo, err error) error {
   103  		if err != nil {
   104  			return nil
   105  		}
   106  
   107  		if info.IsDir() {
   108  			return nil
   109  		}
   110  
   111  		if testName, envName, ok := detector.IsEnvName(path); ok {
   112  
   113  			if _, ok := testEnvs[envName]; !ok {
   114  				testEnvs[envName] = make(map[string]string, 0)
   115  			}
   116  
   117  			testEnvs[envName][testName] = path
   118  			return nil
   119  		}
   120  
   121  		if secretTestName, secretEnvName, ok := detector.IsSecretEnvName(path); ok {
   122  
   123  			if _, ok := testSecretEnvs[secretEnvName]; !ok {
   124  				testSecretEnvs[secretEnvName] = make(map[string]string, 0)
   125  			}
   126  
   127  			testSecretEnvs[secretEnvName][secretTestName] = path
   128  			return nil
   129  		}
   130  
   131  		cmd.Flags().Set("file", path)
   132  		test, err := tests.NewUpsertTestOptionsFromFlags(cmd)
   133  		if err != nil {
   134  			ui.Warn(fmt.Sprintf("generate test for file %s got an error: %v", path, err))
   135  			ui.UseStderr()
   136  			return err
   137  		}
   138  
   139  		test.Name = utils.SanitizeName(filepath.Base(path))
   140  
   141  		testName, ok := detector.IsTestName(path)
   142  		if !ok {
   143  			testName = test.Name
   144  		}
   145  
   146  		preRunScriptBody := string(preRunScript)
   147  		if preRunScriptBody != "" {
   148  			preRunScriptBody = fmt.Sprintf("%q", strings.TrimSpace(preRunScriptBody))
   149  		}
   150  
   151  		postRunScriptBody := string(postRunScript)
   152  		if postRunScriptBody != "" {
   153  			postRunScriptBody = fmt.Sprintf("%q", strings.TrimSpace(postRunScriptBody))
   154  		}
   155  
   156  		for key, value := range flags.Envs {
   157  			if value != "" {
   158  				flags.Envs[key] = fmt.Sprintf("%q", value)
   159  			}
   160  		}
   161  
   162  		vars, err := common.CreateVariables(cmd, true)
   163  		if err != nil {
   164  			return err
   165  		}
   166  
   167  		for name, variable := range vars {
   168  			if variable.Value != "" {
   169  				variable.Value = fmt.Sprintf("%q", variable.Value)
   170  				vars[name] = variable
   171  			}
   172  		}
   173  
   174  		test.ExecutionRequest = &testkube.ExecutionRequest{
   175  			Command:       flags.Command,
   176  			Args:          flags.ExecutorArgs,
   177  			ArgsMode:      flags.ArgsMode,
   178  			Envs:          flags.Envs,
   179  			Variables:     vars,
   180  			PreRunScript:  preRunScriptBody,
   181  			PostRunScript: postRunScriptBody,
   182  		}
   183  		detectedTests[testName] = test
   184  		return nil
   185  	})
   186  
   187  	ui.ExitOnError("getting directory content", err)
   188  
   189  	generateCRDs(addEnvToTests(detectedTests, testEnvs, testSecretEnvs))
   190  	return nil
   191  
   192  }
   193  
   194  // addEnvToTest adds env files to tests
   195  func addEnvToTests(tests map[string]client.UpsertTestOptions,
   196  	testEnvs, testSecretEnvs map[string]map[string]string) (envTests []client.UpsertTestOptions) {
   197  	detector := postman.Detector{}
   198  	for testName, test := range tests {
   199  		testMap := map[string]client.UpsertTestOptions{}
   200  		for envName := range testEnvs {
   201  			if filename, ok := testEnvs[envName][testName]; ok {
   202  				data, err := os.ReadFile(filename)
   203  				if err != nil {
   204  					ui.UseStderr()
   205  					ui.Warn(fmt.Sprintf("read variables file %s got an error: %v", filename, err))
   206  					continue
   207  				}
   208  
   209  				envTest := test
   210  				envTest.Name = utils.SanitizeName(envTest.Name + "-" + envName)
   211  				if test.ExecutionRequest != nil {
   212  					envTest.ExecutionRequest = &testkube.ExecutionRequest{}
   213  					*envTest.ExecutionRequest = *test.ExecutionRequest
   214  				}
   215  
   216  				if envTest.ExecutionRequest == nil {
   217  					envTest.ExecutionRequest = &testkube.ExecutionRequest{}
   218  				}
   219  
   220  				envTest.ExecutionRequest.VariablesFile = fmt.Sprintf("%q", strings.TrimSpace(string(data)))
   221  				testMap[envTest.Name] = envTest
   222  			}
   223  		}
   224  
   225  		for secretEnvName := range testSecretEnvs {
   226  			if filename, ok := testSecretEnvs[secretEnvName][testName]; ok {
   227  				data, err := os.ReadFile(filename)
   228  				if err != nil {
   229  					ui.UseStderr()
   230  					ui.Warn(fmt.Sprintf("read secret variables file %s got an error: %v", filename, err))
   231  					continue
   232  				}
   233  
   234  				variables, err := detector.GetSecretVariables(string(data))
   235  				if err != nil {
   236  					ui.UseStderr()
   237  					ui.Warn(fmt.Sprintf("parse secret file %s got an error: %v", filename, err))
   238  					continue
   239  				}
   240  
   241  				secretEnvTest := test
   242  				secretEnvTest.Name = utils.SanitizeName(secretEnvTest.Name + "-" + secretEnvName)
   243  				if test.ExecutionRequest != nil {
   244  					secretEnvTest.ExecutionRequest = &testkube.ExecutionRequest{}
   245  					*secretEnvTest.ExecutionRequest = *test.ExecutionRequest
   246  				}
   247  
   248  				if envTest, ok := testMap[secretEnvTest.Name]; ok {
   249  					secretEnvTest = envTest
   250  				}
   251  
   252  				if secretEnvTest.ExecutionRequest == nil {
   253  					secretEnvTest.ExecutionRequest = &testkube.ExecutionRequest{}
   254  				}
   255  
   256  				for key, value := range variables {
   257  					if value.Value != "" {
   258  						value.Value = fmt.Sprintf("%q", value.Value)
   259  						variables[key] = value
   260  					}
   261  				}
   262  
   263  				secretEnvTest.ExecutionRequest.Variables = variables
   264  				testMap[secretEnvTest.Name] = secretEnvTest
   265  			}
   266  		}
   267  
   268  		if len(testMap) == 0 {
   269  			testMap[test.Name] = test
   270  		}
   271  
   272  		for _, envTest := range testMap {
   273  			envTests = append(envTests, envTest)
   274  		}
   275  	}
   276  
   277  	return envTests
   278  }
   279  
   280  // generateCRDs generates CRDs for tests
   281  func generateCRDs(envTests []client.UpsertTestOptions) {
   282  	firstEntry := true
   283  	for _, test := range envTests {
   284  		if !firstEntry {
   285  			fmt.Printf("\n---\n")
   286  		} else {
   287  			firstEntry = false
   288  		}
   289  
   290  		yaml, err := crd.ExecuteTemplate(crd.TemplateTest, test)
   291  		ui.ExitOnError("executing test template", err)
   292  
   293  		fmt.Print(yaml)
   294  	}
   295  }