diff --git a/.vscode/settings.json b/.vscode/settings.json index 3c73f3d1..2b39ac72 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,5 +2,11 @@ "go.lintTool": "golangci-lint", "go.lintFlags": [ "--fast" - ] + ], + "files.associations": { + "*.spec": "gauge", + "*.cpt": "gauge" + }, + "files.autoSave": "afterDelay", + "files.autoSaveDelay": 500 } diff --git a/pkg/opc/opc.go b/pkg/opc/opc.go new file mode 100644 index 00000000..203ae67d --- /dev/null +++ b/pkg/opc/opc.go @@ -0,0 +1,401 @@ +package opc + +import ( + "fmt" + "strconv" + "strings" + + "github.com/openshift-pipelines/release-tests/pkg/cmd" +) + +type PacInfoInstall struct { + PipelinesAsCode PipelinesAsCodeSection + GithubApplication GithubApplicationSection + RepositoriesCR RepositoriesCRSection +} + +type PipelinesAsCodeSection struct { + InstallVersion string + InstallNamespace string +} + +type GithubApplicationSection struct { + Name string + URL string + HomePage string + Description string + Created string + InstallationsCount string + WebhookURL string +} + +type RepositoriesCRSection struct { + Count int + Repositories []Repository +} + +type Repository struct { + Namespace string + URL string +} + +type PipelineRunList struct { + Name string + Started string + Duration string + Status string +} + +func GetOpcPacInfoInstall() (*PacInfoInstall, error) { + result := cmd.MustSucceed("opc", "pac", "info", "install") + output := result.Stdout() + lines := strings.Split(output, "\n") + + var pacInfo PacInfoInstall + section := "" // current section: "pipelines", "github", or "repositories" + tableHeaderParsed := false + + for _, rawLine := range lines { + line := strings.TrimSpace(rawLine) + if line == "" { + continue + } + + switch line { + case "Pipelines as Code:": + section = "pipelines" + continue + case "Github Application:": + section = "github" + continue + } + + if strings.HasPrefix(line, "Repositories CR:") { + section = "repositories" + parts := strings.SplitN(line, ":", 2) + if len(parts) == 2 { + countStr := strings.TrimSpace(parts[1]) + count, err := strconv.Atoi(countStr) + if err != nil { + return nil, fmt.Errorf("failed to parse repository count: %v", err) + } + pacInfo.RepositoriesCR.Count = count + } + continue + } + + if section == "pipelines" || section == "github" { + if !strings.Contains(line, ":") { + continue + } + parts := strings.SplitN(line, ":", 2) + key := strings.TrimSpace(parts[0]) + value := strings.TrimSpace(parts[1]) + + if section == "pipelines" { + switch key { + case "Install Version": + pacInfo.PipelinesAsCode.InstallVersion = value + case "Install Namespace": + pacInfo.PipelinesAsCode.InstallNamespace = value + } + } else if section == "github" { + switch key { + case "Name": + pacInfo.GithubApplication.Name = value + case "URL": + pacInfo.GithubApplication.URL = value + case "HomePage": + pacInfo.GithubApplication.HomePage = value + case "Description": + pacInfo.GithubApplication.Description = value + case "Created": + pacInfo.GithubApplication.Created = value + case "Installations Count": + pacInfo.GithubApplication.InstallationsCount = value + case "Webhook URL": + pacInfo.GithubApplication.WebhookURL = value + } + } + continue + } + + if section == "repositories" { + if !tableHeaderParsed && strings.Contains(line, "Namespace") && strings.Contains(line, "URL") { + tableHeaderParsed = true + continue + } + if strings.HasPrefix(line, "- ") { + line = strings.TrimPrefix(line, "-") + line = strings.TrimSpace(line) + } + fields := strings.Fields(line) + if len(fields) < 2 { + continue + } + repo := Repository{ + Namespace: fields[0], + URL: fields[1], + } + pacInfo.RepositoriesCR.Repositories = append(pacInfo.RepositoriesCR.Repositories, repo) + } + } + + if pacInfo.PipelinesAsCode.InstallVersion == "" { + return nil, fmt.Errorf("output of 'opc pac info install' is empty or missing Pipelines as Code information") + } + + return &pacInfo, nil +} + +func GetOpcPrList() ([]PipelineRunList, error) { + result := cmd.MustSucceed("opc", "pr", "ls") + output := strings.TrimSpace(result.Stdout()) + lines := strings.Split(output, "\n") + if len(lines) < 2 { + return nil, fmt.Errorf("unexpected output: %s", output) + } + + var runs []PipelineRunList + for _, line := range lines[1:] { + line = strings.TrimSpace(line) + if line == "" { + continue + } + fields := strings.Fields(line) + if len(fields) < 4 { + return nil, fmt.Errorf("unexpected row format: %s", line) + } + + nameFieldCount := len(fields) - 3 + run := PipelineRunList{ + Name: strings.Join(fields[:nameFieldCount], " "), + Started: fields[nameFieldCount], + Duration: fields[nameFieldCount+1], + Status: fields[nameFieldCount+2], + } + runs = append(runs, run) + } + return runs, nil +} + +func GetOpcPr(pipelineRunName string) (*PipelineRun, error) { + runs, err := GetOpcPrList() + if err != nil { + return nil, err + } + for _, run := range runs { + if run.Name == pipelineRunName { + return &run, nil + } + } + return nil, fmt.Errorf("pipeline run %q not found", pipelineRunName) +} + +// func GetOpcClusterTriggerBinding() ([]string, error) { +// cmd := exec.Command(opcPath, "clustertriggerbinding") +// output, err := cmd.CombinedOutput() +// if err != nil { +// return nil, fmt.Errorf("opc clustertriggerbinding failed: %s", string(output)) +// } +// lines := strings.Split(strings.TrimSpace(string(output)), "\n") +// var results []string +// for _, line := range lines { +// if trimmed := strings.TrimSpace(line); trimmed != "" { +// results = append(results, trimmed) +// } +// } +// return results, nil +// } + +// func StartPipelineUseDefaults(pipelineName string) ([]string, error) { +// cmd := exec.Command(opcPath, "pipeline", "start", "--use-defaults", pipelineName) +// output, err := cmd.CombinedOutput() +// if err != nil { +// return nil, fmt.Errorf("opc pipeline start --use-defaults failed: %s", string(output)) +// } +// lines := strings.Split(strings.TrimSpace(string(output)), "\n") +// var results []string +// for _, line := range lines { +// if trimmed := strings.TrimSpace(line); trimmed != "" { +// results = append(results, trimmed) +// } +// } +// return results, nil +// } + +// func StartPipelineWithWorkspace(pipelineName, workspace string) ([]string, error) { +// info, err := os.Stat(workspace) +// if err != nil || !info.IsDir() { +// return nil, fmt.Errorf("invalid workspace directory: %s", workspace) +// } +// cmd := exec.Command(opcPath, "pipeline", "start", pipelineName) +// cmd.Dir = workspace +// output, err := cmd.CombinedOutput() +// if err != nil { +// return nil, fmt.Errorf("opc pipeline start (workspace) failed: %s", string(output)) +// } +// lines := strings.Split(strings.TrimSpace(string(output)), "\n") +// var results []string +// for _, line := range lines { +// if trimmed := strings.TrimSpace(line); trimmed != "" { +// results = append(results, trimmed) +// } +// } +// return results, nil +// } + +// func GetOpcEventListenerList() ([]string, error) { +// cmd := exec.Command(opcPath, "eventlistener", "list") +// output, err := cmd.CombinedOutput() +// if err != nil { +// return nil, fmt.Errorf("opc eventlistener list failed: %s", string(output)) +// } +// lines := strings.Split(strings.TrimSpace(string(output)), "\n") +// var results []string +// for _, line := range lines { +// if trimmed := strings.TrimSpace(line); trimmed != "" { +// results = append(results, trimmed) +// } +// } +// return results, nil +// } + +// // GetOpcHubSearch runs "opc hub search " and returns its output. +// func GetOpcHubSearch(query string) ([]string, error) { +// cmd := exec.Command(opcPath, "hub", "search", query) +// output, err := cmd.CombinedOutput() +// if err != nil { +// return nil, fmt.Errorf("opc hub search failed: %s", string(output)) +// } +// lines := strings.Split(strings.TrimSpace(string(output)), "\n") +// var results []string +// for _, line := range lines { +// if trimmed := strings.TrimSpace(line); trimmed != "" { +// results = append(results, trimmed) +// } +// } +// return results, nil +// } + +// func GetOpcTriggerTemplateList() ([]string, error) { +// cmd := exec.Command(opcPath, "triggertemplate", "ls") +// output, err := cmd.CombinedOutput() +// if err != nil { +// return nil, fmt.Errorf("opc triggertemplate ls failed: %s", string(output)) +// } +// lines := strings.Split(strings.TrimSpace(string(output)), "\n") +// var results []string +// for _, line := range lines { +// if trimmed := strings.TrimSpace(line); trimmed != "" { +// results = append(results, trimmed) +// } +// } +// return results, nil +// } + +// --------------------------- + +// func splitNonEmptyLines(text string) []string { +// lines := strings.Split(text, "\n") +// var results []string +// for _, line := range lines { +// if trimmed := strings.TrimSpace(line); trimmed != "" { +// results = append(results, trimmed) +// } +// } +// return results +// } + +// // parseKeyValuePairs converts lines of the form "Key: Value" into a dictionary. +// func parseKeyValuePairs(text string) map[string]string { +// infoMap := make(map[string]string) +// lines := strings.Split(text, "\n") +// for _, line := range lines { +// trimmed := strings.TrimSpace(line) +// if trimmed == "" { +// continue +// } +// parts := strings.SplitN(trimmed, ":", 2) +// if len(parts) == 2 { +// key := strings.TrimSpace(parts[0]) +// value := strings.TrimSpace(parts[1]) +// infoMap[key] = value +// } else { +// infoMap[trimmed] = "" +// } +// } +// return infoMap +// } + +// // GetOpcClusterTriggerBinding runs "opc clustertriggerbinding" and returns its output as a slice of strings. +// func GetOpcClusterTriggerBinding() ([]string, error) { +// result := cmd.MustSucceed(opcPath, "clustertriggerbinding") +// output := strings.TrimSpace(result.Stdout()) +// return splitNonEmptyLines(output), nil +// } + +// // StartPipelineUseDefaults runs "opc pipeline start --use-defaults " +// // and returns its output as a slice of strings. +// func StartPipelineUseDefaults(pipelineName string) ([]string, error) { +// result := cmd.MustSucceed(opcPath, "pipeline", "start", "--use-defaults", pipelineName) +// output := strings.TrimSpace(result.Stdout()) +// return splitNonEmptyLines(output), nil +// } + +// // StartPipelineWithWorkspace runs "opc pipeline start " in the specified workspace directory. +// // The workspace must exist (e.g. an empty directory). Returns the output as a slice of strings. +// func StartPipelineWithWorkspace(pipelineName, workspace string) ([]string, error) { +// // Verify the workspace directory exists. +// info, err := os.Stat(workspace) +// if err != nil || !info.IsDir() { +// return nil, fmt.Errorf("invalid workspace directory: %s", workspace) +// } + +// // Change working directory temporarily. +// originalDir, err := os.Getwd() +// if err != nil { +// return nil, fmt.Errorf("failed to get current directory: %v", err) +// } +// if err := os.Chdir(workspace); err != nil { +// return nil, fmt.Errorf("failed to change directory to workspace: %v", err) +// } +// // Ensure we change back to the original directory. +// defer os.Chdir(originalDir) + +// result := cmd.MustSucceed(opcPath, "pipeline", "start", pipelineName) +// output := strings.TrimSpace(result.Stdout()) +// return splitNonEmptyLines(output), nil +// } + +// // GetOpcEventListenerList runs "opc eventlistener list" and returns its output as a slice of strings. +// func GetOpcEventListenerList() ([]string, error) { +// result := cmd.MustSucceed(opcPath, "eventlistener", "list") +// output := strings.TrimSpace(result.Stdout()) +// return splitNonEmptyLines(output), nil +// } + +// // GetOpcHubSearch runs "opc hub search " and returns its output as a slice of strings. +// func GetOpcHubSearch(query string) ([]string, error) { +// result := cmd.MustSucceed(opcPath, "hub", "search", query) +// output := strings.TrimSpace(result.Stdout()) +// return splitNonEmptyLines(output), nil +// } + +// // GetOpcTriggerBindingList runs "opc triggerbinding ls" and returns its output as a slice of strings. +// func GetOpcTriggerBindingList() ([]string, error) { +// result := cmd.MustSucceed(opcPath, "triggerbinding", "ls") +// output := strings.TrimSpace(result.Stdout()) +// return splitNonEmptyLines(output), nil +// } + +// // GetOpcTriggerTemplateLs runs "opc triggertemplate ls" and returns its output as a slice of strings. +// func GetOpcTriggerTemplates() ([]string, error) { +// result := cmd.MustSucceed(opcPath, "triggertemplate", "ls") +// output := strings.TrimSpace(result.Stdout()) +// return splitNonEmptyLines(output), nil +// } + +// ----------------------------------------------------------- + +// ---------------------------------------- diff --git a/pkg/pac/pac.go b/pkg/pac/pac.go index fea2f49c..8d6bb2b3 100644 --- a/pkg/pac/pac.go +++ b/pkg/pac/pac.go @@ -21,6 +21,7 @@ import ( "github.com/openshift-pipelines/release-tests/pkg/clients" "github.com/openshift-pipelines/release-tests/pkg/k8s" "github.com/openshift-pipelines/release-tests/pkg/oc" + "github.com/openshift-pipelines/release-tests/pkg/opc" "github.com/openshift-pipelines/release-tests/pkg/pipelines" "github.com/openshift-pipelines/release-tests/pkg/store" gitlab "github.com/xanzy/go-gitlab" @@ -468,6 +469,25 @@ func ConfigurePreviewChanges(client *gitlab.Client, projectID int) string { return pipelineName } +func AssertPACInfoInstall() { + pacinfo, err := opc.GetOpcPacInfoInstall() + if err != nil { + testsuit.T.Fail(fmt.Errorf("failed to get pac info: %v", err)) + } + for field, value := range map[string]string{ + "Name": pacinfo.GithubApplication.Name, + "URL": pacinfo.GithubApplication.URL, + "WebhookURL": pacinfo.GithubApplication.WebhookURL, + } { + if value == "" { + testsuit.T.Fail(fmt.Errorf("GithubApplication field %q is empty", field)) + } + } + if pacinfo.RepositoriesCR.Count > 0 { + testsuit.T.Fail(fmt.Errorf("Repository CR is %v", pacinfo.RepositoriesCR.Count)) + } +} + func deleteGitlabProject(client *gitlab.Client, projectID int) error { _, err := client.Projects.DeleteProject(projectID) if err != nil { diff --git a/pkg/tkn/tkn.go b/pkg/tkn/tkn.go index 5f2fb8e1..ae39c05b 100644 --- a/pkg/tkn/tkn.go +++ b/pkg/tkn/tkn.go @@ -15,6 +15,12 @@ import ( "gotest.tools/v3/icmd" ) +var ( + tknPath string + tknPacPath string + opcPath string +) + type Cmd struct { // path to tkn binary Path string @@ -32,7 +38,7 @@ func AssertComponentVersion(version string, component string) { var actualVersion string switch component { case "pipeline", "triggers", "operator", "chains": - actualVersion = cmd.MustSucceed("tkn", "version", "--component", component).Stdout() + actualVersion = cmd.MustSucceed("opc", "version", "--component", component).Stdout() case "OSP": actualVersion = cmd.MustSucceed("oc", "get", "tektonconfig", "config", "-o", "jsonpath={.status.version}").Stdout() case "pac": @@ -68,7 +74,7 @@ func AssertClientVersion(binary string) { switch binary { case "tkn-pac": - commandResult = cmd.MustSucceed("/tmp/tkn-pac", "version").Stdout() + commandResult = cmd.MustSucceed(tknPacPath, "version").Stdout() expectedVersion := os.Getenv("PAC_VERSION") if !strings.Contains(commandResult, expectedVersion) { testsuit.T.Errorf("tkn-pac has an unexpected version: %s. Expected: %s", commandResult, expectedVersion) @@ -76,19 +82,19 @@ func AssertClientVersion(binary string) { case "tkn": expectedVersion := os.Getenv("TKN_CLIENT_VERSION") - commandResult = cmd.MustSucceed("/tmp/tkn", "version").Stdout() - var splittedCommandResult = strings.Split(commandResult, "\n") - for i := range splittedCommandResult { - if strings.Contains(splittedCommandResult[i], "Client") { - if !strings.Contains(splittedCommandResult[i], expectedVersion) { - unexpectedVersion = splittedCommandResult[i] + commandResult = cmd.MustSucceed(tknPath, "version").Stdout() + splittedCommandResult := strings.Split(commandResult, "\n") + for _, line := range splittedCommandResult { + if strings.Contains(line, "Client") { + if !strings.Contains(line, expectedVersion) { + unexpectedVersion = line testsuit.T.Errorf("tkn client has an unexpected version: %s. Expected: %s", unexpectedVersion, expectedVersion) } } } case "opc": - commandResult = cmd.MustSucceed("/tmp/opc", "version").Stdout() + commandResult = cmd.MustSucceed(opcPath, "version").Stdout() components := [3]string{"OpenShift Pipelines Client", "Tekton CLI", "Pipelines as Code CLI"} expectedVersions := [3]string{os.Getenv("OSP_VERSION"), os.Getenv("TKN_CLIENT_VERSION"), os.Getenv("PAC_VERSION")} splittedCommandResult := strings.Split(commandResult, "\n") @@ -111,7 +117,7 @@ func AssertServerVersion(binary string) { switch binary { case "opc": - commandResult = cmd.MustSucceed("/tmp/opc", "version", "--server").Stdout() + commandResult = cmd.MustSucceed(opcPath, "version", "--server").Stdout() components := [4]string{"Chains version", "Pipeline version", "Triggers version", "Operator version"} expectedVersions := [4]string{os.Getenv("CHAINS_VERSION"), os.Getenv("PIPELINE_VERSION"), os.Getenv("TRIGGERS_VERSION"), os.Getenv("OPERATOR_VERSION")} splittedCommandResult := strings.Split(commandResult, "\n") @@ -126,7 +132,6 @@ func AssertServerVersion(binary string) { default: testsuit.T.Errorf("Unknown binary or client") } - } func ValidateQuickstarts() { @@ -175,7 +180,7 @@ func (w *CapturingPassThroughWriter) Bytes() []byte { func StartPipeline(pipelineName string, params map[string]string, workspaces map[string]string, namespace string, args ...string) string { var commandArgs []string - commandArgs = append(commandArgs, "tkn", "pipeline", "start", pipelineName, "-o", "name", "-n", namespace) + commandArgs = append(commandArgs, tknPath, "pipeline", "start", pipelineName, "-o", "name", "-n", namespace) for key, value := range params { commandArgs = append(commandArgs, fmt.Sprintf("-p %s=%s", key, value)) } diff --git a/specs/cli/opc.spec b/specs/cli/opc.spec new file mode 100644 index 00000000..4a19fefb --- /dev/null +++ b/specs/cli/opc.spec @@ -0,0 +1,14 @@ +PIPELINES-34 +# CLI tests + +## Configure CLI tests: PIPELINES-34-TC01 +Tags: cli, sanity, e2e, opc +Component: CLI +Level: Integration +Type: Functional +Importance: Critical + +This scenario tests CLI binaries downloaded from registry or brew + +Steps: + * Setup CLI paths and assert diff --git a/steps/cli/opc.go b/steps/cli/opc.go new file mode 100644 index 00000000..fd4a0e67 --- /dev/null +++ b/steps/cli/opc.go @@ -0,0 +1,11 @@ +package cli + +import ( + "github.com/getgauge-contrib/gauge-go/gauge" + "github.com/openshift-pipelines/release-tests/pkg/tkn" +) + +var _ = gauge.Step("Run OPC tests", func() { + tkn.SetupCLIPaths() + +}) diff --git a/steps/pac/pac.go b/steps/pac/pac.go index 8037fc84..a0e2d1b9 100644 --- a/steps/pac/pac.go +++ b/steps/pac/pac.go @@ -22,5 +22,6 @@ var _ = gauge.Step("Configure GitLab repo and validate pipelinerun", func() { project := pac.SetupGitLabProject(client) pipelineName := pac.ConfigurePreviewChanges(client, project.ID) pipelines.ValidatePipelineRun(store.Clients(), pipelineName, "successful", "no", store.Namespace()) + pac.AssertPACInfoInstall() pac.CleanupPAC(client, store.Clients(), project.ID, store.GetScenarioData("smee_deployment_name"), store.Namespace()) }) diff --git a/tc_spec_map.json b/tc_spec_map.json index e1bb1cd6..371dc665 100644 --- a/tc_spec_map.json +++ b/tc_spec_map.json @@ -31,5 +31,6 @@ "PIPELINES-30": "specs/pac/pac-gitlab.spec", "PIPELINES-31": "specs/pipelines/http-resolvers.spec", "PIPELINES-32": "specs/ecosystem/ecosystem-multiarch.spec", - "PIPELINES-33": "specs/ecosystem/ecosystem-s2i.spec" + "PIPELINES-33": "specs/ecosystem/ecosystem-s2i.spec", + "PIPELINES-34": "specs/cli/opc.spec" }