github.com/mook-as/cf-cli@v7.0.0-beta.28.0.20200120190804-b91c115fae48+incompatible/util/v6manifestparser/parser.go (about)

     1  package v6manifestparser
     2  
     3  import (
     4  	"errors"
     5  	"io/ioutil"
     6  	"os"
     7  	"path/filepath"
     8  
     9  	"github.com/cloudfoundry/bosh-cli/director/template"
    10  	"gopkg.in/yaml.v2"
    11  )
    12  
    13  type Parser struct {
    14  	Applications []Application
    15  
    16  	pathToManifest string
    17  	rawManifest    []byte
    18  	validators     []validatorFunc
    19  	hasParsed      bool
    20  }
    21  
    22  func NewParser() *Parser {
    23  	return new(Parser)
    24  }
    25  
    26  func (parser Parser) AppNames() []string {
    27  	var names []string
    28  	for _, app := range parser.Applications {
    29  		names = append(names, app.Name)
    30  	}
    31  	return names
    32  }
    33  
    34  func (parser Parser) Apps() []Application {
    35  	return parser.Applications
    36  }
    37  
    38  func (parser Parser) ContainsManifest() bool {
    39  	return parser.hasParsed
    40  }
    41  
    42  func (parser Parser) ContainsMultipleApps() bool {
    43  	return len(parser.Applications) > 1
    44  }
    45  
    46  func (parser Parser) ContainsPrivateDockerImages() bool {
    47  	for _, app := range parser.Applications {
    48  		if app.Docker != nil && app.Docker.Username != "" {
    49  			return true
    50  		}
    51  	}
    52  	return false
    53  }
    54  
    55  func (parser Parser) FullRawManifest() []byte {
    56  	return parser.rawManifest
    57  }
    58  
    59  func (parser Parser) GetPathToManifest() string {
    60  	return parser.pathToManifest
    61  }
    62  
    63  // InterpolateAndParse reads the manifest at the provided paths, interpolates
    64  // variables if a vars file is provided, and sets the current manifest to the
    65  // resulting manifest.
    66  // For manifests with only 1 application, appName will override the name of the
    67  // single app defined.
    68  // For manifests with multiple applications, appName will filter the
    69  // applications and leave only a single application in the resulting parsed
    70  // manifest structure.
    71  func (parser *Parser) InterpolateAndParse(pathToManifest string, pathsToVarsFiles []string, vars []template.VarKV, appName string) error {
    72  	rawManifest, err := ioutil.ReadFile(pathToManifest)
    73  	if err != nil {
    74  		return err
    75  	}
    76  
    77  	tpl := template.NewTemplate(rawManifest)
    78  	fileVars := template.StaticVariables{}
    79  
    80  	for _, path := range pathsToVarsFiles {
    81  		rawVarsFile, ioerr := ioutil.ReadFile(path)
    82  		if ioerr != nil {
    83  			return ioerr
    84  		}
    85  
    86  		var sv template.StaticVariables
    87  
    88  		err = yaml.Unmarshal(rawVarsFile, &sv)
    89  		if err != nil {
    90  			return InvalidYAMLError{Err: err}
    91  		}
    92  
    93  		for k, v := range sv {
    94  			fileVars[k] = v
    95  		}
    96  	}
    97  
    98  	for _, kv := range vars {
    99  		fileVars[kv.Name] = kv.Value
   100  	}
   101  
   102  	rawManifest, err = tpl.Evaluate(fileVars, nil, template.EvaluateOpts{ExpectAllKeys: true})
   103  	if err != nil {
   104  		return InterpolationError{Err: err}
   105  	}
   106  
   107  	parser.pathToManifest = pathToManifest
   108  	return parser.parse(rawManifest, appName)
   109  }
   110  
   111  func (parser Parser) RawAppManifest(appName string) ([]byte, error) {
   112  	var appManifest manifest
   113  	for _, app := range parser.Applications {
   114  		if app.Name == appName {
   115  			appManifest.Applications = []Application{app}
   116  			return yaml.Marshal(appManifest)
   117  		}
   118  	}
   119  	return nil, AppNotInManifestError{Name: appName}
   120  }
   121  
   122  func (parser *Parser) parse(manifestBytes []byte, appName string) error {
   123  	parser.rawManifest = manifestBytes
   124  	pathToManifest := parser.GetPathToManifest()
   125  	var raw manifest
   126  
   127  	err := yaml.Unmarshal(manifestBytes, &raw)
   128  	if err != nil {
   129  		return err
   130  	}
   131  
   132  	if len(raw.Applications) == 0 {
   133  		return errors.New("must have at least one application")
   134  	}
   135  
   136  	if len(raw.Applications) == 1 && appName != "" {
   137  		raw.Applications[0].Name = appName
   138  		raw.Applications[0].FullUnmarshalledApplication["name"] = appName
   139  	}
   140  
   141  	filteredIndex := -1
   142  
   143  	for i := range raw.Applications {
   144  		if raw.Applications[i].Name == "" {
   145  			return errors.New("Found an application with no name specified")
   146  		}
   147  
   148  		if raw.Applications[i].Name == appName {
   149  			filteredIndex = i
   150  		}
   151  
   152  		if raw.Applications[i].Path == "" {
   153  			continue
   154  		}
   155  
   156  		var finalPath = raw.Applications[i].Path
   157  		if !filepath.IsAbs(finalPath) {
   158  			finalPath = filepath.Join(filepath.Dir(pathToManifest), finalPath)
   159  		}
   160  		finalPath, err = filepath.EvalSymlinks(finalPath)
   161  		if err != nil {
   162  			if os.IsNotExist(err) {
   163  				return InvalidManifestApplicationPathError{
   164  					Path: raw.Applications[i].Path,
   165  				}
   166  			}
   167  			return err
   168  		}
   169  		raw.Applications[i].Path = finalPath
   170  	}
   171  
   172  	if filteredIndex >= 0 {
   173  		raw.Applications = []Application{raw.Applications[filteredIndex]}
   174  	} else if appName != "" {
   175  		return AppNotInManifestError{Name: appName}
   176  	}
   177  
   178  	parser.Applications = raw.Applications
   179  	parser.rawManifest, err = yaml.Marshal(raw)
   180  	if err != nil {
   181  		return err
   182  	}
   183  
   184  	parser.hasParsed = true
   185  	return nil
   186  }