From 8fc6beaf72cd25b14726ffbc610997c7135c18d6 Mon Sep 17 00:00:00 2001 From: Yolanda Robla Date: Thu, 4 Jul 2019 18:03:12 +0200 Subject: [PATCH] Add prepare_manifests endpoint Added new command to prepare the manifests for the site. It combines the original manifests declaration with the output of kustomize for the site, to generate a complete set of manifests, ready to be applied. Change-Id: I911bee254ee05322f2e683ce24391acdbfd81c72 Signed-off-by: Yolanda Robla --- cmd/prepare_manifests.go | 59 ++++++++++ pkg/manifests/manifests.go | 223 ++++++++++++++++++++++++++++++++++++ pkg/requirements/requirements.go | 6 +- pkg/site/site.go | 239 +++++++++++++++++++++++++++++++++------ pkg/utils/utils.go | 61 ++++++++++ 5 files changed, 549 insertions(+), 39 deletions(-) create mode 100644 cmd/prepare_manifests.go create mode 100644 pkg/manifests/manifests.go create mode 100644 pkg/utils/utils.go diff --git a/cmd/prepare_manifests.go b/cmd/prepare_manifests.go new file mode 100644 index 0000000..edee639 --- /dev/null +++ b/cmd/prepare_manifests.go @@ -0,0 +1,59 @@ +// Copyright © 2019 Red Hat +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "fmt" + "log" + "os" + + "gerrit.akraino.org/kni/installer/pkg/site" + "github.com/spf13/cobra" +) + +// prepareManifestsCmd represents the prepare_manifests command +var prepareManifestsCmd = &cobra.Command{ + Use: "prepare_manifests siteName [--build_path=]", + Short: "Command to prepare the manifests needed for a site", + Long: ``, + TraverseChildren: true, + Run: func(cmd *cobra.Command, args []string) { + // retrieve config values and start fetching + var siteName string + if len(args) == 0 { + log.Fatal("Please specify site name as first argument") + os.Exit(1) + } else { + siteName = args[0] + } + + buildPath, _ := cmd.Flags().GetString("build_path") + if len(buildPath) == 0 { + // will generate a temporary directory + buildPath = fmt.Sprintf("%s/.kni", os.Getenv("HOME")) + } + + // define a site object and proceed with requirements fetch + s := site.NewWithName(siteName, buildPath) + s.PrepareManifests() + }, +} + +func init() { + rootCmd.AddCommand(prepareManifestsCmd) + + prepareManifestsCmd.Flags().StringP("build_path", "", "", "Directory to use as build path. If that doesn't exist, the installer will generate a default directory") + +} diff --git a/pkg/manifests/manifests.go b/pkg/manifests/manifests.go new file mode 100644 index 0000000..43ab69c --- /dev/null +++ b/pkg/manifests/manifests.go @@ -0,0 +1,223 @@ +package manifests + +import ( + "fmt" + "io/ioutil" + "log" + "os" + "path/filepath" + "reflect" + "strings" + + "gopkg.in/yaml.v2" +) + +// Generates an unique identifier based on the GKV (from K8s) + Name +func GetGKVN(manifestObj map[interface{}]interface{}) string { + + // retrieves version, and defaults to ~G/~V if not + version, ok := manifestObj["apiVersion"] + if !ok { + version = "~G/~V" + } + + kind, ok := manifestObj["kind"] + if !ok { + kind = "~K" + } + + var metadata map[interface{}]interface{} + var name string + + metadata, ok = manifestObj["metadata"].(map[interface{}]interface{}) + if ok { + name, ok = metadata["name"].(string) + if !ok { + name = "~N" + } + } else { + name = "~N" + } + + if !strings.Contains(version.(string), "/") { + version = fmt.Sprintf("~G/%s", version) + } + + // prepare the final key + GVKN := fmt.Sprintf("%s/%s|%s", version, kind, name) + return GVKN +} + +// we have a list of items, we need to split and get their individual gvkn +func GetNestedManifestsWithGVKN(manifestObj map[interface{}]interface{}) map[string]map[interface{}]interface{} { + var items []interface{} + var parsedItem map[interface{}]interface{} + var GVKN string + GVKNS := make(map[string]map[interface{}]interface{}) + + items = manifestObj["items"].([]interface{}) + for _, item := range items { + parsedItem = item.(map[interface{}]interface{}) + GVKN = GetGKVN(parsedItem) + if len(GVKN) > 0 { + GVKNS[GVKN] = parsedItem + } + } + + return GVKNS +} + +// given a gvkn, gets a name from it +func NameFromGVKN(GVKN string) string { + items := strings.Split(GVKN, "|") + subItems := strings.Split(items[0], "/") + name := fmt.Sprintf("%s-%s", subItems[2], items[1]) + if subItems[0] != "~G" { + name = fmt.Sprintf("%s-%s", subItems[0], name) + } + return strings.ToLower(name) +} + +// utility to merge manifests +func MergeManifests(content string, siteBuildPath string) { + manifests := strings.Split(content, "\n---\n") + kustomizeManifests := make(map[string]map[interface{}]interface{}) + + // first split all manifests and unmarshall into objects + for _, manifest := range manifests { + var manifestObj map[interface{}]interface{} + + err := yaml.Unmarshal([]byte(manifest), &manifestObj) + if err != nil { + log.Println(fmt.Sprintf("Error parsing manifest: %s", err)) + os.Exit(1) + } + // add to the list of manifests with the generated key + GVKN := GetGKVN(manifestObj) + if GVKN == "~G/v1/List|~N" { + nestedManifests := GetNestedManifestsWithGVKN(manifestObj) + for k, v := range nestedManifests { + kustomizeManifests[k] = v + } + } else { + kustomizeManifests[GVKN] = manifestObj + } + } + + // now read all the manifests that have been generated by installer + processedManifests := make(map[string]string) + filepath.Walk(fmt.Sprintf("%s/blueprint/base/00_cluster", siteBuildPath), func(path string, info os.FileInfo, err error) error { + if err == nil { + // check if it is a file ending with yml/yaml and it is inside openshift or manifests directory + if !info.IsDir() && (strings.Contains(path, "/openshift/") || strings.Contains(path, "/manifests/")) && (strings.HasSuffix(path, ".yaml") || strings.HasSuffix(path, ".yml")) { + // read file content and unmarshal it + manifestContent, err := ioutil.ReadFile(path) + if err != nil { + log.Println(fmt.Sprintf("Error reading manifest content: %s", err)) + os.Exit(1) + } + var manifestContentObj map[interface{}]interface{} + err = yaml.Unmarshal(manifestContent, &manifestContentObj) + if err != nil { + log.Println(fmt.Sprintf("Error parsing manifest: %s", err)) + os.Exit(1) + } + + GVKN := GetGKVN(manifestContentObj) + var walkedManifests map[string]map[interface{}]interface{} + if GVKN == "~G/v1/List|~N" { + walkedManifests = GetNestedManifestsWithGVKN(manifestContentObj) + for k, _ := range walkedManifests { + processedManifests[k] = "" + } + } else { + walkedManifests = make(map[string]map[interface{}]interface{}) + walkedManifests[GVKN] = manifestContentObj + processedManifests[GVKN] = "" + } + + // now compare each content with the ones from kustomize + counter := 0 + for k, v := range walkedManifests { + kustomizedContentObj, ok := kustomizeManifests[k] + if ok { + if !reflect.DeepEqual(kustomizedContentObj, v) { + // do a backup of the original file + if _, err := os.Stat(path); err == nil { + err = os.Rename(path, fmt.Sprintf("%s.orig", path)) + } + + kustomizedString, err := yaml.Marshal(kustomizedContentObj) + if err != nil { + log.Fatal(fmt.Sprintf("Error marshaling kustomized content: %s", err)) + os.Exit(1) + } + + if len(walkedManifests) == 1 { + // just rewrite with the original name + err = ioutil.WriteFile(path, kustomizedString, 0644) + if err != nil { + log.Fatal(fmt.Sprintf("Error writing new manifest content: %s", err)) + os.Exit(1) + } + } else { + // rewrite with a prefix + newPath := fmt.Sprintf("%02d_%s", counter, path) + err = ioutil.WriteFile(newPath, kustomizedString, 0644) + if err != nil { + log.Fatal(fmt.Sprintf("Error writing new manifest content: %s", err)) + os.Exit(1) + } + counter = counter + 1 + } + } + } + } + + } + } else { + log.Println(fmt.Sprintf("Error walking on manifests directory: %s", err)) + os.Exit(1) + } + return nil + }) + + // now find manifests not yet in assets dir and write them out + counter := 0 + for k, v := range kustomizeManifests { + _, ok := processedManifests[k] + if !ok { + // the manifest is not there, add it + manifestName := fmt.Sprintf("99_%04d_%s.yaml", counter, NameFromGVKN(k)) + log.Println(fmt.Sprintf("Blueprint added manifests %s, writing to %s", k, manifestName)) + + newPath := fmt.Sprintf("%s/blueprint/base/00_cluster/manifests/%s", siteBuildPath, manifestName) + + // marshal the file to write + kustomizedString, err := yaml.Marshal(v) + if err != nil { + log.Fatal(fmt.Sprintf("Error marshing manifest: %s", err)) + os.Exit(1) + } + err = ioutil.WriteFile(newPath, kustomizedString, 0644) + if err != nil { + log.Fatal(fmt.Sprintf("Error writing manifest: %s", err)) + os.Exit(1) + } + counter = counter + 1 + + } + } + + // finally, move content to final manifests + os.RemoveAll(fmt.Sprintf("%s/final_manifests", siteBuildPath)) + err := os.Rename(fmt.Sprintf("%s/blueprint/base/00_cluster", siteBuildPath), fmt.Sprintf("%s/final_manifests", siteBuildPath)) + if err != nil { + log.Fatal(fmt.Sprintf("Error moving to final manifests folder: %s", err)) + os.Exit(1) + } else { + log.Println(fmt.Sprintf("*** Manifest generation finished. You can run now: %s/requirements/openshift-install create cluster --dir=%s/final_manifests to create the site cluster ***", siteBuildPath, siteBuildPath)) + log.Println(fmt.Sprintf("In order to destroy the cluster you can run: %s/requirements/openshift-install destroy cluster --dir %s/final_manifests", siteBuildPath, siteBuildPath)) + } + +} diff --git a/pkg/requirements/requirements.go b/pkg/requirements/requirements.go index 1fe40e8..80947a1 100644 --- a/pkg/requirements/requirements.go +++ b/pkg/requirements/requirements.go @@ -45,9 +45,9 @@ func (r Requirement) FetchRequirementFolder() { err = filepath.Walk(extractDir, func(path string, info os.FileInfo, err error) error { if (info.Name() == r.binaryName || info.Name() == alternativeBinaryName) && !info.IsDir() { // we found the binary, move it. Give exec perms as well - finalName := fmt.Sprintf("%s/%s", r.buildPath, r.binaryName) - os.Rename(path, finalName) - os.Chmod(finalName, 0755) + finalBinary := fmt.Sprintf("%s/%s", r.buildPath, r.binaryName) + os.Rename(path, finalBinary) + os.Chmod(finalBinary, 0755) os.RemoveAll(extractDir) return nil } diff --git a/pkg/site/site.go b/pkg/site/site.go index 0da768a..66ac819 100644 --- a/pkg/site/site.go +++ b/pkg/site/site.go @@ -6,10 +6,14 @@ import ( "io/ioutil" "log" "os" + "os/exec" "path" + "path/filepath" "strings" + "gerrit.akraino.org/kni/installer/pkg/manifests" "gerrit.akraino.org/kni/installer/pkg/requirements" + "gerrit.akraino.org/kni/installer/pkg/utils" getter "github.com/hashicorp/go-getter" "gopkg.in/yaml.v2" ) @@ -36,26 +40,36 @@ func New(siteRepo string, buildPath string) Site { return s } +// new constructor but just passing the name and path +func NewWithName(siteName string, buildPath string) Site { + s := Site{"", siteName, buildPath} + return s +} + // given a site repo, downloads the content and places into buildPath func (s Site) DownloadSite() { - // Clone the site repository - log.Println(fmt.Sprintf("Cloning the site repository from %s", s.siteRepo)) - siteBuildPath := fmt.Sprintf("%s/%s/site", s.buildPath, s.siteName) - client := &getter.Client{Src: s.siteRepo, Dst: siteBuildPath, Mode: getter.ClientModeAny} - err := client.Get() - if err != nil { - log.Fatal(fmt.Sprintf("Error cloning site repository: %s", err)) + if s.siteRepo != "" { + // Clone the site repository + log.Println(fmt.Sprintf("Cloning the site repository from %s", s.siteRepo)) + siteLayerPath := fmt.Sprintf("%s/%s/site", s.buildPath, s.siteName) + os.RemoveAll(siteLayerPath) + client := &getter.Client{Src: s.siteRepo, Dst: siteLayerPath, Mode: getter.ClientModeAny} + err := client.Get() + if err != nil { + log.Fatal(fmt.Sprintf("Error cloning site repository: %s", err)) + } + } else { + log.Fatal(fmt.Sprintf("Site repository does not exist for the site %s", s.siteName)) + os.Exit(1) } } -// using the downloaded site content, fetches (and builds) the specified requirements -func (s Site) FetchRequirements() { - log.Println(fmt.Sprintf("Downloading requirements for %s", s.siteName)) - siteBuildPath := fmt.Sprintf("%s/%s", s.buildPath, s.siteName) +// retrieves the given profile used in a site +func (s Site) GetProfileFromSite() (string, string) { + sitePath := fmt.Sprintf("%s/%s", s.buildPath, s.siteName) - // searches for file containing the profile of the blueprint - profileFile := fmt.Sprintf("%s/site/00_install-config/kustomization.yaml", siteBuildPath) + profileFile := fmt.Sprintf("%s/site/00_install-config/kustomization.yaml", sitePath) if _, err := os.Stat(profileFile); err == nil { // parse yaml and extract base @@ -77,40 +91,193 @@ func (s Site) FetchRequirements() { // given the profile repo, we need to get the full path without file, and clone it profileBits := strings.Split(profileRepo, "/") profileName := profileBits[len(profileBits)-2] - profilePath := strings.TrimSuffix(profileRepo, profileBits[len(profileBits)-1]) + profileLayerPath := strings.TrimSuffix(profileRepo, profileBits[len(profileBits)-1]) - profileBuildPath := fmt.Sprintf("%s/%s", siteBuildPath, profileName) - log.Println(fmt.Sprintf("Downloading profile repo from %s into %s", profilePath, profileBuildPath)) - client := &getter.Client{Src: profilePath, Dst: profileBuildPath, Mode: getter.ClientModeAny} - err = client.Get() - if err != nil { - log.Fatal(fmt.Sprintf("Error cloning profile repository: %s", err)) + return profileName, profileLayerPath + } else if os.IsNotExist(err) { + log.Fatal(fmt.Sprintf("File %s does not exist, exiting", profileFile)) + os.Exit(1) + } + + return "", "" +} + +// using the downloaded site content, fetches (and builds) the specified requirements +func (s Site) FetchRequirements() { + log.Println(fmt.Sprintf("Downloading requirements for %s", s.siteName)) + sitePath := fmt.Sprintf("%s/%s", s.buildPath, s.siteName) + + // searches for file containing the profile of the blueprint + profileName, profileLayerPath := s.GetProfileFromSite() + + profileBuildPath := fmt.Sprintf("%s/%s", sitePath, profileName) + log.Println(fmt.Sprintf("Downloading profile repo from %s into %s", profileLayerPath, profileBuildPath)) + client := &getter.Client{Src: profileLayerPath, Dst: profileBuildPath, Mode: getter.ClientModeAny} + err := client.Get() + if err != nil { + log.Fatal(fmt.Sprintf("Error cloning profile repository: %s", err)) + } + + // read yaml from requirements and start fetching the bits + requirementsFile := fmt.Sprintf("%s/requirements.yaml", profileBuildPath) + file, err := os.Open(requirementsFile) + if err != nil { + log.Fatal("Error reading requirements file") + os.Exit(1) + } + defer file.Close() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + requirementsLine := scanner.Text() + + // requirements is composed of binary and source + requirementsBits := strings.SplitN(strings.TrimSpace(requirementsLine), ":", 2) + r := requirements.New(strings.TrimSpace(requirementsBits[0]), strings.TrimSpace(requirementsBits[1]), fmt.Sprintf("%s/requirements", sitePath)) + r.FetchRequirement() + } + + // remove profile folder + os.RemoveAll(profileBuildPath) + +} + +// using the downloaded site content, prepares the manifests for it +func (s Site) PrepareManifests() { + sitePath := fmt.Sprintf("%s/%s", s.buildPath, s.siteName) + log.Println(fmt.Sprintf("Preparing manifests for %s", s.siteName)) + + // do the initial validation of pre-requisites + utils.ValidateRequirements(s.buildPath, s.siteName) + binariesPath := fmt.Sprintf("%s/requirements", sitePath) + + // retrieve profile path and clone the repo + _, profileLayerPath := s.GetProfileFromSite() + indexGit := strings.LastIndex(profileLayerPath, "//") + var blueprintRepo string + var absoluteBlueprintRepo string + if indexGit == -1 { + blueprintRepo = profileLayerPath + absoluteBlueprintRepo = profileLayerPath + } else { + blueprintRepo = profileLayerPath[0:indexGit] + absoluteBlueprintRepo = profileLayerPath[0:(indexGit + 2)] + } + + log.Println(fmt.Sprintf("Downloading blueprint repo from %s", blueprintRepo)) + blueprintDir := fmt.Sprintf("%s/blueprint", sitePath) + os.RemoveAll(blueprintDir) + client := &getter.Client{Src: blueprintRepo, Dst: blueprintDir, Mode: getter.ClientModeAny} + err := client.Get() + if err != nil { + log.Fatal(fmt.Sprintf("Error cloning profile repository: %s", err)) + } + + // and now copy site inside the sites folder, replacing the absolute references to relative + cmd := exec.Command("cp", "-R", fmt.Sprintf("%s/site", sitePath), fmt.Sprintf("%s/blueprint/sites/site", sitePath)) + err = cmd.Run() + if err != nil { + log.Fatal(fmt.Sprintf("Error copying site into blueprint: %s", err)) + os.Exit(1) + } + + err = filepath.Walk(fmt.Sprintf("%s/blueprint/sites/site", sitePath), func(path string, info os.FileInfo, err error) error { + if err == nil { + if info.Name() == "kustomization.yaml" { + readKustomization, err := ioutil.ReadFile(path) + if err != nil { + log.Fatal(fmt.Sprintf("Error opening kustomization file: %s", err)) + os.Exit(1) + } + newKustomization := strings.Replace(string(readKustomization), absoluteBlueprintRepo, "../../../", -1) + + err = ioutil.WriteFile(path, []byte(newKustomization), 0) + if err != nil { + log.Fatal(fmt.Sprintf("Error writing modified kustomization file: %s", err)) + os.Exit(1) + } + return nil + } + } else { + log.Println(fmt.Sprintf("Error walking on site directory: %s", err)) + os.Exit(1) } - // read yaml from requirements and start fetching the bits - requirementsFile := fmt.Sprintf("%s/requirements.yaml", profileBuildPath) - file, err := os.Open(requirementsFile) + return nil + }) + + // generate openshift-install manifests based on phase 00_install-config + assetsPath := fmt.Sprintf("%s/generated_assets", sitePath) + os.RemoveAll(assetsPath) + os.Mkdir(assetsPath, 0755) + + out := utils.ApplyKustomize(fmt.Sprintf("%s/kustomize", binariesPath), fmt.Sprintf("%s/blueprint/sites/site/00_install-config", sitePath)) + // check if we have any content and write to the target file + if len(out) > 0 { + err := ioutil.WriteFile(fmt.Sprintf("%s/install-config.yaml", assetsPath), out, 0644) if err != nil { - log.Fatal("Error reading requirements file") + log.Fatal(fmt.Sprintf("Error writing final install-config file: %s", err)) os.Exit(1) } - defer file.Close() - scanner := bufio.NewScanner(file) - for scanner.Scan() { - requirementsLine := scanner.Text() + } else { + log.Fatal("Error, kustomize did not return any content") + os.Exit(1) + } - // requirements is composed of binary and source - requirementsBits := strings.SplitN(strings.TrimSpace(requirementsLine), ":", 2) - r := requirements.New(strings.TrimSpace(requirementsBits[0]), strings.TrimSpace(requirementsBits[1]), fmt.Sprintf("%s/requirements", siteBuildPath)) - r.FetchRequirement() + // now generate the manifests + cmd = exec.Command(fmt.Sprintf("%s/openshift-install", binariesPath), "create", "manifests", fmt.Sprintf("--dir=%s", assetsPath), "--log-level", "debug") + err = cmd.Run() + if err != nil { + log.Fatal(fmt.Sprintf("Error creating manifests: %s", err)) + os.Exit(1) + } + + // iterate over all the generated files and create a kustomization file + f, err := os.Create(fmt.Sprintf("%s/kustomization.yaml", assetsPath)) + if err != nil { + log.Fatal(fmt.Sprintf("Error creating kustomization file: %s", err)) + os.Exit(1) + } + defer f.Close() + + _, err = f.WriteString("resources:\n") + if err != nil { + log.Fatal(fmt.Sprintf("Error writing kustomization file: %s", err)) + os.Exit(1) + } + + filePatterns := []string{fmt.Sprintf("%s/manifests/*.yaml", assetsPath), fmt.Sprintf("%s/manifests/*.yml", assetsPath), fmt.Sprintf("%s/openshift/*.yaml", assetsPath)} + for _, filePattern := range filePatterns { + files, err := filepath.Glob(filePattern) + if err != nil { + log.Fatal(fmt.Sprintf("Error reading manifest files: %s", err)) + os.Exit(1) } - // remove profile folder - os.RemoveAll(profileBuildPath) + // iterate over each file, remove the absolute path and write it + for _, fileName := range files { + strippedName := strings.TrimPrefix(fileName, fmt.Sprintf("%s/", assetsPath)) + _, err := f.WriteString(fmt.Sprintf("- %s\n", strippedName)) + if err != nil { + log.Fatal(fmt.Sprintf("Error writing kustomization file: %s", err)) + os.Exit(1) + } + } + } - } else if os.IsNotExist(err) { - log.Fatal(fmt.Sprintf("File %s does not exist, exiting", profileFile)) + // move the content of the generated site to blueprint base + os.Rename(fmt.Sprintf("%s/generated_assets/", sitePath), fmt.Sprintf("%s/blueprint/base/00_cluster/", sitePath)) + + // apply kustomize on cluster-mods + out = utils.ApplyKustomize(fmt.Sprintf("%s/kustomize", binariesPath), fmt.Sprintf("%s/blueprint/sites/site/01_cluster-mods", sitePath)) + if len(out) > 0 { + // now apply modifications on the manifests + manifests.MergeManifests(string(out), sitePath) + + } else { + log.Fatal("Error, kustomize did not return any content") os.Exit(1) } + } diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go new file mode 100644 index 0000000..d1d19fc --- /dev/null +++ b/pkg/utils/utils.go @@ -0,0 +1,61 @@ +package utils + +import ( + "fmt" + "log" + "os" + "os/exec" + "path/filepath" +) + +// utility to validate pre-requisites for deploying +func ValidateRequirements(buildPath string, siteName string) { + // check for pull-secret.json + if _, err := os.Stat(fmt.Sprintf("%s/pull-secret.json", buildPath)); os.IsNotExist(err) { + log.Fatal(fmt.Sprintf("Error, no valid pull-secret.json found in %s", buildPath)) + os.Exit(1) + } + + // check for ssh key , and generate if it does not exist + if _, err := os.Stat(fmt.Sprintf("%s/id_rsa.pub", buildPath)); os.IsNotExist(err) { + log.Println(fmt.Sprintf("No SSH public key (id_rsa.pub) found in %s. Generating keypair.", buildPath)) + + cmd := exec.Command("ssh-keygen", "-b", "2048", "-f", fmt.Sprintf("%s/id_rsa", buildPath), "-C", "user@example.com", "-q", "-N", "\"\"") + err = cmd.Run() + if err != nil { + log.Fatal(fmt.Sprintf("Error generating ssh keypair: %s", err)) + os.Exit(1) + } + } + + // check if requirements folder exist + requirementsFolder := fmt.Sprintf("%s/%s/requirements", buildPath, siteName) + if _, err := os.Stat(requirementsFolder); os.IsNotExist(err) { + log.Fatal(fmt.Sprintf("Error, requirements folder not found in %s", requirementsFolder)) + os.Exit(1) + } + +} + +// utility to apply kustomize on a given directory +func ApplyKustomize(kustomizeBinary string, kustomizePath string) []byte { + // retrieve executable path to inject env var + ex, err := os.Executable() + if err != nil { + log.Fatal("Error retrieving the current running path") + os.Exit(1) + } + exPath := filepath.Dir(ex) + + cmd := exec.Command(kustomizeBinary, "build", "--enable_alpha_plugins", "--reorder", "none", kustomizePath) + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, fmt.Sprintf("XDG_CONFIG_HOME=%s/plugins", exPath)) + out, err := cmd.Output() + + if err != nil { + log.Fatal(fmt.Sprintf("Error kustomizing manifests for %s: %s", kustomizePath, err)) + os.Exit(1) + } + + return out +} -- 2.16.6