package kustomize import ( "fmt" "os" "path/filepath" "github.com/bazelbuild/bazel-gazelle/label" "github.com/bazelbuild/bazel-gazelle/language" "github.com/bazelbuild/bazel-gazelle/resolve" "github.com/bazelbuild/bazel-gazelle/rule" "gopkg.in/yaml.v3" "sigs.k8s.io/kustomize/api/types" container "edge-infra.dev/hack/build/rules/container/gazelle/language" ) const ( visibility = "visibility" visibilityPublic = "//visibility:public" ) var kustFileNames = map[string]interface{}{ "kustomization.yaml": nil, "kustomization.yml": nil, "Kustomization": nil, } // kustomizationRuleName takes a path and returns the directory name + kustomization language name // as a formatting string for a kustomization rule name func kustomizationRuleName(dir string, ruleFile *rule.File, otherGen []*rule.Rule) string { // Determine name for our generated kustomization rule. If there is already // a rule in the package or one will be generated with the same name as the // package (eg //pkg/foo:foo), use an alternative name, "kustomization" // // We can use a constant name because there can only be one kustomization per // package, or directory. rName := filepath.Base(dir) var fileRules []*rule.Rule if ruleFile != nil { fileRules = append(fileRules, ruleFile.Rules...) } for _, r := range append(fileRules, otherGen...) { // Another non-kustomization rule is named after the package. // Check the rule kind so we don't get tripped up on an existing rule we // generated previously. // TODO(aj185259): If this does happen, the toImportSpec will likely break // since it's going to try to find the rule with the directory name // but it should be "kustomization" instead // // An optional solution would be to refer to all kustomization // rule names as "{dir}_kustomization" which I think I'm a fan of if r.Name() == rName && r.Kind() != kustomizationLangName { rName = kustomizationLangName } } return rName } // toImportSpec takes a rel relative path to the current directory i.e. args.Rel // and dep which is a relative path to a dependency i.e. ../base // These are joined together and added as the Imp member of the resolve.ImportSpec // as well as the language name constant for the Lang. func toImportSpec(rel, dep string) resolve.ImportSpec { return resolve.ImportSpec{Lang: kustomizationLangName, Imp: filepath.Join(rel, dep)} } // toRemoteSrc returns an absolute package path of the supplied src func toRemoteSrc(rel, dep string) string { targetPath := filepath.Join(rel, dep) l := label.New("", filepath.Dir(targetPath), filepath.Base(targetPath)) return l.String() } func (k *Kustomize) KnownDirectives() []string { return []string{ "kustomize-prefix", } } // GenerateRules for Kustomize implements the GenerateRules function for the Gazelle // Language interface func (k *Kustomize) GenerateRules(args language.GenerateArgs) language.GenerateResult { gr := language.GenerateResult{ Gen: []*rule.Rule{}, Imports: []any{}, } // Detect Kustomization file and read it file := "" for _, f := range args.RegularFiles { if _, found := kustFileNames[f]; found { file = f } } if file == "" { return language.GenerateResult{} } yamlBytes, err := os.ReadFile(filepath.Join(args.Dir, file)) if err != nil { fmt.Println("can't read file") fmt.Println(err) return language.GenerateResult{} } kcfg := &types.Kustomization{} if err := yaml.Unmarshal(yamlBytes, kcfg); err != nil { fmt.Println("can't unmarshal yaml") fmt.Println(err) return language.GenerateResult{} } rName := kustomizationRuleName(args.Dir, args.File, args.OtherGen) // Create kustomization rule r := rule.NewRule(kustomizationLangName, rName) makeDefaults(r) var ( srcs []string kustomizeImports []resolve.ImportSpec ) // Resources resSrcs, resImgs, resKustImps, err := GenerateResources(args, kcfg.Resources) if err != nil { return language.GenerateResult{} } srcs = append(srcs, resSrcs...) kustomizeImports = append(kustomizeImports, resKustImps...) // if images are generated, create a private attr to ensure mapping of images to this rule // Patches patchSrcs := GeneratePatches(args, kcfg.Patches) srcs = append(srcs, patchSrcs...) // PatchesStrategicMerge psmSrcs, psmKustImps, err := GeneratePatchesStrategicMerge(args, kcfg.Patches) if err != nil { return language.GenerateResult{} } srcs = append(srcs, psmSrcs...) kustomizeImports = append(kustomizeImports, psmKustImps...) // Configurations cfgSrcs := GenerateConfigurations(args, kcfg.Configurations) srcs = append(srcs, cfgSrcs...) // Components compKustImps := GenerateComponents(args, kcfg.Components) kustomizeImports = append(kustomizeImports, compKustImps...) // Bases - Deprecated but covering our bases baseSrcs, baseKustImps, err := GenerateBases(args, kcfg.Bases) //nolint: staticcheck if err != nil { return language.GenerateResult{} } srcs = append(srcs, baseSrcs...) kustomizeImports = append(kustomizeImports, baseKustImps...) // PatchsJson6902 - Deprecated but covering our bases pjSrcs := GeneratePatchesJSON6902(args, kcfg.PatchesJson6902) //nolint: staticcheck srcs = append(srcs, pjSrcs...) // ConfigmapGenerator cmgSrcs := GenerateConfigmapGenerators(args, kcfg.ConfigMapGenerator) srcs = append(srcs, cmgSrcs...) if len(srcs) > 0 { r.SetAttr(srcsAttr, srcs) } r.SetAttr("kustomization_yaml", file) // Create import for each image name found for _, img := range resImgs { kustomizeImports = append(kustomizeImports, resolve.ImportSpec{ Lang: container.ContainerLangName, Imp: img, }) } gr.Gen = append(gr.Gen, r) gr.Imports = append(gr.Imports, kustomizeImports) return gr } func makeDefaults(r *rule.Rule) { r.SetAttr(visibility, []string{visibilityPublic}) }