Add prepare_manifests endpoint 24/1124/10
authorYolanda Robla <yroblamo@redhat.com>
Thu, 4 Jul 2019 16:03:12 +0000 (18:03 +0200)
committerYolanda Robla <yroblamo@redhat.com>
Wed, 10 Jul 2019 14:43:05 +0000 (16:43 +0200)
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 <yroblamo@redhat.com>
cmd/prepare_manifests.go [new file with mode: 0644]
pkg/manifests/manifests.go [new file with mode: 0644]
pkg/requirements/requirements.go
pkg/site/site.go
pkg/utils/utils.go [new file with mode: 0644]

diff --git a/cmd/prepare_manifests.go b/cmd/prepare_manifests.go
new file mode 100644 (file)
index 0000000..edee639
--- /dev/null
@@ -0,0 +1,59 @@
+// Copyright © 2019 Red Hat <yroblamo@redhat.com>
+//
+// 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=<local_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 (file)
index 0000000..43ab69c
--- /dev/null
@@ -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))
+       }
+
+}
index 1fe40e8..80947a1 100644 (file)
@@ -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
                }
index 0da768a..66ac819 100644 (file)
@@ -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 (file)
index 0000000..d1d19fc
--- /dev/null
@@ -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
+}