.ConvertSystemError(err)
		}
		log.Infof("Set installer state directory: %v.", i.ReadStateDir)
	}
	if i.WriteStateDir == "" {
		if i.WriteStateDir, err = ioutil.TempDir("", "gravity-wizard"); err!= nil {
			return trace.ConvertSystemError(err)
		}
		log.Infof("Installer write layer: %v.", i.WriteStateDir)
	}
	isDir, err := utils.IsDirectory(i.ReadStateDir)
	if!isDir {
		return trace.BadParameter("the specified state path %v is not "+
			"a directory", i.ReadStateDir)
	}
	if err!= nil {
		if trace.IsAccessDenied(err) {
			return trace.Wrap(err, "access denied to the specified state "+
				"dir %v", i.ReadStateDir)
		}
		if trace.IsNotFound(err) {
			return trace.Wrap(err, "the specified state dir %v is not "+
				"found", i.ReadStateDir)
		}
		return trace.Wrap(err)
	}
	if i.InstallToken == "" {
		if i.InstallToken, err = teleutils.CryptoRandomHex(6); err!= nil {
			return trace.Wrap(err)
		}
		log.Infof("Generated install token: %v.", i.InstallToken)
	}
	serviceUser, err := install.GetOrCreateServiceUser(i.ServiceUID, i.ServiceGID)
	if err!= nil {
		return trace.Wrap(err)
	}
	if i.VxlanPort == 0 {
		i.VxlanPort = defaults.VxlanPort
	}
	if err := i.validateDNSConfig(); err!= nil {
		return trace.Wrap(err)
	}
	i.ServiceUser = *serviceUser
	if i.NewProcess == nil {
		i.NewProcess = process.NewProcess
	}
	return nil
}

// GetAdvertiseAddr return the advertise address provided in the config, or
// asks the user to choose it among the host's interfaces
func (i *InstallConfig) GetAdvertiseAddr() (string, error) {
	// if it was set explicitly with --advertise-addr flag, use it
	if i.AdvertiseAddr!= "" {
		return i.AdvertiseAddr, nil
	}
	// in interactive install mode ask user to choose among host's interfaces
	if i.Mode == constants.InstallModeInteractive {
		return selectNetworkInterface()
	}
	// otherwise, try to pick an address among machine's interfaces
	addr, err := utils.PickAdvertiseIP()
	if err!= nil {
		return "", trace.Wrap(err, "could not pick advertise address among "+
			"the host's network interfaces, please set the advertise address "+
			"via --advertise-addr flag")
	}
	return addr, nil
}

// GetAppPackage returns the application package for this installer
func (i *InstallConfig) GetAppPackage() (*loc.Locator, error) {
	if i.AppPackage!= "" {
		return loc.MakeLocator(i.AppPackage)
	}
	env, err := localenv.New(i.ReadStateDir)
	if err!= nil {
		return nil, trace.Wrap(err)
	}
	defer env.Close()
	locator, err := install.GetAppPackage(env.Apps)
	if err!= nil {
		if trace.IsNotFound(err) {
			return nil, trace.NotFound("the specified state dir %v does not "+
				"contain application data, please provide a path to the "+
				"unpacked installer tarball or specify an application "+
				"package via --app flag", i.ReadStateDir)
		}
		return nil, trace.Wrap(err)
	}
	return locator, nil
}

// GetResouces returns additional Kubernetes resources
func (i *InstallConfig) GetResources() ([]byte, error) {
	if i.ResourcesPath == "" {
		return nil, trace.NotFound("no resources provided")
	}
	resources, err := utils.ReadPath(i.ResourcesPath)
	if err!= nil {
		return nil, trace.Wrap(err)
	}
	return resources, nil
}

// getDNSOverrides converts DNS overrides specified on CLI to the storage format
func (i *InstallConfig) getDNSOverrides() (*storage.DNSOverrides, error) {
	overrides := &storage.DNSOverrides{
		Hosts: make(map[string]string),
		Zones: make(map[string][]string),
	}
	for _, hostOverride := range i.DNSHosts {
		host, ip, err := utils.ParseHostOverride(hostOverride)
		if err!= nil {
			return nil, trace.Wrap(err)
		}
		overrides.Hosts[host] = ip
	}
	for _, zoneOverride := range i.DNSZones {
		zone, nameserver, err := utils.ParseZoneOverride(zoneOverride)
		if err!= nil {
			return nil, trace.Wrap(err)
		}
		overrides.Zones[zone] = append(overrides.Zones[zone], nameserver)
	}
	return overrides, nil
}

// ToInstallerConfig converts CLI config to installer format
func (i *InstallConfig) ToInstallerConfig(env *localenv.LocalEnvironment, validator resources.Validator) (*install.Config, error) {
	advertiseAddr, err := i.GetAdvertiseAddr()
	if err!= nil {
		return nil, trace.Wrap(err)
	}
	var kubernetesResources []runtime.Object
	var gravityResources []storage.UnknownResource
	if i.ResourcesPath!= "" {
		kubernetesResources, gravityResources, err = i.splitResources(validator)
		if err!= nil {
			return nil, trace.Wrap(err)
		}
	}
	appPackage, err := i.GetAppPackage()
	if err!= nil {
		return nil, trace.Wrap(err)
	}
	dnsOverrides, err := i.getDNSOverrides()
	if err!= nil {
		return nil, trace.Wrap(err)
	}
	gravityResources, err = i.updateClusterConfig(gravityResources)
	if err!= nil {
		return nil, trace.Wrap(err)
	}
	ctx, cancel := context.WithCancel(context.Background())
	return &install.Config{
		Context:            ctx,
		Cancel:             cancel,
		EventsC:            make(chan install.Event, 100),
		AdvertiseAddr:      advertiseAddr,
		AppPackage:         appPackage,
		LocalPackages:      env.Packages,
		LocalApps:          env.Apps,
		LocalBackend:       env.Backend,
		Silent:             env.Silent,
		SiteDomain:         i.SiteDomain,
		StateDir:           i.ReadStateDir,
		WriteStateDir:      i.WriteStateDir,
		UserLogFile:        i.UserLogFile,
		SystemLogFile:      i.SystemLogFile,
		Token:              i.InstallToken,
		CloudProvider:      i.CloudProvider,
		GCENodeTags:        i.NodeTags,
		Flavor:             i.Flavor,
		Role:               i.Role,
		SystemDevice:       i.SystemDevice,
		DockerDevice:       i.DockerDevice,
		Mounts:             i.Mounts,
		DNSOverrides:       *dnsOverrides,
		DNSConfig:          i.DNSConfig,
		Mode:               i.Mode,
		PodCIDR:            i.PodCIDR,
		ServiceCIDR:        i.ServiceCIDR,
		VxlanPort:          i.VxlanPort,
		Docker:             i.Docker,
		Insecure:           i.Insecure,
		Manual:             i.Manual,
		ServiceUser:        i.ServiceUser,
		NewProcess:         i.NewProcess,
		LocalClusterClient: env.SiteOperator,
		RuntimeResources:   kubernetesResources,
		ClusterResources:   gravityResources,
	}, nil
}

// splitResources validates the resources specified in ResourcePath
// using the given validator and splits them into Kubernetes and Gravity-specific
func (