github.com/giantswarm/apiextensions/v6@v6.6.0/pkg/crd/renderer.go (about) 1 package crd 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/base64" 7 "fmt" 8 "io" 9 "net/http" 10 "os" 11 "path/filepath" 12 "strings" 13 14 "github.com/giantswarm/microerror" 15 "github.com/google/go-github/v39/github" 16 v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 17 "k8s.io/apimachinery/pkg/runtime/schema" 18 ) 19 20 var ( 21 // Kubernetes API group, version and kind for v1 CRDs 22 crdV1GVK = schema.GroupVersionKind{ 23 Group: "apiextensions.k8s.io", 24 Version: "v1", 25 Kind: "CustomResourceDefinition", 26 } 27 ) 28 29 func (r Renderer) patchCRDs(provider string, crds []v1.CustomResourceDefinition) ([]v1.CustomResourceDefinition, error) { 30 patchedCRDs := make([]v1.CustomResourceDefinition, 0, len(crds)) 31 for _, crd := range crds { 32 patched, err := patchCRD(provider, r.Patches, crd) 33 if err != nil { 34 return nil, microerror.Mask(err) 35 } 36 patchedCRDs = append(patchedCRDs, patched) 37 } 38 return patchedCRDs, nil 39 } 40 41 // Render creates helm chart templates for the given provider by downloading upstream CRDs, merging them with local 42 // CRDs, patching them, and writing them to the corresponding provider helm template directory. 43 func (r Renderer) Render(ctx context.Context, provider string) error { 44 outputDirectory := filepath.Join(helmChartDirectory(r.OutputDirectory, provider), "templates") 45 46 { 47 localCRDs, err := r.getLocalCRDs(provider) 48 if err != nil { 49 return microerror.Mask(err) 50 } 51 52 remoteCRDs, err := r.getRemoteCRDs(ctx, provider) 53 if err != nil { 54 return microerror.Mask(err) 55 } 56 57 internalCRDs, err := r.patchCRDs(provider, append(localCRDs, remoteCRDs...)) 58 if err != nil { 59 return microerror.Mask(err) 60 } 61 62 err = writeCRDsToDirectory(outputDirectory, internalCRDs) 63 if err != nil { 64 return microerror.Mask(err) 65 } 66 } 67 68 { 69 upstreamCRDs, err := r.getUpstreamCRDs(ctx, provider) 70 if err != nil { 71 return microerror.Mask(err) 72 } 73 74 upstreamCRDs, err = r.patchCRDs(provider, upstreamCRDs) 75 if err != nil { 76 return microerror.Mask(err) 77 } 78 79 err = writeCRDsToDirectory(outputDirectory, upstreamCRDs) 80 if err != nil { 81 return microerror.Mask(err) 82 } 83 } 84 85 return nil 86 } 87 88 // downloadReleaseAssetCRDs returns a slice of CRDs by downloading the given GitHub release asset, parsing it as YAML, 89 // and filtering for only CRD objects. 90 func (r Renderer) downloadReleaseAssetCRDs(ctx context.Context, asset ReleaseAssetFileDefinition) ([]v1.CustomResourceDefinition, error) { 91 release, _, err := r.GithubClient.Repositories.GetReleaseByTag(ctx, asset.Owner, asset.Repo, asset.Version) 92 if err != nil { 93 return nil, microerror.Mask(err) 94 } 95 96 var targetAssets []*github.ReleaseAsset 97 for _, releaseAsset := range release.Assets { 98 for _, file := range asset.Files { 99 if releaseAsset.GetName() == file { 100 targetAssets = append(targetAssets, releaseAsset) 101 } 102 } 103 } 104 if targetAssets == nil { 105 return nil, notFoundError 106 } 107 108 var allCrds []v1.CustomResourceDefinition 109 for _, targetAsset := range targetAssets { 110 contentReader, _, err := r.GithubClient.Repositories.DownloadReleaseAsset(ctx, asset.Owner, asset.Repo, targetAsset.GetID(), http.DefaultClient) 111 if err != nil { 112 return nil, microerror.Mask(err) 113 } 114 115 crds, err := decodeCRDs(contentReader) 116 if err != nil { 117 return nil, microerror.Mask(err) 118 } 119 120 allCrds = append(allCrds, crds...) 121 } 122 123 return allCrds, nil 124 } 125 126 // getUpstreamCRDs returns all upstream CRDs for a provider based on the Renderer's upstream asset configuration. 127 func (r Renderer) getUpstreamCRDs(ctx context.Context, provider string) ([]v1.CustomResourceDefinition, error) { 128 var crds []v1.CustomResourceDefinition 129 for _, releaseAsset := range r.UpstreamAssets { 130 if releaseAsset.Provider != provider { 131 continue 132 } 133 134 releaseAssetCRDs, err := r.downloadReleaseAssetCRDs(ctx, releaseAsset) 135 if err != nil { 136 return nil, microerror.Mask(err) 137 } 138 139 crds = append(crds, releaseAssetCRDs...) 140 } 141 142 return crds, nil 143 } 144 145 // getLocalCRDs reads the configured local directory and returns a slice of CRDs that have the given category. 146 func (r Renderer) getLocalCRDs(category string) ([]v1.CustomResourceDefinition, error) { 147 var crds []v1.CustomResourceDefinition 148 err := filepath.WalkDir(r.LocalCRDDirectory, func(path string, entry os.DirEntry, walkErr error) error { 149 if walkErr != nil { 150 return microerror.Mask(walkErr) 151 } 152 if entry.IsDir() { 153 return nil 154 } 155 156 file, err := os.Open(path) 157 if err != nil { 158 return microerror.Mask(err) 159 } 160 161 fileCRDs, err := decodeCRDs(file) 162 if err != nil { 163 return microerror.Mask(err) 164 } 165 166 for _, crd := range fileCRDs { 167 if contains(crd.Spec.Names.Categories, category) { 168 crds = append(crds, crd) 169 } 170 } 171 172 return nil 173 }) 174 if err != nil { 175 return nil, microerror.Mask(err) 176 } 177 178 return crds, nil 179 } 180 181 // getRemoteCRDs returns all remote CRDs for a provider based on the Renderer's remote repository configuration. 182 func (r Renderer) getRemoteCRDs(ctx context.Context, provider string) ([]v1.CustomResourceDefinition, error) { 183 var crds []v1.CustomResourceDefinition 184 for _, releaseAsset := range r.RemoteRepositories { 185 if releaseAsset.Provider != provider { 186 continue 187 } 188 189 remoteCRDs, err := r.downloadRepositoryCRDs(ctx, releaseAsset) 190 if err != nil { 191 return nil, microerror.Mask(err) 192 } 193 194 crds = append(crds, remoteCRDs...) 195 } 196 197 return crds, nil 198 } 199 200 // downloadRepositoryCRDs returns a slice of CRDs by downloading the given GitHub repository tree, listing files in the 201 // given path, parsing them as YAML, and filtering for only CRD objects. 202 func (r Renderer) downloadRepositoryCRDs(ctx context.Context, repo RemoteRepositoryDefinition) ([]v1.CustomResourceDefinition, error) { 203 refString := fmt.Sprintf("tags/%s", repo.Reference) 204 ref, response, err := r.GithubClient.Git.GetRef(ctx, repo.Owner, repo.Name, refString) 205 if err != nil && response.StatusCode == 404 { 206 refString = fmt.Sprintf("heads/%s", repo.Reference) 207 ref, _, err = r.GithubClient.Git.GetRef(ctx, repo.Owner, repo.Name, refString) 208 } 209 if err != nil { 210 return nil, microerror.Mask(err) 211 } 212 213 commit, _, err := r.GithubClient.Git.GetCommit(ctx, repo.Owner, repo.Name, ref.Object.GetSHA()) 214 if err != nil { 215 return nil, microerror.Mask(err) 216 } 217 218 tree, _, err := r.GithubClient.Git.GetTree(ctx, repo.Owner, repo.Name, commit.Tree.GetSHA(), true) 219 if err != nil { 220 return nil, microerror.Mask(err) 221 } 222 223 var targetEntries []*github.TreeEntry 224 for _, entry := range tree.Entries { 225 if entry.GetType() == "blob" && strings.HasPrefix(entry.GetPath(), repo.Path) { 226 targetEntries = append(targetEntries, entry) 227 } 228 } 229 if targetEntries == nil { 230 return nil, notFoundError 231 } 232 233 var allCrds []v1.CustomResourceDefinition 234 for _, entry := range targetEntries { 235 blob, _, err := r.GithubClient.Git.GetBlob(ctx, repo.Owner, repo.Name, entry.GetSHA()) 236 if err != nil { 237 return nil, microerror.Mask(err) 238 } 239 240 content, err := base64.StdEncoding.DecodeString(blob.GetContent()) 241 if err != nil { 242 return nil, microerror.Mask(err) 243 } 244 245 contentReader := io.NopCloser(bytes.NewReader(content)) 246 247 crds, err := decodeCRDs(contentReader) 248 if err != nil { 249 return nil, microerror.Mask(err) 250 } 251 252 allCrds = append(allCrds, crds...) 253 } 254 255 return allCrds, nil 256 }