Example #1
0
func checkForPorts(repo *app.SourceRepository) []string {
	info := repo.Info()
	if info == nil || info.Dockerfile == nil {
		return nil
	}
	node := info.Dockerfile.AST()
	return dockerfile.LastExposedPorts(node)
}
Example #2
0
func exposedPortsAreNumeric(node *dockerfileparser.Node) error {
	for _, port := range dockerfileutil.LastExposedPorts(node) {
		if _, err := strconv.ParseInt(port, 10, 32); err != nil {
			return fmt.Errorf("could not parse %q: must be numeric", port)
		}
	}
	return nil
}
Example #3
0
// FromDockerfile generates an ImageRef from a given name, directory, and context path.
// The directory and context path will be joined and the resulting path should be a
// Dockerfile from where the image's ports will be extracted.
func (g *imageRefGenerator) FromDockerfile(name string, dir string, context string) (*ImageRef, error) {
	// Look for Dockerfile in repository
	file, err := os.Open(filepath.Join(dir, context, "Dockerfile"))
	if err != nil {
		return nil, err
	}

	node, err := parser.Parse(file)
	if err != nil {
		return nil, err
	}
	ports := dockerfile.LastExposedPorts(node)

	return g.FromNameAndPorts(name, ports)
}
Example #4
0
// buildPipelines converts a set of resolved, valid references into pipelines.
func (c *AppConfig) buildPipelines(components app.ComponentReferences, environment app.Environment) (app.PipelineGroup, error) {
	pipelines := app.PipelineGroup{}
	names := map[string]int{}
	for _, group := range components.Group() {
		glog.V(4).Infof("found group: %#v", group)
		common := app.PipelineGroup{}
		for _, ref := range group {
			var pipeline *app.Pipeline
			var name string
			if ref.Input().ExpectToBuild {
				glog.V(4).Infof("will use %q as the base image for a source build of %q", ref, ref.Input().Uses)
				input, err := app.InputImageFromMatch(ref.Input().ResolvedMatch)
				if err != nil {
					return nil, fmt.Errorf("can't build %q: %v", ref.Input(), err)
				}
				if !input.AsImageStream {
					glog.Warningf("Could not find an image stream match for %q. Make sure that a Docker image with that tag is available on the node for the build to succeed.", ref.Input().ResolvedMatch.Value)
				}
				strategy, source, err := app.StrategyAndSourceForRepository(ref.Input().Uses, input)
				if err != nil {
					return nil, fmt.Errorf("can't build %q: %v", ref.Input(), err)
				}

				// Override resource names from the cli
				name = c.Name
				if len(name) == 0 {
					var ok bool
					name, ok = (app.NameSuggestions{source, input}).SuggestName()
					if !ok {
						return nil, fmt.Errorf("can't suggest a valid name, please specify a name with --name")
					}
				}
				name, err = ensureValidUniqueName(names, name)
				source.Name = name
				if err != nil {
					return nil, err
				}

				// Append any exposed ports from Dockerfile to input image
				if ref.Input().Uses.IsDockerBuild() && ref.Input().Uses.Info() != nil {
					node := ref.Input().Uses.Info().Dockerfile.AST()
					ports := dockerfileutil.LastExposedPorts(node)
					if len(ports) > 0 {
						if input.Info == nil {
							input.Info = &imageapi.DockerImage{
								Config: &imageapi.DockerConfig{},
							}
						}
						input.Info.Config.ExposedPorts = map[string]struct{}{}
						for _, p := range ports {
							input.Info.Config.ExposedPorts[p] = struct{}{}
						}
					}
				}
				if pipeline, err = app.NewBuildPipeline(ref.Input().String(), input, c.OutputDocker, strategy, c.GetBuildEnvironment(environment), source); err != nil {
					return nil, fmt.Errorf("can't build %q: %v", ref.Input(), err)
				}

			} else {
				glog.V(4).Infof("will include %q", ref)
				input, err := app.InputImageFromMatch(ref.Input().ResolvedMatch)
				if err != nil {
					return nil, fmt.Errorf("can't include %q: %v", ref.Input(), err)
				}
				name = c.Name
				if len(name) == 0 {
					var ok bool
					name, ok = input.SuggestName()
					if !ok {
						return nil, fmt.Errorf("can't suggest a valid name, please specify a name with --name")
					}
				}
				name, err = ensureValidUniqueName(names, name)
				if err != nil {
					return nil, err
				}
				input.ObjectName = name
				if pipeline, err = app.NewImagePipeline(ref.Input().String(), input); err != nil {
					return nil, fmt.Errorf("can't include %q: %v", ref.Input(), err)
				}
			}

			if err := pipeline.NeedsDeployment(environment, c.Labels, name); err != nil {
				return nil, fmt.Errorf("can't set up a deployment for %q: %v", ref.Input(), err)
			}
			common = append(common, pipeline)
		}

		if err := common.Reduce(); err != nil {
			return nil, fmt.Errorf("can't create a pipeline from %s: %v", common, err)
		}
		pipelines = append(pipelines, common...)
	}
	return pipelines, nil
}
Example #5
0
// NewBuildPipeline creates a new pipeline with components that are expected to
// be built.
func (pb *pipelineBuilder) NewBuildPipeline(from string, input *ImageRef, sourceRepository *SourceRepository) (*Pipeline, error) {
	strategy, source, err := StrategyAndSourceForRepository(sourceRepository, input)
	if err != nil {
		return nil, fmt.Errorf("can't build %q: %v", from, err)
	}

	var name string
	output := &ImageRef{
		OutputImage:   true,
		AsImageStream: !pb.outputDocker,
	}
	if len(pb.to) > 0 {
		outputImageRef, err := image.ParseDockerImageReference(pb.to)
		if err != nil {
			return nil, err
		}
		output.Reference = outputImageRef
		name, err = pb.nameGenerator.Generate(NameSuggestions{source, output, input})
		if err != nil {
			return nil, err
		}
	} else {
		name, err = pb.nameGenerator.Generate(NameSuggestions{source, input})
		if err != nil {
			return nil, err
		}
		output.Reference = image.DockerImageReference{
			Name: name,
			Tag:  image.DefaultImageTag,
		}
	}
	source.Name = name

	// Append any exposed ports from Dockerfile to input image
	if sourceRepository.IsDockerBuild() && sourceRepository.Info() != nil {
		node := sourceRepository.Info().Dockerfile.AST()
		ports := dockerfile.LastExposedPorts(node)
		if len(ports) > 0 {
			if input.Info == nil {
				input.Info = &image.DockerImage{
					Config: &image.DockerConfig{},
				}
			}
			input.Info.Config.ExposedPorts = map[string]struct{}{}
			for _, p := range ports {
				input.Info.Config.ExposedPorts[p] = struct{}{}
			}
		}
	}

	if input != nil {
		// TODO: assumes that build doesn't change the image metadata. In the future
		// we could get away with deferred generation possibly.
		output.Info = input.Info
	}

	build := &BuildRef{
		Source:   source,
		Input:    input,
		Strategy: strategy,
		Output:   output,
		Env:      pb.environment,
	}

	return &Pipeline{
		Name:       name,
		From:       from,
		InputImage: input,
		Image:      output,
		Build:      build,
	}, nil
}
Example #6
0
// NewBuildPipeline creates a new pipeline with components that are expected to
// be built.
func (pb *pipelineBuilder) NewBuildPipeline(from string, resolvedMatch *ComponentMatch, sourceRepository *SourceRepository) (*Pipeline, error) {
	input, err := InputImageFromMatch(resolvedMatch)
	if err != nil {
		return nil, fmt.Errorf("can't build %q: %v", from, err)
	}
	if !input.AsImageStream {
		msg := "Could not find an image stream match for %q. Make sure that a Docker image with that tag is available on the node for the build to succeed."
		glog.Warningf(msg, resolvedMatch.Value)
	}

	strategy, source, err := StrategyAndSourceForRepository(sourceRepository, input)
	if err != nil {
		return nil, fmt.Errorf("can't build %q: %v", from, err)
	}

	name, err := pb.nameGenerator.Generate(NameSuggestions{source, input})
	if err != nil {
		return nil, err
	}
	source.Name = name

	// Append any exposed ports from Dockerfile to input image
	if sourceRepository.IsDockerBuild() && sourceRepository.Info() != nil {
		node := sourceRepository.Info().Dockerfile.AST()
		ports := dockerfile.LastExposedPorts(node)
		if len(ports) > 0 {
			if input.Info == nil {
				input.Info = &image.DockerImage{
					Config: &image.DockerConfig{},
				}
			}
			input.Info.Config.ExposedPorts = map[string]struct{}{}
			for _, p := range ports {
				input.Info.Config.ExposedPorts[p] = struct{}{}
			}
		}
	}

	output := &ImageRef{
		Reference: image.DockerImageReference{
			Name: name,
			Tag:  image.DefaultImageTag,
		},
		OutputImage:   true,
		AsImageStream: !pb.outputDocker,
	}
	if input != nil {
		// TODO: assumes that build doesn't change the image metadata. In the future
		// we could get away with deferred generation possibly.
		output.Info = input.Info
	}

	build := &BuildRef{
		Source:   source,
		Input:    input,
		Strategy: strategy,
		Output:   output,
		Env:      pb.environment,
	}

	return &Pipeline{
		Name:       name,
		From:       from,
		InputImage: input,
		Image:      output,
		Build:      build,
	}, nil
}
Example #7
0
// Generate accepts a path to an app.json file and generates a template from it
func (g *Generator) Generate(body []byte) (*templateapi.Template, error) {
	appJSON := &AppJSON{}
	if err := json.Unmarshal(body, appJSON); err != nil {
		return nil, err
	}

	glog.V(4).Infof("app.json: %#v", appJSON)

	name := g.Name
	if len(name) == 0 && len(g.LocalPath) > 0 {
		name = filepath.Base(g.LocalPath)
	}

	template := &templateapi.Template{}
	template.Name = name
	template.Annotations = make(map[string]string)
	template.Annotations["openshift.io/website"] = appJSON.Website
	template.Annotations["k8s.io/display-name"] = appJSON.Name
	template.Annotations["k8s.io/description"] = appJSON.Description
	template.Annotations["tags"] = strings.Join(appJSON.Keywords, ",")
	template.Annotations["iconURL"] = appJSON.Logo

	// create parameters and environment for containers
	allEnv := make(app.Environment)
	for k, v := range appJSON.Env {
		if v.EnvVar != nil {
			allEnv[k] = fmt.Sprintf("${%s}", k)
		}
	}
	envVars := allEnv.List()
	for _, v := range envVars {
		env := appJSON.Env[v.Name]
		if env.EnvVar == nil {
			continue
		}
		e := env.EnvVar
		displayName := v.Name
		displayName = strings.Join(strings.Split(strings.ToLower(displayName), "_"), " ")
		displayName = strings.ToUpper(displayName[:1]) + displayName[1:]
		param := templateapi.Parameter{
			Name:        v.Name,
			DisplayName: displayName,
			Description: e.Description,
			Value:       e.Value,
		}
		switch e.Generator {
		case "secret":
			param.Generate = "expression"
			param.From = "[a-zA-Z0-9]{14}"
		}
		if len(param.Value) == 0 && e.Default != nil {
			switch t := e.Default.(type) {
			case string:
				param.Value = t
			case float64, float32:
				out, _ := json.Marshal(t)
				param.Value = string(out)
			}
		}
		template.Parameters = append(template.Parameters, param)
	}

	warnings := make(map[string][]string)

	if len(appJSON.Formation) == 0 {
		glog.V(4).Infof("No formation in app.json, adding a default web")
		// TODO: read Procfile for command?
		appJSON.Formation = map[string]Formation{
			"web": {
				Quantity: 1,
			},
		}
		msg := "adding a default formation 'web' with scale 1"
		warnings[msg] = append(warnings[msg], "app.json")
	}

	formations := sets.NewString()
	for k := range appJSON.Formation {
		formations.Insert(k)
	}

	var primaryFormation = "web"
	if _, ok := appJSON.Formation["web"]; !ok || len(appJSON.Formation) == 1 {
		for k := range appJSON.Formation {
			primaryFormation = k
			break
		}
	}

	imageGen := app.NewImageRefGenerator()

	buildPath := appJSON.Repository
	if len(buildPath) == 0 && len(g.LocalPath) > 0 {
		buildPath = g.LocalPath
	}
	if len(buildPath) == 0 {
		return nil, fmt.Errorf("app.json did not contain a repository URL and no local path was specified")
	}

	repo, err := app.NewSourceRepository(buildPath, generate.StrategyDocker)
	if err != nil {
		return nil, err
	}

	var ports []string

	var pipelines app.PipelineGroup
	baseImage := g.BaseImage
	if len(baseImage) == 0 {
		baseImage = appJSON.Image
	}
	if len(baseImage) == 0 {
		return nil, fmt.Errorf("Docker image required: provide an --image flag or 'image' key in app.json")
	}

	fakeDockerfile := heredoc.Docf(`
      # Generated from app.json
      FROM %s
    `, baseImage)

	dockerfilePath := filepath.Join(buildPath, "Dockerfile")
	if df, err := app.NewDockerfileFromFile(dockerfilePath); err == nil {
		repo.Info().Dockerfile = df
		repo.Info().Path = dockerfilePath
		ports = dockerfile.LastExposedPorts(df.AST())
	}
	// TODO: look for procfile for more info?

	image, err := imageGen.FromNameAndPorts(baseImage, ports)
	if err != nil {
		return nil, err
	}
	image.AsImageStream = true
	image.TagDirectly = true
	image.ObjectName = name
	image.Tag = "from"

	pipeline, err := app.NewPipelineBuilder(name, nil, false).To(name).NewBuildPipeline(name, image, repo)
	if err != nil {
		return nil, err
	}

	// TODO: this should not be necessary
	pipeline.Build.Source.Name = name
	pipeline.Build.Source.DockerfileContents = fakeDockerfile
	pipeline.Name = name
	pipeline.Image.ObjectName = name
	glog.V(4).Infof("created pipeline %+v", pipeline)

	pipelines = append(pipelines, pipeline)

	var errs []error

	// create deployments for each formation
	var group app.PipelineGroup
	for _, component := range formations.List() {
		componentName := fmt.Sprintf("%s-%s", name, component)
		if formations.Len() == 1 {
			componentName = name
		}
		formationName := component
		formation := appJSON.Formation[component]

		inputImage := pipelines[0].Image

		inputImage.ContainerFn = func(c *kapi.Container) {
			for _, s := range ports {
				if port, err := strconv.Atoi(s); err == nil {
					c.Ports = append(c.Ports, kapi.ContainerPort{ContainerPort: int32(port)})
				}
			}
			if len(formation.Command) > 0 {
				c.Args = []string{formation.Command}
			} else {
				msg := "no command defined, defaulting to command in the Procfile"
				warnings[msg] = append(warnings[msg], formationName)
				c.Args = []string{"/bin/sh", "-c", fmt.Sprintf("$(grep %s Procfile | cut -f 2 -d :)", formationName)}
			}
			c.Env = append(c.Env, envVars...)

			c.Resources = resourcesForProfile(formation.Size)
		}

		pipeline, err := app.NewPipelineBuilder(componentName, nil, true).To(componentName).NewImagePipeline(componentName, inputImage)
		if err != nil {
			errs = append(errs, err)
			break
		}

		if err := pipeline.NeedsDeployment(nil, nil, false); err != nil {
			return nil, err
		}

		if cmd, ok := appJSON.Scripts["postdeploy"]; ok && primaryFormation == component {
			pipeline.Deployment.PostHook = &app.DeploymentHook{Shell: cmd}
			delete(appJSON.Scripts, "postdeploy")
		}

		group = append(group, pipeline)
	}
	if err := group.Reduce(); err != nil {
		return nil, err
	}
	pipelines = append(pipelines, group...)

	if len(errs) > 0 {
		return nil, utilerrs.NewAggregate(errs)
	}

	acceptors := app.Acceptors{app.NewAcceptUnique(kapi.Scheme), app.AcceptNew}
	objects := app.Objects{}
	accept := app.NewAcceptFirst()
	for _, p := range pipelines {
		accepted, err := p.Objects(accept, acceptors)
		if err != nil {
			return nil, fmt.Errorf("can't setup %q: %v", p.From, err)
		}
		objects = append(objects, accepted...)
	}

	// create services for each object with a name based on alias.
	var services []*kapi.Service
	for _, obj := range objects {
		switch t := obj.(type) {
		case *deployapi.DeploymentConfig:
			ports := app.UniqueContainerToServicePorts(app.AllContainerPorts(t.Spec.Template.Spec.Containers...))
			if len(ports) == 0 {
				continue
			}
			svc := app.GenerateService(t.ObjectMeta, t.Spec.Selector)
			svc.Spec.Ports = ports
			services = append(services, svc)
		}
	}
	for _, svc := range services {
		objects = append(objects, svc)
	}

	template.Objects = objects

	// generate warnings
	warnUnusableAppJSONElements("app.json", appJSON, warnings)
	if len(warnings) > 0 {
		allWarnings := sets.NewString()
		for msg, services := range warnings {
			allWarnings.Insert(fmt.Sprintf("%s: %s", strings.Join(services, ","), msg))
		}
		if template.Annotations == nil {
			template.Annotations = make(map[string]string)
		}
		template.Annotations[app.GenerationWarningAnnotation] = fmt.Sprintf("not all app.json fields were honored:\n* %s", strings.Join(allWarnings.List(), "\n* "))
	}

	return template, nil
}