diff --git a/contrib/completions/bash/oadm b/contrib/completions/bash/oadm index 833f3c2fd3df..3e308c031bc9 100644 --- a/contrib/completions/bash/oadm +++ b/contrib/completions/bash/oadm @@ -188,6 +188,8 @@ _oadm_new-project() flags+=("--description=") flags+=("--display-name=") flags+=("--node-selector=") + flags+=("--output=") + two_word_flags+=("-o") flags+=("--alsologtostderr") flags+=("--api-version=") flags+=("--boot-id-file=") @@ -1595,6 +1597,8 @@ _oadm_groups_new() flags_with_completion=() flags_completion=() + flags+=("--output=") + two_word_flags+=("-o") flags+=("--alsologtostderr") flags+=("--api-version=") flags+=("--boot-id-file=") diff --git a/contrib/completions/bash/openshift b/contrib/completions/bash/openshift index 055bde340652..6eb276abe157 100644 --- a/contrib/completions/bash/openshift +++ b/contrib/completions/bash/openshift @@ -801,6 +801,8 @@ _openshift_admin_new-project() flags+=("--description=") flags+=("--display-name=") flags+=("--node-selector=") + flags+=("--output=") + two_word_flags+=("-o") flags+=("--alsologtostderr") flags+=("--api-version=") flags+=("--boot-id-file=") @@ -2208,6 +2210,8 @@ _openshift_admin_groups_new() flags_with_completion=() flags_completion=() + flags+=("--output=") + two_word_flags+=("-o") flags+=("--alsologtostderr") flags+=("--api-version=") flags+=("--boot-id-file=") diff --git a/pkg/cmd/admin/groups/new.go b/pkg/cmd/admin/groups/new.go index e923f6c85af2..8a73eb829828 100644 --- a/pkg/cmd/admin/groups/new.go +++ b/pkg/cmd/admin/groups/new.go @@ -5,13 +5,14 @@ import ( "fmt" "io" + "github.com/spf13/cobra" + kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/util/sets" - "github.com/spf13/cobra" - "github.com/openshift/origin/pkg/client" "github.com/openshift/origin/pkg/cmd/util/clientcmd" + "github.com/openshift/origin/pkg/cmd/util/mutation" userapi "github.com/openshift/origin/pkg/user/api" ) @@ -34,6 +35,9 @@ type NewGroupOptions struct { Group string Users []string + + // MutationOutputOptions allows us to correctly output the mutations we make to objects in etcd + MutationOutputOptions mutation.MutationOutputOptions } func NewCmdNewGroup(name, fullName string, f *clientcmd.Factory, out io.Writer) *cobra.Command { @@ -49,10 +53,14 @@ func NewCmdNewGroup(name, fullName string, f *clientcmd.Factory, out io.Writer) kcmdutil.CheckErr(kcmdutil.UsageError(cmd, err.Error())) } + options.MutationOutputOptions = mutation.NewMutationOutputOptions(f.Factory, cmd, out) + kcmdutil.CheckErr(options.AddGroup()) }, } + kcmdutil.AddOutputFlagsForMutation(cmd) + return cmd } @@ -90,5 +98,11 @@ func (o *NewGroupOptions) AddGroup() error { } _, err := o.GroupClient.Create(group) - return err + if err != nil { + return err + } + if err := o.MutationOutputOptions.PrintSuccess(group, "created"); err != nil { + return err + } + return nil } diff --git a/pkg/cmd/admin/groups/sync/cli/prune.go b/pkg/cmd/admin/groups/sync/cli/prune.go index c9f209c31fee..4c005f1bd205 100644 --- a/pkg/cmd/admin/groups/sync/cli/prune.go +++ b/pkg/cmd/admin/groups/sync/cli/prune.go @@ -18,6 +18,7 @@ import ( "github.com/openshift/origin/pkg/cmd/server/api" "github.com/openshift/origin/pkg/cmd/server/api/validation" "github.com/openshift/origin/pkg/cmd/util/clientcmd" + "github.com/openshift/origin/pkg/cmd/util/mutation" ) const ( @@ -69,6 +70,9 @@ type PruneOptions struct { // Out is the writer to write output to Out io.Writer + + // MutationOutputOptions allows us to correctly output the mutations we make to objects in etcd + MutationOutputOptions mutation.MutationOutputOptions } func NewPruneOptions() *PruneOptions { @@ -96,6 +100,8 @@ func NewCmdPrune(name, fullName string, f *clientcmd.Factory, out io.Writer) *co cmdutil.CheckErr(cmdutil.UsageError(c, err.Error())) } + options.MutationOutputOptions = mutation.NewMutationOutputOptions(f.Factory, c, options.Out) + if err := options.Validate(); err != nil { cmdutil.CheckErr(cmdutil.UsageError(c, err.Error())) } @@ -187,6 +193,7 @@ func (o *PruneOptions) Run(cmd *cobra.Command, f *clientcmd.Factory) error { Out: o.Out, Err: os.Stderr, + MutationOutputOptions: o.MutationOutputOptions, } listerMapper, err := getOpenShiftGroupListerMapper(clientConfig.Host(), o) diff --git a/pkg/cmd/admin/groups/sync/cli/sync.go b/pkg/cmd/admin/groups/sync/cli/sync.go index 5871f568b31c..52f4bfec0298 100644 --- a/pkg/cmd/admin/groups/sync/cli/sync.go +++ b/pkg/cmd/admin/groups/sync/cli/sync.go @@ -11,7 +11,6 @@ import ( "github.com/spf13/cobra" configapilatest "github.com/openshift/origin/pkg/cmd/server/api/latest" - kapi "k8s.io/kubernetes/pkg/api" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" kerrs "k8s.io/kubernetes/pkg/util/errors" "k8s.io/kubernetes/pkg/util/sets" @@ -25,6 +24,7 @@ import ( "github.com/openshift/origin/pkg/cmd/server/api" "github.com/openshift/origin/pkg/cmd/server/api/validation" "github.com/openshift/origin/pkg/cmd/util/clientcmd" + "github.com/openshift/origin/pkg/cmd/util/mutation" ) const ( @@ -99,6 +99,9 @@ type SyncOptions struct { // Out is the writer to write output to Out io.Writer + + // MutationOutputOptions allows us to correctly output the mutations we make to objects in etcd + MutationOutputOptions mutation.MutationOutputOptions } func NewSyncOptions() *SyncOptions { @@ -127,6 +130,8 @@ func NewCmdSync(name, fullName string, f *clientcmd.Factory, out io.Writer) *cob cmdutil.CheckErr(cmdutil.UsageError(c, err.Error())) } + options.MutationOutputOptions = mutation.NewMutationOutputOptions(f.Factory, c, options.Out) + if err := options.Validate(); err != nil { cmdutil.CheckErr(cmdutil.UsageError(c, err.Error())) } @@ -337,6 +342,7 @@ func (o *SyncOptions) Run(cmd *cobra.Command, f *clientcmd.Factory) error { Out: o.Out, Err: os.Stderr, + MutationOutputOptions: o.MutationOutputOptions, } switch o.Source { @@ -375,20 +381,7 @@ func (o *SyncOptions) Run(cmd *cobra.Command, f *clientcmd.Factory) error { } // Now we run the Syncer and report any errors - openshiftGroups, syncErrors := syncer.Sync() - if o.Confirm { - return kerrs.NewAggregate(syncErrors) - } - - list := &kapi.List{} - for _, item := range openshiftGroups { - list.Items = append(list.Items, item) - } - if err := f.Factory.PrintObject(cmd, list, o.Out); err != nil { - return err - } - - return kerrs.NewAggregate(syncErrors) + return kerrs.NewAggregate(syncer.Sync()) } func buildSyncBuilder(clientConfig ldapclient.Config, syncConfig *api.LDAPSyncConfig) (SyncBuilder, error) { diff --git a/pkg/cmd/admin/groups/sync/grouppruner.go b/pkg/cmd/admin/groups/sync/grouppruner.go index cf295638d266..9f2e86948845 100644 --- a/pkg/cmd/admin/groups/sync/grouppruner.go +++ b/pkg/cmd/admin/groups/sync/grouppruner.go @@ -8,6 +8,7 @@ import ( "github.com/openshift/origin/pkg/client" "github.com/openshift/origin/pkg/cmd/admin/groups/sync/interfaces" + "github.com/openshift/origin/pkg/cmd/util/mutation" ) // GroupPruner runs a prune job on Groups @@ -33,6 +34,9 @@ type LDAPGroupPruner struct { // Out is used to provide output while the sync job is happening Out io.Writer Err io.Writer + + // MutationOutputOptions allows us to correctly output the mutations we make to objects in etcd + MutationOutputOptions mutation.MutationOutputOptions } var _ GroupPruner = &LDAPGroupPruner{} @@ -79,7 +83,13 @@ func (s *LDAPGroupPruner) Prune() []error { } } - fmt.Fprintf(s.Out, "group/%s\n", groupName) + // The entire prune operation is not atomic, so we need to output what we do when we do it, so if we encounter + // a failure on another group the work we have already done is accounted for. + if s.DryRun { + s.MutationOutputOptions.PrintSuccessForDescription("Group", groupName, "would be deleted") + } else { + s.MutationOutputOptions.PrintSuccessForDescription("Group", groupName, "deleted") + } } return errors diff --git a/pkg/cmd/admin/groups/sync/groupsyncer.go b/pkg/cmd/admin/groups/sync/groupsyncer.go index 3a6a03d837e4..5e24258f211b 100644 --- a/pkg/cmd/admin/groups/sync/groupsyncer.go +++ b/pkg/cmd/admin/groups/sync/groupsyncer.go @@ -14,13 +14,14 @@ import ( "github.com/openshift/origin/pkg/auth/ldaputil" "github.com/openshift/origin/pkg/client" "github.com/openshift/origin/pkg/cmd/admin/groups/sync/interfaces" + "github.com/openshift/origin/pkg/cmd/util/mutation" userapi "github.com/openshift/origin/pkg/user/api" ) // GroupSyncer runs a Sync job on Groups type GroupSyncer interface { // Sync syncs groups in OpenShift with records from an external source - Sync() (groupsAffected []*userapi.Group, errors []error) + Sync() (errors []error) } // LDAPGroupSyncer sync Groups with records on an external LDAP server @@ -43,13 +44,15 @@ type LDAPGroupSyncer struct { // Out is used to provide output while the sync job is happening Out io.Writer Err io.Writer + + // MutationOutputOptions allows us to correctly output the mutations we make to objects in etcd + MutationOutputOptions mutation.MutationOutputOptions } var _ GroupSyncer = &LDAPGroupSyncer{} // Sync allows the LDAPGroupSyncer to be a GroupSyncer -func (s *LDAPGroupSyncer) Sync() ([]*userapi.Group, []error) { - openshiftGroups := []*userapi.Group{} +func (s *LDAPGroupSyncer) Sync() []error { var errors []error // determine what to sync @@ -57,7 +60,7 @@ func (s *LDAPGroupSyncer) Sync() ([]*userapi.Group, []error) { ldapGroupUIDs, err := s.GroupLister.ListGroups() if err != nil { errors = append(errors, err) - return nil, errors + return errors } glog.V(1).Infof("Sync ldapGroupUIDs %v", ldapGroupUIDs) @@ -88,19 +91,29 @@ func (s *LDAPGroupSyncer) Sync() ([]*userapi.Group, []error) { errors = append(errors, err) continue } - openshiftGroups = append(openshiftGroups, openshiftGroup) if !s.DryRun { - fmt.Fprintf(s.Out, "group/%s\n", openshiftGroup.Name) if err := s.updateOpenShiftGroup(openshiftGroup); err != nil { fmt.Fprintf(s.Err, "Error updating OpenShift group %q for LDAP group %q: %v.\n", openshiftGroup.Name, ldapGroupUID, err) errors = append(errors, err) continue } } + + // The entire sync operation is not atomic, so we need to output what we do when we do it, so if we encounter + // a failure on another group the work we have already done is accounted for. + if s.DryRun { + if err := s.MutationOutputOptions.PrintSuccess(openshiftGroup, "would be updated"); err != nil { + errors = append(errors, err) + } + } else { + if err := s.MutationOutputOptions.PrintSuccess(openshiftGroup, "updated"); err != nil { + errors = append(errors, err) + } + } } - return openshiftGroups, errors + return errors } // determineUsers determines the OpenShift Users that correspond to a list of LDAP member entries diff --git a/pkg/cmd/admin/project/new_project.go b/pkg/cmd/admin/project/new_project.go index 02ba1a2ca325..b1cbf05bb0f6 100644 --- a/pkg/cmd/admin/project/new_project.go +++ b/pkg/cmd/admin/project/new_project.go @@ -15,6 +15,7 @@ import ( "github.com/openshift/origin/pkg/cmd/admin/policy" "github.com/openshift/origin/pkg/cmd/server/bootstrappolicy" "github.com/openshift/origin/pkg/cmd/util/clientcmd" + "github.com/openshift/origin/pkg/cmd/util/mutation" projectapi "github.com/openshift/origin/pkg/project/api" ) @@ -30,6 +31,9 @@ type NewProjectOptions struct { AdminRole string AdminUser string + + // MutationOutputOptions allows us to correctly output the mutations we make to objects in etcd + MutationOutputOptions mutation.MutationOutputOptions } const newProjectLong = ` @@ -52,6 +56,8 @@ func NewCmdNewProject(name, fullName string, f *clientcmd.Factory, out io.Writer kcmdutil.CheckErr(kcmdutil.UsageError(cmd, err.Error())) } + options.MutationOutputOptions = mutation.NewMutationOutputOptions(f.Factory, cmd, out) + var err error if options.Client, _, err = f.Clients(); err != nil { kcmdutil.CheckErr(err) @@ -72,6 +78,7 @@ func NewCmdNewProject(name, fullName string, f *clientcmd.Factory, out io.Writer cmd.Flags().StringVar(&options.DisplayName, "display-name", "", "Project display name") cmd.Flags().StringVar(&options.Description, "description", "", "Project description") cmd.Flags().StringVar(&options.NodeSelector, "node-selector", "", "Restrict pods onto nodes matching given label selector. Format: '=, =...'. Specifying \"\" means any node, not default. If unspecified, cluster default node selector will be used.") + kcmdutil.AddOutputFlagsForMutation(cmd) return cmd } @@ -107,7 +114,9 @@ func (o *NewProjectOptions) Run(useNodeSelector bool) error { return err } - fmt.Printf("Created project %v\n", o.ProjectName) + if err := o.MutationOutputOptions.PrintSuccess(project, "created"); err != nil { + return err + } errs := []error{} if len(o.AdminUser) != 0 { diff --git a/pkg/cmd/cli/secrets/basicauth.go b/pkg/cmd/cli/secrets/basicauth.go index c7beedb1fd21..5c5071246bc7 100644 --- a/pkg/cmd/cli/secrets/basicauth.go +++ b/pkg/cmd/cli/secrets/basicauth.go @@ -11,6 +11,7 @@ import ( kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" cmdutil "github.com/openshift/origin/pkg/cmd/util" + "github.com/openshift/origin/pkg/cmd/util/mutation" "github.com/spf13/cobra" ) @@ -52,6 +53,9 @@ type CreateBasicAuthSecretOptions struct { Out io.Writer SecretsInterface client.SecretsInterface + + // MutationOutputOptions allows us to correctly output the mutations we make to objects in etcd + MutationOutputOptions mutation.MutationOutputOptions } // NewCmdCreateBasicAuthSecret implements the OpenShift cli secrets new-basicauth subcommand @@ -71,6 +75,8 @@ func NewCmdCreateBasicAuthSecret(name, fullName string, f *kcmdutil.Factory, rea kcmdutil.CheckErr(kcmdutil.UsageError(c, err.Error())) } + o.MutationOutputOptions = mutation.NewMutationOutputOptions(f, c, o.Out) + if err := o.Validate(); err != nil { kcmdutil.CheckErr(kcmdutil.UsageError(c, err.Error())) } @@ -115,7 +121,9 @@ func (o *CreateBasicAuthSecretOptions) CreateBasicAuthSecret() error { return err } - fmt.Fprintf(o.GetOut(), "secret/%s\n", secret.Name) + if err := o.MutationOutputOptions.PrintSuccess(secret, "created"); err != nil { + return err + } return nil } diff --git a/pkg/cmd/cli/secrets/dockercfg.go b/pkg/cmd/cli/secrets/dockercfg.go index 627cf06c9f00..4c549621e7aa 100644 --- a/pkg/cmd/cli/secrets/dockercfg.go +++ b/pkg/cmd/cli/secrets/dockercfg.go @@ -13,6 +13,7 @@ import ( "k8s.io/kubernetes/pkg/credentialprovider" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + "github.com/openshift/origin/pkg/cmd/util/mutation" "github.com/spf13/cobra" ) @@ -53,6 +54,9 @@ type CreateDockerConfigOptions struct { SecretsInterface client.SecretsInterface Out io.Writer + + // MutationOutputOptions allows us to correctly output the mutations we make to objects in etcd + MutationOutputOptions mutation.MutationOutputOptions } // NewCmdCreateDockerConfigSecret creates a command object for making a dockercfg secret @@ -69,6 +73,8 @@ func NewCmdCreateDockerConfigSecret(name, fullName string, f *cmdutil.Factory, o cmdutil.CheckErr(cmdutil.UsageError(c, err.Error())) } + o.MutationOutputOptions = mutation.NewMutationOutputOptions(f, c, o.Out) + if err := o.Validate(); err != nil { cmdutil.CheckErr(cmdutil.UsageError(c, err.Error())) } @@ -107,8 +113,9 @@ func (o CreateDockerConfigOptions) CreateDockerSecret() error { return err } - fmt.Fprintf(o.GetOut(), "secret/%s\n", secret.Name) - + if err := o.MutationOutputOptions.PrintSuccess(secret, "created"); err != nil { + return err + } return nil } diff --git a/pkg/cmd/cli/secrets/new.go b/pkg/cmd/cli/secrets/new.go index b4ceea872387..74ce4478621f 100644 --- a/pkg/cmd/cli/secrets/new.go +++ b/pkg/cmd/cli/secrets/new.go @@ -15,6 +15,7 @@ import ( cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "github.com/openshift/origin/pkg/cmd/util/clientcmd" + "github.com/openshift/origin/pkg/cmd/util/mutation" "github.com/spf13/cobra" ) @@ -67,6 +68,9 @@ type CreateSecretOptions struct { Quiet bool AllowUnknownTypes bool + + // MutationOutputOptions allows us to correctly output the mutations we make to objects in etcd + MutationOutputOptions mutation.MutationOutputOptions } func NewCmdCreateSecret(name, fullName string, f *clientcmd.Factory, out io.Writer) *cobra.Command { @@ -83,6 +87,8 @@ func NewCmdCreateSecret(name, fullName string, f *clientcmd.Factory, out io.Writ cmdutil.CheckErr(cmdutil.UsageError(c, err.Error())) } + options.MutationOutputOptions = mutation.NewMutationOutputOptions(f.Factory, c, options.Out) + if err := options.Validate(); err != nil { cmdutil.CheckErr(cmdutil.UsageError(c, err.Error())) } @@ -177,8 +183,11 @@ func (o *CreateSecretOptions) CreateSecret() (*kapi.Secret, error) { } persistedSecret, err := o.SecretsInterface.Create(secret) + if err == nil { - fmt.Fprintf(o.Out, "secret/%s\n", persistedSecret.Name) + if err := o.MutationOutputOptions.PrintSuccess(secret, "created"); err != nil { + return nil, err + } } return persistedSecret, err diff --git a/pkg/cmd/cli/secrets/sshauth.go b/pkg/cmd/cli/secrets/sshauth.go index 473eda1d7421..b609ab2f9344 100644 --- a/pkg/cmd/cli/secrets/sshauth.go +++ b/pkg/cmd/cli/secrets/sshauth.go @@ -10,6 +10,7 @@ import ( client "k8s.io/kubernetes/pkg/client/unversioned" kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + "github.com/openshift/origin/pkg/cmd/util/mutation" "github.com/spf13/cobra" ) @@ -48,6 +49,9 @@ type CreateSSHAuthSecretOptions struct { Out io.Writer SecretsInterface client.SecretsInterface + + // MutationOutputOptions allows us to correctly output the mutations we make to objects in etcd + MutationOutputOptions mutation.MutationOutputOptions } // NewCmdCreateSSHAuthSecret implements the OpenShift cli secrets new-sshauth subcommand @@ -66,6 +70,8 @@ func NewCmdCreateSSHAuthSecret(name, fullName string, f *kcmdutil.Factory, out i kcmdutil.CheckErr(kcmdutil.UsageError(c, err.Error())) } + o.MutationOutputOptions = mutation.NewMutationOutputOptions(f, c, o.Out) + if err := o.Validate(); err != nil { kcmdutil.CheckErr(kcmdutil.UsageError(c, err.Error())) } @@ -109,7 +115,9 @@ func (o *CreateSSHAuthSecretOptions) CreateSSHAuthSecret() error { return err } - fmt.Fprintf(o.GetOut(), "secret/%s\n", secret.Name) + if err := o.MutationOutputOptions.PrintSuccess(secret, "created"); err != nil { + return err + } return nil } diff --git a/pkg/cmd/util/mutation/mutate.go b/pkg/cmd/util/mutation/mutate.go new file mode 100644 index 000000000000..bd771b0f0d49 --- /dev/null +++ b/pkg/cmd/util/mutation/mutate.go @@ -0,0 +1,51 @@ +package mutation + +import ( + "io" + "io/ioutil" + + "github.com/spf13/cobra" + + "k8s.io/kubernetes/pkg/api/meta" + kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + "k8s.io/kubernetes/pkg/kubectl/resource" + "k8s.io/kubernetes/pkg/runtime" +) + +func NewMutationOutputOptions(factory *kcmdutil.Factory, cmd *cobra.Command, out io.Writer) MutationOutputOptions { + mapper, typer := factory.Object() + if out == nil { + out = ioutil.Discard + } + + return MutationOutputOptions{ + mapper: mapper, + resourceMapper: resource.Mapper{ObjectTyper: typer, RESTMapper: mapper, ClientMapper: factory.ClientMapperForCommand()}, + shortOutput: kcmdutil.GetFlagString(cmd, "output") == "name", + out: out, + } +} + +// MutationOutputOptions holds all of the information necessary to correctly output the reslult of mutating +// an object in etcd. +type MutationOutputOptions struct { + mapper meta.RESTMapper + resourceMapper resource.Mapper + shortOutput bool + out io.Writer +} + +// PrintSuccess wraps around kcmdutil.PrintSuccess +func (o *MutationOutputOptions) PrintSuccess(object runtime.Object, operation string) error { + info, err := o.resourceMapper.InfoForObject(object) + if err != nil { + return err + } + kcmdutil.PrintSuccess(o.mapper, o.shortOutput, o.out, info.Mapping.Resource, info.Name, operation) + return nil +} + +// PrintSuccessForDescription allows for success printing when no object is present +func (o *MutationOutputOptions) PrintSuccessForDescription(resource, name, operation string) { + kcmdutil.PrintSuccess(o.mapper, o.shortOutput, o.out, resource, name, operation) +}