func addACBuildAnnotation(cmd *cobra.Command, args []string) error { const annoNamePattern = "appc.io/acbuild/command-%d" acb := newACBuild() man, err := util.GetManifest(acb.CurrentACIPath) if err != nil { return err } var acbuildCount int for _, ann := range man.Annotations { var tmpCount int n, _ := fmt.Sscanf(string(ann.Name), annoNamePattern, &tmpCount) if n == 1 && tmpCount > acbuildCount { acbuildCount = tmpCount } } command := cmd.Name() tmpcmd := cmd.Parent() for { command = tmpcmd.Name() + " " + command if tmpcmd == cmdAcbuild { break } tmpcmd = tmpcmd.Parent() } for _, a := range args { command += fmt.Sprintf(" %q", a) } return acb.AddAnnotation(fmt.Sprintf(annoNamePattern, acbuildCount+1), command) }
func (c *CLI) isInitDriverManagersCmd(cmd *cobra.Command) bool { return cmd.Parent() != nil && cmd != c.adapterCmd && cmd != c.adapterGetTypesCmd && cmd != c.versionCmd && c.isServiceCmd(cmd) && c.isModuleCmd(cmd) }
func flagName(cmd *cobra.Command, name string) string { parentName := doit.NSRoot if cmd.Parent() != nil { parentName = cmd.Parent().Name() } return fmt.Sprintf("%s-%s-%s", parentName, cmd.Name(), name) }
func cmdNS(cmd *cobra.Command) string { parentName := doit.NSRoot if cmd.Parent() != nil { parentName = cmd.Parent().Name() } return fmt.Sprintf("%s-%s", parentName, cmd.Name()) }
func tokenType(cmd *cobra.Command) string { if cmd.Parent().Name() == "team" { return tokenTeamManage } if asMember, _ := cmd.Flags().GetString("as-member"); asMember != "" { return tokenTeamAccess } return tokenPersonal }
func GenMarkdownCustom(cmd *cobra.Command, out io.Writer, linkHandler func(string) string) { name := cmd.CommandPath() short := cmd.Short long := cmd.Long if len(long) == 0 { long = short } fmt.Fprintf(out, "## %s\n\n", name) fmt.Fprintf(out, "%s\n\n", short) fmt.Fprintf(out, "### Synopsis\n\n") fmt.Fprintf(out, "\n%s\n\n", long) if cmd.Runnable() { fmt.Fprintf(out, "```\n%s\n```\n\n", cmd.UseLine()) } if len(cmd.Example) > 0 { fmt.Fprintf(out, "### Examples\n\n") fmt.Fprintf(out, "```\n%s\n```\n\n", cmd.Example) } printOptions(out, cmd, name) if hasSeeAlso(cmd) { fmt.Fprintf(out, "### SEE ALSO\n") if cmd.HasParent() { parent := cmd.Parent() pname := parent.CommandPath() link := pname + ".md" link = strings.Replace(link, " ", "_", -1) fmt.Fprintf(out, "* [%s](%s)\t - %s\n", pname, linkHandler(link), parent.Short) cmd.VisitParents(func(c *cobra.Command) { if c.DisableAutoGenTag { cmd.DisableAutoGenTag = c.DisableAutoGenTag } }) } children := cmd.Commands() sort.Sort(byName(children)) for _, child := range children { if !child.IsAvailableCommand() || child.IsHelpCommand() { continue } cname := name + " " + child.Name() link := cname + ".md" link = strings.Replace(link, " ", "_", -1) fmt.Fprintf(out, "* [%s](%s)\t - %s\n", cname, linkHandler(link), child.Short) } fmt.Fprintf(out, "\n") } if !cmd.DisableAutoGenTag { fmt.Fprintf(out, "###### Auto generated by spf13/cobra on %s\n", time.Now().Format("2-Jan-2006")) } }
// IsSiblingCommandExists receives a pointer to a cobra command and a target string. // Returns true if the target string is found in the list of sibling commands. func IsSiblingCommandExists(cmd *cobra.Command, targetCmdName string) bool { for _, c := range cmd.Parent().Commands() { if c.Name() == targetCmdName { return true } } return false }
func missingFlag(cmd *cobra.Command, name string) (Result, error) { util.Errorf("No option -%s specified!\n", hostPathFlag) text := cmd.Name() parent := cmd.Parent() if parent != nil { text = parent.Name() + " " + text } util.Infof("Please try something like: %s --%s='some value' ...\n\n", text, hostPathFlag) return Failure, nil }
func AddCommand(parent *cobra.Command, cmd *cobra.Command, local bool) *cobra.Command { parent.AddCommand(cmd) for i := range extensions { ext := &extensions[i] if ext.run == false && local == ext.local && ext.After == cmd.Name() { ext.Func(cmd.Parent()) ext.run = true } } return cmd }
func findChildOfRootCommand(command *cobra.Command) (*cobra.Command, bool) { for command.HasParent() { if !command.Parent().HasParent() { return command, true } command = command.Parent() } return nil, false }
func genMan(cmd *cobra.Command, header *GenManHeader) []byte { // something like `rootcmd subcmd1 subcmd2` commandName := cmd.CommandPath() // something like `rootcmd-subcmd1-subcmd2` dashCommandName := strings.Replace(commandName, " ", "-", -1) fillHeader(header, commandName) buf := new(bytes.Buffer) short := cmd.Short long := cmd.Long if len(long) == 0 { long = short } manPreamble(buf, header, commandName, short, long) manPrintOptions(buf, cmd) if len(cmd.Example) > 0 { fmt.Fprintf(buf, "# EXAMPLE\n") fmt.Fprintf(buf, "```\n%s\n```\n", cmd.Example) } if hasSeeAlso(cmd) { fmt.Fprintf(buf, "# SEE ALSO\n") seealsos := make([]string, 0) if cmd.HasParent() { parentPath := cmd.Parent().CommandPath() dashParentPath := strings.Replace(parentPath, " ", "-", -1) seealso := fmt.Sprintf("**%s(%s)**", dashParentPath, header.Section) seealsos = append(seealsos, seealso) cmd.VisitParents(func(c *cobra.Command) { if c.DisableAutoGenTag { cmd.DisableAutoGenTag = c.DisableAutoGenTag } }) } children := cmd.Commands() sort.Sort(byName(children)) for _, c := range children { if !c.IsAvailableCommand() || c.IsHelpCommand() { continue } seealso := fmt.Sprintf("**%s-%s(%s)**", dashCommandName, c.Name(), header.Section) seealsos = append(seealsos, seealso) } fmt.Fprintf(buf, "%s\n", strings.Join(seealsos, ", ")) } if !cmd.DisableAutoGenTag { fmt.Fprintf(buf, "# HISTORY\n%s Auto generated by spf13/cobra\n", header.Date.Format("2-Jan-2006")) } return buf.Bytes() }
// Complete completes the options for the Openshift cli status command. func (o *StatusOptions) Complete(f *clientcmd.Factory, cmd *cobra.Command, baseCLIName string, args []string, out io.Writer) error { if len(args) > 0 { return kcmdutil.UsageError(cmd, "no arguments should be provided") } o.logsCommandName = fmt.Sprintf("%s logs", cmd.Parent().CommandPath()) o.securityPolicyCommandFormat = "oadm policy add-scc-to-user anyuid -n %s -z %s" o.setProbeCommandName = fmt.Sprintf("%s set probe", cmd.Parent().CommandPath()) client, kclient, kclientset, err := f.Clients() if err != nil { return err } config, err := f.OpenShiftClientConfig.ClientConfig() if err != nil { return err } if o.allNamespaces { o.namespace = kapi.NamespaceAll } else { namespace, _, err := f.DefaultNamespace() if err != nil { return err } o.namespace = namespace } if baseCLIName == "" { baseCLIName = "oc" } o.describer = &describe.ProjectStatusDescriber{ OldK: kclient, K: kclientset, C: client, Server: config.Host, Suggest: o.verbose, CommandBaseName: baseCLIName, // TODO: Remove these and reference them inside the markers using constants. LogsCommandName: o.logsCommandName, SecurityPolicyCommandFormat: o.securityPolicyCommandFormat, SetProbeCommandName: o.setProbeCommandName, } o.out = out return nil }
func RunCompletion(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string) error { if len(args) == 0 { return cmdutil.UsageError(cmd, "Shell not specified.") } if len(args) > 1 { return cmdutil.UsageError(cmd, "Too many arguments. Expected only the shell type.") } run, found := completion_shells[args[0]] if !found { return cmdutil.UsageError(cmd, "Unsupported shell type %q.", args[0]) } return run(out, cmd.Parent()) }
// Complete verifies command line arguments and loads data from the command environment func (p *ExecOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, argsIn []string, argsLenAtDash int) error { // Let kubectl exec follow rules for `--`, see #13004 issue if len(p.PodName) == 0 && (len(argsIn) == 0 || argsLenAtDash == 0) { return cmdutil.UsageError(cmd, execUsageStr) } if len(p.PodName) != 0 { printDeprecationWarning("exec POD_NAME", "-p POD_NAME") if len(argsIn) < 1 { return cmdutil.UsageError(cmd, execUsageStr) } p.Command = argsIn } else { p.PodName = argsIn[0] p.Command = argsIn[1:] if len(p.Command) < 1 { return cmdutil.UsageError(cmd, execUsageStr) } } cmdParent := cmd.Parent() if cmdParent != nil { p.FullCmdName = cmdParent.CommandPath() } if len(p.FullCmdName) > 0 && cmdutil.IsSiblingCommandExists(cmd, "describe") { p.SuggestedCmdUsage = fmt.Sprintf("Use '%s describe pod/%s' to see all of the containers in this pod.", p.FullCmdName, p.PodName) } namespace, _, err := f.DefaultNamespace() if err != nil { return err } p.Namespace = namespace config, err := f.ClientConfig() if err != nil { return err } p.Config = config clientset, err := f.ClientSet() if err != nil { return err } p.PodClient = clientset.Core() return nil }
func isInitDriverManagersCmd(cmd *cobra.Command) bool { return cmd.Parent() != nil && cmd != adapterCmd && cmd != adapterGetTypesCmd && cmd != versionCmd && cmd != serviceCmd && cmd != serviceInitSysCmd && cmd != installCmd && cmd != uninstallCmd && cmd != serviceStatusCmd && cmd != serviceStopCmd && !(cmd == serviceStartCmd && (client != "" || fg || force)) && cmd != moduleCmd && cmd != moduleTypesCmd && cmd != moduleInstancesCmd && cmd != moduleInstancesListCmd }
// SiblingCommand returns a sibling command to the given command func SiblingCommand(cmd *cobra.Command, name string) string { c := cmd.Parent() command := []string{} for c != nil { glog.V(5).Infof("Found parent command: %s", c.Name()) command = append([]string{c.Name()}, command...) c = c.Parent() } // Replace the root command with what was actually used // in the command line glog.V(4).Infof("Setting root command to: %s", os.Args[0]) command[0] = os.Args[0] // Append the sibling command command = append(command, name) glog.V(4).Infof("The sibling command is: %s", strings.Join(command, " ")) return strings.Join(command, " ") }
// Complete completes the options for the Openshift cli status command. func (o *StatusOptions) Complete(f *clientcmd.Factory, cmd *cobra.Command, args []string, out io.Writer) error { if len(args) > 0 { return kcmdutil.UsageError(cmd, "no arguments should be provided") } o.logsCommandName = fmt.Sprintf("%s logs -p", cmd.Parent().CommandPath()) o.securityPolicyCommandFormat = "oadm policy add-scc-to-user anyuid -n %s -z %s" client, kclient, err := f.Clients() if err != nil { return err } config, err := f.OpenShiftClientConfig.ClientConfig() if err != nil { return err } if o.allNamespaces { o.namespace = kapi.NamespaceAll } else { namespace, _, err := f.DefaultNamespace() if err != nil { return err } o.namespace = namespace } o.describer = &describe.ProjectStatusDescriber{ K: kclient, C: client, Server: config.Host, Suggest: o.verbose, LogsCommandName: o.logsCommandName, SecurityPolicyCommandFormat: o.securityPolicyCommandFormat, } o.out = out return nil }
func isInitDriverManagersCmd(cmd *cobra.Command) bool { isClient, _ := cmd.Flags().GetBool("client") isForeground, _ := cmd.Flags().GetBool("foreground") return cmd.Parent() != nil && cmd != adapterCmd && cmd != adapterGetTypesCmd && cmd != versionCmd && cmd != serviceCmd && cmd != serviceInitSysCmd && cmd != serviceInstallCmd && cmd != serviceStatusCmd && cmd != serviceStopCmd && !(cmd == serviceStartCmd && (isClient || isForeground)) && cmd != moduleCmd && cmd != moduleTypesCmd && cmd != moduleInstancesCmd && cmd != moduleInstancesListCmd }
func RunCompletion(out io.Writer, boilerPlate string, cmd *cobra.Command, args []string) error { if len(args) == 0 { return cmdutil.UsageError(cmd, "Shell not specified.") } if len(args) > 1 { return cmdutil.UsageError(cmd, "Too many arguments. Expected only the shell type.") } run, found := completion_shells[args[0]] if !found { return cmdutil.UsageError(cmd, "Unsupported shell type %q.", args[0]) } if len(boilerPlate) == 0 { boilerPlate = defaultBoilerPlate } if _, err := out.Write([]byte(boilerPlate)); err != nil { return err } return run(out, cmd.Parent()) }
func HandleList(cmd *cobra.Command, args []string) { var ( node string target []string tag []string group []string err tree_lib.TreeError ) err.From = tree_lib.FROM_HANDLE_LIST node, err.Err = cmd.Flags().GetString("node") if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } target, err.Err = cmd.Flags().GetStringSlice("target") if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } tag, err.Err = cmd.Flags().GetStringSlice("tag") if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } group, err.Err = cmd.Flags().GetStringSlice("group") if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } var ( docker_cmd = tree_docker.DockerCmd{} ) docker_cmd.Content = make(map[string]string) if cmd.Parent().Name() == "container" { docker_cmd.Command = tree_docker.COMMAND_DOCKER_CONTAINER_LIST } else { docker_cmd.Command = tree_docker.COMMAND_DOCKER_IMAGE_LIST } docker_cmd.Content["all"] = "yes" SendDockerCommand(docker_cmd, node, target, tag, group) }
// RunGet implements the generic Get command // TODO: convert all direct flag accessors to a struct and pass that instead of cmd func RunGet(f *cmdutil.Factory, out io.Writer, errOut io.Writer, cmd *cobra.Command, args []string, options *GetOptions) error { if len(options.Raw) > 0 { restClient, err := f.RESTClient() if err != nil { return err } stream, err := restClient.Get().RequestURI(options.Raw).Stream() if err != nil { return err } defer stream.Close() for { buffer := make([]byte, 1024, 1024) bytesRead, err := stream.Read(buffer) if bytesRead > 0 { fmt.Printf("%s", string(buffer[:bytesRead])) } if err == io.EOF { return nil } if err != nil { return err } } } selector := cmdutil.GetFlagString(cmd, "selector") allNamespaces := cmdutil.GetFlagBool(cmd, "all-namespaces") showKind := cmdutil.GetFlagBool(cmd, "show-kind") mapper, typer := f.Object() printAll := false filterFuncs := f.DefaultResourceFilterFunc() filterOpts := f.DefaultResourceFilterOptions(cmd, allNamespaces) cmdNamespace, enforceNamespace, err := f.DefaultNamespace() if err != nil { return err } if allNamespaces { enforceNamespace = false } if len(args) == 0 && cmdutil.IsFilenameEmpty(options.Filenames) { fmt.Fprint(errOut, "You must specify the type of resource to get. ", valid_resources) fullCmdName := cmd.Parent().CommandPath() usageString := "Required resource not specified." if len(fullCmdName) > 0 && cmdutil.IsSiblingCommandExists(cmd, "explain") { usageString = fmt.Sprintf("%s\nUse \"%s explain <resource>\" for a detailed description of that resource (e.g. %[2]s explain pods).", usageString, fullCmdName) } return cmdutil.UsageError(cmd, usageString) } // determine if args contains "all" for _, a := range args { if a == "all" { printAll = true break } } // always show resources when getting by name or filename argsHasNames, err := resource.HasNames(args) if err != nil { return err } if len(options.Filenames) > 0 || argsHasNames { cmd.Flag("show-all").Value.Set("true") } export := cmdutil.GetFlagBool(cmd, "export") // handle watch separately since we cannot watch multiple resource types isWatch, isWatchOnly := cmdutil.GetFlagBool(cmd, "watch"), cmdutil.GetFlagBool(cmd, "watch-only") if isWatch || isWatchOnly { r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). NamespaceParam(cmdNamespace).DefaultNamespace().AllNamespaces(allNamespaces). FilenameParam(enforceNamespace, &options.FilenameOptions). SelectorParam(selector). ExportParam(export). ResourceTypeOrNameArgs(true, args...). SingleResourceType(). Latest(). Do() err := r.Err() if err != nil { return err } infos, err := r.Infos() if err != nil { return err } if len(infos) != 1 { return fmt.Errorf("watch is only supported on individual resources and resource collections - %d resources were found", len(infos)) } info := infos[0] mapping := info.ResourceMapping() printer, err := f.PrinterForMapping(cmd, mapping, allNamespaces) if err != nil { return err } obj, err := r.Object() if err != nil { return err } // watching from resourceVersion 0, starts the watch at ~now and // will return an initial watch event. Starting form ~now, rather // the rv of the object will insure that we start the watch from // inside the watch window, which the rv of the object might not be. rv := "0" isList := meta.IsListType(obj) if isList { // the resourceVersion of list objects is ~now but won't return // an initial watch event rv, err = mapping.MetadataAccessor.ResourceVersion(obj) if err != nil { return err } } // print the current object filteredResourceCount := 0 if !isWatchOnly { if err := printer.PrintObj(obj, out); err != nil { return fmt.Errorf("unable to output the provided object: %v", err) } filteredResourceCount++ cmdutil.PrintFilterCount(filteredResourceCount, mapping.Resource, errOut, filterOpts) } // print watched changes w, err := r.Watch(rv) if err != nil { return err } first := true filteredResourceCount = 0 intr := interrupt.New(nil, w.Stop) intr.Run(func() error { _, err := watch.Until(0, w, func(e watch.Event) (bool, error) { if !isList && first { // drop the initial watch event in the single resource case first = false return false, nil } err := printer.PrintObj(e.Object, out) if err != nil { return false, err } filteredResourceCount++ cmdutil.PrintFilterCount(filteredResourceCount, mapping.Resource, errOut, filterOpts) return false, nil }) return err }) return nil } r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). NamespaceParam(cmdNamespace).DefaultNamespace().AllNamespaces(allNamespaces). FilenameParam(enforceNamespace, &options.FilenameOptions). SelectorParam(selector). ExportParam(export). ResourceTypeOrNameArgs(true, args...). ContinueOnError(). Latest(). Flatten(). Do() err = r.Err() if err != nil { return err } printer, generic, err := cmdutil.PrinterForCommand(cmd) if err != nil { return err } if generic { clientConfig, err := f.ClientConfig() if err != nil { return err } // the outermost object will be converted to the output-version, but inner // objects can use their mappings version, err := cmdutil.OutputVersion(cmd, clientConfig.GroupVersion) if err != nil { return err } var errs []error singular := false infos, err := r.IntoSingular(&singular).Infos() if err != nil { if singular { return err } errs = append(errs, err) } res := "" if len(infos) > 0 { res = infos[0].ResourceMapping().Resource } obj, err := resource.AsVersionedObject(infos, !singular, version, f.JSONEncoder()) if err != nil { return err } isList := meta.IsListType(obj) if isList { filteredResourceCount, items, err := cmdutil.FilterResourceList(obj, filterFuncs, filterOpts) if err != nil { return err } filteredObj, err := cmdutil.ObjectListToVersionedObject(items, version) if err != nil { return err } if err := printer.PrintObj(filteredObj, out); err != nil { errs = append(errs, err) } cmdutil.PrintFilterCount(filteredResourceCount, res, errOut, filterOpts) return utilerrors.Reduce(utilerrors.Flatten(utilerrors.NewAggregate(errs))) } filteredResourceCount := 0 if isFiltered, err := filterFuncs.Filter(obj, filterOpts); !isFiltered { if err != nil { glog.V(2).Infof("Unable to filter resource: %v", err) } else if err := printer.PrintObj(obj, out); err != nil { errs = append(errs, err) } } else if isFiltered { filteredResourceCount++ } cmdutil.PrintFilterCount(filteredResourceCount, res, errOut, filterOpts) return utilerrors.Reduce(utilerrors.Flatten(utilerrors.NewAggregate(errs))) } allErrs := []error{} infos, err := r.Infos() if err != nil { allErrs = append(allErrs, err) } objs := make([]runtime.Object, len(infos)) for ix := range infos { objs[ix] = infos[ix].Object } sorting, err := cmd.Flags().GetString("sort-by") if err != nil { return err } var sorter *kubectl.RuntimeSort if len(sorting) > 0 && len(objs) > 1 { clientConfig, err := f.ClientConfig() if err != nil { return err } version, err := cmdutil.OutputVersion(cmd, clientConfig.GroupVersion) if err != nil { return err } for ix := range infos { objs[ix], err = infos[ix].Mapping.ConvertToVersion(infos[ix].Object, version) if err != nil { allErrs = append(allErrs, err) continue } } // TODO: questionable if sorter, err = kubectl.SortObjects(f.Decoder(true), objs, sorting); err != nil { return err } } // use the default printer for each object printer = nil var lastMapping *meta.RESTMapping w := kubectl.GetNewTabWriter(out) filteredResourceCount := 0 if cmdutil.MustPrintWithKinds(objs, infos, sorter, printAll) { showKind = true } for ix := range objs { var mapping *meta.RESTMapping var original runtime.Object if sorter != nil { mapping = infos[sorter.OriginalPosition(ix)].Mapping original = infos[sorter.OriginalPosition(ix)].Object } else { mapping = infos[ix].Mapping original = infos[ix].Object } if printer == nil || lastMapping == nil || mapping == nil || mapping.Resource != lastMapping.Resource { if printer != nil { w.Flush() cmdutil.PrintFilterCount(filteredResourceCount, lastMapping.Resource, errOut, filterOpts) } printer, err = f.PrinterForMapping(cmd, mapping, allNamespaces) if err != nil { allErrs = append(allErrs, err) continue } // add linebreak between resource groups (if there is more than one) // skip linebreak above first resource group noHeaders := cmdutil.GetFlagBool(cmd, "no-headers") if lastMapping != nil && !noHeaders { fmt.Fprintf(errOut, "%s\n", "") } lastMapping = mapping } // filter objects if filter has been defined for current object if isFiltered, err := filterFuncs.Filter(original, filterOpts); isFiltered { if err == nil { filteredResourceCount++ continue } allErrs = append(allErrs, err) } if resourcePrinter, found := printer.(*kubectl.HumanReadablePrinter); found { resourceName := resourcePrinter.GetResourceKind() if mapping != nil { if resourceName == "" { resourceName = mapping.Resource } if alias, ok := kubectl.ResourceShortFormFor(mapping.Resource); ok { resourceName = alias } else if resourceName == "" { resourceName = "none" } } else { resourceName = "none" } if showKind { resourcePrinter.EnsurePrintWithKind(resourceName) } if err := printer.PrintObj(original, w); err != nil { allErrs = append(allErrs, err) } continue } if err := printer.PrintObj(original, w); err != nil { allErrs = append(allErrs, err) continue } } w.Flush() if printer != nil && lastMapping != nil { cmdutil.PrintFilterCount(filteredResourceCount, lastMapping.Resource, errOut, filterOpts) } return utilerrors.NewAggregate(allErrs) }
func Run(f *cmdutil.Factory, opts *RunOptions, cmdIn io.Reader, cmdOut, cmdErr io.Writer, cmd *cobra.Command, args []string, argsLenAtDash int) error { if len(os.Args) > 1 && os.Args[1] == "run-container" { printDeprecationWarning("run", "run-container") } // Let kubectl run follow rules for `--`, see #13004 issue if len(args) == 0 || argsLenAtDash == 0 { return cmdutil.UsageError(cmd, "NAME is required for run") } interactive := cmdutil.GetFlagBool(cmd, "stdin") tty := cmdutil.GetFlagBool(cmd, "tty") if tty && !interactive { return cmdutil.UsageError(cmd, "-i/--stdin is required for containers with -t/--tty=true") } replicas := cmdutil.GetFlagInt(cmd, "replicas") if interactive && replicas != 1 { return cmdutil.UsageError(cmd, fmt.Sprintf("-i/--stdin requires that replicas is 1, found %d", replicas)) } namespace, _, err := f.DefaultNamespace() if err != nil { return err } restartPolicy, err := getRestartPolicy(cmd, interactive) if err != nil { return err } if restartPolicy != api.RestartPolicyAlways && replicas != 1 { return cmdutil.UsageError(cmd, fmt.Sprintf("--restart=%s requires that --replicas=1, found %d", restartPolicy, replicas)) } generatorName := cmdutil.GetFlagString(cmd, "generator") if len(generatorName) == 0 && opts != nil { switch { case restartPolicy == api.RestartPolicyAlways: generatorName = opts.DefaultRestartAlwaysGenerator default: generatorName = opts.DefaultGenerator } } if len(generatorName) == 0 { client, err := f.Client() if err != nil { return err } resourcesList, err := client.Discovery().ServerResources() // ServerResources ignores errors for old servers do not expose discovery if err != nil { return fmt.Errorf("failed to discover supported resources: %v", err) } switch restartPolicy { case api.RestartPolicyAlways: if contains(resourcesList, v1beta1.SchemeGroupVersion.WithResource("deployments")) { generatorName = "deployment/v1beta1" } else { generatorName = "run/v1" } case api.RestartPolicyOnFailure: if contains(resourcesList, batchv1.SchemeGroupVersion.WithResource("jobs")) { generatorName = "job/v1" } else if contains(resourcesList, v1beta1.SchemeGroupVersion.WithResource("jobs")) { generatorName = "job/v1beta1" } else { generatorName = "run-pod/v1" } case api.RestartPolicyNever: generatorName = "run-pod/v1" } } generators := f.Generators("run") generator, found := generators[generatorName] if !found { return cmdutil.UsageError(cmd, fmt.Sprintf("generator %q not found.", generatorName)) } names := generator.ParamNames() params := kubectl.MakeParams(cmd, names) params["name"] = args[0] if len(args) > 1 { params["args"] = args[1:] } params["env"] = cmdutil.GetFlagStringSlice(cmd, "env") obj, _, mapper, mapping, err := createGeneratedObject(f, cmd, generator, names, params, cmdutil.GetFlagString(cmd, "overrides"), namespace) if err != nil { return err } if cmdutil.GetFlagBool(cmd, "expose") { serviceGenerator := cmdutil.GetFlagString(cmd, "service-generator") if len(serviceGenerator) == 0 { return cmdutil.UsageError(cmd, fmt.Sprintf("No service generator specified")) } if err := generateService(f, cmd, args, serviceGenerator, params, namespace, cmdOut); err != nil { return err } } attachFlag := cmd.Flags().Lookup("attach") attach := cmdutil.GetFlagBool(cmd, "attach") if !attachFlag.Changed && interactive { attach = true } remove := cmdutil.GetFlagBool(cmd, "rm") if !attach && remove { return cmdutil.UsageError(cmd, "--rm should only be used for attached containers") } if attach { opts := &AttachOptions{ In: cmdIn, Out: cmdOut, Err: cmdErr, Stdin: interactive, TTY: tty, CommandName: cmd.Parent().CommandPath() + " attach", Attach: &DefaultRemoteAttach{}, } config, err := f.ClientConfig() if err != nil { return err } opts.Config = config client, err := f.Client() if err != nil { return err } opts.Client = client attachablePod, err := f.AttachablePodForObject(obj) if err != nil { return err } err = handleAttachPod(f, client, attachablePod, opts) if err != nil { return err } if remove { namespace, err = mapping.MetadataAccessor.Namespace(obj) if err != nil { return err } var name string name, err = mapping.MetadataAccessor.Name(obj) if err != nil { return err } _, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd)) r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). ContinueOnError(). NamespaceParam(namespace).DefaultNamespace(). ResourceNames(mapping.Resource, name). Flatten(). Do() return ReapResult(r, f, cmdOut, true, true, 0, -1, false, mapper) } return nil } outputFormat := cmdutil.GetFlagString(cmd, "output") if outputFormat != "" { return f.PrintObject(cmd, mapper, obj, cmdOut) } cmdutil.PrintSuccess(mapper, false, cmdOut, mapping.Resource, args[0], "created") return nil }
// RunGet implements the generic Get command // TODO: convert all direct flag accessors to a struct and pass that instead of cmd func RunGet(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args []string, options *GetOptions) error { if len(options.Raw) > 0 { restClient, err := f.RESTClient() if err != nil { return err } stream, err := restClient.Get().RequestURI(options.Raw).Stream() if err != nil { return err } defer stream.Close() _, err = io.Copy(out, stream) if err != nil && err != io.EOF { return err } return nil } selector := cmdutil.GetFlagString(cmd, "selector") allNamespaces := cmdutil.GetFlagBool(cmd, "all-namespaces") showKind := cmdutil.GetFlagBool(cmd, "show-kind") mapper, typer, err := f.UnstructuredObject() if err != nil { return err } filterFuncs := f.DefaultResourceFilterFunc() filterOpts := f.DefaultResourceFilterOptions(cmd, allNamespaces) cmdNamespace, enforceNamespace, err := f.DefaultNamespace() if err != nil { return err } if allNamespaces { enforceNamespace = false } if len(args) == 0 && cmdutil.IsFilenameEmpty(options.Filenames) { fmt.Fprint(errOut, "You must specify the type of resource to get. ", valid_resources) fullCmdName := cmd.Parent().CommandPath() usageString := "Required resource not specified." if len(fullCmdName) > 0 && cmdutil.IsSiblingCommandExists(cmd, "explain") { usageString = fmt.Sprintf("%s\nUse \"%s explain <resource>\" for a detailed description of that resource (e.g. %[2]s explain pods).", usageString, fullCmdName) } return cmdutil.UsageError(cmd, usageString) } // always show resources when getting by name or filename argsHasNames, err := resource.HasNames(args) if err != nil { return err } if len(options.Filenames) > 0 || argsHasNames { cmd.Flag("show-all").Value.Set("true") } export := cmdutil.GetFlagBool(cmd, "export") // handle watch separately since we cannot watch multiple resource types isWatch, isWatchOnly := cmdutil.GetFlagBool(cmd, "watch"), cmdutil.GetFlagBool(cmd, "watch-only") if isWatch || isWatchOnly { r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), runtime.UnstructuredJSONScheme). NamespaceParam(cmdNamespace).DefaultNamespace().AllNamespaces(allNamespaces). FilenameParam(enforceNamespace, &options.FilenameOptions). SelectorParam(selector). ExportParam(export). ResourceTypeOrNameArgs(true, args...). SingleResourceType(). Latest(). Do() err := r.Err() if err != nil { return err } infos, err := r.Infos() if err != nil { return err } if len(infos) != 1 { return fmt.Errorf("watch is only supported on individual resources and resource collections - %d resources were found", len(infos)) } info := infos[0] mapping := info.ResourceMapping() printer, err := f.PrinterForMapping(cmd, mapping, allNamespaces) if err != nil { return err } obj, err := r.Object() if err != nil { return err } // watching from resourceVersion 0, starts the watch at ~now and // will return an initial watch event. Starting form ~now, rather // the rv of the object will insure that we start the watch from // inside the watch window, which the rv of the object might not be. rv := "0" isList := meta.IsListType(obj) if isList { // the resourceVersion of list objects is ~now but won't return // an initial watch event rv, err = mapping.MetadataAccessor.ResourceVersion(obj) if err != nil { return err } } // print the current object filteredResourceCount := 0 if !isWatchOnly { if err := printer.PrintObj(obj, out); err != nil { return fmt.Errorf("unable to output the provided object: %v", err) } filteredResourceCount++ cmdutil.PrintFilterCount(filteredResourceCount, mapping.Resource, filterOpts) } // print watched changes w, err := r.Watch(rv) if err != nil { return err } first := true filteredResourceCount = 0 intr := interrupt.New(nil, w.Stop) intr.Run(func() error { _, err := watch.Until(0, w, func(e watch.Event) (bool, error) { if !isList && first { // drop the initial watch event in the single resource case first = false return false, nil } err := printer.PrintObj(e.Object, out) if err != nil { return false, err } filteredResourceCount++ cmdutil.PrintFilterCount(filteredResourceCount, mapping.Resource, filterOpts) return false, nil }) return err }) return nil } r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), runtime.UnstructuredJSONScheme). NamespaceParam(cmdNamespace).DefaultNamespace().AllNamespaces(allNamespaces). FilenameParam(enforceNamespace, &options.FilenameOptions). SelectorParam(selector). ExportParam(export). ResourceTypeOrNameArgs(true, args...). ContinueOnError(). Latest(). Flatten(). Do() err = r.Err() if err != nil { return err } printer, generic, err := cmdutil.PrinterForCommand(cmd) if err != nil { return err } if generic { // we flattened the data from the builder, so we have individual items, but now we'd like to either: // 1. if there is more than one item, combine them all into a single list // 2. if there is a single item and that item is a list, leave it as its specific list // 3. if there is a single item and it is not a a list, leave it as a single item var errs []error singular := false infos, err := r.IntoSingular(&singular).Infos() if err != nil { if singular { return err } errs = append(errs, err) } if len(infos) == 0 && len(errs) == 0 { outputEmptyListWarning(errOut) } res := "" if len(infos) > 0 { res = infos[0].ResourceMapping().Resource } var obj runtime.Object if singular { obj = infos[0].Object } else { // we have more than one item, so coerce all items into a list list := &runtime.UnstructuredList{ Object: map[string]interface{}{ "kind": "List", "apiVersion": "v1", "metadata": map[string]interface{}{}, }, } for _, info := range infos { list.Items = append(list.Items, info.Object.(*runtime.Unstructured)) } obj = list } isList := meta.IsListType(obj) if isList { filteredResourceCount, items, err := cmdutil.FilterResourceList(obj, filterFuncs, filterOpts) if err != nil { return err } // take the filtered items and create a new list for display list := &runtime.UnstructuredList{ Object: map[string]interface{}{ "kind": "List", "apiVersion": "v1", "metadata": map[string]interface{}{}, }, } if listMeta, err := meta.ListAccessor(obj); err == nil { list.Object["selfLink"] = listMeta.GetSelfLink() list.Object["resourceVersion"] = listMeta.GetResourceVersion() } for _, item := range items { list.Items = append(list.Items, item.(*runtime.Unstructured)) } if err := printer.PrintObj(list, out); err != nil { errs = append(errs, err) } cmdutil.PrintFilterCount(filteredResourceCount, res, filterOpts) return utilerrors.Reduce(utilerrors.Flatten(utilerrors.NewAggregate(errs))) } filteredResourceCount := 0 if isFiltered, err := filterFuncs.Filter(obj, filterOpts); !isFiltered { if err != nil { glog.V(2).Infof("Unable to filter resource: %v", err) } else if err := printer.PrintObj(obj, out); err != nil { errs = append(errs, err) } } else if isFiltered { filteredResourceCount++ } cmdutil.PrintFilterCount(filteredResourceCount, res, filterOpts) return utilerrors.Reduce(utilerrors.Flatten(utilerrors.NewAggregate(errs))) } allErrs := []error{} errs := sets.NewString() infos, err := r.Infos() if err != nil { allErrs = append(allErrs, err) } if len(infos) == 0 && len(allErrs) == 0 { outputEmptyListWarning(errOut) } objs := make([]runtime.Object, len(infos)) for ix := range infos { objs[ix] = infos[ix].Object } sorting, err := cmd.Flags().GetString("sort-by") if err != nil { return err } var sorter *kubectl.RuntimeSort if len(sorting) > 0 && len(objs) > 1 { // TODO: questionable if sorter, err = kubectl.SortObjects(f.Decoder(true), objs, sorting); err != nil { return err } } // use the default printer for each object printer = nil var lastMapping *meta.RESTMapping w := kubectl.GetNewTabWriter(out) filteredResourceCount := 0 if resource.MultipleTypesRequested(args) || cmdutil.MustPrintWithKinds(objs, infos, sorter) { showKind = true } for ix := range objs { var mapping *meta.RESTMapping var original runtime.Object if sorter != nil { mapping = infos[sorter.OriginalPosition(ix)].Mapping original = infos[sorter.OriginalPosition(ix)].Object } else { mapping = infos[ix].Mapping original = infos[ix].Object } if printer == nil || lastMapping == nil || mapping == nil || mapping.Resource != lastMapping.Resource { if printer != nil { w.Flush() cmdutil.PrintFilterCount(filteredResourceCount, lastMapping.Resource, filterOpts) } printer, err = f.PrinterForMapping(cmd, mapping, allNamespaces) if err != nil { if !errs.Has(err.Error()) { errs.Insert(err.Error()) allErrs = append(allErrs, err) } continue } // add linebreak between resource groups (if there is more than one) // skip linebreak above first resource group noHeaders := cmdutil.GetFlagBool(cmd, "no-headers") if lastMapping != nil && !noHeaders { fmt.Fprintf(errOut, "%s\n", "") } lastMapping = mapping } // filter objects if filter has been defined for current object if isFiltered, err := filterFuncs.Filter(original, filterOpts); isFiltered { if err == nil { filteredResourceCount++ continue } if !errs.Has(err.Error()) { errs.Insert(err.Error()) allErrs = append(allErrs, err) } } if resourcePrinter, found := printer.(*kubectl.HumanReadablePrinter); found { resourceName := resourcePrinter.GetResourceKind() if mapping != nil { if resourceName == "" { resourceName = mapping.Resource } if alias, ok := kubectl.ResourceShortFormFor(mapping.Resource); ok { resourceName = alias } else if resourceName == "" { resourceName = "none" } } else { resourceName = "none" } if showKind { resourcePrinter.EnsurePrintWithKind(resourceName) } if err := printer.PrintObj(original, w); err != nil { if !errs.Has(err.Error()) { errs.Insert(err.Error()) allErrs = append(allErrs, err) } } continue } if err := printer.PrintObj(original, w); err != nil { if !errs.Has(err.Error()) { errs.Insert(err.Error()) allErrs = append(allErrs, err) } continue } } w.Flush() if printer != nil && lastMapping != nil { cmdutil.PrintFilterCount(filteredResourceCount, lastMapping.Resource, filterOpts) } return utilerrors.NewAggregate(allErrs) }
func Run(f cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer, cmd *cobra.Command, args []string, argsLenAtDash int) error { if len(os.Args) > 1 && os.Args[1] == "run-container" { printDeprecationWarning("run", "run-container") } // Let kubectl run follow rules for `--`, see #13004 issue if len(args) == 0 || argsLenAtDash == 0 { return cmdutil.UsageError(cmd, "NAME is required for run") } // validate image name imageName := cmdutil.GetFlagString(cmd, "image") validImageRef := reference.ReferenceRegexp.MatchString(imageName) if !validImageRef { return fmt.Errorf("Invalid image name %q: %v", imageName, reference.ErrReferenceInvalidFormat) } interactive := cmdutil.GetFlagBool(cmd, "stdin") tty := cmdutil.GetFlagBool(cmd, "tty") if tty && !interactive { return cmdutil.UsageError(cmd, "-i/--stdin is required for containers with -t/--tty=true") } replicas := cmdutil.GetFlagInt(cmd, "replicas") if interactive && replicas != 1 { return cmdutil.UsageError(cmd, fmt.Sprintf("-i/--stdin requires that replicas is 1, found %d", replicas)) } namespace, _, err := f.DefaultNamespace() if err != nil { return err } restartPolicy, err := getRestartPolicy(cmd, interactive) if err != nil { return err } if restartPolicy != api.RestartPolicyAlways && replicas != 1 { return cmdutil.UsageError(cmd, fmt.Sprintf("--restart=%s requires that --replicas=1, found %d", restartPolicy, replicas)) } attachFlag := cmd.Flags().Lookup("attach") attach := cmdutil.GetFlagBool(cmd, "attach") if !attachFlag.Changed && interactive { attach = true } remove := cmdutil.GetFlagBool(cmd, "rm") if !attach && remove { return cmdutil.UsageError(cmd, "--rm should only be used for attached containers") } if attach && cmdutil.GetDryRunFlag(cmd) { return cmdutil.UsageError(cmd, "--dry-run can't be used with attached containers options (--attach, --stdin, or --tty)") } if err := verifyImagePullPolicy(cmd); err != nil { return err } generatorName := cmdutil.GetFlagString(cmd, "generator") schedule := cmdutil.GetFlagString(cmd, "schedule") if len(schedule) != 0 && len(generatorName) == 0 { generatorName = "cronjob/v2alpha1" } if len(generatorName) == 0 { clientset, err := f.ClientSet() if err != nil { return err } resourcesList, err := clientset.Discovery().ServerResources() // ServerResources ignores errors for old servers do not expose discovery if err != nil { return fmt.Errorf("failed to discover supported resources: %v", err) } switch restartPolicy { case api.RestartPolicyAlways: if contains(resourcesList, v1beta1.SchemeGroupVersion.WithResource("deployments")) { generatorName = "deployment/v1beta1" } else { generatorName = "run/v1" } case api.RestartPolicyOnFailure: if contains(resourcesList, batchv1.SchemeGroupVersion.WithResource("jobs")) { generatorName = "job/v1" } else if contains(resourcesList, v1beta1.SchemeGroupVersion.WithResource("jobs")) { generatorName = "job/v1beta1" } else { generatorName = "run-pod/v1" } case api.RestartPolicyNever: generatorName = "run-pod/v1" } } if generatorName == "job/v1beta1" { fmt.Fprintf(cmdErr, "DEPRECATED: --generator=job/v1beta1 is deprecated, use job/v1 instead.\n") } generators := f.Generators("run") generator, found := generators[generatorName] if !found { return cmdutil.UsageError(cmd, fmt.Sprintf("generator %q not found.", generatorName)) } names := generator.ParamNames() params := kubectl.MakeParams(cmd, names) params["name"] = args[0] if len(args) > 1 { params["args"] = args[1:] } params["env"] = cmdutil.GetFlagStringSlice(cmd, "env") obj, _, mapper, mapping, err := createGeneratedObject(f, cmd, generator, names, params, cmdutil.GetFlagString(cmd, "overrides"), namespace) if err != nil { return err } if cmdutil.GetFlagBool(cmd, "expose") { serviceGenerator := cmdutil.GetFlagString(cmd, "service-generator") if len(serviceGenerator) == 0 { return cmdutil.UsageError(cmd, fmt.Sprintf("No service generator specified")) } if err := generateService(f, cmd, args, serviceGenerator, params, namespace, cmdOut); err != nil { return err } } if attach { quiet := cmdutil.GetFlagBool(cmd, "quiet") opts := &AttachOptions{ StreamOptions: StreamOptions{ In: cmdIn, Out: cmdOut, Err: cmdErr, Stdin: interactive, TTY: tty, Quiet: quiet, }, CommandName: cmd.Parent().CommandPath() + " attach", Attach: &DefaultRemoteAttach{}, } config, err := f.ClientConfig() if err != nil { return err } opts.Config = config clientset, err := f.ClientSet() if err != nil { return err } opts.PodClient = clientset.Core() attachablePod, err := f.AttachablePodForObject(obj) if err != nil { return err } err = handleAttachPod(f, clientset.Core(), attachablePod.Namespace, attachablePod.Name, opts, quiet) if err != nil { return err } var pod *api.Pod leaveStdinOpen := cmdutil.GetFlagBool(cmd, "leave-stdin-open") waitForExitCode := !leaveStdinOpen && restartPolicy == api.RestartPolicyNever if waitForExitCode { pod, err = waitForPodTerminated(clientset.Core(), attachablePod.Namespace, attachablePod.Name, opts.Out, quiet) if err != nil { return err } } if remove { namespace, err = mapping.MetadataAccessor.Namespace(obj) if err != nil { return err } var name string name, err = mapping.MetadataAccessor.Name(obj) if err != nil { return err } _, typer := f.Object() r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). ContinueOnError(). NamespaceParam(namespace).DefaultNamespace(). ResourceNames(mapping.Resource, name). Flatten(). Do() // Note: we pass in "true" for the "quiet" parameter because // ReadResult will only print one thing based on the "quiet" // flag, and that's the "pod xxx deleted" message. If they // asked for us to remove the pod (via --rm) then telling them // its been deleted is unnecessary since that's what they asked // for. We should only print something if the "rm" fails. err = ReapResult(r, f, cmdOut, true, true, 0, -1, false, false, mapper, true) if err != nil { return err } } // after removal is done, return successfully if we are not interested in the exit code if !waitForExitCode { return nil } switch pod.Status.Phase { case api.PodSucceeded: return nil case api.PodFailed: unknownRcErr := fmt.Errorf("pod %s/%s failed with unknown exit code", pod.Namespace, pod.Name) if len(pod.Status.ContainerStatuses) == 0 || pod.Status.ContainerStatuses[0].State.Terminated == nil { return unknownRcErr } // assume here that we have at most one status because kubectl-run only creates one container per pod rc := pod.Status.ContainerStatuses[0].State.Terminated.ExitCode if rc == 0 { return unknownRcErr } return uexec.CodeExitError{ Err: fmt.Errorf("pod %s/%s terminated", pod.Namespace, pod.Name), Code: int(rc), } default: return fmt.Errorf("pod %s/%s left in phase %s", pod.Namespace, pod.Name, pod.Status.Phase) } } outputFormat := cmdutil.GetFlagString(cmd, "output") if outputFormat != "" || cmdutil.GetDryRunFlag(cmd) { return f.PrintObject(cmd, mapper, obj, cmdOut) } cmdutil.PrintSuccess(mapper, false, cmdOut, mapping.Resource, args[0], cmdutil.GetDryRunFlag(cmd), "created") return nil }
func GenerateSingle(cmd *cobra.Command, out *bytes.Buffer, linkHandler func(string) string, specs []string, render_dir string) { name := cmd.CommandPath() short := cmd.Short long := cmd.Long if len(long) == 0 { long = short } fmt.Fprintf(out, "# %s\n\n", name) fmt.Fprintf(out, "%s\n\n", short) fmt.Fprintf(out, "## Synopsis\n") fmt.Fprintf(out, "\n%s\n\n", long) if cmd.Runnable() { fmt.Fprintf(out, "```bash\n%s\n```\n\n", cmd.UseLine()) } if len(cmd.Example) > 0 { fmt.Fprintf(out, "## Examples\n\n") fmt.Fprintf(out, "```bash\n%s\n```\n\n", cmd.Example) } flags := cmd.NonInheritedFlags() flags.SetOutput(out) if flags.HasFlags() { fmt.Fprintf(out, "## Options\n\n```\n") flags.PrintDefaults() fmt.Fprintf(out, "```\n\n") } parentFlags := cmd.InheritedFlags() parentFlags.SetOutput(out) if parentFlags.HasFlags() { fmt.Fprintf(out, "## Options inherited from parent commands\n\n```\n") parentFlags.PrintDefaults() fmt.Fprintf(out, "```\n\n") } if len(cmd.Commands()) > 0 { fmt.Fprintf(out, "## Subcommands\n\n") children := cmd.Commands() sort.Sort(byName(children)) for _, child := range children { if len(child.Deprecated) > 0 { continue } cname := name + " " + child.Name() link := cname + ".md" link = strings.Replace(link, " ", "_", -1) fmt.Fprintf(out, "* [%s](%s)\t - %s\n", cname, linkHandler(link), child.Short) } } if len(cmd.Commands()) > 0 && cmd.HasParent() { fmt.Fprintf(out, "\n") } if cmd.HasParent() { fmt.Fprintf(out, "## See Also\n\n") parent := cmd.Parent() pname := parent.CommandPath() link := pname + ".md" link = strings.Replace(link, " ", "_", -1) fmt.Fprintf(out, "* [%s](%s)\t - %s\n", pname, linkHandler(link), parent.Short) } fmt.Fprintf(out, "\n## Specifications\n\n") for _, spec := range specs { spec = strings.Replace(spec, render_dir, "", 1) title := strings.Replace(spec, "_", " ", -1) title = strings.Replace(title, ".md", "", 1) // title = strings.Replace(title, "spec", "specification", 1) title = strings.Title(title) fmt.Fprintf(out, "* [%s](%s)\n", title, linkHandler(spec)) } fmt.Fprintf(out, "\n") }