github.com/helmwave/helmwave@v0.36.4-0.20240509190856-b35563eba4c6/pkg/plan/new.go (about)

     1  package plan
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"os"
     8  	"path/filepath"
     9  
    10  	"github.com/helmwave/helmwave/pkg/helper"
    11  	"github.com/helmwave/helmwave/pkg/hooks"
    12  	"github.com/helmwave/helmwave/pkg/monitor"
    13  	"github.com/helmwave/helmwave/pkg/registry"
    14  	"github.com/helmwave/helmwave/pkg/release"
    15  	"github.com/helmwave/helmwave/pkg/release/uniqname"
    16  	"github.com/helmwave/helmwave/pkg/repo"
    17  	"github.com/helmwave/helmwave/pkg/version"
    18  	"github.com/invopop/jsonschema"
    19  	log "github.com/sirupsen/logrus"
    20  	"gopkg.in/yaml.v3"
    21  )
    22  
    23  const (
    24  	// Dir is the default directory for generated files.
    25  	Dir = ".helmwave/"
    26  
    27  	// File is the default file name for planfile.
    28  	File = "planfile"
    29  
    30  	// Body is a default file name for the main config.
    31  	Body = "helmwave.yml"
    32  
    33  	// Manifest is the default directory under Dir for manifests.
    34  	Manifest = "manifest/"
    35  
    36  	// Values is default directory for values.
    37  	Values = "values/"
    38  )
    39  
    40  // Plan contains full helmwave state.
    41  type Plan struct {
    42  	body      *planBody
    43  	dir       string
    44  	fullPath  string
    45  	tmpDir    string
    46  	graphMD   string
    47  	templater string
    48  
    49  	manifests map[uniqname.UniqName]string
    50  	unchanged release.Configs
    51  }
    52  
    53  // NewAndImport wrapper for New and Import in one.
    54  func NewAndImport(ctx context.Context, src string) (p *Plan, err error) {
    55  	p = New(src)
    56  
    57  	err = p.Import(ctx)
    58  	if err != nil {
    59  		return p, err
    60  	}
    61  
    62  	return p, nil
    63  }
    64  
    65  // Logger will pretty build log.Entry.
    66  func (p *Plan) Logger() *log.Entry {
    67  	a := helper.SlicesMap(p.body.Releases, func(r release.Config) string {
    68  		return r.Uniq().String()
    69  	})
    70  
    71  	b := helper.SlicesMap(p.body.Repositories, func(r repo.Config) string {
    72  		return r.Name()
    73  	})
    74  
    75  	c := helper.SlicesMap(p.body.Registries, func(r registry.Config) string {
    76  		return r.Host()
    77  	})
    78  
    79  	return log.WithFields(log.Fields{
    80  		"releases":     a,
    81  		"repositories": b,
    82  		"registries":   c,
    83  	})
    84  }
    85  
    86  //nolint:lll
    87  type planBody struct {
    88  	Project      string           `yaml:"project" json:"project" jsonschema:"title=project name,description=reserved for future,example=my-awesome-project"`
    89  	Version      string           `yaml:"version" json:"version" jsonschema:"title=version of helmwave,description=will check current version and project version,pattern=^[0-9]+\\.[0-9]+\\.[0-9]+$,example=0.23.0,example=0.22.1"`
    90  	Monitors     monitor.Configs  `yaml:"monitors" json:"monitors" jsonschema:"title=monitors list"`
    91  	Repositories repo.Configs     `yaml:"repositories" json:"repositories" jsonschema:"title=repositories list,description=helm repositories"`
    92  	Registries   registry.Configs `yaml:"registries" json:"registries" jsonschema:"title=registries list,description=helm OCI registries"`
    93  	Releases     release.Configs  `yaml:"releases" json:"releases" jsonschema:"title=helm releases,description=what you wanna deploy"`
    94  	Lifecycle    hooks.Lifecycle  `yaml:"lifecycle" json:"lifecycle" jsonschema:"title=lifecycle,description=helmwave lifecycle hooks"`
    95  }
    96  
    97  func GenSchema() *jsonschema.Schema {
    98  	r := &jsonschema.Reflector{
    99  		DoNotReference:             true,
   100  		RequiredFromJSONSchemaTags: true,
   101  	}
   102  
   103  	schema := r.Reflect(&planBody{})
   104  	schema.AdditionalProperties = jsonschema.TrueSchema // to allow anchors at the top level
   105  
   106  	return schema
   107  }
   108  
   109  // NewBody parses plan from file.
   110  func NewBody(_ context.Context, file string, validate bool) (*planBody, error) {
   111  	b := &planBody{
   112  		Version: version.Version,
   113  	}
   114  
   115  	src, err := os.ReadFile(file)
   116  	if err != nil {
   117  		return b, fmt.Errorf("failed to read plan file %s: %w", file, err)
   118  	}
   119  
   120  	decoder := yaml.NewDecoder(bytes.NewBuffer(src))
   121  	err = decoder.Decode(b)
   122  	if err != nil {
   123  		return b, fmt.Errorf("failed to unmarshal YAML plan %s: %w", file, err)
   124  	}
   125  
   126  	if validate {
   127  		err = b.Validate()
   128  		if err != nil {
   129  			return nil, err
   130  		}
   131  	}
   132  
   133  	return b, nil
   134  }
   135  
   136  // New returns empty *Plan for provided directory.
   137  func New(dir string) *Plan {
   138  	tmpDir, err := os.MkdirTemp("", "")
   139  	if err != nil {
   140  		log.WithError(err).Warn("failed to create temporary directory")
   141  		tmpDir = os.TempDir()
   142  	}
   143  
   144  	plan := &Plan{
   145  		tmpDir:    tmpDir,
   146  		dir:       dir,
   147  		fullPath:  filepath.Join(dir, File),
   148  		manifests: make(map[uniqname.UniqName]string),
   149  	}
   150  
   151  	return plan
   152  }