diff --git a/pkg/oc/oc.go b/pkg/oc/oc.go index 01fdf269..9d5e05fa 100644 --- a/pkg/oc/oc.go +++ b/pkg/oc/oc.go @@ -4,10 +4,13 @@ import ( "encoding/json" "fmt" "log" + "os" "slices" "strings" "time" + "regexp" + "github.com/getgauge-contrib/gauge-go/testsuit" "github.com/openshift-pipelines/release-tests/pkg/cmd" "github.com/openshift-pipelines/release-tests/pkg/config" @@ -169,3 +172,299 @@ func CopySecret(secretName string, sourceNamespace string, destNamespace string) cmd.MustSucceed("bash", "-c", fmt.Sprintf(`echo '%s' | kubectl apply -n %s -f -`, cmdOutput, destNamespace)) log.Printf("Successfully copied secret %s from %s to %s", secretName, sourceNamespace, destNamespace) } + +func FetchOlmSkipRange() (map[string]string, error) { + skipRangesJson := cmd.MustSucceed("bash", "-c", `oc get packagemanifests openshift-pipelines-operator-rh -n openshift-marketplace -o json | jq -r '.status.channels[].currentCSVDesc.annotations["olm.skipRange"]'`).Stdout() + skipRanges := strings.Split(strings.TrimSpace(skipRangesJson), "\n") + channelsJson := cmd.MustSucceed("bash", "-c", `oc get packagemanifests openshift-pipelines-operator-rh -n openshift-marketplace -o json | jq -r '.status.channels[].name'`).Stdout() + channels := strings.Split(strings.TrimSpace(channelsJson), "\n") + + if len(channels) != len(skipRanges) { + return nil, fmt.Errorf("mismatch between number of channels (%d) and skipRanges (%d)", len(channels), len(skipRanges)) + } + + channelSkipRangeMap := make(map[string]string) + for i, channel := range channels { + if skipRanges[i] != "null" && skipRanges[i] != "" { + channelSkipRangeMap[channel] = skipRanges[i] + } + } + + if len(channelSkipRangeMap) == 0 { + return nil, fmt.Errorf("no valid OLM Skip Ranges found") + } + return channelSkipRangeMap, nil +} + +// extractMajorMinor extracts major.minor version from a full version string +// e.g., "1.19.2" -> "1.19", "1.18.1" -> "1.18" +func extractMajorMinor(version string) string { + versionRegex := regexp.MustCompile(`^(\d+\.\d+)\.?\d*`) + matches := versionRegex.FindStringSubmatch(version) + if len(matches) >= 2 { + return matches[1] + } + // Fallback: if regex fails, return the original version + return version +} + +func GetOlmSkipRange(upgradeType, fieldName, fileName string) { + skipRangeMap, err := FetchOlmSkipRange() + if err != nil { + log.Printf("Error fetching OLM Skip Range: %v", err) + return + } + file, err := os.OpenFile(config.Path(fileName), os.O_RDWR, 0644) + if err != nil { + log.Printf("Error opening file %s: %v", fileName, err) + return + } + defer file.Close() + var existingData map[string]interface{} + if err := json.NewDecoder(file).Decode(&existingData); err != nil { + log.Printf("Error decoding existing data from file %s: %v", fileName, err) + return + } + switch upgradeType { + case "pre-upgrade": + existingData["pre-upgrade-olm-skip-range"] = skipRangeMap + log.Printf("Pre-upgrade OLM Skip Range is stored as: %+v", skipRangeMap) + case "post-upgrade": + existingData["post-upgrade-olm-skip-range"] = skipRangeMap + log.Printf("Post-upgrade OLM Skip Range is stored as: %+v", skipRangeMap) + } + if _, err := file.Seek(0, 0); err != nil { + log.Printf("Error seeking file %s: %v", fileName, err) + return + } + encoder := json.NewEncoder(file) + encoder.SetIndent("", " ") // Pretty-print the JSON output + if err := encoder.Encode(existingData); err != nil { + log.Printf("Error writing updated data to file %s: %v", fileName, err) + return + } + log.Printf("OLM Skip Range for '%s' has been saved to file %s", fieldName, fileName) +} + +func ValidateOlmSkipRange() { + skipRangeMap, err := FetchOlmSkipRange() + if err != nil { + log.Printf("Error fetching OLM Skip Range: %v", err) + return + } + + ospVersion := os.Getenv("OSP_VERSION") + log.Printf("Validating OSP_VERSION: %s", ospVersion) + found := false + + if ospVersion == "5.0.5" { + log.Printf("Detected nightly build (OSP_VERSION=5.0.5), validating only skipRange, not channel name") + for channel, skipRange := range skipRangeMap { + if channel == "latest" { + log.Printf("Skipping 'latest' channel as requested") + continue + } + skipRangeContainsVersion := strings.Contains(skipRange, ospVersion) + log.Printf("Channel: %s, SkipRange: %s", channel, skipRange) + log.Printf(" - SkipRange contains OSP_VERSION '%s': %v", ospVersion, skipRangeContainsVersion) + + if skipRangeContainsVersion { + log.Printf("Success: OSP_VERSION '%s' found in skipRange for channel '%s': '%s'", ospVersion, channel, skipRange) + found = true + break + } + } + } else { + log.Printf("Regular release build, validating both channel name and skipRange") + + // Extract major.minor from OSP_VERSION for channel matching + // e.g., "1.19.2" -> "1.19" to match with "pipelines-1.19" + ospMajorMinor := extractMajorMinor(ospVersion) + log.Printf("Extracted major.minor '%s' from OSP_VERSION '%s' for channel matching", ospMajorMinor, ospVersion) + + for channel, skipRange := range skipRangeMap { + if channel == "latest" { + log.Printf("Skipping 'latest' channel as requested") + continue + } + + // Check if channel contains the major.minor version + channelContainsVersion := strings.Contains(channel, ospMajorMinor) + skipRangeContainsVersion := strings.Contains(skipRange, ospVersion) + + log.Printf("Channel: %s, SkipRange: %s", channel, skipRange) + log.Printf(" - Channel contains major.minor '%s': %v", ospMajorMinor, channelContainsVersion) + log.Printf(" - SkipRange contains OSP_VERSION '%s': %v", ospVersion, skipRangeContainsVersion) + + if channelContainsVersion && skipRangeContainsVersion { + log.Printf("Success: OSP_VERSION '%s' found in channel '%s' (major.minor match) and its skipRange '%s'", ospVersion, channel, skipRange) + found = true + break + } + } + } + + if !found { + log.Printf("Available channels and their skipRanges:") + for channel, skipRange := range skipRangeMap { + if channel != "latest" { + log.Printf(" - Channel: %s, SkipRange: %s", channel, skipRange) + } + } + + if ospVersion == "5.0.5" { + testsuit.T.Fail(fmt.Errorf("Error: OSP_VERSION '%s' not found in skipRange for any non-latest channel", ospVersion)) + } else { + ospMajorMinor := extractMajorMinor(ospVersion) + testsuit.T.Fail(fmt.Errorf("Error: OSP_VERSION '%s' (major.minor: %s) not found in both channel name and skipRange for any non-latest channel", ospVersion, ospMajorMinor)) + } + } +} + +func isValidOspVersionPatchUpdate(preSkipRange, postSkipRange string) bool { + ospVersion := os.Getenv("OSP_VERSION") + + if !skipRangeContainsVersion(postSkipRange, ospVersion) { + log.Printf("Post-upgrade skip range '%s' does not contain OSP_VERSION '%s'", postSkipRange, ospVersion) + return false + } + + rangeRegex := regexp.MustCompile(`>=(\d+\.\d+\.\d+)\s*<(\d+\.\d+\.\d+)`) + preMatches := rangeRegex.FindStringSubmatch(preSkipRange) + postMatches := rangeRegex.FindStringSubmatch(postSkipRange) + + preUpperBound := preMatches[2] // Upper bound from pre-upgrade + postUpperBound := postMatches[2] // Upper bound from post-upgrade + + versionRegex := regexp.MustCompile(`^(\d+)\.(\d+)\.(\d+)$`) + preUpperMatches := versionRegex.FindStringSubmatch(preUpperBound) + postUpperMatches := versionRegex.FindStringSubmatch(postUpperBound) + + preUpperPatch := preUpperMatches[3] + postUpperPatch := postUpperMatches[3] + + var preUpperPatchInt, postUpperPatchInt int + fmt.Sscanf(preUpperPatch, "%d", &preUpperPatchInt) + fmt.Sscanf(postUpperPatch, "%d", &postUpperPatchInt) + + if postUpperPatchInt <= preUpperPatchInt { + log.Printf("Version did not increase: %s -> %s", preUpperBound, postUpperBound) + return false + } + + log.Printf("Valid OSP version-based patch update detected: %s -> %s (OSP_VERSION: %s)", + preUpperBound, postUpperBound, ospVersion) + return true +} + +func skipRangeContainsVersion(skipRange, version string) bool { + return strings.Contains(skipRange, version) +} + +func ValidateOlmSkipRangeDiff(fileName string, preUpgradeSkipRange string, postUpgradeSkipRange string) { + file, err := os.Open(config.Path(fileName)) + if err != nil { + log.Printf("Error opening file %s: %v", fileName, err) + testsuit.T.Fail(fmt.Errorf("Error opening file %s: %v", fileName, err)) + return + } + defer file.Close() + var skipRangeData map[string]interface{} + decoder := json.NewDecoder(file) + if err := decoder.Decode(&skipRangeData); err != nil { + log.Printf("Error decoding JSON from file %s: %v", fileName, err) + testsuit.T.Fail(fmt.Errorf("Error decoding JSON from file %s: %v", fileName, err)) + return + } + preUpgradeData, preExists := skipRangeData[preUpgradeSkipRange] + postUpgradeData, postExists := skipRangeData[postUpgradeSkipRange] + if !preExists || !postExists { + log.Printf("Error: One of the skip ranges is missing. Pre-Upgrade exists: %v, Post-Upgrade exists: %v", preExists, postExists) + testsuit.T.Fail(fmt.Errorf("One of the skip ranges is missing. Pre-Upgrade exists: %v, Post-Upgrade exists: %v", preExists, postExists)) + return + } + + preUpgradeMap, ok1 := preUpgradeData.(map[string]interface{}) + postUpgradeMap, ok2 := postUpgradeData.(map[string]interface{}) + + if !ok1 || !ok2 { + log.Printf("Error: Skip range data is not in expected map format") + testsuit.T.Fail(fmt.Errorf("Skip range data is not in expected map format")) + return + } + + log.Printf("Pre-Upgrade Skip Range: %+v", preUpgradeMap) + log.Printf("Post-Upgrade Skip Range: %+v", postUpgradeMap) + + validationErrors := []string{} + + log.Printf("Validating that all pre-upgrade channels are preserved in post-upgrade (ignoring 'latest' channel)") + + // Check each channel from pre-upgrade data + for preChannel, preSkipRange := range preUpgradeMap { + // Skip 'latest' channel from pre-upgrade validation + if preChannel == "latest" { + log.Printf("Skipping 'latest' channel from pre-upgrade data as requested") + continue + } + + if postSkipRange, exists := postUpgradeMap[preChannel]; exists { + if preSkipRange == postSkipRange { + log.Printf("✅ Success: Channel '%s' preserved with skipRange: %v", preChannel, preSkipRange) + } else { + // There's a skipRange mismatch - check if it's related to current OSP_VERSION + preSkipRangeStr, ok1 := preSkipRange.(string) + postSkipRangeStr, ok2 := postSkipRange.(string) + + if ok1 && ok2 { + ospVersion := os.Getenv("OSP_VERSION") + + if ospVersion != "" && (strings.Contains(preSkipRangeStr, ospVersion) || strings.Contains(postSkipRangeStr, ospVersion)) { + log.Printf("ℹ️ SkipRange mismatch involves current OSP_VERSION '%s', applying OSP_VERSION-based validation", ospVersion) + + if isValidOspVersionPatchUpdate(preSkipRangeStr, postSkipRangeStr) { + log.Printf("✅ Success: Channel '%s' updated with valid OSP_VERSION-based patch release: %v -> %v", preChannel, preSkipRange, postSkipRange) + } else { + validationErrors = append(validationErrors, fmt.Sprintf("Channel '%s' skipRange changed from '%v' to '%v' (not a valid OSP_VERSION-based patch update for OSP_VERSION '%s')", preChannel, preSkipRange, postSkipRange, ospVersion)) + } + } else { + if ospVersion == "" { + log.Printf("ℹ️ OSP_VERSION not set, applying standard validation (no changes allowed)") + } else { + log.Printf("ℹ️ SkipRange mismatch does not involve current OSP_VERSION '%s', applying standard validation (no changes allowed)", ospVersion) + } + validationErrors = append(validationErrors, fmt.Sprintf("Channel '%s' skipRange changed from '%v' to '%v' (should remain unchanged)", preChannel, preSkipRange, postSkipRange)) + } + } else { + validationErrors = append(validationErrors, fmt.Sprintf("Channel '%s' skipRange changed from '%v' to '%v' (invalid format)", preChannel, preSkipRange, postSkipRange)) + } + } + } else { + validationErrors = append(validationErrors, fmt.Sprintf("Pre-upgrade channel '%s' is missing in post-upgrade data", preChannel)) + } + } + + log.Printf("Additional channels found in post-upgrade data:") + for postChannel, postSkipRange := range postUpgradeMap { + if _, existedInPre := preUpgradeMap[postChannel]; existedInPre { + continue + } + + if postChannel == "latest" { + log.Printf(" - Ignoring 'latest' channel in post-upgrade as requested") + continue + } + + log.Printf(" - New channel '%s' with skipRange: %v", postChannel, postSkipRange) + } + + if len(validationErrors) > 0 { + log.Printf("❌ OLM Skip Range validation failed with errors:") + for _, err := range validationErrors { + log.Printf(" - %s", err) + } + testsuit.T.Fail(fmt.Errorf("OLM Skip Range validation failed: %v", strings.Join(validationErrors, "; "))) + } else { + log.Printf("✅ Success: OLM Skip Range validation passed - all expected changes detected correctly") + } +} diff --git a/specs/operator/post-upgrade.spec b/specs/operator/post-upgrade.spec index 6d24beaf..3d4ff19a 100644 --- a/specs/operator/post-upgrade.spec +++ b/specs/operator/post-upgrade.spec @@ -83,4 +83,15 @@ Importance: Critical Steps: * Switch to project "releasetest-upgrade-s2i" * Get tags of the imagestream "golang" from namespace "openshift" and store to variable "golang-tags" - * Start and verify pipeline "s2i-go-pipeline" with param "VERSION" with values stored in variable "golang-tags" with workspace "name=source,claimName=shared-pvc" \ No newline at end of file + * Start and verify pipeline "s2i-go-pipeline" with param "VERSION" with values stored in variable "golang-tags" with workspace "name=source,claimName=shared-pvc" + +## Validate olm skiprange post upgrade: PIPELINES-19-TC06 +Tags: post-upgrade, olm +Component: Pipelines +Level: Integration +Type: Functional +Importance: Critical + +Steps: + * Get olm-skip-range "post-upgrade" and save to field "post-upgrade-olm-skip-range" in file "testdata/olm/skiprange.json" + * Validate the fields "pre-upgrade-olm-skip-range" and "post-upgrade-olm-skip-range" are same in file "testdata/olm/skiprange.json" \ No newline at end of file diff --git a/specs/operator/pre-upgrade.spec b/specs/operator/pre-upgrade.spec index fb209ef5..6c44d967 100644 --- a/specs/operator/pre-upgrade.spec +++ b/specs/operator/pre-upgrade.spec @@ -126,4 +126,14 @@ Steps: |S.NO|resource_dir | |----|------------------------------------------------------| |1 |testdata/ecosystem/pipelines/s2i-go.yaml| - |2 |testdata/pvc/pvc.yaml | \ No newline at end of file + |2 |testdata/pvc/pvc.yaml | + +## Validate olm skiprange pre upgrade: PIPELINES-18-TC06 +Tags: pre-upgrade, olm +Component: Pipelines +Level: Integration +Type: Functional +Importance: Critical + +Steps: + * Get olm-skip-range "pre-upgrade" and save to field "pre-upgrade-olm-skip-range" in file "testdata/olm/skiprange.json" \ No newline at end of file diff --git a/specs/versions.spec b/specs/versions.spec index 766f20c6..e882e128 100644 --- a/specs/versions.spec +++ b/specs/versions.spec @@ -30,3 +30,12 @@ Steps: * Check "tkn-pac" version * Check "opc" client version * Check "opc" server version + +## Check OperatorVersion in OlmSkipRange : PIPELINES-22-TC03 +Tags: e2e, sanity, olm +Component: Operator +Level: Integration +Type: Functional +Importance: High +Steps: + * Validate OperatorVersion in OlmSkipRange \ No newline at end of file diff --git a/steps/olm/operator.go b/steps/olm/operator.go index 7b2df656..67dae1a9 100644 --- a/steps/olm/operator.go +++ b/steps/olm/operator.go @@ -196,3 +196,15 @@ var _ = gauge.Step("Store Cosign public key in file", func() { var _ = gauge.Step("Verify version from the pipelinerun logs", func(binary string) { pipelines.CheckLogVersion(store.Clients(), binary, store.Namespace()) }) + +var _ = gauge.Step("Get olm-skip-range and save to field in file ", func(upgradeType string, fieldName string, filename string) { + oc.GetOlmSkipRange(upgradeType, fieldName, filename) +}) + +var _ = gauge.Step("Validate the fields and are same in file ", func(preUpgradeSkipRange string, postUpgradeSkipRange string, fileName string) { + oc.ValidateOlmSkipRangeDiff(fileName, preUpgradeSkipRange, postUpgradeSkipRange) +}) + +var _ = gauge.Step("Validate OperatorVersion in OlmSkipRange", func() { + oc.ValidateOlmSkipRange() +}) diff --git a/testdata/olm/skiprange.json b/testdata/olm/skiprange.json new file mode 100644 index 00000000..9b5cb99c --- /dev/null +++ b/testdata/olm/skiprange.json @@ -0,0 +1,4 @@ +{ + "post-upgrade-olm-skip-range": {}, + "pre-upgrade-olm-skip-range": {} +}