|
| 1 | +package main |
| 2 | + |
| 3 | +import ( |
| 4 | + "encoding/base64" |
| 5 | + "encoding/json" |
| 6 | + "fmt" |
| 7 | + "log" |
| 8 | + "os" |
| 9 | + "path/filepath" |
| 10 | + "strings" |
| 11 | + |
| 12 | + "gopkg.in/yaml.v3" |
| 13 | +) |
| 14 | + |
| 15 | +func readCSV(csvFilename string, csv *map[string]interface{}) { |
| 16 | + yamlFile, err := os.ReadFile(csvFilename) |
| 17 | + if err != nil { |
| 18 | + log.Fatal(fmt.Sprintf("Error: Failed to read file '%s'", csvFilename)) |
| 19 | + } |
| 20 | + |
| 21 | + err = yaml.Unmarshal(yamlFile, csv) |
| 22 | + if err != nil { |
| 23 | + log.Fatal(fmt.Sprintf("Error: Failed to unmarshal yaml file '%s'", csvFilename)) |
| 24 | + } |
| 25 | +} |
| 26 | + |
| 27 | +func replaceCSV(csvFilename string, outputCSVFilename string, csv map[string]interface{}) { |
| 28 | + err := os.Remove(csvFilename) |
| 29 | + if err != nil { |
| 30 | + log.Fatal(fmt.Sprintf("Error: Failed to remofe file '%s'", csvFilename)) |
| 31 | + } |
| 32 | + |
| 33 | + f, err := os.Create(outputCSVFilename) |
| 34 | + if err != nil { |
| 35 | + log.Fatal(fmt.Sprintf("Error: Failed to create file '%s'", outputCSVFilename)) |
| 36 | + } |
| 37 | + |
| 38 | + enc := yaml.NewEncoder(f) |
| 39 | + defer enc.Close() |
| 40 | + enc.SetIndent(2) |
| 41 | + |
| 42 | + err = enc.Encode(csv) |
| 43 | + if err != nil { |
| 44 | + log.Fatal("Error: Failed encode the CSV into yaml") |
| 45 | + } |
| 46 | +} |
| 47 | + |
| 48 | +func getInputCSVFilePath(dir string) string { |
| 49 | + filenames, err := os.ReadDir(dir) |
| 50 | + if err != nil { |
| 51 | + log.Fatal("Failed to find manifest dir") |
| 52 | + } |
| 53 | + |
| 54 | + for _, filename := range filenames { |
| 55 | + if strings.HasSuffix(filename.Name(), "clusterserviceversion.yaml") { |
| 56 | + return filepath.Join(dir, filename.Name()) |
| 57 | + } |
| 58 | + } |
| 59 | + |
| 60 | + log.Fatal("Failed to find CSV file in manifest dir") |
| 61 | + return "" |
| 62 | +} |
| 63 | + |
| 64 | +func getOutputCSVFilePath(dir string, version string) string { |
| 65 | + return filepath.Join(dir, fmt.Sprintf("compliance-operator.v%s.clusterserviceversion.yaml", version)) |
| 66 | +} |
| 67 | + |
| 68 | +func addRequiredAnnotations(csv map[string]interface{}) { |
| 69 | + requiredAnnotations := map[string]string{ |
| 70 | + "features.operators.openshift.io/cnf": "false", |
| 71 | + "features.operators.openshift.io/cni": "false", |
| 72 | + "features.operators.openshift.io/csi": "false", |
| 73 | + "features.operators.openshift.io/disconnected": "true", |
| 74 | + "features.operators.openshift.io/fips-compliant": "true", |
| 75 | + "features.operators.openshift.io/proxy-aware": "false", |
| 76 | + "features.operators.openshift.io/tls-profiles": "false", |
| 77 | + "features.operators.openshift.io/token-auth-aws": "false", |
| 78 | + "features.operators.openshift.io/token-auth-azure": "false", |
| 79 | + "features.operators.openshift.io/token-auth-gcp": "false", |
| 80 | + } |
| 81 | + |
| 82 | + annotations, ok := csv["metadata"].(map[string]interface{})["annotations"].(map[string]interface{}) |
| 83 | + if !ok { |
| 84 | + log.Fatal("Error: 'annotations' does not exist within 'metadata' in the CSV content") |
| 85 | + } |
| 86 | + |
| 87 | + for key, value := range requiredAnnotations { |
| 88 | + annotations[key] = value |
| 89 | + } |
| 90 | + fmt.Println("Added required annotations") |
| 91 | +} |
| 92 | + |
| 93 | +func replaceVersion(oldVersion, newVersion string, csv map[string]interface{}) { |
| 94 | + spec, ok := csv["spec"].(map[string]interface{}) |
| 95 | + metadata, ok := csv["metadata"].(map[string]interface{}) |
| 96 | + if !ok { |
| 97 | + log.Fatal("Error: 'spec' does not exist in the CSV content") |
| 98 | + } |
| 99 | + |
| 100 | + fmt.Println(fmt.Sprintf("Updating version references from %s to %s", oldVersion, newVersion)) |
| 101 | + |
| 102 | + spec["version"] = newVersion |
| 103 | + spec["replaces"] = "compliance-operator.v" + oldVersion |
| 104 | + |
| 105 | + metadata["name"] = strings.Replace(metadata["name"].(string), oldVersion, newVersion, 1) |
| 106 | + |
| 107 | + annotations := metadata["annotations"].(map[string]interface{}) |
| 108 | + annotations["olm.skipRange"] = strings.Replace(annotations["olm.skipRange"].(string), oldVersion, newVersion, 1) |
| 109 | + |
| 110 | + fmt.Println(fmt.Sprintf("Updated version references from %s to %s", oldVersion, newVersion)) |
| 111 | +} |
| 112 | + |
| 113 | +func replaceIcon(csv map[string]interface{}) { |
| 114 | + |
| 115 | + s, ok := csv["spec"] |
| 116 | + if !ok { |
| 117 | + log.Fatal("Error: 'spec' does not exist in the CSV content") |
| 118 | + } |
| 119 | + spec := s.(map[string]interface{}) |
| 120 | + |
| 121 | + iconPath := "../bundle/icons/icon.png" |
| 122 | + iconData, err := os.ReadFile(iconPath) |
| 123 | + if err != nil { |
| 124 | + log.Fatal(fmt.Sprintf("Error: Failed to read icon file '%s'", iconPath)) |
| 125 | + } |
| 126 | + icon := make(map[string]string) |
| 127 | + icon["base64data"] = base64.StdEncoding.EncodeToString(iconData) |
| 128 | + icon["media"] = "image/png" |
| 129 | + |
| 130 | + var icons = make([]map[string]string, 1) |
| 131 | + icons[0] = icon |
| 132 | + |
| 133 | + spec["icon"] = icons |
| 134 | + |
| 135 | + fmt.Println(fmt.Sprintf("Updated the operator image to use icon in %s", iconPath)) |
| 136 | +} |
| 137 | + |
| 138 | +//func recoverFromReplaceImages() { |
| 139 | +// if r := recover(); r != nil { |
| 140 | +// log.Fatal("Error: It was not possible to replace RELATED_IMAGE_OPERATOR") |
| 141 | +// } |
| 142 | +//} |
| 143 | + |
| 144 | +func getPullSpecSha(pullSpec string) string { |
| 145 | + delimiter := "@" |
| 146 | + |
| 147 | + parts := strings.Split(pullSpec, delimiter) |
| 148 | + if len(parts) > 2 { |
| 149 | + log.Fatalf("Error: Failed to safely determine image SHA from Konflux pull spec: %s", pullSpec) |
| 150 | + } |
| 151 | + return parts[1] |
| 152 | +} |
| 153 | + |
| 154 | +func replaceImages(csv map[string]interface{}) { |
| 155 | +// defer recoverFromReplaceImages() |
| 156 | + |
| 157 | + // Konflux will automatically update the image sha based on the most |
| 158 | + // recent builds. We want to peel off the SHA and append it to the Red |
| 159 | + // Hat registry so that the bundle image will work when it's available |
| 160 | + // there. |
| 161 | + konfluxOperatorPullSpec := "quay.io/redhat-user-workloads/ocp-isc-tenant/compliance-operator-release@sha256:8b07d699804a263a567715422f86c8086c39a45baccbcae3734b062b57c67b1e" |
| 162 | + konfluxContentPullSpec := "quay.io/redhat-user-workloads/ocp-isc-tenant/compliance-operator-content-release@sha256:8b07d699804a263a567715422f86c8086c39a45baccbcae3734b062b57c67b1e" |
| 163 | + konfluxOpenscapPullSpec := "quay.io/redhat-user-workloads/ocp-isc-tenant/compliance-operator-openscap-release@sha256:c27b9680fdee28b566fbfa636777c18b03c4332a5694ac07ca3ada30420698e3" |
| 164 | + konfluxMustGatherPullSpec := "quay.io/redhat-user-workloads/ocp-isc-tenant/compliance-operator-must-gather-release@sha256:b9a914216870c2a3b78ff641add7d0548e1805d70a411880109b645efa724262" |
| 165 | + |
| 166 | + imageShaOperator := getPullSpecSha(konfluxOperatorPullSpec) |
| 167 | + imageShaOpenscap := getPullSpecSha(konfluxOpenscapPullSpec) |
| 168 | + imageShaContent := getPullSpecSha(konfluxContentPullSpec) |
| 169 | + imageShaMustGather := getPullSpecSha(konfluxMustGatherPullSpec) |
| 170 | + |
| 171 | + |
| 172 | + env, ok := csv["spec"].(map[string]interface{})["install"].(map[string]interface{})["spec"].(map[string]interface{})["deployments"].([]interface{})[0].(map[string]interface{})["spec"].(map[string]interface{})["template"].(map[string]interface{})["spec"].(map[string]interface{})["containers"].([]interface{})[0].(map[string]interface{})["env"].([]interface{}) |
| 173 | + if !ok { |
| 174 | + log.Fatal("Error: 'env' with RELATED_IMAGE_OPERATOR does not exist in the CSV content") |
| 175 | + } |
| 176 | + |
| 177 | + delimiter := "@" |
| 178 | + registryPrefix := "registry.redhat.io/compliance/" |
| 179 | + newPullSpecs := map[string]string { |
| 180 | + "RELATED_IMAGE_OPERATOR": registryPrefix + "openshift-compliance-rhel8-operator" + delimiter + imageShaOperator, |
| 181 | + "RELATED_IMAGE_OPENSCAP": registryPrefix + "openshift-compliance-openscap-rhel8" + delimiter + imageShaOpenscap, |
| 182 | + "RELATED_IMAGE_PROFILE": registryPrefix + "openshift-compliance-content-rhel8" + delimiter + imageShaContent, |
| 183 | + "RELATED_IMAGE_MUST_GATHER": registryPrefix + "openshift-compliance-must-gather-rhel8" + delimiter + imageShaMustGather, |
| 184 | + } |
| 185 | + |
| 186 | + for _, item := range env { |
| 187 | + variable := item.(map[string]interface{}) |
| 188 | + variable["value"] = newPullSpecs[variable["name"].(string)] |
| 189 | + } |
| 190 | + |
| 191 | + // Update the image pull spec |
| 192 | + containersMap := csv["spec"].(map[string]interface{})["install"].(map[string]interface{})["spec"].(map[string]interface{})["deployments"].([]interface{})[0].(map[string]interface{})["spec"].(map[string]interface{})["template"].(map[string]interface{})["spec"].(map[string]interface{})["containers"].([]interface{})[0].(map[string]interface{}) |
| 193 | + containersMap["image"] = newPullSpecs["RELATED_IMAGE_OPERATOR"] |
| 194 | + |
| 195 | + // Update the must-gather-image annotation |
| 196 | + annotations := csv["metadata"].(map[string]interface{})["annotations"].(map[string]interface{}) |
| 197 | + annotations["must-gather-image"] = newPullSpecs["RELATED_IMAGE_MUST_GATHER"] |
| 198 | + |
| 199 | + // Update the alm-examples |
| 200 | + var almExamplesJson interface{} |
| 201 | + if err := json.Unmarshal([]byte(annotations["alm-examples"].(string)), &almExamplesJson); err != nil { |
| 202 | + log.Fatal("Error: Failed to decode alm-examples") |
| 203 | + } |
| 204 | + for _, item := range almExamplesJson.([]interface{}) { |
| 205 | + if spec, ok := item.(map[string]interface{})["spec"]; ok { |
| 206 | + if _, ok := spec.(map[string]interface{})["contentImage"]; ok { |
| 207 | + spec.(map[string]interface{})["contentImage"] = newPullSpecs["RELATED_IMAGE_OPERATOR"] |
| 208 | + } |
| 209 | + |
| 210 | + if scans, ok := spec.(map[string]interface{})["scans"]; ok { |
| 211 | + for _, scan := range scans.([]interface{}) { |
| 212 | + if _, ok := scan.(map[string]interface{})["contentImage"]; ok { |
| 213 | + scan.(map[string]interface{})["contentImage"] = newPullSpecs["RELATED_IMAGE_OPERATOR"] |
| 214 | + } |
| 215 | + } |
| 216 | + } |
| 217 | + } |
| 218 | + |
| 219 | + } |
| 220 | + |
| 221 | + almExamplesDecoded, _ := json.MarshalIndent(almExamplesJson, "", " ") |
| 222 | + annotations["alm-examples"] = string(almExamplesDecoded) |
| 223 | + |
| 224 | + fmt.Println("Updated the deployment manifest to use downstream builds") |
| 225 | +} |
| 226 | + |
| 227 | +func removeRelated(csv map[string]interface{}) { |
| 228 | + spec, ok := csv["spec"].(map[string]interface{}) |
| 229 | + if !ok { |
| 230 | + log.Fatal("Error: 'spec' does not exist in the CSV content") |
| 231 | + } |
| 232 | + |
| 233 | + delete(spec, "relatedImages") |
| 234 | + fmt.Println("Removed the operator from operator manifest") |
| 235 | +} |
| 236 | + |
| 237 | +func main() { |
| 238 | + var csv map[string]interface{} |
| 239 | + |
| 240 | + manifestsDir := os.Args[1] |
| 241 | + oldVersion := os.Args[2] |
| 242 | + newVersion := os.Args[3] |
| 243 | + |
| 244 | + csvFilename := getInputCSVFilePath(manifestsDir) |
| 245 | + fmt.Println(fmt.Sprintf("Found manifest in %s", csvFilename)) |
| 246 | + |
| 247 | + readCSV(csvFilename, &csv) |
| 248 | + |
| 249 | + addRequiredAnnotations(csv) |
| 250 | + replaceVersion(oldVersion, newVersion, csv) |
| 251 | + replaceIcon(csv) |
| 252 | + replaceImages(csv) |
| 253 | + removeRelated(csv) |
| 254 | + |
| 255 | + outputCSVFilename := getOutputCSVFilePath(manifestsDir, newVersion) |
| 256 | + replaceCSV(csvFilename, outputCSVFilename, csv) |
| 257 | + fmt.Println(fmt.Sprintf("Replaced CSV manifest for %s", newVersion)) |
| 258 | +} |
0 commit comments