@@ -23,6 +23,7 @@ import (
2323 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2424 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2525 "k8s.io/apimachinery/pkg/types"
26+ "k8s.io/client-go/dynamic"
2627
2728 "github.com/crunchydata/postgres-operator-client/internal"
2829 "github.com/crunchydata/postgres-operator-client/internal/apis/postgres-operator.crunchydata.com/v1beta1"
@@ -47,6 +48,8 @@ the --force-conflicts flag.
4748 postgresclusters.postgres-operator.crunchydata.com [get patch]
4849
4950### Usage` ,
51+ // Limit the number of args, that is, only one cluster name
52+ Args : cobra .ExactArgs (1 ),
5053 }
5154
5255 cmdBackup .Example = internal .FormatExample (`# Trigger a backup on the 'hippo' postgrescluster using the current spec options
@@ -63,13 +66,10 @@ pgo backup hippo --force-conflicts
6366### Example output
6467postgresclusters/hippo backup initiated` )
6568
66- // Limit the number of args, that is, only one cluster name
67- cmdBackup .Args = cobra .ExactArgs (1 )
68-
6969 // `backup` command accepts `repoName`, `force-conflicts` and `options` flags;
7070 // multiple options flags can be used, with each becoming a new line
7171 // in the options array on the spec
72- backup := pgBackRestBackup {}
72+ backup := pgBackRestBackupArgs {}
7373 cmdBackup .Flags ().BoolVar (& backup .ForceConflicts , "force-conflicts" , false , "take ownership and overwrite the backup settings" )
7474 cmdBackup .Flags ().StringVar (& backup .RepoName , "repoName" , "" , "repoName to backup to" )
7575 cmdBackup .Flags ().StringArrayVar (& backup .Options , "options" , []string {},
@@ -79,88 +79,47 @@ postgresclusters/hippo backup initiated`)
7979 cmdBackup .RunE = func (cmd * cobra.Command , args []string ) error {
8080
8181 // configure client
82- ctx := context .Background ()
8382 mapping , client , err := v1beta1 .NewPostgresClusterClient (config )
8483 if err != nil {
8584 return err
8685 }
8786
88- // Get the namespace. This will either be from the Kubernetes configuration
89- // or from the --namespace (-n) flag.
90- configNamespace , err := config .Namespace ()
91- if err != nil {
92- return err
93- }
94-
95- cluster , err := client .Namespace (configNamespace ).Get (ctx ,
96- args [0 ], // the name of the cluster object, limited to one name through `ExactArgs(1)`
97- metav1.GetOptions {},
98- )
99- if err != nil {
100- return err
101- }
102-
103- intent := new (unstructured.Unstructured )
104- if err := internal .ExtractFieldsInto (cluster , intent , config .Patch .FieldManager ); err != nil {
105- return err
106- }
107- if err := backup .modifyIntent (intent , time .Now ()); err != nil {
108- return err
109- }
110-
111- patch , err := intent .MarshalJSON ()
112- if err != nil {
113- cmd .Printf ("\n Error packaging payload: %s\n " , err )
114- return err
115- }
87+ // Pass args[0] as the name of the cluster object, limited to one through `ExactArgs(1)`
88+ backup .ClusterName = args [0 ]
11689
117- // Update the spec/annotate
118- // TODO(benjaminjb): Would we want to allow a dry-run option here?
119- patchOptions := metav1.PatchOptions {}
120- if backup .ForceConflicts {
121- b := true
122- patchOptions .Force = & b
90+ msg , err := backup .Run (client , config )
91+ if msg != "" {
92+ cmd .Println (msg )
12393 }
124- _ , err = client .Namespace (configNamespace ).Patch (ctx ,
125- args [0 ], // the name of the cluster object, limited to one name through `ExactArgs(1)`
126- types .ApplyPatchType ,
127- patch ,
128- config .Patch .PatchOptions (patchOptions ),
129- )
130-
131- if err != nil {
132- if apierrors .IsConflict (err ) {
133- cmd .Printf ("SUGGESTION: The --force-conflicts flag may help in performing this operation." )
134- }
135- cmd .Printf ("\n Error requesting update: %s\n " , err )
136- return err
94+ if err == nil {
95+ // Our `backup` command initiates a job, but does not signal to the user
96+ // that a backup has finished; consider a `--wait` flag to wait until the
97+ // backup is done.
98+ cmd .Printf ("%s/%s backup initiated\n " , mapping .Resource .Resource , backup .ClusterName )
13799 }
138100
139- // Print the output received.
140- // TODO(benjaminjb): consider a more informative output
141- cmd .Printf ("%s/%s backup initiated\n " , mapping .Resource .Resource , args [0 ])
142-
143- return nil
101+ return err
144102 }
145103
146104 return cmdBackup
147105}
148106
149- type pgBackRestBackup struct {
107+ type pgBackRestBackupArgs struct {
108+ ClusterName string
109+ ForceConflicts bool
150110 Options []string
151111 RepoName string
152- ForceConflicts bool
153112}
154113
155- func (config pgBackRestBackup ) modifyIntent (
114+ func (backup pgBackRestBackupArgs ) modifyIntent (
156115 intent * unstructured.Unstructured , now time.Time ,
157116) error {
158117 intent .SetAnnotations (internal .MergeStringMaps (
159118 intent .GetAnnotations (), map [string ]string {
160119 "postgres-operator.crunchydata.com/pgbackrest-backup" : now .UTC ().Format (time .RFC3339 ),
161120 }))
162121
163- if value , path := config .Options , []string {
122+ if value , path := backup .Options , []string {
164123 "spec" , "backups" , "pgbackrest" , "manual" , "options" ,
165124 }; len (value ) == 0 {
166125 unstructured .RemoveNestedField (intent .Object , path ... )
@@ -170,7 +129,7 @@ func (config pgBackRestBackup) modifyIntent(
170129 return err
171130 }
172131
173- if value , path := config .RepoName , []string {
132+ if value , path := backup .RepoName , []string {
174133 "spec" , "backups" , "pgbackrest" , "manual" , "repoName" ,
175134 }; len (value ) == 0 {
176135 unstructured .RemoveNestedField (intent .Object , path ... )
@@ -185,3 +144,63 @@ func (config pgBackRestBackup) modifyIntent(
185144
186145 return nil
187146}
147+
148+ func (backup pgBackRestBackupArgs ) Run (client dynamic.NamespaceableResourceInterface ,
149+ config * internal.Config ) (string , error ) {
150+
151+ var (
152+ cluster * unstructured.Unstructured
153+ err error
154+ namespace string
155+ patch []byte
156+ )
157+
158+ ctx := context .Background ()
159+
160+ // Get the namespace. This will either be from the Kubernetes configuration
161+ // or from the --namespace (-n) flag.
162+ if namespace , err = config .Namespace (); err != nil {
163+ return "" , err
164+ }
165+
166+ if cluster , err = client .Namespace (namespace ).Get (ctx ,
167+ backup .ClusterName ,
168+ metav1.GetOptions {},
169+ ); err != nil {
170+ return "" , err
171+ }
172+
173+ intent := new (unstructured.Unstructured )
174+ if err = internal .ExtractFieldsInto (
175+ cluster , intent , config .Patch .FieldManager ); err != nil {
176+ return "" , err
177+ }
178+ if err = backup .modifyIntent (intent , time .Now ()); err != nil {
179+ return "" , err
180+ }
181+
182+ if patch , err = intent .MarshalJSON (); err != nil {
183+ return "Error packaging payload" , err
184+ }
185+
186+ // Update the spec/annotate
187+ // TODO(benjaminjb): Would we want to allow a dry-run option here?
188+ patchOptions := metav1.PatchOptions {}
189+ if backup .ForceConflicts {
190+ b := true
191+ patchOptions .Force = & b
192+ }
193+ if _ , err = client .Namespace (namespace ).Patch (ctx ,
194+ backup .ClusterName ,
195+ types .ApplyPatchType ,
196+ patch ,
197+ config .Patch .PatchOptions (patchOptions ),
198+ ); err != nil {
199+ if apierrors .IsConflict (err ) {
200+ return "SUGGESTION: The --force-conflicts flag may help in performing this operation." , err
201+ }
202+ return "Error requesting update" , err
203+ }
204+
205+ return "" , err
206+ }
0 commit comments