// Validate validates that required fields in StartConfig have been populated func (c *ClientStartConfig) Validate(out, errout io.Writer) error { cmdutil.WarnAboutCommaSeparation(errout, c.Environment, "--env") if len(c.Tasks) == 0 { return fmt.Errorf("no startup tasks to execute") } return nil }
// Complete sets any default behavior for the command func (o *NewAppOptions) Complete(baseName, name string, f *clientcmd.Factory, c *cobra.Command, args []string, in io.Reader, out, errout io.Writer) error { o.In = in o.Out = out o.ErrOut = errout o.Output = kcmdutil.GetFlagString(c, "output") // Only output="" should print descriptions of intermediate steps. Everything // else should print only some specific output (json, yaml, go-template, ...) o.Config.In = o.In if len(o.Output) == 0 { o.Config.Out = o.Out } else { o.Config.Out = ioutil.Discard } o.Config.ErrOut = o.ErrOut o.Action.Out, o.Action.ErrOut = o.Out, o.ErrOut o.Action.Bulk.Mapper = clientcmd.ResourceMapper(f) o.Action.Bulk.Op = configcmd.Create // Retry is used to support previous versions of the API server that will // consider the presence of an unknown trigger type to be an error. o.Action.Bulk.Retry = retryBuildConfig o.Config.DryRun = o.Action.DryRun cmdutil.WarnAboutCommaSeparation(o.ErrOut, o.Config.Environment, "--env") cmdutil.WarnAboutCommaSeparation(o.ErrOut, o.Config.TemplateParameters, "--param") o.CommandPath = c.CommandPath() o.BaseName = baseName o.CommandName = name mapper, _ := f.Object(false) o.PrintObject = cmdutil.VersionedPrintObject(f.PrintObject, c, mapper, out) o.LogsForObject = f.LogsForObject if err := CompleteAppConfig(o.Config, f, c, args); err != nil { return err } if err := setAppConfigLabels(c, o.Config); err != nil { return err } return nil }
func (o *DeploymentHookOptions) Validate() error { if o.Remove { if len(o.Command) > 0 || len(o.Volumes) > 0 || len(o.Environment) > 0 || len(o.Container) > 0 { return fmt.Errorf("--remove may not be used with any option except --pre, --mid, or --post") } if !o.Pre && !o.Mid && !o.Post { return fmt.Errorf("you must specify at least one of --pre, --mid, or --post with the --remove flag") } return nil } cnt := 0 if o.Pre { cnt++ } if o.Mid { cnt++ } if o.Post { cnt++ } if cnt == 0 || cnt > 1 { return fmt.Errorf("you must specify one of --pre, --mid, or --post") } if len(o.Command) == 0 { return fmt.Errorf("you must specify a command for the deployment hook") } cmdutil.WarnAboutCommaSeparation(o.Err, o.Environment, "--environment") return nil }
func (o *StartBuildOptions) Complete(f *clientcmd.Factory, in io.Reader, out, errout io.Writer, cmd *cobra.Command, cmdFullName string, args []string) error { o.In = in o.Out = out o.ErrOut = errout o.Git = git.NewRepository() o.ClientConfig = f.OpenShiftClientConfig o.Mapper, _ = f.Object(false) webhook := o.FromWebhook buildName := o.FromBuild fromFile := o.FromFile fromDir := o.FromDir fromRepo := o.FromRepo buildLogLevel := o.LogLevel outputFormat := kcmdutil.GetFlagString(cmd, "output") if outputFormat != "name" && outputFormat != "" { return kcmdutil.UsageError(cmd, "Unsupported output format: %s", outputFormat) } o.ShortOutput = outputFormat == "name" switch { case len(webhook) > 0: if len(args) > 0 || len(buildName) > 0 || len(fromFile) > 0 || len(fromDir) > 0 || len(fromRepo) > 0 { return kcmdutil.UsageError(cmd, "The '--from-webhook' flag is incompatible with arguments and all '--from-*' flags") } return nil case len(args) != 1 && len(buildName) == 0: return kcmdutil.UsageError(cmd, "Must pass a name of a build config or specify build name with '--from-build' flag.\nUse \"%s get bc\" to list all available build configs.", cmdFullName) } if len(buildName) != 0 && (len(fromFile) != 0 || len(fromDir) != 0 || len(fromRepo) != 0) { // TODO: we should support this, it should be possible to clone a build to run again with new uploaded artifacts. // Doing so requires introducing a new clonebinary endpoint. return kcmdutil.UsageError(cmd, "Cannot use '--from-build' flag with binary builds") } o.AsBinary = len(fromFile) > 0 || len(fromDir) > 0 || len(fromRepo) > 0 namespace, _, err := f.DefaultNamespace() if err != nil { return err } client, _, err := f.Clients() if err != nil { return err } o.Client = client var ( name = buildName resource = buildapi.Resource("builds") ) if len(name) == 0 && len(args) > 0 && len(args[0]) > 0 { mapper, _ := f.Object(false) resource, name, err = cmdutil.ResolveResource(buildapi.Resource("buildconfigs"), args[0], mapper) if err != nil { return err } switch resource { case buildapi.Resource("buildconfigs"): // no special handling required case buildapi.Resource("builds"): if len(o.ListWebhooks) == 0 { return fmt.Errorf("use --from-build to rerun your builds") } default: return fmt.Errorf("invalid resource provided: %v", resource) } } // when listing webhooks, allow --from-build to lookup a build config if resource == buildapi.Resource("builds") && len(o.ListWebhooks) > 0 { build, err := client.Builds(namespace).Get(name) if err != nil { return err } ref := build.Status.Config if ref == nil { return fmt.Errorf("the provided Build %q was not created from a BuildConfig and cannot have webhooks", name) } if len(ref.Namespace) > 0 { namespace = ref.Namespace } name = ref.Name } if len(name) == 0 { return fmt.Errorf("a resource name is required either as an argument or by using --from-build") } o.Namespace = namespace o.Name = name cmdutil.WarnAboutCommaSeparation(o.ErrOut, o.Env, "--env") env, _, err := cmdutil.ParseEnv(o.Env, in) if err != nil { return err } if len(buildLogLevel) > 0 { env = append(env, kapi.EnvVar{Name: "BUILD_LOGLEVEL", Value: buildLogLevel}) } o.EnvVar = env return nil }
// RunProcess contains all the necessary functionality for the OpenShift cli process command func RunProcess(f *clientcmd.Factory, out, errout io.Writer, cmd *cobra.Command, args []string) error { templateName, valueArgs := "", []string{} for _, s := range args { isValue := strings.Contains(s, "=") switch { case isValue: valueArgs = append(valueArgs, s) case !isValue && len(templateName) == 0: templateName = s case !isValue && len(templateName) > 0: return kcmdutil.UsageError(cmd, "template name must be specified only once: %s", s) } } keys := sets.NewString() duplicatedKeys := sets.NewString() var flagValues []string if cmd.Flag("value").Changed { flagValues = getFlagStringArray(cmd, "value") } cmdutil.WarnAboutCommaSeparation(errout, flagValues, "--value") for _, value := range flagValues { key := strings.Split(value, "=")[0] if keys.Has(key) { duplicatedKeys.Insert(key) } keys.Insert(key) } for _, value := range valueArgs { key := strings.Split(value, "=")[0] if keys.Has(key) { duplicatedKeys.Insert(key) } keys.Insert(key) } if len(duplicatedKeys) != 0 { return kcmdutil.UsageError(cmd, fmt.Sprintf("The following values were provided more than once: %s", strings.Join(duplicatedKeys.List(), ", "))) } filename := kcmdutil.GetFlagString(cmd, "filename") if len(templateName) == 0 && len(filename) == 0 { return kcmdutil.UsageError(cmd, "Must pass a filename or name of stored template") } if kcmdutil.GetFlagBool(cmd, "parameters") { for _, flag := range []string{"value", "labels", "output", "output-version", "raw", "template"} { if f := cmd.Flags().Lookup(flag); f != nil && f.Changed { return kcmdutil.UsageError(cmd, "The --parameters flag does not process the template, can't be used with --%v", flag) } } } namespace, explicit, err := f.DefaultNamespace() if err != nil { return err } mapper, typer := f.Object(false) client, _, err := f.Clients() if err != nil { return err } var ( objects []runtime.Object infos []*resource.Info ) mapping, err := mapper.RESTMapping(templateapi.Kind("Template")) if err != nil { return err } // When templateName is not empty, then we fetch the template from the // server, otherwise we require to set the `-f` parameter. if len(templateName) > 0 { var ( storedTemplate, rs string sourceNamespace string ok bool ) sourceNamespace, rs, storedTemplate, ok = parseNamespaceResourceName(templateName, namespace) if !ok { return fmt.Errorf("invalid argument %q", templateName) } if len(rs) > 0 && (rs != "template" && rs != "templates") { return fmt.Errorf("unable to process invalid resource %q", rs) } if len(storedTemplate) == 0 { return fmt.Errorf("invalid value syntax %q", templateName) } templateObj, err := client.Templates(sourceNamespace).Get(storedTemplate) if err != nil { if errors.IsNotFound(err) { return fmt.Errorf("template %q could not be found", storedTemplate) } return err } templateObj.CreationTimestamp = unversioned.Now() infos = append(infos, &resource.Info{Object: templateObj}) } else { infos, err = resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), kapi.Codecs.UniversalDecoder()). NamespaceParam(namespace).RequireNamespace(). FilenameParam(explicit, false, filename). Do(). Infos() if err != nil { return err } } if len(infos) > 1 { // in order to run validation on the input given to us by a user, we only support the processing // of one template in a list. For instance, we want to be able to fail when a user does not give // a parameter that the template wants or when they give a parameter the template doesn't need, // as this may indicate that they have mis-used `oc process`. This is much less complicated when // we process at most one template. fmt.Fprintf(out, "%d input templates found, but only the first will be processed", len(infos)) } obj, ok := infos[0].Object.(*templateapi.Template) if !ok { sourceName := filename if len(templateName) > 0 { sourceName = namespace + "/" + templateName } return fmt.Errorf("unable to parse %q, not a valid Template but %s\n", sourceName, reflect.TypeOf(infos[0].Object)) } // If 'parameters' flag is set it does not do processing but only print // the template parameters to console for inspection. if kcmdutil.GetFlagBool(cmd, "parameters") { return describe.PrintTemplateParameters(obj.Parameters, out) } if label := kcmdutil.GetFlagString(cmd, "labels"); len(label) > 0 { lbl, err := kubectl.ParseLabels(label) if err != nil { return fmt.Errorf("error parsing labels: %v\n", err) } if obj.ObjectLabels == nil { obj.ObjectLabels = make(map[string]string) } for key, value := range lbl { obj.ObjectLabels[key] = value } } // Override the values for the current template parameters // when user specify the --value if cmd.Flag("value").Changed { values := getFlagStringArray(cmd, "value") if errs := injectUserVars(values, obj); errs != nil { return kerrors.NewAggregate(errs) } } if errs := injectUserVars(valueArgs, obj); errs != nil { return kerrors.NewAggregate(errs) } resultObj, err := client.TemplateConfigs(namespace).Create(obj) if err != nil { return fmt.Errorf("error processing the template %q: %v\n", obj.Name, err) } outputFormat := kcmdutil.GetFlagString(cmd, "output") if outputFormat == "describe" { if s, err := (&describe.TemplateDescriber{ MetadataAccessor: meta.NewAccessor(), ObjectTyper: kapi.Scheme, ObjectDescriber: nil, }).DescribeTemplate(resultObj); err != nil { return fmt.Errorf("error describing %q: %v\n", obj.Name, err) } else { _, err := fmt.Fprintf(out, s) return err } } objects = append(objects, resultObj.Objects...) p, _, err := kubectl.GetPrinter(outputFormat, "", false) if err != nil { return err } gv := mapping.GroupVersionKind.GroupVersion() version, err := kcmdutil.OutputVersion(cmd, &gv) if err != nil { return err } p = kubectl.NewVersionedPrinter(p, kapi.Scheme, version) // use generic output if kcmdutil.GetFlagBool(cmd, "raw") { for i := range objects { p.PrintObj(objects[i], out) } return nil } return p.PrintObj(&kapi.List{ ListMeta: unversioned.ListMeta{}, Items: objects, }, out) }
// RunEnv contains all the necessary functionality for the OpenShift cli env command // TODO: refactor to share the common "patch resource" pattern of probe func RunEnv(f *clientcmd.Factory, in io.Reader, out, errout io.Writer, cmd *cobra.Command, args []string, envParams, filenames []string) error { resources, envArgs, ok := cmdutil.SplitEnvironmentFromResources(args) if !ok { return kcmdutil.UsageError(cmd, "all resources must be specified before environment changes: %s", strings.Join(args, " ")) } if len(filenames) == 0 && len(resources) < 1 { return kcmdutil.UsageError(cmd, "one or more resources must be specified as <resource> <name> or <resource>/<name>") } containerMatch := kcmdutil.GetFlagString(cmd, "containers") list := kcmdutil.GetFlagBool(cmd, "list") resolve := kcmdutil.GetFlagBool(cmd, "resolve") selector := kcmdutil.GetFlagString(cmd, "selector") all := kcmdutil.GetFlagBool(cmd, "all") overwrite := kcmdutil.GetFlagBool(cmd, "overwrite") resourceVersion := kcmdutil.GetFlagString(cmd, "resource-version") outputFormat := kcmdutil.GetFlagString(cmd, "output") from := kcmdutil.GetFlagString(cmd, "from") prefix := kcmdutil.GetFlagString(cmd, "prefix") if list && len(outputFormat) > 0 { return kcmdutil.UsageError(cmd, "--list and --output may not be specified together") } clientConfig, err := f.ClientConfig() if err != nil { return err } cmdNamespace, explicit, err := f.DefaultNamespace() if err != nil { return err } cmdutil.WarnAboutCommaSeparation(errout, envParams, "--env") env, remove, err := cmdutil.ParseEnv(append(envParams, envArgs...), in) if err != nil { return err } if len(from) != 0 { mapper, typer := f.Object(false) b := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), kapi.Codecs.UniversalDecoder()). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(explicit, false, filenames...). SelectorParam(selector). ResourceTypeOrNameArgs(all, from). Flatten() one := false infos, err := b.Do().IntoSingular(&one).Infos() if err != nil { return err } for _, info := range infos { switch from := info.Object.(type) { case *kapi.Secret: for key := range from.Data { envVar := kapi.EnvVar{ Name: keyToEnvName(key), ValueFrom: &kapi.EnvVarSource{ SecretKeyRef: &kapi.SecretKeySelector{ LocalObjectReference: kapi.LocalObjectReference{ Name: from.Name, }, Key: key, }, }, } env = append(env, envVar) } case *kapi.ConfigMap: for key := range from.Data { envVar := kapi.EnvVar{ Name: keyToEnvName(key), ValueFrom: &kapi.EnvVarSource{ ConfigMapKeyRef: &kapi.ConfigMapKeySelector{ LocalObjectReference: kapi.LocalObjectReference{ Name: from.Name, }, Key: key, }, }, } env = append(env, envVar) } default: return fmt.Errorf("unsupported resource specified in --from") } } } if len(prefix) != 0 { for i := range env { env[i].Name = fmt.Sprintf("%s%s", prefix, env[i].Name) } } mapper, typer := f.Object(false) b := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), kapi.Codecs.UniversalDecoder()). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(explicit, false, filenames...). SelectorParam(selector). ResourceTypeOrNameArgs(all, resources...). Flatten() one := false infos, err := b.Do().IntoSingular(&one).Infos() if err != nil { return err } // only apply resource version locking on a single resource if !one && len(resourceVersion) > 0 { return kcmdutil.UsageError(cmd, "--resource-version may only be used with a single resource") } // Keep a copy of the original objects prior to updating their environment. // Used in constructing the patch(es) that will be applied in the server. gv := *clientConfig.GroupVersion oldObjects, err := resource.AsVersionedObjects(infos, gv, kapi.Codecs.LegacyCodec(gv)) if err != nil { return err } if len(oldObjects) != len(infos) { return fmt.Errorf("could not convert all objects to API version %q", clientConfig.GroupVersion) } oldData := make([][]byte, len(infos)) for i := range oldObjects { old, err := json.Marshal(oldObjects[i]) if err != nil { return err } oldData[i] = old } skipped := 0 errored := []*resource.Info{} for _, info := range infos { ok, err := f.UpdatePodSpecForObject(info.Object, func(spec *kapi.PodSpec) error { resolutionErrorsEncountered := false containers, _ := selectContainers(spec.Containers, containerMatch) if len(containers) == 0 { fmt.Fprintf(errout, "warning: %s/%s does not have any containers matching %q\n", info.Mapping.Resource, info.Name, containerMatch) return nil } for _, c := range containers { if !overwrite { if err := validateNoOverwrites(c.Env, env); err != nil { errored = append(errored, info) return err } } c.Env = updateEnv(c.Env, env, remove) if list { resolveErrors := map[string][]string{} store := newResourceStore() fmt.Fprintf(out, "# %s %s, container %s\n", info.Mapping.Resource, info.Name, c.Name) for _, env := range c.Env { // Print the simple value if env.ValueFrom == nil { fmt.Fprintf(out, "%s=%s\n", env.Name, env.Value) continue } // Print the reference version if !resolve { fmt.Fprintf(out, "# %s from %s\n", env.Name, getEnvVarRefString(env.ValueFrom)) continue } value, err := getEnvVarRefValue(f, store, env.ValueFrom, info.Object, c) // Print the resolved value if err == nil { fmt.Fprintf(out, "%s=%s\n", env.Name, value) continue } // Print the reference version and save the resolve error fmt.Fprintf(out, "# %s from %s\n", env.Name, getEnvVarRefString(env.ValueFrom)) errString := err.Error() resolveErrors[errString] = append(resolveErrors[errString], env.Name) resolutionErrorsEncountered = true } // Print any resolution errors errs := []string{} for err, vars := range resolveErrors { sort.Strings(vars) errs = append(errs, fmt.Sprintf("error retrieving reference for %s: %v", strings.Join(vars, ", "), err)) } sort.Strings(errs) for _, err := range errs { fmt.Fprintln(errout, err) } } } if resolutionErrorsEncountered { errored = append(errored, info) return errors.New("failed to retrieve valueFrom references") } return nil }) if !ok { // This is a fallback function for objects that don't have pod spec. ok, err = f.UpdateObjectEnvironment(info.Object, func(vars *[]kapi.EnvVar) error { if vars == nil { return fmt.Errorf("no environment variables provided") } if !overwrite { if err := validateNoOverwrites(*vars, env); err != nil { errored = append(errored, info) return err } } *vars = updateEnv(*vars, env, remove) if list { fmt.Fprintf(out, "# %s %s\n", info.Mapping.Resource, info.Name) for _, env := range *vars { fmt.Fprintf(out, "%s=%s\n", env.Name, env.Value) } } return nil }) if !ok { skipped++ continue } } if err != nil { fmt.Fprintf(errout, "error: %s/%s %v\n", info.Mapping.Resource, info.Name, err) continue } } if one && skipped == len(infos) { return fmt.Errorf("%s/%s is not a pod or does not have a pod template", infos[0].Mapping.Resource, infos[0].Name) } if len(errored) == len(infos) { return cmdutil.ErrExit } if list { return nil } if len(outputFormat) > 0 { return f.PrintResourceInfos(cmd, infos, out) } objects, err := resource.AsVersionedObjects(infos, gv, kapi.Codecs.LegacyCodec(gv)) if err != nil { return err } if len(objects) != len(infos) { return fmt.Errorf("could not convert all objects to API version %q", clientConfig.GroupVersion) } failed := false updates: for i, info := range infos { for _, erroredInfo := range errored { if info == erroredInfo { continue updates } } newData, err := json.Marshal(objects[i]) if err != nil { return err } patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData[i], newData, objects[i]) if err != nil { return err } obj, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, kapi.StrategicMergePatchType, patchBytes) if err != nil { handlePodUpdateError(errout, err, "environment variables") failed = true continue } info.Refresh(obj, true) // make sure arguments to set or replace environment variables are set // before returning a successful message if len(env) == 0 && len(envArgs) == 0 { return fmt.Errorf("at least one environment variable must be provided") } shortOutput := kcmdutil.GetFlagString(cmd, "output") == "name" kcmdutil.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, false, "updated") } if failed { return cmdutil.ErrExit } return nil }