| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327 |
- package parser
- import (
- "fmt"
- "strings"
- "github.com/porter-dev/porter/api/types"
- "github.com/porter-dev/porter/internal/helm"
- "github.com/porter-dev/porter/internal/models"
- "github.com/porter-dev/porter/internal/templater"
- "github.com/porter-dev/porter/internal/templater/infra"
- "github.com/porter-dev/porter/internal/templater/utils"
- "github.com/stefanmcshane/helm/pkg/chart"
- "github.com/stefanmcshane/helm/pkg/release"
- "k8s.io/client-go/dynamic"
- "sigs.k8s.io/yaml"
- td "github.com/porter-dev/porter/internal/templater/dynamic"
- tm "github.com/porter-dev/porter/internal/templater/helm/manifests"
- tv "github.com/porter-dev/porter/internal/templater/helm/values"
- )
- // ClientConfigDefault is a set of default clients to be used if a context in
- // form.yaml does not declare otherwise.
- type ClientConfigDefault struct {
- DynamicClient dynamic.Interface
- HelmAgent *helm.Agent
- HelmRelease *release.Release
- HelmChart *chart.Chart
- InfraOperation *models.Operation
- }
- // ContextConfig can read/write from a specified context (data source)
- type ContextConfig struct {
- FromType string // "live" or "declared"
- Capabilities []string // "read", "write"
- TemplateReader templater.TemplateReader
- TemplateWriter templater.TemplateWriter
- }
- // GetFormFromRelease returns the form by parsing a release's files. Returns nil if
- // the form is not found, throws an error if the form was found but there was a parsing
- // error.
- func GetFormFromRelease(def *ClientConfigDefault, rel *release.Release) (*types.FormYAML, error) {
- for _, file := range rel.Chart.Files {
- if strings.Contains(file.Name, "form.yaml") {
- return FormYAMLFromBytes(def, file.Data, "", "")
- }
- }
- return nil, nil
- }
- // FormYAMLFromBytes generates a usable form yaml from raw form config and a
- // set of default clients.
- //
- // stateType refers to the types of state that should be read. The two state types
- // are "live" and "declared" -- if stateType is "", this will read both live and
- // declared states.
- func FormYAMLFromBytes(def *ClientConfigDefault, bytes []byte, stateType, contextType string) (*types.FormYAML, error) {
- form, err := unqueriedFormYAMLFromBytes(bytes, contextType)
- if err != nil {
- return nil, err
- }
- lookup := formToLookupTable(def, form, stateType)
- // merge data from lookup
- data := make(map[string]interface{})
- for _, lookupVal := range lookup {
- if lookupVal != nil {
- queryRes, err := lookupVal.TemplateReader.Read()
- if err != nil {
- continue
- }
- for queryResKey, queryResVal := range queryRes {
- data[queryResKey] = queryResVal
- }
- }
- }
- for i, tab := range form.Tabs {
- for j, section := range tab.Sections {
- for k, content := range section.Contents {
- key := fmt.Sprintf("tabs[%d].sections[%d].contents[%d]", i, j, k)
- if val, ok := data[key]; ok {
- content.Value = val
- }
- if content.Value == nil && content.Settings.Default != nil {
- content.Value = []interface{}{content.Settings.Default}
- }
- }
- }
- }
- // if the client config defaults are Helm-related, this is a cluster-scoped resource
- if def.HelmAgent != nil || def.HelmChart != nil || def.HelmRelease != nil {
- form.IsClusterScoped = true
- }
- return form, nil
- }
- func FormStreamer(
- def *ClientConfigDefault,
- bytes []byte,
- stateType string,
- targetContext *types.FormContext,
- on templater.OnDataStream,
- stopCh <-chan struct{},
- ) error {
- form, err := unqueriedFormYAMLFromBytes(bytes, targetContext.Type)
- if err != nil {
- return err
- }
- lookup := formToLookupTable(def, form, stateType)
- for lookupContext, lookupVal := range lookup {
- if lookupVal != nil && areContextsEqual(targetContext, lookupContext) {
- err := lookupVal.TemplateReader.ReadStream(on, stopCh)
- if err != nil {
- continue
- }
- }
- }
- return nil
- }
- func areContextsEqual(context1, context2 *types.FormContext) bool {
- if context1.Type != context2.Type {
- return false
- }
- subset12 := isSubset(context1.Config, context2.Config)
- subset21 := isSubset(context2.Config, context1.Config)
- return subset12 && subset21
- }
- func isSubset(map1, map2 map[string]string) bool {
- subset12 := true
- for key1, val1 := range map1 {
- if val2, exists := map2[key1]; !exists || val2 != val1 {
- subset12 = false
- break
- }
- }
- return subset12
- }
- // unqueriedFormYAMLFromBytes returns a FormYAML without values queries populated
- func unqueriedFormYAMLFromBytes(bytes []byte, contextType string) (*types.FormYAML, error) {
- // parse bytes into object
- form := &types.FormYAML{}
- err := yaml.Unmarshal(bytes, form)
- if err != nil {
- return nil, err
- }
- // populate all context fields, with default set to helm/values with no config
- parent := &types.FormContext{}
- if contextType == "" {
- parent.Type = "helm/values"
- } else {
- parent.Type = contextType
- }
- for _, tab := range form.Tabs {
- if tab.Context == nil {
- tab.Context = parent
- }
- for _, section := range tab.Sections {
- if section.Context == nil {
- section.Context = tab.Context
- }
- for _, content := range section.Contents {
- if content.Context == nil {
- content.Context = section.Context
- }
- }
- }
- }
- return form, nil
- }
- // create map[*FormContext]*ContextConfig
- // assumes all contexts populated
- func formToLookupTable(def *ClientConfigDefault, form *types.FormYAML, stateType string) map[*types.FormContext]*ContextConfig {
- lookup := make(map[*types.FormContext]*ContextConfig)
- for i, tab := range form.Tabs {
- for j, section := range tab.Sections {
- for k, content := range section.Contents {
- if content.Context == nil {
- continue
- }
- if _, ok := lookup[content.Context]; !ok {
- lookup[content.Context] = formContextToContextConfig(def, content.Context, stateType)
- }
- if lookup[content.Context] == nil {
- continue
- }
- if content.Value != nil && fmt.Sprintf("%v", content.Value) != "" {
- // TODO -- case on whether value is proper query string, if not resolve it to a
- // proper query string
- query, err := utils.NewQuery(
- fmt.Sprintf("tabs[%d].sections[%d].contents[%d]", i, j, k),
- fmt.Sprintf("%v", content.Value),
- content.Settings.Default,
- )
- if err != nil {
- continue
- }
- if stateType == "" || stateType == lookup[content.Context].FromType {
- lookup[content.Context].TemplateReader.RegisterQuery(query)
- }
- } else if content.Variable != "" {
- // if variable field set without value field set, make variable field into jsonpath
- // query
- query, err := utils.NewQuery(
- fmt.Sprintf("tabs[%d].sections[%d].contents[%d]", i, j, k),
- fmt.Sprintf(".%v", content.Variable),
- content.Settings.Default,
- )
- if err != nil {
- continue
- }
- if stateType == "" || stateType == lookup[content.Context].FromType {
- lookup[content.Context].TemplateReader.RegisterQuery(query)
- }
- }
- }
- }
- }
- return lookup
- }
- // TODO -- this needs to be able to construct new context configs based on
- // configuration for each context, but right now just uses the default config
- // for everything
- func formContextToContextConfig(def *ClientConfigDefault, context *types.FormContext, stateType string) *ContextConfig {
- res := &ContextConfig{}
- if context.Type == "helm/values" && (stateType == "" || stateType == "declared") {
- res.FromType = "declared"
- res.Capabilities = []string{"read", "write"}
- res.TemplateReader = &tv.TemplateReader{
- Release: def.HelmRelease,
- Chart: def.HelmChart,
- }
- relName := ""
- if def.HelmRelease != nil {
- relName = def.HelmRelease.Name
- }
- res.TemplateWriter = &tv.TemplateWriter{
- Agent: def.HelmAgent,
- Chart: def.HelmChart,
- ReleaseName: relName,
- }
- } else if context.Type == "helm/manifests" && (stateType == "" || stateType == "live") {
- res.FromType = "live"
- res.Capabilities = []string{"read"}
- res.TemplateReader = &tm.TemplateReader{
- Release: def.HelmRelease,
- }
- } else if context.Type == "cluster" && (stateType == "" || stateType == "live") {
- res.FromType = "live"
- res.Capabilities = []string{"read"}
- // identify object based on passed config
- obj := &td.Object{
- Group: context.Config["group"],
- Version: context.Config["version"],
- Resource: context.Config["resource"],
- Namespace: context.Config["namespace"],
- Name: context.Config["name"],
- }
- res.TemplateReader = td.NewDynamicTemplateReader(def.DynamicClient, obj)
- } else if context.Type == "infra" && def.InfraOperation != nil {
- res.FromType = "declared"
- res.TemplateReader = &infra.OperationReader{
- Operation: def.InfraOperation,
- }
- } else {
- return nil
- }
- return res
- }
|