github.com/loggregator/cli@v6.33.1-0.20180224010324-82334f081791+incompatible/actor/pushaction/merge_and_validate_settings_and_manifest.go (about)

     1  package pushaction
     2  
     3  import (
     4  	"net/url"
     5  	"os"
     6  	"path/filepath"
     7  
     8  	"code.cloudfoundry.org/cli/actor/actionerror"
     9  	"code.cloudfoundry.org/cli/util/manifest"
    10  	log "github.com/sirupsen/logrus"
    11  )
    12  
    13  func (actor Actor) MergeAndValidateSettingsAndManifests(settings CommandLineSettings, apps []manifest.Application) ([]manifest.Application, error) {
    14  	var mergedApps []manifest.Application
    15  
    16  	if len(apps) == 0 {
    17  		log.Info("no manifest, generating one from command line settings")
    18  		mergedApps = append(mergedApps, settings.OverrideManifestSettings(manifest.Application{}))
    19  	} else {
    20  		if settings.Name != "" && len(apps) > 1 {
    21  			var err error
    22  			apps, err = actor.selectApp(settings.Name, apps)
    23  			if err != nil {
    24  				return nil, err
    25  			}
    26  		}
    27  
    28  		err := actor.validatePremergedSettings(settings, apps)
    29  		if err != nil {
    30  			return nil, err
    31  		}
    32  
    33  		for _, app := range apps {
    34  			mergedApps = append(mergedApps, settings.OverrideManifestSettings(app))
    35  		}
    36  	}
    37  
    38  	mergedApps, err := actor.sanitizeAppPath(mergedApps)
    39  	if err != nil {
    40  		return nil, err
    41  	}
    42  	mergedApps = actor.setSaneEndpoint(mergedApps)
    43  
    44  	log.Debugf("merged app settings: %#v", mergedApps)
    45  
    46  	err = actor.validateMergedSettings(mergedApps)
    47  	if err != nil {
    48  		log.Errorln("validation error post merge:", err)
    49  		return nil, err
    50  	}
    51  	return mergedApps, nil
    52  }
    53  
    54  func (Actor) selectApp(appName string, apps []manifest.Application) ([]manifest.Application, error) {
    55  	var returnedApps []manifest.Application
    56  	for _, app := range apps {
    57  		if app.Name == appName {
    58  			returnedApps = append(returnedApps, app)
    59  		}
    60  	}
    61  	if len(returnedApps) == 0 {
    62  		return nil, actionerror.AppNotFoundInManifestError{Name: appName}
    63  	}
    64  
    65  	return returnedApps, nil
    66  }
    67  
    68  func (Actor) setSaneEndpoint(apps []manifest.Application) []manifest.Application {
    69  	for i, app := range apps {
    70  		if app.HealthCheckType == "http" && app.HealthCheckHTTPEndpoint == "" {
    71  			apps[i].HealthCheckHTTPEndpoint = "/"
    72  		}
    73  	}
    74  
    75  	return apps
    76  }
    77  
    78  func (Actor) sanitizeAppPath(apps []manifest.Application) ([]manifest.Application, error) {
    79  	for i, app := range apps {
    80  		if app.Path != "" {
    81  			var err error
    82  			apps[i].Path, err = filepath.Abs(app.Path)
    83  			if err != nil {
    84  				return nil, err
    85  			}
    86  		}
    87  	}
    88  
    89  	return apps, nil
    90  }
    91  
    92  func (Actor) validatePremergedSettings(settings CommandLineSettings, apps []manifest.Application) error {
    93  	if len(apps) > 1 {
    94  		switch {
    95  		case
    96  			settings.Buildpack.IsSet,
    97  			settings.Command.IsSet,
    98  			settings.DefaultRouteDomain != "",
    99  			settings.DefaultRouteHostname != "",
   100  			settings.DiskQuota != 0,
   101  			settings.DockerImage != "",
   102  			settings.DockerUsername != "",
   103  			settings.HealthCheckTimeout != 0,
   104  			settings.HealthCheckType != "",
   105  			settings.Instances.IsSet,
   106  			settings.Memory != 0,
   107  			settings.NoHostname,
   108  			settings.NoRoute,
   109  			settings.ProvidedAppPath != "",
   110  			settings.RandomRoute,
   111  			settings.RoutePath != "",
   112  			settings.StackName != "":
   113  			log.Error("cannot use some parameters with multiple apps")
   114  			return actionerror.CommandLineOptionsWithMultipleAppsError{}
   115  		}
   116  	}
   117  
   118  	for _, app := range apps {
   119  		switch {
   120  		case app.NoRoute && len(app.Routes) > 0:
   121  			return actionerror.PropertyCombinationError{AppName: app.Name, Properties: []string{"no-route", "routes"}}
   122  		case app.DeprecatedDomain != nil || app.DeprecatedDomains != nil || app.DeprecatedHost != nil || app.DeprecatedHosts != nil || app.DeprecatedNoHostname != nil:
   123  			deprecatedFields := []string{}
   124  			if app.DeprecatedDomain != nil {
   125  				deprecatedFields = append(deprecatedFields, "domain")
   126  			}
   127  			if app.DeprecatedDomains != nil {
   128  				deprecatedFields = append(deprecatedFields, "domains")
   129  			}
   130  			if app.DeprecatedHost != nil {
   131  				deprecatedFields = append(deprecatedFields, "host")
   132  			}
   133  			if app.DeprecatedHosts != nil {
   134  				deprecatedFields = append(deprecatedFields, "hosts")
   135  			}
   136  			if app.DeprecatedNoHostname != nil {
   137  				deprecatedFields = append(deprecatedFields, "no-hostname")
   138  			}
   139  			return actionerror.TriggerLegacyPushError{DomainHostRelated: deprecatedFields}
   140  		case len(app.Routes) > 0:
   141  			commandLineOptionsAndManifestConflictErr := actionerror.CommandLineOptionsAndManifestConflictError{
   142  				ManifestAttribute:  "route",
   143  				CommandLineOptions: []string{"-d", "--hostname", "-n", "--no-hostname", "--route-path"},
   144  			}
   145  			if settings.DefaultRouteDomain != "" ||
   146  				settings.DefaultRouteHostname != "" ||
   147  				settings.NoHostname != false ||
   148  				settings.RoutePath != "" {
   149  				return commandLineOptionsAndManifestConflictErr
   150  			}
   151  		}
   152  	}
   153  
   154  	return nil
   155  }
   156  
   157  func (actor Actor) validateMergedSettings(apps []manifest.Application) error {
   158  	for i, app := range apps {
   159  		log.WithField("index", i).Info("validating app")
   160  		if app.Name == "" {
   161  			log.WithField("index", i).Error("does not contain an app name")
   162  			return actionerror.MissingNameError{}
   163  		}
   164  
   165  		for _, route := range app.Routes {
   166  			err := actor.validateRoute(route)
   167  			if err != nil {
   168  				return err
   169  			}
   170  		}
   171  
   172  		if app.DockerImage == "" {
   173  			_, err := os.Stat(app.Path)
   174  			if os.IsNotExist(err) {
   175  				log.WithField("path", app.Path).Error("app path does not exist")
   176  				return actionerror.NonexistentAppPathError{Path: app.Path}
   177  			}
   178  		} else {
   179  			if app.DockerUsername != "" && app.DockerPassword == "" {
   180  				log.WithField("app", app.Name).Error("no docker password found")
   181  				return actionerror.DockerPasswordNotSetError{}
   182  			}
   183  			if app.Buildpack.IsSet {
   184  				return actionerror.PropertyCombinationError{AppName: app.Name, Properties: []string{"docker", "buildpack"}}
   185  			}
   186  			if app.Path != "" {
   187  				return actionerror.PropertyCombinationError{AppName: app.Name, Properties: []string{"docker", "path"}}
   188  			}
   189  		}
   190  
   191  		if app.NoRoute {
   192  			if app.Hostname != "" {
   193  				return actionerror.PropertyCombinationError{AppName: app.Name, Properties: []string{"hostname", "no-route"}}
   194  			}
   195  			if app.NoHostname {
   196  				return actionerror.PropertyCombinationError{AppName: app.Name, Properties: []string{"no-hostname", "no-route"}}
   197  			}
   198  			if app.RoutePath != "" {
   199  				return actionerror.PropertyCombinationError{AppName: app.Name, Properties: []string{"route-path", "no-route"}}
   200  			}
   201  		}
   202  
   203  		if app.HealthCheckHTTPEndpoint != "" && app.HealthCheckType != "http" {
   204  			return actionerror.HTTPHealthCheckInvalidError{}
   205  		}
   206  	}
   207  	return nil
   208  }
   209  
   210  func (actor Actor) validateRoute(route string) error {
   211  	_, err := url.Parse(route)
   212  	if err != nil || !actor.urlValidator.MatchString(route) {
   213  		return actionerror.InvalidRouteError{Route: route}
   214  	}
   215  
   216  	return nil
   217  }