github.com/randomtask1155/cli@v6.41.1-0.20181227003417-a98eed78cbde+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  // MergeAndValidateSettingsAndManifests merges command line settings and
    14  // manifest settings. It does this by:
    15  // - Validating command line setting and their effect on the provided manifests
    16  // - Override manifest settings with command line settings (when applicable)
    17  // - Sanitizing the inputs
    18  // - Validate merged manifest
    19  func (actor Actor) MergeAndValidateSettingsAndManifests(cmdLineSettings CommandLineSettings, apps []manifest.Application) ([]manifest.Application, error) {
    20  	var mergedApps []manifest.Application
    21  
    22  	if len(apps) == 0 {
    23  		log.Info("no manifest, generating one from command line settings")
    24  		mergedApps = append(mergedApps, cmdLineSettings.OverrideManifestSettings(manifest.Application{}))
    25  	} else {
    26  		if cmdLineSettings.Name != "" && len(apps) > 1 {
    27  			var err error
    28  			apps, err = actor.selectApp(cmdLineSettings.Name, apps)
    29  			if err != nil {
    30  				return nil, err
    31  			}
    32  		}
    33  		err := actor.validateCommandLineSettingsAndManifestCombinations(cmdLineSettings, apps)
    34  		if err != nil {
    35  			return nil, err
    36  		}
    37  
    38  		for _, app := range apps {
    39  			mergedApps = append(mergedApps, cmdLineSettings.OverrideManifestSettings(app))
    40  		}
    41  	}
    42  
    43  	mergedApps, err := actor.sanitizeAppPath(mergedApps)
    44  	if err != nil {
    45  		return nil, err
    46  	}
    47  	mergedApps = actor.setSaneEndpoint(mergedApps)
    48  
    49  	log.Debugf("merged app settings: %#v", mergedApps)
    50  
    51  	err = actor.validateMergedSettings(mergedApps)
    52  	if err != nil {
    53  		log.Errorln("validation error post merge:", err)
    54  		return nil, err
    55  	}
    56  	return mergedApps, nil
    57  }
    58  
    59  func (Actor) selectApp(appName string, apps []manifest.Application) ([]manifest.Application, error) {
    60  	var returnedApps []manifest.Application
    61  	for _, app := range apps {
    62  		if app.Name == appName {
    63  			returnedApps = append(returnedApps, app)
    64  		}
    65  	}
    66  	if len(returnedApps) == 0 {
    67  		return nil, actionerror.AppNotFoundInManifestError{Name: appName}
    68  	}
    69  
    70  	return returnedApps, nil
    71  }
    72  
    73  func (Actor) setSaneEndpoint(apps []manifest.Application) []manifest.Application {
    74  	for i, app := range apps {
    75  		if app.HealthCheckType == "http" && app.HealthCheckHTTPEndpoint == "" {
    76  			apps[i].HealthCheckHTTPEndpoint = "/"
    77  		}
    78  	}
    79  
    80  	return apps
    81  }
    82  
    83  func (Actor) sanitizeAppPath(apps []manifest.Application) ([]manifest.Application, error) {
    84  	for i, app := range apps {
    85  		if app.Path != "" {
    86  			var err error
    87  			apps[i].Path, err = filepath.Abs(app.Path)
    88  			if err != nil {
    89  				return nil, err
    90  			}
    91  		}
    92  	}
    93  
    94  	return apps, nil
    95  }
    96  
    97  func (Actor) validateCommandLineSettingsAndManifestCombinations(cmdLineSettings CommandLineSettings, apps []manifest.Application) error {
    98  	if len(apps) > 1 {
    99  		switch {
   100  		case
   101  			cmdLineSettings.Buildpacks != nil,
   102  			cmdLineSettings.Command.IsSet,
   103  			cmdLineSettings.DefaultRouteDomain != "",
   104  			cmdLineSettings.DefaultRouteHostname != "",
   105  			cmdLineSettings.DiskQuota != 0,
   106  			cmdLineSettings.DockerImage != "",
   107  			cmdLineSettings.DockerUsername != "",
   108  			cmdLineSettings.DropletPath != "",
   109  			cmdLineSettings.HealthCheckTimeout != 0,
   110  			cmdLineSettings.HealthCheckType != "",
   111  			cmdLineSettings.Instances.IsSet,
   112  			cmdLineSettings.Memory != 0,
   113  			cmdLineSettings.NoHostname,
   114  			cmdLineSettings.NoRoute,
   115  			cmdLineSettings.ProvidedAppPath != "",
   116  			cmdLineSettings.RandomRoute,
   117  			cmdLineSettings.RoutePath != "",
   118  			cmdLineSettings.StackName != "":
   119  			log.Error("cannot use some parameters with multiple apps")
   120  			return actionerror.CommandLineOptionsWithMultipleAppsError{}
   121  		}
   122  	}
   123  
   124  	for _, app := range apps {
   125  		switch {
   126  		case app.NoRoute && len(app.Routes) > 0:
   127  			return actionerror.PropertyCombinationError{AppName: app.Name, Properties: []string{"no-route", "routes"}}
   128  		case app.DeprecatedDomain != nil ||
   129  			app.DeprecatedDomains != nil ||
   130  			app.DeprecatedHost != nil ||
   131  			app.DeprecatedHosts != nil ||
   132  			app.DeprecatedNoHostname != nil:
   133  
   134  			deprecatedFields := []string{}
   135  			if app.DeprecatedDomain != nil {
   136  				deprecatedFields = append(deprecatedFields, "domain")
   137  			}
   138  			if app.DeprecatedDomains != nil {
   139  				deprecatedFields = append(deprecatedFields, "domains")
   140  			}
   141  			if app.DeprecatedHost != nil {
   142  				deprecatedFields = append(deprecatedFields, "host")
   143  			}
   144  			if app.DeprecatedHosts != nil {
   145  				deprecatedFields = append(deprecatedFields, "hosts")
   146  			}
   147  			if app.DeprecatedNoHostname != nil {
   148  				deprecatedFields = append(deprecatedFields, "no-hostname")
   149  			}
   150  			return actionerror.TriggerLegacyPushError{DomainHostRelated: deprecatedFields}
   151  		case len(app.Routes) > 0:
   152  			commandLineOptionsAndManifestConflictErr := actionerror.CommandLineOptionsAndManifestConflictError{
   153  				ManifestAttribute:  "route",
   154  				CommandLineOptions: []string{"-d", "--hostname", "-n", "--no-hostname", "--route-path"},
   155  			}
   156  			if cmdLineSettings.DefaultRouteDomain != "" ||
   157  				cmdLineSettings.DefaultRouteHostname != "" ||
   158  				cmdLineSettings.NoHostname != false ||
   159  				cmdLineSettings.RoutePath != "" {
   160  				return commandLineOptionsAndManifestConflictErr
   161  			}
   162  		}
   163  	}
   164  
   165  	return nil
   166  }
   167  
   168  func (actor Actor) validateMergedSettings(apps []manifest.Application) error {
   169  	for i, app := range apps {
   170  		log.WithField("index", i).Info("validating app")
   171  		if app.Name == "" {
   172  			log.WithField("index", i).Error("does not contain an app name")
   173  			return actionerror.MissingNameError{}
   174  		}
   175  
   176  		for _, route := range app.Routes {
   177  			err := actor.validateRoute(route)
   178  			if err != nil {
   179  				return err
   180  			}
   181  		}
   182  
   183  		if app.DockerImage != "" {
   184  			if app.DockerUsername != "" && app.DockerPassword == "" {
   185  				log.WithField("app", app.Name).Error("no docker password found")
   186  				return actionerror.DockerPasswordNotSetError{}
   187  			}
   188  			if app.Buildpack.IsSet {
   189  				return actionerror.PropertyCombinationError{AppName: app.Name, Properties: []string{"docker", "buildpack"}}
   190  			}
   191  			if app.Buildpacks != nil {
   192  				return actionerror.PropertyCombinationError{AppName: app.Name, Properties: []string{"docker", "buildpacks"}}
   193  			}
   194  			if app.Path != "" {
   195  				return actionerror.PropertyCombinationError{AppName: app.Name, Properties: []string{"docker", "path"}}
   196  			}
   197  			if app.DropletPath != "" {
   198  				return actionerror.PropertyCombinationError{AppName: app.Name, Properties: []string{"docker", "droplet"}}
   199  			}
   200  		}
   201  
   202  		if app.DropletPath != "" {
   203  			if app.Path != "" {
   204  				return actionerror.PropertyCombinationError{AppName: app.Name, Properties: []string{"droplet", "path"}}
   205  			}
   206  			if app.Buildpack.IsSet {
   207  				return actionerror.PropertyCombinationError{AppName: app.Name, Properties: []string{"droplet", "buildpack"}}
   208  			}
   209  			if app.Buildpacks != nil {
   210  				return actionerror.PropertyCombinationError{AppName: app.Name, Properties: []string{"droplet", "buildpacks"}}
   211  			}
   212  		}
   213  
   214  		if app.DockerImage == "" && app.DropletPath == "" {
   215  			_, err := os.Stat(app.Path)
   216  			if os.IsNotExist(err) {
   217  				log.WithField("path", app.Path).Error("app path does not exist")
   218  				return actionerror.NonexistentAppPathError{Path: app.Path}
   219  			}
   220  		}
   221  
   222  		if app.NoRoute {
   223  			if app.Hostname != "" {
   224  				return actionerror.PropertyCombinationError{AppName: app.Name, Properties: []string{"hostname", "no-route"}}
   225  			}
   226  			if app.NoHostname {
   227  				return actionerror.PropertyCombinationError{AppName: app.Name, Properties: []string{"no-hostname", "no-route"}}
   228  			}
   229  			if app.RoutePath != "" {
   230  				return actionerror.PropertyCombinationError{AppName: app.Name, Properties: []string{"route-path", "no-route"}}
   231  			}
   232  		}
   233  
   234  		if app.HealthCheckHTTPEndpoint != "" && app.HealthCheckType != "http" {
   235  			return actionerror.HTTPHealthCheckInvalidError{}
   236  		}
   237  
   238  		if app.Buildpacks != nil && app.Buildpack.IsSet {
   239  			return actionerror.PropertyCombinationError{AppName: app.Name, Properties: []string{"buildpack", "buildpacks"}}
   240  		}
   241  
   242  		if len(app.Buildpacks) > 1 {
   243  			for _, b := range app.Buildpacks {
   244  				if b == "null" || b == "default" {
   245  					return actionerror.InvalidBuildpacksError{}
   246  				}
   247  			}
   248  		}
   249  	}
   250  	return nil
   251  }
   252  
   253  func (actor Actor) validateRoute(route string) error {
   254  	_, err := url.Parse(route)
   255  	if err != nil || !actor.urlValidator.MatchString(route) {
   256  		return actionerror.InvalidRouteError{Route: route}
   257  	}
   258  
   259  	return nil
   260  }