diff --git a/Makefile b/Makefile index d5aa58902..c71a4261e 100644 --- a/Makefile +++ b/Makefile @@ -60,6 +60,8 @@ integration-only: install-tools ## Run integration tests alert_rules \ compliance \ configure \ + query \ + policy \ event \ help \ integration \ diff --git a/api/lql.go b/api/lql.go index 9e7da5b6b..2b1aed0d3 100644 --- a/api/lql.go +++ b/api/lql.go @@ -28,7 +28,7 @@ import ( type NewQuery struct { QueryID string `json:"queryId" yaml:"queryId"` QueryText string `json:"queryText" yaml:"queryText"` - EvaluatorID string `json:"evaluatorId,omitempty" yaml:"evaluatorId"` + EvaluatorID string `json:"evaluatorId,omitempty" yaml:"evaluatorId,omitempty"` } type UpdateQuery struct { diff --git a/cli/cmd/lql_update.go b/cli/cmd/lql_update.go index fbd2b2469..5ab6af7e5 100644 --- a/cli/cmd/lql_update.go +++ b/cli/cmd/lql_update.go @@ -19,8 +19,12 @@ package cmd import ( + "fmt" + + "github.com/AlecAivazis/survey/v2" "github.com/pkg/errors" "github.com/spf13/cobra" + "gopkg.in/yaml.v2" "github.com/lacework/go-sdk/api" ) @@ -28,12 +32,14 @@ import ( var ( // queryUpdateCmd represents the lql update command queryUpdateCmd = &cobra.Command{ - Use: "update", + Use: "update [query_id]", Short: "Update a query", + Args: cobra.RangeArgs(0, 1), Long: ` There are multiple ways you can update a query: * Typing the query into your default editor (via $EDITOR) + * Passing a query id to load it into your default editor * From a local file on disk using the flag '--file' * From a URL using the flag '--url' @@ -46,7 +52,6 @@ To launch your default editor and update a query. lacework query update `, - Args: cobra.NoArgs, RunE: updateQuery, } ) @@ -61,23 +66,67 @@ func init() { func updateQuery(cmd *cobra.Command, args []string) error { msg := "unable to update query" - // input query - queryString, err := inputQuery(cmd) - if err != nil { - return errors.Wrap(err, msg) + var ( + queryString string + err error + ) + + if len(args) != 0 { + // query id via argument + cli.StartProgress("Retrieving query...") + queryRes, err := cli.LwApi.V2.Query.Get(args[0]) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to load query from your account") + } + + queryYaml, err := yaml.Marshal(&api.NewQuery{ + QueryID: queryRes.Data.QueryID, + QueryText: queryRes.Data.QueryText, + EvaluatorID: queryRes.Data.EvaluatorID, + }) + if err != nil { + return errors.Wrap(err, msg) + } + + prompt := &survey.Editor{ + Message: fmt.Sprintf("Update query %s", args[0]), + Default: string(queryYaml), + HideDefault: true, + AppendDefault: true, + FileName: "query*.yaml", + } + var queryStr string + err = survey.AskOne(prompt, &queryStr) + if err != nil { + return errors.Wrap(err, msg) + } + + queryString = queryStr + } else { + // input query + queryString, err = inputQuery(cmd) + if err != nil { + return errors.Wrap(err, msg) + } } + // parse query newQuery, err := parseQuery(queryString) if err != nil { return errors.Wrap(err, msg) } - updateQuery := api.UpdateQuery{ - QueryText: newQuery.QueryText, + + // avoid letting the user change the query id + if len(args) != 0 && newQuery.QueryID != args[0] { + return errors.New("changes to query id not supported") } cli.Log.Debugw("updating query", "query", queryString) cli.StartProgress(" Updating query...") - update, err := cli.LwApi.V2.Query.Update(newQuery.QueryID, updateQuery) + update, err := cli.LwApi.V2.Query.Update(newQuery.QueryID, api.UpdateQuery{ + QueryText: newQuery.QueryText, + }) cli.StopProgress() if err != nil { return errors.Wrap(err, msg) diff --git a/integration/aws_generation_test.go b/integration/aws_generation_test.go index 16cdce38a..7196878fe 100644 --- a/integration/aws_generation_test.go +++ b/integration/aws_generation_test.go @@ -5,49 +5,34 @@ package integration import ( "fmt" "io/ioutil" - "log" "os" "path/filepath" "testing" - "time" "github.com/Netflix/go-expect" - "github.com/hinshun/vt10x" "github.com/lacework/go-sdk/cli/cmd" "github.com/lacework/go-sdk/lwgenerate/aws" "github.com/stretchr/testify/assert" ) -func expectString(c *expect.Console, str string, runError *error) { - out, err := c.Expect(expect.WithTimeout(time.Second), expect.String(str)) - if err != nil { - fmt.Println(out) // To see the errored line, you can enable this and update _ above to out - *runError = err - } -} - // Test failing due to no selection func TestGenerationErrorOnNoSelection(t *testing.T) { os.Setenv("LW_NOCACHE", "true") defer os.Setenv("LW_NOCACHE", "") - var runError error // Run CLI runGenerateTest(t, func(c *expect.Console) { - expectString(c, cmd.QuestionAwsEnableConfig, &runError) + expectString(t, c, cmd.QuestionAwsEnableConfig) c.SendLine("n") - expectString(c, cmd.QuestionEnableCloudtrail, &runError) + expectString(t, c, cmd.QuestionEnableCloudtrail) c.SendLine("n") - expectString(c, "ERROR collecting/confirming parameters: must enable cloudtrail or config", &runError) + expectString(t, c, "ERROR collecting/confirming parameters: must enable cloudtrail or config") }, "cloud", "iac", "aws", ) - - // Ensure CLI errored properly - assert.Nil(t, runError) } // Test barebones generation with no customization @@ -55,21 +40,20 @@ func TestGenerationSimple(t *testing.T) { os.Setenv("LW_NOCACHE", "true") defer os.Setenv("LW_NOCACHE", "") var final string - var runError error region := "us-east-2" // Run CLI tfResult := runGenerateTest(t, func(c *expect.Console) { - expectString(c, cmd.QuestionAwsEnableConfig, &runError) + expectString(t, c, cmd.QuestionAwsEnableConfig) c.SendLine("y") - expectString(c, cmd.QuestionEnableCloudtrail, &runError) + expectString(t, c, cmd.QuestionEnableCloudtrail) c.SendLine("y") - expectString(c, cmd.QuestionAwsRegion, &runError) + expectString(t, c, cmd.QuestionAwsRegion) c.SendLine(region) - expectString(c, cmd.QuestionAwsConfigAdvanced, &runError) + expectString(t, c, cmd.QuestionAwsConfigAdvanced) c.SendLine("n") - expectString(c, cmd.QuestionRunTfPlan, &runError) + expectString(t, c, cmd.QuestionRunTfPlan) c.SendLine("n") final, _ = c.ExpectEOF() }, @@ -79,7 +63,6 @@ func TestGenerationSimple(t *testing.T) { ) // Ensure CLI ran correctly - assert.Nil(t, runError) assert.Contains(t, final, "Terraform code saved in") // Create the TF directly with lwgenerate and validate same result via CLI @@ -92,7 +75,6 @@ func TestGenerationCustomizedOutputLocation(t *testing.T) { os.Setenv("LW_NOCACHE", "true") defer os.Setenv("LW_NOCACHE", "") var final string - var runError error region := "us-east-2" // Tempdir for test @@ -105,22 +87,22 @@ func TestGenerationCustomizedOutputLocation(t *testing.T) { // Run CLI runGenerateTest(t, func(c *expect.Console) { - expectString(c, cmd.QuestionAwsEnableConfig, &runError) + expectString(t, c, cmd.QuestionAwsEnableConfig) c.SendLine("y") - expectString(c, cmd.QuestionEnableCloudtrail, &runError) + expectString(t, c, cmd.QuestionEnableCloudtrail) c.SendLine("y") - expectString(c, cmd.QuestionAwsRegion, &runError) + expectString(t, c, cmd.QuestionAwsRegion) c.SendLine(region) - expectString(c, cmd.QuestionAwsConfigAdvanced, &runError) + expectString(t, c, cmd.QuestionAwsConfigAdvanced) c.SendLine("y") - expectString(c, cmd.AwsAdvancedOptDone, &runError) + expectString(t, c, cmd.AwsAdvancedOptDone) c.Send("\x1B[B") c.SendLine("\x1B[B") - expectString(c, cmd.QuestionAwsCustomizeOutputLocation, &runError) + expectString(t, c, cmd.QuestionAwsCustomizeOutputLocation) c.SendLine(dir) - expectString(c, cmd.QuestionAwsAnotherAdvancedOpt, &runError) + expectString(t, c, cmd.QuestionAwsAnotherAdvancedOpt) c.SendLine("n") - expectString(c, cmd.QuestionRunTfPlan, &runError) + expectString(t, c, cmd.QuestionRunTfPlan) c.SendLine("n") final, _ = c.ExpectEOF() }, @@ -130,7 +112,6 @@ func TestGenerationCustomizedOutputLocation(t *testing.T) { ) // Ensure CLI ran correctly - assert.Nil(t, runError) assert.Contains(t, final, "Terraform code saved in") // Get result @@ -146,21 +127,20 @@ func TestGenerationConfigOnly(t *testing.T) { os.Setenv("LW_NOCACHE", "true") defer os.Setenv("LW_NOCACHE", "") var final string - var runError error region := "us-east-2" // Run CLI tfResult := runGenerateTest(t, func(c *expect.Console) { - expectString(c, cmd.QuestionAwsEnableConfig, &runError) + expectString(t, c, cmd.QuestionAwsEnableConfig) c.SendLine("y") - expectString(c, cmd.QuestionEnableCloudtrail, &runError) + expectString(t, c, cmd.QuestionEnableCloudtrail) c.SendLine("n") - expectString(c, cmd.QuestionAwsRegion, &runError) + expectString(t, c, cmd.QuestionAwsRegion) c.SendLine(region) - expectString(c, cmd.QuestionAwsConfigAdvanced, &runError) + expectString(t, c, cmd.QuestionAwsConfigAdvanced) c.SendLine("n") - expectString(c, cmd.QuestionRunTfPlan, &runError) + expectString(t, c, cmd.QuestionRunTfPlan) c.SendLine("n") final, _ = c.ExpectEOF() }, @@ -170,7 +150,6 @@ func TestGenerationConfigOnly(t *testing.T) { ) // Ensure CLI ran correctly - assert.Nil(t, runError) assert.Contains(t, final, "Terraform code saved in") // Create the TF directly with lwgenerate and validate same result via CLI @@ -183,25 +162,24 @@ func TestGenerationAdvancedOptsDone(t *testing.T) { os.Setenv("LW_NOCACHE", "true") defer os.Setenv("LW_NOCACHE", "") var final string - var runError error region := "us-east-2" // Run CLI tfResult := runGenerateTest(t, func(c *expect.Console) { - expectString(c, cmd.QuestionAwsEnableConfig, &runError) + expectString(t, c, cmd.QuestionAwsEnableConfig) c.SendLine("y") - expectString(c, cmd.QuestionEnableCloudtrail, &runError) + expectString(t, c, cmd.QuestionEnableCloudtrail) c.SendLine("y") - expectString(c, cmd.QuestionAwsRegion, &runError) + expectString(t, c, cmd.QuestionAwsRegion) c.SendLine(region) - expectString(c, cmd.QuestionAwsConfigAdvanced, &runError) + expectString(t, c, cmd.QuestionAwsConfigAdvanced) c.SendLine("y") - expectString(c, cmd.AwsAdvancedOptDone, &runError) + expectString(t, c, cmd.AwsAdvancedOptDone) c.Send("\x1B[B") c.Send("\x1B[B") c.SendLine("\x1B[B") - expectString(c, cmd.QuestionRunTfPlan, &runError) + expectString(t, c, cmd.QuestionRunTfPlan) c.SendLine("n") final, _ = c.ExpectEOF() }, @@ -211,7 +189,6 @@ func TestGenerationAdvancedOptsDone(t *testing.T) { ) // Ensure CLI ran correctly - assert.Nil(t, runError) assert.Contains(t, final, "Terraform code saved in") // Create the TF directly with lwgenerate and validate same result via CLI @@ -224,31 +201,30 @@ func TestGenerationAdvancedOptsConsolidatedAndForceDestroy(t *testing.T) { os.Setenv("LW_NOCACHE", "true") defer os.Setenv("LW_NOCACHE", "") var final string - var runError error region := "us-east-2" // Run CLI tfResult := runGenerateTest(t, func(c *expect.Console) { - expectString(c, cmd.QuestionAwsEnableConfig, &runError) + expectString(t, c, cmd.QuestionAwsEnableConfig) c.SendLine("y") - expectString(c, cmd.QuestionEnableCloudtrail, &runError) + expectString(t, c, cmd.QuestionEnableCloudtrail) c.SendLine("y") - expectString(c, cmd.QuestionAwsRegion, &runError) + expectString(t, c, cmd.QuestionAwsRegion) c.SendLine(region) - expectString(c, cmd.QuestionAwsConfigAdvanced, &runError) + expectString(t, c, cmd.QuestionAwsConfigAdvanced) c.SendLine("y") - expectString(c, cmd.AwsAdvancedOptDone, &runError) + expectString(t, c, cmd.AwsAdvancedOptDone) c.SendLine("") - expectString(c, cmd.QuestionConsolidatedCloudtrail, &runError) + expectString(t, c, cmd.QuestionConsolidatedCloudtrail) c.SendLine("y") - expectString(c, cmd.QuestionUseExistingCloudtrail, &runError) + expectString(t, c, cmd.QuestionUseExistingCloudtrail) c.SendLine("n") - expectString(c, cmd.QuestionForceDestroyS3Bucket, &runError) + expectString(t, c, cmd.QuestionForceDestroyS3Bucket) c.SendLine("y") - expectString(c, cmd.QuestionAwsAnotherAdvancedOpt, &runError) + expectString(t, c, cmd.QuestionAwsAnotherAdvancedOpt) c.SendLine("n") - expectString(c, cmd.QuestionRunTfPlan, &runError) + expectString(t, c, cmd.QuestionRunTfPlan) c.SendLine("n") final, _ = c.ExpectEOF() }, @@ -258,7 +234,6 @@ func TestGenerationAdvancedOptsConsolidatedAndForceDestroy(t *testing.T) { ) // Ensure CLI ran correctly - assert.Nil(t, runError) assert.Contains(t, final, "Terraform code saved in") // Create the TF directly with lwgenerate and validate same result via CLI @@ -272,33 +247,32 @@ func TestGenerationAdvancedOptsUseExistingCloudtrail(t *testing.T) { os.Setenv("LW_NOCACHE", "true") defer os.Setenv("LW_NOCACHE", "") var final string - var runError error region := "us-east-2" // Run CLI tfResult := runGenerateTest(t, func(c *expect.Console) { - expectString(c, cmd.QuestionAwsEnableConfig, &runError) + expectString(t, c, cmd.QuestionAwsEnableConfig) c.SendLine("y") - expectString(c, cmd.QuestionEnableCloudtrail, &runError) + expectString(t, c, cmd.QuestionEnableCloudtrail) c.SendLine("y") - expectString(c, cmd.QuestionAwsRegion, &runError) + expectString(t, c, cmd.QuestionAwsRegion) c.SendLine(region) - expectString(c, cmd.QuestionAwsConfigAdvanced, &runError) + expectString(t, c, cmd.QuestionAwsConfigAdvanced) c.SendLine("y") - expectString(c, cmd.AwsAdvancedOptDone, &runError) + expectString(t, c, cmd.AwsAdvancedOptDone) c.SendLine("") - expectString(c, cmd.QuestionConsolidatedCloudtrail, &runError) + expectString(t, c, cmd.QuestionConsolidatedCloudtrail) c.SendLine("n") - expectString(c, cmd.QuestionUseExistingCloudtrail, &runError) + expectString(t, c, cmd.QuestionUseExistingCloudtrail) c.SendLine("y") - expectString(c, cmd.QuestionCloudtrailExistingBucketArn, &runError) + expectString(t, c, cmd.QuestionCloudtrailExistingBucketArn) c.SendLine("notright") // test our validator is working - expectString(c, "invalid arn supplied", &runError) + expectString(t, c, "invalid arn supplied") c.SendLine("arn:aws:s3:::bucket_name") - expectString(c, cmd.QuestionAwsAnotherAdvancedOpt, &runError) + expectString(t, c, cmd.QuestionAwsAnotherAdvancedOpt) c.SendLine("n") - expectString(c, cmd.QuestionRunTfPlan, &runError) + expectString(t, c, cmd.QuestionRunTfPlan) c.SendLine("n") final, _ = c.ExpectEOF() }, @@ -308,7 +282,6 @@ func TestGenerationAdvancedOptsUseExistingCloudtrail(t *testing.T) { ) // Ensure CLI ran correctly - assert.Nil(t, runError) assert.Contains(t, final, "Terraform code saved in") // Create the TF directly with lwgenerate and validate same result via CLI @@ -322,50 +295,49 @@ func TestGenerationAdvancedOptsConsolidatedWithSubAccounts(t *testing.T) { os.Setenv("LW_NOCACHE", "true") defer os.Setenv("LW_NOCACHE", "") var final string - var runError error region := "us-east-2" // Run CLI tfResult := runGenerateTest(t, func(c *expect.Console) { - expectString(c, cmd.QuestionAwsEnableConfig, &runError) + expectString(t, c, cmd.QuestionAwsEnableConfig) c.SendLine("y") - expectString(c, cmd.QuestionEnableCloudtrail, &runError) + expectString(t, c, cmd.QuestionEnableCloudtrail) c.SendLine("y") - expectString(c, cmd.QuestionAwsRegion, &runError) + expectString(t, c, cmd.QuestionAwsRegion) c.SendLine(region) - expectString(c, cmd.QuestionAwsConfigAdvanced, &runError) + expectString(t, c, cmd.QuestionAwsConfigAdvanced) c.SendLine("y") - expectString(c, cmd.AwsAdvancedOptDone, &runError) + expectString(t, c, cmd.AwsAdvancedOptDone) c.SendLine("") - expectString(c, cmd.QuestionConsolidatedCloudtrail, &runError) + expectString(t, c, cmd.QuestionConsolidatedCloudtrail) c.SendLine("y") - expectString(c, cmd.QuestionUseExistingCloudtrail, &runError) + expectString(t, c, cmd.QuestionUseExistingCloudtrail) c.SendLine("n") - expectString(c, cmd.QuestionForceDestroyS3Bucket, &runError) + expectString(t, c, cmd.QuestionForceDestroyS3Bucket) c.SendLine("n") - expectString(c, cmd.QuestionAwsAnotherAdvancedOpt, &runError) + expectString(t, c, cmd.QuestionAwsAnotherAdvancedOpt) c.SendLine("y") - expectString(c, cmd.AwsAdvancedOptDone, &runError) + expectString(t, c, cmd.AwsAdvancedOptDone) c.Send("\x1B[B") // Down arrow twice and enter on the submenu to add subaccounts c.SendLine("\x1B[B") - expectString(c, cmd.QuestionPrimaryAwsAccountProfile, &runError) + expectString(t, c, cmd.QuestionPrimaryAwsAccountProfile) c.SendLine("default") - expectString(c, cmd.QuestionSubAccountProfileName, &runError) + expectString(t, c, cmd.QuestionSubAccountProfileName) c.SendLine("account1") - expectString(c, cmd.QuestionSubAccountRegion, &runError) + expectString(t, c, cmd.QuestionSubAccountRegion) c.SendLine("us-east-1") - expectString(c, cmd.QuestionSubAccountAddMore, &runError) + expectString(t, c, cmd.QuestionSubAccountAddMore) c.SendLine("y") - expectString(c, cmd.QuestionSubAccountProfileName, &runError) + expectString(t, c, cmd.QuestionSubAccountProfileName) c.SendLine("account2") - expectString(c, cmd.QuestionSubAccountRegion, &runError) + expectString(t, c, cmd.QuestionSubAccountRegion) c.SendLine("us-east-2") - expectString(c, cmd.QuestionSubAccountAddMore, &runError) + expectString(t, c, cmd.QuestionSubAccountAddMore) c.SendLine("n") - expectString(c, cmd.QuestionAwsAnotherAdvancedOpt, &runError) + expectString(t, c, cmd.QuestionAwsAnotherAdvancedOpt) c.SendLine("n") - expectString(c, cmd.QuestionRunTfPlan, &runError) + expectString(t, c, cmd.QuestionRunTfPlan) c.SendLine("n") final, _ = c.ExpectEOF() }, @@ -375,7 +347,6 @@ func TestGenerationAdvancedOptsConsolidatedWithSubAccounts(t *testing.T) { ) // Ensure CLI ran correctly - assert.Nil(t, runError) assert.Contains(t, final, "Terraform code saved in") // Create the TF directly with lwgenerate and validate same result via CLI @@ -392,42 +363,41 @@ func TestGenerationAdvancedOptsConsolidatedWithSubAccountsPassedByFlag(t *testin os.Setenv("LW_NOCACHE", "true") defer os.Setenv("LW_NOCACHE", "") var final string - var runError error region := "us-east-2" // Run CLI tfResult := runGenerateTest(t, func(c *expect.Console) { - expectString(c, cmd.QuestionAwsEnableConfig, &runError) + expectString(t, c, cmd.QuestionAwsEnableConfig) c.SendLine("y") - expectString(c, cmd.QuestionEnableCloudtrail, &runError) + expectString(t, c, cmd.QuestionEnableCloudtrail) c.SendLine("y") - expectString(c, cmd.QuestionAwsRegion, &runError) + expectString(t, c, cmd.QuestionAwsRegion) c.SendLine(region) - expectString(c, cmd.QuestionAwsConfigAdvanced, &runError) + expectString(t, c, cmd.QuestionAwsConfigAdvanced) c.SendLine("y") - expectString(c, cmd.AwsAdvancedOptDone, &runError) + expectString(t, c, cmd.AwsAdvancedOptDone) c.Send("\x1B[B") // Down arrow twice and enter on the submenu to add subaccounts c.SendLine("\x1B[B") - expectString(c, cmd.QuestionPrimaryAwsAccountProfile, &runError) + expectString(t, c, cmd.QuestionPrimaryAwsAccountProfile) c.SendLine("default") - expectString(c, fmt.Sprintf(cmd.QuestionSubAccountReplace, "testaccount:us-east-1, testaccount1:us-east-2"), &runError) + expectString(t, c, fmt.Sprintf(cmd.QuestionSubAccountReplace, "testaccount:us-east-1, testaccount1:us-east-2")) c.SendLine("y") - expectString(c, cmd.QuestionSubAccountProfileName, &runError) + expectString(t, c, cmd.QuestionSubAccountProfileName) c.SendLine("account1") - expectString(c, cmd.QuestionSubAccountRegion, &runError) + expectString(t, c, cmd.QuestionSubAccountRegion) c.SendLine("us-east-1") - expectString(c, cmd.QuestionSubAccountAddMore, &runError) + expectString(t, c, cmd.QuestionSubAccountAddMore) c.SendLine("y") - expectString(c, cmd.QuestionSubAccountProfileName, &runError) + expectString(t, c, cmd.QuestionSubAccountProfileName) c.SendLine("account2") - expectString(c, cmd.QuestionSubAccountRegion, &runError) + expectString(t, c, cmd.QuestionSubAccountRegion) c.SendLine("us-east-2") - expectString(c, cmd.QuestionSubAccountAddMore, &runError) + expectString(t, c, cmd.QuestionSubAccountAddMore) c.SendLine("n") - expectString(c, cmd.QuestionAwsAnotherAdvancedOpt, &runError) + expectString(t, c, cmd.QuestionAwsAnotherAdvancedOpt) c.SendLine("n") - expectString(c, cmd.QuestionRunTfPlan, &runError) + expectString(t, c, cmd.QuestionRunTfPlan) c.SendLine("n") final, _ = c.ExpectEOF() }, @@ -442,7 +412,6 @@ func TestGenerationAdvancedOptsConsolidatedWithSubAccountsPassedByFlag(t *testin ) // Ensure CLI ran correctly - assert.Nil(t, runError) assert.Contains(t, final, "Terraform code saved in") // Create the TF directly with lwgenerate and validate same result via CLI @@ -459,7 +428,6 @@ func TestGenerationAdvancedOptsUseExistingIAM(t *testing.T) { os.Setenv("LW_NOCACHE", "true") defer os.Setenv("LW_NOCACHE", "") var final string - var runError error region := "us-east-2" roleName := "test-iamrole" roleArn := "arn:aws:iam::123456789012:role/application_abc/component_xyz/abc_role" @@ -468,25 +436,25 @@ func TestGenerationAdvancedOptsUseExistingIAM(t *testing.T) { // Run CLI tfResult := runGenerateTest(t, func(c *expect.Console) { - expectString(c, cmd.QuestionAwsEnableConfig, &runError) + expectString(t, c, cmd.QuestionAwsEnableConfig) c.SendLine("y") - expectString(c, cmd.QuestionEnableCloudtrail, &runError) + expectString(t, c, cmd.QuestionEnableCloudtrail) c.SendLine("y") - expectString(c, cmd.QuestionAwsRegion, &runError) + expectString(t, c, cmd.QuestionAwsRegion) c.SendLine(region) - expectString(c, cmd.QuestionAwsConfigAdvanced, &runError) + expectString(t, c, cmd.QuestionAwsConfigAdvanced) c.SendLine("y") - expectString(c, cmd.AwsAdvancedOptDone, &runError) + expectString(t, c, cmd.AwsAdvancedOptDone) c.SendLine("\x1B[B") // Down arrow once and return - expectString(c, cmd.QuestionExistingIamRoleName, &runError) + expectString(t, c, cmd.QuestionExistingIamRoleName) c.SendLine(roleName) - expectString(c, cmd.QuestionExistingIamRoleArn, &runError) + expectString(t, c, cmd.QuestionExistingIamRoleArn) c.SendLine(roleArn) - expectString(c, cmd.QuestionExistingIamRoleExtID, &runError) + expectString(t, c, cmd.QuestionExistingIamRoleExtID) c.SendLine(roleExtId) - expectString(c, cmd.QuestionAwsAnotherAdvancedOpt, &runError) + expectString(t, c, cmd.QuestionAwsAnotherAdvancedOpt) c.SendLine("n") - expectString(c, cmd.QuestionRunTfPlan, &runError) + expectString(t, c, cmd.QuestionRunTfPlan) c.SendLine("n") final, _ = c.ExpectEOF() }, @@ -496,7 +464,6 @@ func TestGenerationAdvancedOptsUseExistingIAM(t *testing.T) { ) // Ensure CLI ran correctly - assert.Nil(t, runError) assert.Contains(t, final, "Terraform code saved in") // Create the TF directly with lwgenerate and validate same result via CLI @@ -510,7 +477,6 @@ func TestGenerationAdvancedOptsUseExistingIAM(t *testing.T) { func TestGenerationWithExistingTerraform(t *testing.T) { os.Setenv("LW_NOCACHE", "true") defer os.Setenv("LW_NOCACHE", "") - var runError error region := "us-east-2" // Tempdir for test @@ -528,22 +494,22 @@ func TestGenerationWithExistingTerraform(t *testing.T) { // Run CLI runGenerateTest(t, func(c *expect.Console) { - expectString(c, cmd.QuestionAwsEnableConfig, &runError) + expectString(t, c, cmd.QuestionAwsEnableConfig) c.SendLine("y") - expectString(c, cmd.QuestionEnableCloudtrail, &runError) + expectString(t, c, cmd.QuestionEnableCloudtrail) c.SendLine("y") - expectString(c, cmd.QuestionAwsRegion, &runError) + expectString(t, c, cmd.QuestionAwsRegion) c.SendLine(region) - expectString(c, cmd.QuestionAwsConfigAdvanced, &runError) + expectString(t, c, cmd.QuestionAwsConfigAdvanced) c.SendLine("y") - expectString(c, cmd.AwsAdvancedOptDone, &runError) + expectString(t, c, cmd.AwsAdvancedOptDone) c.Send("\x1B[B") c.SendLine("\x1B[B") - expectString(c, cmd.QuestionAwsCustomizeOutputLocation, &runError) + expectString(t, c, cmd.QuestionAwsCustomizeOutputLocation) c.SendLine(dir) - expectString(c, cmd.QuestionAwsAnotherAdvancedOpt, &runError) + expectString(t, c, cmd.QuestionAwsAnotherAdvancedOpt) c.SendLine("n") - expectString(c, fmt.Sprintf("%s/aws.tf already exists, overwrite?", dir), &runError) + expectString(t, c, fmt.Sprintf("%s/aws.tf already exists, overwrite?", dir)) c.SendLine("n") }, "cloud", @@ -558,7 +524,6 @@ func TestGenerationWithExistingTerraform(t *testing.T) { } assert.Empty(t, data) - assert.Nil(t, runError) } func runGenerateTest(t *testing.T, conditions func(*expect.Console), args ...string) string { @@ -570,7 +535,7 @@ func runGenerateTest(t *testing.T, conditions func(*expect.Console), args ...str defer os.Setenv("HOME", homeCache) defer os.RemoveAll(dir) - runGenerationTestFromDir(t, dir, conditions, args...) + runFakeTerminalTestFromDir(t, dir, conditions, args...) out, err := ioutil.ReadFile(filepath.FromSlash(fmt.Sprintf("%s/lacework/aws.tf", dir))) if err != nil { // Assume couldn't be found @@ -579,32 +544,3 @@ func runGenerateTest(t *testing.T, conditions func(*expect.Console), args ...str return string(out) } - -func runGenerationTestFromDir(t *testing.T, dir string, conditions func(*expect.Console), args ...string) { - console, state, err := vt10x.NewVT10XConsole() - if err != nil { - panic(err) - } - defer console.Close() - - if os.Getenv("DEBUG") != "" { - state.DebugLogger = log.Default() - } - - donec := make(chan struct{}) - go func() { - defer close(donec) - conditions(console) - }() - - cmd := NewLaceworkCLI(dir, nil, args...) - cmd.Stdin = console.Tty() - cmd.Stdout = console.Tty() - cmd.Stderr = console.Tty() - err = cmd.Start() - assert.Nil(t, err) - - // read the remaining bytes - console.Tty().Close() - <-donec -} diff --git a/integration/configure_unix_test.go b/integration/configure_unix_test.go index 965c3e87f..66d3e6010 100644 --- a/integration/configure_unix_test.go +++ b/integration/configure_unix_test.go @@ -22,13 +22,11 @@ package integration import ( "fmt" "io/ioutil" - "log" "os" "path" "testing" "github.com/Netflix/go-expect" - "github.com/hinshun/vt10x" "github.com/stretchr/testify/assert" ) @@ -38,13 +36,13 @@ func TestConfigureCommand(t *testing.T) { _, laceworkTOML := runConfigureTest(t, func(c *expect.Console) { - c.ExpectString("Account:") + expectString(t, c, "Account:") c.SendLine("test-account") - c.ExpectString("Access Key ID:") + expectString(t, c, "Access Key ID:") c.SendLine("INTTEST_ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890AAABBBCCC00") - c.ExpectString("Secret Access Key:") + expectString(t, c, "Secret Access Key:") c.SendLine("_00000000000000000000000000000000") - c.ExpectString("You are all set!") + expectString(t, c, "You are all set!") }, "configure", ) @@ -63,15 +61,15 @@ func TestConfigureCommandForFrankfurtDatacenter(t *testing.T) { _, laceworkTOML := runConfigureTest(t, func(c *expect.Console) { - c.ExpectString("Account:") + expectString(t, c, "Account:") // if the full URL was provided we transform it and inform the user c.SendLine("my-account-in.fra.lacework.net") - c.ExpectString("Passing full 'lacework.net' domain not required. Using 'my-account-in.fra'") - c.ExpectString("Access Key ID:") + expectString(t, c, "Passing full 'lacework.net' domain not required. Using 'my-account-in.fra'") + expectString(t, c, "Access Key ID:") c.SendLine("FRANK_ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890AAABBBCCC0011") - c.ExpectString("Secret Access Key:") + expectString(t, c, "Secret Access Key:") c.SendLine("_00000000000000000000000000000000") - c.ExpectString("You are all set!") + expectString(t, c, "You are all set!") }, "configure", ) @@ -89,18 +87,18 @@ func TestConfigureCommandForOrgAdmins(t *testing.T) { } _, laceworkTOML := runConfigureTest(t, func(c *expect.Console) { - c.ExpectString("Account:") + expectString(t, c, "Account:") c.SendLine(os.Getenv("CI_ACCOUNT")) - c.ExpectString("Access Key ID:") + expectString(t, c, "Access Key ID:") c.SendLine(os.Getenv("CI_API_KEY")) - c.ExpectString("Secret Access Key:") + expectString(t, c, "Secret Access Key:") c.SendLine(os.Getenv("CI_API_SECRET")) - c.ExpectString("Verifying credentials ...") - c.ExpectString("(Org Admins) Managing a sub-account?") + expectString(t, c, "Verifying credentials ...") + expectString(t, c, "(Org Admins) Managing a sub-account?") // @afiune this is needed just because we have two accounts that start exactly the same // and so, we need to key in ARROW DOWN to chose the right one. c.SendLine(fmt.Sprintf("%s\x1B[B", os.Getenv("CI_SUBACCOUNT"))) - c.ExpectString("You are all set!") + expectString(t, c, "You are all set!") }, "configure", ) @@ -120,13 +118,13 @@ func TestConfigureCommandWithProfileFlag(t *testing.T) { _, laceworkTOML := runConfigureTest(t, func(c *expect.Console) { - c.ExpectString("Account:") + expectString(t, c, "Account:") c.SendLine("test-account") - c.ExpectString("Access Key ID:") + expectString(t, c, "Access Key ID:") c.SendLine("INTTEST_ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890AAABBBCCC00") - c.ExpectString("Secret Access Key:") + expectString(t, c, "Secret Access Key:") c.SendLine("_00000000000000000000000000000000") - c.ExpectString("You are all set!") + expectString(t, c, "You are all set!") }, "configure", "--profile", "my-profile", ) @@ -155,13 +153,13 @@ func TestConfigureCommandWithNewJSONFileFlagForStandaloneAccounts(t *testing.T) _, laceworkTOML := runConfigureTest(t, func(c *expect.Console) { - c.ExpectString("Account:") + expectString(t, c, "Account:") c.SendLine("") // using the default, which should be auto-populated from the new JSON file - c.ExpectString("Access Key ID:") + expectString(t, c, "Access Key ID:") c.SendLine("") // using the default, which should be loaded from the JSON file - c.ExpectString("Secret Access Key:") + expectString(t, c, "Secret Access Key:") c.SendLine("") // using the default, which should be loaded from the JSON file - c.ExpectString("You are all set!") + expectString(t, c, "You are all set!") }, "configure", "--json_file", s, ) @@ -191,13 +189,13 @@ func TestConfigureCommandWithNewJSONFileFlagForOrganizationalAccounts(t *testing _, laceworkTOML := runConfigureTest(t, func(c *expect.Console) { - c.ExpectString("Account:") + expectString(t, c, "Account:") c.SendLine("") // using the default, which should be auto-populated from the new JSON file - c.ExpectString("Access Key ID:") + expectString(t, c, "Access Key ID:") c.SendLine("") // using the default, which should be loaded from the JSON file - c.ExpectString("Secret Access Key:") + expectString(t, c, "Secret Access Key:") c.SendLine("") // using the default, which should be loaded from the JSON file - c.ExpectString("You are all set!") + expectString(t, c, "You are all set!") }, "configure", "--json_file", s, ) @@ -226,13 +224,13 @@ func TestConfigureCommandWithOldJSONFileFlag(t *testing.T) { _, laceworkTOML := runConfigureTest(t, func(c *expect.Console) { - c.ExpectString("Account:") + expectString(t, c, "Account:") c.SendLine("") // using the default, which should be auto-populated from the provided --profile flag - c.ExpectString("Access Key ID:") + expectString(t, c, "Access Key ID:") c.SendLine("") // using the default, which should be loaded from the JSON file - c.ExpectString("Secret Access Key:") + expectString(t, c, "Secret Access Key:") c.SendLine("") // using the default, which should be loaded from the JSON file - c.ExpectString("You are all set!") + expectString(t, c, "You are all set!") }, "configure", "--json_file", s, "--profile", "v1-web-ui-test", ) @@ -259,13 +257,13 @@ func TestConfigureCommandWithEnvironmentVariables(t *testing.T) { _, laceworkTOML := runConfigureTest(t, func(c *expect.Console) { - c.ExpectString("Account:") + expectString(t, c, "Account:") c.SendLine("") // using the default, which should be loaded from the environment variables - c.ExpectString("Access Key ID:") + expectString(t, c, "Access Key ID:") c.SendLine("") // using the default, which should be loaded from the environment variables - c.ExpectString("Secret Access Key:") + expectString(t, c, "Secret Access Key:") c.SendLine("") // using the default, which should be loaded from the environment variables - c.ExpectString("You are all set!") + expectString(t, c, "You are all set!") }, "configure", ) @@ -285,13 +283,13 @@ func TestConfigureCommandWithAPIkeysFromFlagsWithoutSubaccount(t *testing.T) { _, laceworkTOML := runConfigureTest(t, func(c *expect.Console) { - c.ExpectString("Account:") + expectString(t, c, "Account:") c.SendLine("") // using the default, which should be loaded from the provided flags - c.ExpectString("Access Key ID:") + expectString(t, c, "Access Key ID:") c.SendLine("") // using the default, which should be loaded from the provided flags - c.ExpectString("Secret Access Key:") + expectString(t, c, "Secret Access Key:") c.SendLine("") // using the default, which should be loaded from the provided flags - c.ExpectString("You are all set!") + expectString(t, c, "You are all set!") }, "configure", "--account", "from-flags", @@ -313,13 +311,13 @@ func TestConfigureCommandWithAPIkeysFromFlagsWithSubaccount(t *testing.T) { _, laceworkTOML := runConfigureTest(t, func(c *expect.Console) { - c.ExpectString("Account:") + expectString(t, c, "Account:") c.SendLine("") // using the default, which should be loaded from the provided flags - c.ExpectString("Access Key ID:") + expectString(t, c, "Access Key ID:") c.SendLine("") // using the default, which should be loaded from the provided flags - c.ExpectString("Secret Access Key:") + expectString(t, c, "Secret Access Key:") c.SendLine("") // using the default, which should be loaded from the provided flags - c.ExpectString("You are all set!") + expectString(t, c, "You are all set!") }, "configure", "--account", "from-flags", @@ -344,19 +342,26 @@ func TestConfigureCommandWithExistingConfigAndMultiProfile(t *testing.T) { dir := createTOMLConfig() defer os.RemoveAll(dir) - _, laceworkTOML := runConfigureTestFromDir(t, dir, + configPath := path.Join(dir, ".lacework.toml") + + _ = runFakeTerminalTestFromDir(t, dir, func(c *expect.Console) { - c.ExpectString("Account:") + expectString(t, c, "Account:") c.SendLine("super-cool-profile") - c.ExpectString("Access Key ID:") + expectString(t, c, "Access Key ID:") c.SendLine("TEST_ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ") - c.ExpectString("Secret Access Key:") + expectString(t, c, "Secret Access Key:") c.SendLine("_uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu") - c.ExpectString("You are all set!") + expectString(t, c, "You are all set!") }, "configure", "--profile", "new-profile", ) + // assert.Contains(t, state, "You are all set!", "you are not all set, check configure cmd") + assert.FileExists(t, configPath, "the configuration file is missing") + laceworkTOML, err := ioutil.ReadFile(configPath) + assert.Nil(t, err) + assert.Equal(t, `[default] account = "test.account" api_key = "INTTEST_ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890AAABBBCCC00" @@ -391,22 +396,27 @@ func TestConfigureCommandWithExistingConfigAndMultiProfile(t *testing.T) { subaccount = "sub-account" api_key = "V2CONFIG_KEY" api_secret = "_secret" -`, laceworkTOML, "there is a problem with the generated config") +`, string(laceworkTOML), "there is a problem with the generated config") t.Run("Reconfigure", func(t *testing.T) { - _, laceworkTOML := runConfigureTestFromDir(t, dir, + _ = runFakeTerminalTestFromDir(t, dir, func(c *expect.Console) { - c.ExpectString("Account:") + expectString(t, c, "Account:") c.SendLine("new-account") - c.ExpectString("Access Key ID:") + expectString(t, c, "Access Key ID:") c.SendLine("TEST_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") - c.ExpectString("Secret Access Key:") + expectString(t, c, "Secret Access Key:") c.SendLine("_oooooooooooooooooooooooooooooooo") - c.ExpectString("You are all set!") + expectString(t, c, "You are all set!") }, "configure", "--profile", "v2", ) + // assert.Contains(t, state, "You are all set!", "you are not all set, check configure cmd") + assert.FileExists(t, configPath, "the configuration file is missing") + laceworkTOML, err := ioutil.ReadFile(configPath) + assert.Nil(t, err) + assert.Equal(t, `[default] account = "test.account" api_key = "INTTEST_ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890AAABBBCCC00" @@ -441,7 +451,7 @@ func TestConfigureCommandWithExistingConfigAndMultiProfile(t *testing.T) { api_key = "TEST_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" api_secret = "_oooooooooooooooooooooooooooooooo" version = 2 -`, laceworkTOML, "there is a problem with the generated config") +`, string(laceworkTOML), "there is a problem with the generated config") }) } @@ -451,21 +461,21 @@ func TestConfigureCommandErrors(t *testing.T) { _, laceworkTOML := runConfigureTest(t, func(c *expect.Console) { - c.ExpectString("Account:") + expectString(t, c, "Account:") c.SendLine("") - c.ExpectString("The account subdomain of URL is required") + expectString(t, c, "The account subdomain of URL is required") // if the full URL was provided we transform it and inform the user c.SendLine("https://my-account.lacework.net") - c.ExpectString("Passing full 'lacework.net' domain not required. Using 'my-account'") - c.ExpectString("Access Key ID:") + expectString(t, c, "Passing full 'lacework.net' domain not required. Using 'my-account'") + expectString(t, c, "Access Key ID:") c.SendLine("") - c.ExpectString("The API access key id must have more than 55 characters") + expectString(t, c, "The API access key id must have more than 55 characters") c.SendLine("INTTEST_ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890AAABBBCCC00") - c.ExpectString("Secret Access Key:") + expectString(t, c, "Secret Access Key:") c.SendLine("") - c.ExpectString("The API secret access key must have more than 30 characters") + expectString(t, c, "The API secret access key must have more than 30 characters") c.SendLine("_00000000000000000000000000000000") - c.ExpectString("You are all set!") + expectString(t, c, "You are all set!") }, "configure", ) @@ -478,54 +488,6 @@ func TestConfigureCommandErrors(t *testing.T) { `, laceworkTOML, "there is a problem with the generated config") } -func runConfigureTest(t *testing.T, conditions func(*expect.Console), args ...string) (string, string) { - // create a temporal directory where we will check that the - // configuration file is deployed (.lacework.toml) - dir, err := ioutil.TempDir("", "lacework-cli") - if err != nil { - panic(err) - } - defer os.RemoveAll(dir) - return runConfigureTestFromDir(t, dir, conditions, args...) -} - -func runConfigureTestFromDir(t *testing.T, dir string, conditions func(*expect.Console), args ...string) (string, string) { - console, state, err := vt10x.NewVT10XConsole() - if err != nil { - panic(err) - } - defer console.Close() - - if os.Getenv("DEBUG") != "" { - state.DebugLogger = log.Default() - } - - donec := make(chan struct{}) - go func() { - defer close(donec) - conditions(console) - }() - - // spawn a new `lacework configure' command - cmd := NewLaceworkCLI(dir, nil, args...) - cmd.Stdin = console.Tty() - cmd.Stdout = console.Tty() - cmd.Stderr = console.Tty() - err = cmd.Start() - assert.Nil(t, err) - - // read the remaining bytes - console.Tty().Close() - <-donec - - configPath := path.Join(dir, ".lacework.toml") - assert.Contains(t, state.String(), "You are all set!", "you are not all set, check configure cmd") - assert.FileExists(t, configPath, "the configuration file is missing") - laceworkTOML, err := ioutil.ReadFile(configPath) - assert.Nil(t, err) - return state.String(), string(laceworkTOML) -} - func TestConfigureCommandWithJSONFileFlagError(t *testing.T) { out, err, exitcode := LaceworkCLI("configure", "--json_file", "foo") assert.Empty(t, @@ -551,3 +513,23 @@ func TestConfigureSwitchProfileHelp(t *testing.T) { assert.Equal(t, 0, exitcode, "EXITCODE is not the expected one") } + +func runConfigureTest(t *testing.T, conditions func(*expect.Console), args ...string) (string, string) { + // create a temporal directory where we will check that the + // configuration file is deployed (.lacework.toml) + dir, err := ioutil.TempDir("", "lacework-cli") + if err != nil { + fmt.Println(err) + t.FailNow() + } + defer os.RemoveAll(dir) + + state := runFakeTerminalTestFromDir(t, dir, conditions, args...) + + configPath := path.Join(dir, ".lacework.toml") + assert.Contains(t, state, "You are all set!", "you are not all set, check configure cmd") + assert.FileExists(t, configPath, "the configuration file is missing") + laceworkTOML, err := ioutil.ReadFile(configPath) + assert.Nil(t, err) + return state, string(laceworkTOML) +} diff --git a/integration/framework_test.go b/integration/framework_test.go index 4ceb79f53..18b9e12e4 100644 --- a/integration/framework_test.go +++ b/integration/framework_test.go @@ -29,12 +29,19 @@ import ( "path" "path/filepath" "runtime" + "testing" + "time" + "github.com/Netflix/go-expect" + "github.com/hinshun/vt10x" "github.com/lacework/go-sdk/api" - "github.com/lacework/go-sdk/lwupdater" + "github.com/stretchr/testify/assert" ) +// When emulating a terminal, the timeout to wait for output +var expectStringTimeout = time.Second * 3 + // Use this function to execute a real lacework CLI command, under the hood the function // will detect the correct binary depending on the running OS and architecture, if you // need to override the binary to use at runtime, set the `LW_CLI_BIN` environment @@ -298,3 +305,58 @@ func createTemporaryFile(name, content string) (*os.File, error) { return file, err } + +func runFakeTerminalTestFromDir(t *testing.T, dir string, conditions func(*expect.Console), args ...string) string { + // Multiplex output to a buffer as well for the raw bytes. + buf := new(bytes.Buffer) + + console, state, err := vt10x.NewVT10XConsole(expect.WithStdout(buf)) + if err != nil { + panic(err) + } + defer console.Close() + + if os.Getenv("DEBUG") != "" { + state.DebugLogger = log.Default() + } + + donec := make(chan struct{}) + go func() { + defer close(donec) + conditions(console) + }() + + // spawn a new `lacework configure' command + cmd := NewLaceworkCLI(dir, nil, args...) + cmd.Stdin = console.Tty() + cmd.Stdout = console.Tty() + cmd.Stderr = console.Tty() + err = cmd.Start() + assert.Nil(t, err) + + // read the remaining bytes + console.Tty().Close() + <-donec + + t.Logf("Raw output: %q", buf.String()) + + // Dump the terminal's screen. + t.Logf( + "Terminal output:\n%s", + expect.StripTrailingEmptyLines(state.String()), + ) + + return state.String() +} + +func expectString(t *testing.T, c *expect.Console, str string) { + out, err := c.Expect( + expect.WithTimeout(expectStringTimeout), + expect.String(str), + ) + if err != nil { + fmt.Println(out) + fmt.Println(err) + t.FailNow() + } +} diff --git a/integration/lql_update_test.go b/integration/lql_update_test.go index c49279f26..881995e34 100644 --- a/integration/lql_update_test.go +++ b/integration/lql_update_test.go @@ -23,19 +23,12 @@ import ( "fmt" "os" "testing" + "time" + "github.com/Netflix/go-expect" "github.com/stretchr/testify/assert" ) -func TestQueryUpdateHelp(t *testing.T) { - out, err, exitcode := LaceworkCLI("help", "query", "update") - assert.Contains(t, out.String(), "lacework query update [flags]") - assert.Contains(t, out.String(), "-f, --file string") - assert.Contains(t, out.String(), "-u, --url string") - assert.Empty(t, err.String(), "STDERR should be empty") - assert.Equal(t, 0, exitcode, "EXITCODE is not the expected one") -} - func TestQueryUpdateEditor(t *testing.T) { out, err, exitcode := LaceworkCLIWithTOMLConfig("query", "update") assert.Contains(t, out.String(), "Type a query to update") @@ -99,3 +92,61 @@ func TestQueryUpdateURL(t *testing.T) { assert.Empty(t, stderr.String(), "STDERR should be empty") assert.Equal(t, 0, exitcode, "EXITCODE is not the expected one") } + +func TestQueryUpdateFromIDNotFound(t *testing.T) { + out, stderr, exitcode := LaceworkCLIWithTOMLConfig("query", "update", "ID_NOT_FOUND", "--noninteractive") + + assert.Empty(t, out.String(), "STDOUT should be empty") // added --noninteractive to avoid polluting STDOUT + assert.Contains(t, stderr.String(), "unable to load query from your account") + assert.Contains(t, stderr.String(), "/api/v2/Queries/ID_NOT_FOUND") + assert.Contains(t, stderr.String(), "Query id ID_NOT_FOUND not found") + assert.Equal(t, 1, exitcode, "EXITCODE is not the expected one") +} + +func TestQueryUpdateFromIDEditor(t *testing.T) { + // get temp file + file, err := createTemporaryFile( + "TestQueryUpdateFromIDEditor", + fmt.Sprintf(queryJSONTemplate, evaluatorID, queryID, queryUpdateText), + ) + if err != nil { + t.FailNow() + } + defer os.Remove(file.Name()) + + dir := createTOMLConfigFromCIvars() + defer os.RemoveAll(dir) + + // setup + LaceworkCLIWithHome(dir, "query", "create", "-u", queryURL) + // teardown + defer LaceworkCLIWithHome(dir, "query", "delete", queryID) + + _ = runFakeTerminalTestFromDir(t, dir, + func(c *expect.Console) { + expectString(t, c, "Update query") + c.SendLine("") + time.Sleep(time.Millisecond) + // Move to line number 4 and add comment "--- Updated from CLI Editor" + c.Send("4Go--- Updated from CLI Editor\x1b") + c.SendLine(":wq!") // save and close + time.Sleep(time.Millisecond) + expectString(t, c, + fmt.Sprintf("The query %s was updated.", queryID)) + }, + "query", "update", queryID, + ) + + t.Run("verify query editions", func(t *testing.T) { + stdout, stderr, exitcode := LaceworkCLIWithHome(dir, "query", "show", queryID) + assert.Empty(t, + stderr.String(), + "STDERR should be empty") + assert.Contains(t, + stdout.String(), + "--- Updated from CLI Editor", + "the query was not editted correctly") + assert.Equal(t, 0, exitcode, + "EXITCODE is not the expected one") + }) +} diff --git a/integration/test_resources/help/query_update b/integration/test_resources/help/query_update index 4d5ca7abc..8015d205e 100644 --- a/integration/test_resources/help/query_update +++ b/integration/test_resources/help/query_update @@ -2,6 +2,7 @@ There are multiple ways you can update a query: * Typing the query into your default editor (via $EDITOR) + * Passing a query id to load it into your default editor * From a local file on disk using the flag '--file' * From a URL using the flag '--url' @@ -15,7 +16,7 @@ To launch your default editor and update a query. lacework query update Usage: - lacework query update [flags] + lacework query update [query_id] [flags] Flags: -f, --file string path to a query to update