github.com/verrazzano/verrazzano@v1.7.0/tools/generate-profiles/generate.go (about)

     1  // Copyright (c) 2022, Oracle and/or its affiliates.
     2  // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
     3  package main
     4  
     5  import (
     6  	"encoding/json"
     7  	"flag"
     8  	"fmt"
     9  	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    10  	"log"
    11  	"os"
    12  	"path/filepath"
    13  	"sigs.k8s.io/yaml"
    14  
    15  	"github.com/verrazzano/verrazzano/pkg/profiles"
    16  	"github.com/verrazzano/verrazzano/platform-operator/apis/verrazzano/v1beta1"
    17  )
    18  
    19  var (
    20  	profileType    string
    21  	outputLocation string
    22  	verrazzanoDir  string
    23  	help           bool
    24  )
    25  
    26  const (
    27  	profileDirSuffix = "/platform-operator/manifests/profiles"
    28  	baseProfile      = "base"
    29  	VzRootDir        = "VERRAZZANO_ROOT"
    30  )
    31  const defautlVerrazzano = `apiVersion: install.verrazzano.io/v1beta1
    32  kind: Verrazzano
    33  metadata:
    34    name: verrazzano
    35    namespace: default`
    36  
    37  const info = `Utility tool to generate the standard Verrazzano profile files: prod, dev and managed cluster
    38  
    39  Options:
    40  	--output-dir	The output directory where the generated profile files will be saved. Defaults to current working directory.
    41  	--profile       The type of profile file to be generated. Defaults to prod.
    42  	--help          Get info about utility and usage.
    43  
    44  Example:
    45  	export VERRAZZANO_ROOT=<local-verrazzano-repo-path>
    46  	go run ${VERRAZZANO_ROOT}/tools/generate-profiles/generate.go --profile dev --output-dir ${HOME}
    47  `
    48  
    49  // main sets up args and calls the helper funcs
    50  func main() {
    51  	defaultDir, err := os.Getwd()
    52  	if err != nil {
    53  		log.Fatal(err)
    54  	}
    55  
    56  	parseFlags(defaultDir)
    57  	if help {
    58  		fmt.Print(info)
    59  		os.Exit(0)
    60  	}
    61  
    62  	err = run(profileType, outputLocation)
    63  	if err != nil {
    64  		log.Fatal(err)
    65  	}
    66  }
    67  
    68  // run checks that VERRAZZANO_ROOT env var is set and the output location is valid
    69  // and then generates the profile files at the desired location
    70  func run(profileType string, outputLocation string) error {
    71  	verrazzanoDir = os.Getenv(VzRootDir)
    72  	if len(verrazzanoDir) == 0 {
    73  		return fmt.Errorf("VERRAZZANO_ROOT environment variable not specified")
    74  	}
    75  
    76  	// Validate the output location
    77  	OLInfo, err := os.Stat(outputLocation)
    78  	if err != nil {
    79  		return err
    80  	}
    81  	if !OLInfo.IsDir() {
    82  		return fmt.Errorf("Invalid parameter to specify directory: %s", outputLocation)
    83  	}
    84  	err = generateAndWrite(profileType, outputLocation, verrazzanoDir)
    85  	if err != nil {
    86  		return err
    87  	}
    88  
    89  	return nil
    90  }
    91  
    92  // generateAndWrite gets the merged Verrazzano CR and writes it to a file
    93  func generateAndWrite(profileType string, outputLocation string, verrazzanoDir string) error {
    94  	cr, err := generateProfile(profileType, verrazzanoDir)
    95  	if err != nil {
    96  		return err
    97  	}
    98  	crYAML, err := yaml.Marshal(cr)
    99  	if err != nil {
   100  		return err
   101  	}
   102  	fileLoc := filepath.Join(outputLocation, profileType+".yaml")
   103  	file, err := os.Create(fileLoc)
   104  	if err != nil {
   105  		return err
   106  	}
   107  	defer file.Close()
   108  	err = os.WriteFile(fileLoc, crYAML, 0600)
   109  	if err != nil {
   110  		return err
   111  	}
   112  	return nil
   113  }
   114  
   115  // generateProfile executes the actual logic to merge the profiles
   116  func generateProfile(profileType string, verrazzanoDir string) (*CRWrapper, error) {
   117  	cr := &v1beta1.Verrazzano{}
   118  	err := yaml.Unmarshal([]byte(defautlVerrazzano), cr)
   119  	if err != nil {
   120  		return nil, err
   121  	}
   122  	cr.Spec.Profile = v1beta1.ProfileType(profileType)
   123  	var profileFiles []string
   124  	profileFiles = append(profileFiles, profileFilePath(verrazzanoDir, cr, baseProfile))
   125  	profileFiles = append(profileFiles, profileFilePath(verrazzanoDir, cr, profileType))
   126  	// The profile type validation is handled here. All the profile template files are
   127  	// present at one location inside the platform-operator dir. If the given profile
   128  	// type is not valid, then an error is returned because of the absence of a YAML file
   129  	// for the given profile.
   130  	mergedCR, err := profiles.MergeProfilesForV1beta1(cr, profileFiles...)
   131  	if err != nil {
   132  		return nil, err
   133  	}
   134  	wrappedCR := CRWrapper(*mergedCR)
   135  	return &wrappedCR, nil
   136  }
   137  
   138  func profileFilePath(verrazzanoDir string, cr *v1beta1.Verrazzano, profileType string) string {
   139  	return filepath.Join(profileFilesDir(verrazzanoDir)+"/"+cr.GroupVersionKind().GroupVersion().Version, profileType+".yaml")
   140  }
   141  
   142  func profileFilesDir(verrazzanoDir string) string {
   143  	return filepath.Join(verrazzanoDir, profileDirSuffix)
   144  }
   145  
   146  func parseFlags(defaultDir string) {
   147  	flag.StringVar(&outputLocation, "output-dir", defaultDir, "The directory path where the profile artifact will be generated, defaults to current working directory")
   148  	flag.StringVar(&profileType, "profile", string(v1beta1.Prod), "Profile type to be generated, defaults to prod")
   149  	flag.BoolVar(&help, "help", false, "Get information about the usage/utility of the tool")
   150  	flag.Parse()
   151  }
   152  
   153  // Workaround to remove cruft. Certain empty fields that aren't required in the profile file (status, metadata.creationTimestamp etc)
   154  // were showing up because Go's json package's (used by k8s yaml package) default behaviour is to Marshal fields of struct type
   155  // even if they're empty.
   156  
   157  // Alias to extend custom Marshalling
   158  type CRWrapper v1beta1.Verrazzano
   159  
   160  type PseudoObjectMeta struct {
   161  	Name      string `json:"name,omitempty"`
   162  	Namespace string `json:"namespace,omitempty"`
   163  }
   164  
   165  func (cr *CRWrapper) MarshalJSON() ([]byte, error) {
   166  	return json.Marshal(&struct {
   167  		v1.TypeMeta      `json:",inline"`
   168  		PseudoObjectMeta `json:"metadata,omitempty"`
   169  		Spec             v1beta1.VerrazzanoSpec `json:"spec,omitempty"`
   170  	}{
   171  		v1.TypeMeta{
   172  			Kind:       cr.Kind,
   173  			APIVersion: cr.APIVersion,
   174  		},
   175  		PseudoObjectMeta{
   176  			Name:      cr.Name,
   177  			Namespace: cr.Namespace,
   178  		},
   179  		cr.Spec,
   180  	})
   181  }