func transformBuildError(err error, baseName, commandName, commandPath string, groups errorGroups) { switch t := err.(type) { case newapp.ErrNoMatch: groups.Add( "no-matches", heredoc.Docf(` The '%[1]s' command will match arguments to the following types: 1. Images tagged into image streams in the current project or the 'openshift' project - if you don't specify a tag, we'll add ':latest' 2. Images in the Docker Hub, on remote registries, or on the local Docker engine 3. Git repository URLs or local paths that point to Git repositories --allow-missing-images can be used to force the use of an image that was not matched See '%[1]s -h' for examples.`, commandPath, ), t, t.Errs..., ) return } switch err { case newcmd.ErrNoInputs: groups.Add("", "", usageError(commandPath, newBuildNoInput, baseName, commandName)) return } transformError(err, baseName, commandName, commandPath, groups) }
func ExampleDocf() { libName := "github.com/MakeNowJust/heredoc" author := "TSUYUSATO Kitsune (@MakeNowJust)" fmt.Printf(heredoc.Docf(` Library Name : %s Author : %s Repository URL: http://%s.git `, libName, author, libName)) // Output: // Library Name : github.com/MakeNowJust/heredoc // Author : TSUYUSATO Kitsune (@MakeNowJust) // Repository URL: http://github.com/MakeNowJust/heredoc.git }
func TestDocf(t *testing.T) { // test case str := ` int: %3d string: %s ` i := 42 s := "Hello" expect := "int: 42\nstring: Hello\n" result := heredoc.Docf(str, i, s) if result != expect { t.Errorf("test failed: expected=> %#v, result=> %#v", expect, result) } }
func withFakeCommand(t *testing.T, envValues string, block func()) { _exec.Command = func(name string, arg ...string) *exec.Cmd { valueFile, _ := ioutil.TempFile("", "valueFile") valueFile.WriteString(heredoc.Doc(envValues)) valueFile.Close() dummy, _ := ioutil.TempFile("", "dummy") dummy.WriteString(heredoc.Docf(` #!/bin/sh cat %s `, valueFile.Name())) dummy.Close() t.Logf("dummy: %s", dummy.Name()) os.Chmod(dummy.Name(), 0755) return exec.Command(dummy.Name()) } block() }
func transformError(err error, baseName, commandName, commandPath string, groups errorGroups) { switch t := err.(type) { case newcmd.ErrRequiresExplicitAccess: if t.Input.Token != nil && t.Input.Token.ServiceAccount { groups.Add( "explicit-access-installer", heredoc.Doc(` WARNING: This will allow the pod to create and manage resources within your namespace - ensure you trust the image with those permissions before you continue. You can see more information about the image by adding the --dry-run flag. If you trust the provided image, include the flag --grant-install-rights.`, ), fmt.Errorf("installing %q requires an 'installer' service account with project editor access", t.Match.Value), ) } else { groups.Add( "explicit-access-you", heredoc.Doc(` WARNING: This will allow the pod to act as you across the entire cluster - ensure you trust the image with those permissions before you continue. You can see more information about the image by adding the --dry-run flag. If you trust the provided image, include the flag --grant-install-rights.`, ), fmt.Errorf("installing %q requires that you grant the image access to run with your credentials", t.Match.Value), ) } return case newapp.ErrNoMatch: groups.Add( "no-matches", heredoc.Docf(` The '%[1]s' command will match arguments to the following types: 1. Images tagged into image streams in the current project or the 'openshift' project - if you don't specify a tag, we'll add ':latest' 2. Images in the Docker Hub, on remote registries, or on the local Docker engine 3. Templates in the current project or the 'openshift' project 4. Git repository URLs or local paths that point to Git repositories --allow-missing-images can be used to point to an image that does not exist yet. See '%[1]s -h' for examples.`, commandPath, ), t, t.Errs..., ) return case newapp.ErrMultipleMatches: buf := &bytes.Buffer{} for i, match := range t.Matches { // If we have more than 5 matches, stop output and recommend searching // after the fifth if i >= 5 { groups.Add( "multiple-matches", heredoc.Docf(` The argument %[1]q could apply to the following Docker images, OpenShift image streams, or templates: %[2]sTo view a full list of matches, use '%[3]s %[4]s -S %[1]s'`, t.Value, buf.String(), baseName, commandName, ), t, t.Errs..., ) return } fmt.Fprintf(buf, "* %s\n", match.Description) fmt.Fprintf(buf, " Use %[1]s to specify this image or template\n\n", match.Argument) } groups.Add( "multiple-matches", heredoc.Docf(` The argument %[1]q could apply to the following Docker images, OpenShift image streams, or templates: %[2]s`, t.Value, buf.String(), ), t, t.Errs..., ) return case newapp.ErrPartialMatch: buf := &bytes.Buffer{} fmt.Fprintf(buf, "* %s\n", t.Match.Description) fmt.Fprintf(buf, " Use %[1]s to specify this image or template\n\n", t.Match.Argument) groups.Add( "partial-match", heredoc.Docf(` The argument %[1]q only partially matched the following Docker image, OpenShift image stream, or template: %[2]s`, t.Value, buf.String(), ), t, t.Errs..., ) return case newapp.ErrNoTagsFound: buf := &bytes.Buffer{} fmt.Fprintf(buf, " Use --allow-missing-imagestream-tags to use this image stream\n\n") groups.Add( "no-tags", heredoc.Docf(` The image stream %[1]q exists, but it has no tags. %[2]s`, t.Match.Name, buf.String(), ), t, t.Errs..., ) return } switch err { case errNoTokenAvailable: // TODO: improve by allowing token generation groups.Add("", "", fmt.Errorf("to install components you must be logged in with an OAuth token (instead of only a certificate)")) case newcmd.ErrNoInputs: // TODO: suggest things to the user groups.Add("", "", usageError(commandPath, newAppNoInput, baseName, commandName)) default: groups.Add("", "", err) } }
// 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 }
// FindRestartingPods inspects all Pods to see if they've restarted more than the threshold. logsCommandName is the name of // the command that should be invoked to see pod logs. securityPolicyCommandPattern is a format string accepting two replacement // variables for fmt.Sprintf - 1, the namespace of the current pod, 2 the service account of the pod. func FindRestartingPods(g osgraph.Graph, f osgraph.Namer, logsCommandName, securityPolicyCommandPattern string) []osgraph.Marker { markers := []osgraph.Marker{} for _, uncastPodNode := range g.NodesByKind(kubegraph.PodNodeKind) { podNode := uncastPodNode.(*kubegraph.PodNode) pod, ok := podNode.Object().(*kapi.Pod) if !ok { continue } for _, containerStatus := range pod.Status.ContainerStatuses { switch { case containerCrashLoopBackOff(containerStatus): var suggestion string switch { case containerIsNonRoot(pod, containerStatus.Name): suggestion = heredoc.Docf(` The container is starting and exiting repeatedly. This usually means the container is unable to start, misconfigured, or limited by security restrictions. Check the container logs with %s %s -c %s Current security policy prevents your containers from being run as the root user. Some images may fail expecting to be able to change ownership or permissions on directories. Your admin can grant you access to run containers that need to run as the root user with this command: %s `, logsCommandName, pod.Name, containerStatus.Name, fmt.Sprintf(securityPolicyCommandPattern, pod.Namespace, pod.Spec.ServiceAccountName)) default: suggestion = heredoc.Docf(` The container is starting and exiting repeatedly. This usually means the container is unable to start, misconfigured, or limited by security restrictions. Check the container logs with %s %s -c %s `, logsCommandName, pod.Name, containerStatus.Name) } markers = append(markers, osgraph.Marker{ Node: podNode, Severity: osgraph.ErrorSeverity, Key: CrashLoopingPodError, Message: fmt.Sprintf("container %q in %s is crash-looping", containerStatus.Name, f.ResourceName(podNode)), Suggestion: osgraph.Suggestion(suggestion), }) case containerRestartedRecently(containerStatus, nowFn()): markers = append(markers, osgraph.Marker{ Node: podNode, Severity: osgraph.WarningSeverity, Key: RestartingPodWarning, Message: fmt.Sprintf("container %q in %s has restarted within the last 10 minutes", containerStatus.Name, f.ResourceName(podNode)), }) case containerRestartedFrequently(containerStatus): markers = append(markers, osgraph.Marker{ Node: podNode, Severity: osgraph.WarningSeverity, Key: RestartingPodWarning, Message: fmt.Sprintf("container %q in %s has restarted %d times", containerStatus.Name, f.ResourceName(podNode), containerStatus.RestartCount), }) } } } return markers }
// Shortcut heredoc.Docf func Df(raw string, args ...interface{}) string { return heredoc.Docf(raw, args...) }