github.com/Racer159/jackal@v0.32.7-0.20240401174413-0bd2339e4f2e/src/extensions/bigbang/flux.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // SPDX-FileCopyrightText: 2021-Present The Jackal Authors 3 4 // Package bigbang contains the logic for installing Big Bang and Flux 5 package bigbang 6 7 import ( 8 "fmt" 9 "os" 10 "path" 11 "path/filepath" 12 13 "github.com/Racer159/jackal/src/internal/packager/kustomize" 14 "github.com/Racer159/jackal/src/pkg/utils" 15 "github.com/Racer159/jackal/src/types" 16 "github.com/Racer159/jackal/src/types/extensions" 17 "github.com/defenseunicorns/pkg/helpers" 18 fluxHelmCtrl "github.com/fluxcd/helm-controller/api/v2beta1" 19 "helm.sh/helm/v3/pkg/chartutil" 20 v1 "k8s.io/api/apps/v1" 21 corev1 "k8s.io/api/core/v1" 22 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 "k8s.io/apimachinery/pkg/runtime" 24 krustytypes "sigs.k8s.io/kustomize/api/types" 25 ) 26 27 // HelmReleaseDependency is a struct that represents a Flux Helm Release from an HR DependsOn list. 28 type HelmReleaseDependency struct { 29 Metadata metav1.ObjectMeta 30 NamespacedDependencies []string 31 NamespacedSource string 32 ValuesFrom []fluxHelmCtrl.ValuesReference 33 } 34 35 // Name returns a namespaced name for the HelmRelease for dependency sorting. 36 func (h HelmReleaseDependency) Name() string { 37 return getNamespacedNameFromMeta(h.Metadata) 38 } 39 40 // Dependencies returns a list of namespaced dependencies for the HelmRelease for dependency sorting. 41 func (h HelmReleaseDependency) Dependencies() []string { 42 return h.NamespacedDependencies 43 } 44 45 // getFlux Creates a component to deploy Flux. 46 func getFlux(baseDir string, cfg *extensions.BigBang) (manifest types.JackalManifest, images []string, err error) { 47 localPath := path.Join(baseDir, "bb-ext-flux.yaml") 48 kustomizePath := path.Join(baseDir, "kustomization.yaml") 49 50 if cfg.Repo == "" { 51 cfg.Repo = bbRepo 52 } 53 54 remotePath := fmt.Sprintf("%s//base/flux?ref=%s", cfg.Repo, cfg.Version) 55 56 fluxKustomization := krustytypes.Kustomization{ 57 Resources: []string{remotePath}, 58 } 59 60 for _, path := range cfg.FluxPatchFiles { 61 absFluxPatchPath, _ := filepath.Abs(path) 62 fluxKustomization.Patches = append(fluxKustomization.Patches, krustytypes.Patch{Path: absFluxPatchPath}) 63 } 64 65 if err := utils.WriteYaml(kustomizePath, fluxKustomization, helpers.ReadWriteUser); err != nil { 66 return manifest, images, fmt.Errorf("unable to write kustomization: %w", err) 67 } 68 69 // Perform Kustomization now to get the flux.yaml file. 70 if err := kustomize.Build(baseDir, localPath, true); err != nil { 71 return manifest, images, fmt.Errorf("unable to build kustomization: %w", err) 72 } 73 74 // Add the flux.yaml file to the component manifests. 75 manifest = types.JackalManifest{ 76 Name: "flux-system", 77 Namespace: "flux-system", 78 Files: []string{localPath}, 79 } 80 81 // Read the flux.yaml file to get the images. 82 if images, err = readFluxImages(localPath); err != nil { 83 return manifest, images, fmt.Errorf("unable to read flux images: %w", err) 84 } 85 86 return manifest, images, nil 87 } 88 89 // readFluxImages finds the images Flux needs to deploy 90 func readFluxImages(localPath string) (images []string, err error) { 91 contents, err := os.ReadFile(localPath) 92 if err != nil { 93 return images, fmt.Errorf("unable to read flux manifest: %w", err) 94 } 95 96 // Break the manifest into separate resources. 97 yamls, _ := utils.SplitYAML(contents) 98 99 // Loop through each resource and find the images. 100 for _, yaml := range yamls { 101 // Flux controllers are Deployments. 102 if yaml.GetKind() == "Deployment" { 103 deployment := v1.Deployment{} 104 content := yaml.UnstructuredContent() 105 106 // Convert the unstructured content into a Deployment. 107 if err := runtime.DefaultUnstructuredConverter.FromUnstructured(content, &deployment); err != nil { 108 return nil, fmt.Errorf("could not parse deployment: %w", err) 109 } 110 111 // Get the pod spec. 112 pod := deployment.Spec.Template.Spec 113 114 // Flux controllers do not have init containers today, but this is future proofing. 115 for _, container := range pod.InitContainers { 116 images = append(images, container.Image) 117 } 118 119 // Add the main containers. 120 for _, container := range pod.Containers { 121 images = append(images, container.Image) 122 } 123 124 } 125 } 126 127 return images, nil 128 } 129 130 // composeValues composes values from a Flux HelmRelease and Secrets Map 131 // (loosely based on upstream https://github.com/fluxcd/helm-controller/blob/main/controllers/helmrelease_controller.go#L551) 132 func composeValues(hr HelmReleaseDependency, secrets map[string]corev1.Secret, configMaps map[string]corev1.ConfigMap) (valuesMap chartutil.Values, err error) { 133 valuesMap = chartutil.Values{} 134 135 for _, v := range hr.ValuesFrom { 136 var valuesData string 137 namespacedName := getNamespacedNameFromStr(hr.Metadata.Namespace, v.Name) 138 139 switch v.Kind { 140 case "ConfigMap": 141 cm, ok := configMaps[namespacedName] 142 if !ok { 143 return nil, fmt.Errorf("could not find values %s '%s'", v.Kind, namespacedName) 144 } 145 146 valuesData, ok = cm.Data[v.GetValuesKey()] 147 if !ok { 148 return nil, fmt.Errorf("missing key '%s' in %s '%s'", v.GetValuesKey(), v.Kind, namespacedName) 149 } 150 case "Secret": 151 sec, ok := secrets[namespacedName] 152 if !ok { 153 return nil, fmt.Errorf("could not find values %s '%s'", v.Kind, namespacedName) 154 } 155 156 valuesData, ok = sec.StringData[v.GetValuesKey()] 157 if !ok { 158 return nil, fmt.Errorf("missing key '%s' in %s '%s'", v.GetValuesKey(), v.Kind, namespacedName) 159 } 160 default: 161 return nil, fmt.Errorf("unsupported ValuesReference kind '%s'", v.Kind) 162 } 163 164 values, err := chartutil.ReadValues([]byte(valuesData)) 165 if err != nil { 166 return nil, fmt.Errorf("unable to read values from key '%s' in %s '%s': %w", v.GetValuesKey(), v.Kind, hr.Name(), err) 167 } 168 169 valuesMap = helpers.MergeMapRecursive(valuesMap, values) 170 } 171 172 return valuesMap, nil 173 } 174 175 func getNamespacedNameFromMeta(o metav1.ObjectMeta) string { 176 return getNamespacedNameFromStr(o.Namespace, o.Name) 177 } 178 179 func getNamespacedNameFromStr(namespace, name string) string { 180 return fmt.Sprintf("%s.%s", namespace, name) 181 }