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) }
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 }
// 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) }
// 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 }
// 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 }
// 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 }
// 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 }