@@ -17,6 +17,8 @@ package cmdrpkgcopy
17
17
import (
18
18
"context"
19
19
"fmt"
20
+ "regexp"
21
+ "strconv"
20
22
21
23
"github.com/GoogleContainerTools/kpt/internal/docs/generated/rpkgdocs"
22
24
"github.com/GoogleContainerTools/kpt/internal/errors"
@@ -26,6 +28,7 @@ import (
26
28
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27
29
"k8s.io/apimachinery/pkg/runtime"
28
30
"k8s.io/cli-runtime/pkg/genericclioptions"
31
+ "k8s.io/client-go/rest"
29
32
"sigs.k8s.io/controller-runtime/pkg/client"
30
33
)
31
34
@@ -42,7 +45,7 @@ func newRunner(ctx context.Context, rcg *genericclioptions.ConfigFlags) *runner
42
45
ctx : ctx ,
43
46
cfg : rcg ,
44
47
}
45
- c : = & cobra.Command {
48
+ r . Command = & cobra.Command {
46
49
Use : "copy SOURCE_PACKAGE NAME" ,
47
50
Aliases : []string {"edit" },
48
51
Short : rpkgdocs .CopyShort ,
@@ -52,10 +55,7 @@ func newRunner(ctx context.Context, rcg *genericclioptions.ConfigFlags) *runner
52
55
RunE : r .runE ,
53
56
Hidden : porch .HidePorchCommands ,
54
57
}
55
- r .Command = c
56
- // TODO (natasha41575): Make the default "latest+1"
57
- c .Flags ().StringVar (& r .revision , "revision" , "" , "Revision of the copied package." )
58
-
58
+ r .Command .Flags ().StringVar (& r .revision , "revision" , "" , "Revision of the copied package." )
59
59
return r
60
60
}
61
61
@@ -85,12 +85,6 @@ func (r *runner) preRunE(cmd *cobra.Command, args []string) error {
85
85
return errors .E (op , fmt .Errorf ("too many arguments; SOURCE_PACKAGE is the only accepted positional arguments" ))
86
86
}
87
87
88
- // TODO(natasha41575): This is temporarily required until we can set a default value for the revision. Now that we are disallowing
89
- // package name changes or editing outside the repository, the copy needs to have a new revision number.
90
- if r .revision == "" {
91
- return errors .E (op , fmt .Errorf ("--revision is a required flag" ))
92
- }
93
-
94
88
r .copy .Source = & porchapi.PackageRevisionRef {
95
89
Name : args [0 ],
96
90
}
@@ -131,24 +125,98 @@ func (r *runner) getPackageRevisionSpec() (*porchapi.PackageRevisionSpec, error)
131
125
return nil , err
132
126
}
133
127
134
- result := porchapi.PackageRevision {}
128
+ packageRevision := porchapi.PackageRevision {}
135
129
err = restClient .
136
130
Get ().
137
131
Namespace (* r .cfg .Namespace ).
138
132
Resource ("packagerevisions" ).
139
133
Name (r .copy .Source .Name ).
140
134
Do (r .ctx ).
141
- Into (& result )
135
+ Into (& packageRevision )
142
136
if err != nil {
143
137
return nil , err
144
138
}
145
139
146
- // TODO(natasha41575): Set a default revision of "latest + 1"
140
+ if r .revision == "" {
141
+ var err error
142
+ r .revision , err = r .defaultPackageRevision (
143
+ packageRevision .Spec .PackageName ,
144
+ packageRevision .Spec .RepositoryName ,
145
+ restClient ,
146
+ )
147
+ if err != nil {
148
+ return nil , err
149
+ }
150
+ }
151
+
147
152
spec := & porchapi.PackageRevisionSpec {
148
- PackageName : result .Spec .PackageName ,
153
+ PackageName : packageRevision .Spec .PackageName ,
149
154
Revision : r .revision ,
150
- RepositoryName : result .Spec .RepositoryName ,
155
+ RepositoryName : packageRevision .Spec .RepositoryName ,
151
156
}
152
157
spec .Tasks = []porchapi.Task {{Type : porchapi .TaskTypeEdit , Edit : & r .copy }}
153
158
return spec , nil
154
159
}
160
+
161
+ // defaultPackageRevision attempts to return a default package revision number
162
+ // of "latest + 1" given a package name, repository, and namespace. It only
163
+ // understands revisions following `v[0-9]+` formats.
164
+ func (r * runner ) defaultPackageRevision (packageName , repository string , restClient rest.Interface ) (string , error ) {
165
+ // get all package revisions
166
+ packageRevisionList := porchapi.PackageRevisionList {}
167
+ err := restClient .
168
+ Get ().
169
+ Namespace (* r .cfg .Namespace ).
170
+ Resource ("packagerevisions" ).
171
+ Do (r .ctx ).
172
+ Into (& packageRevisionList )
173
+ if err != nil {
174
+ return "" , err
175
+ }
176
+
177
+ var latestRevision string
178
+ allRevisions := make (map [string ]bool ) // this is a map for quick access
179
+
180
+ for _ , rev := range packageRevisionList .Items {
181
+ if packageName != rev .Spec .PackageName ||
182
+ repository != rev .Spec .RepositoryName {
183
+ continue
184
+ }
185
+
186
+ if latest , ok := rev .Labels [porchapi .LatestPackageRevisionKey ]; ok {
187
+ if latest == porchapi .LatestPackageRevisionValue {
188
+ latestRevision = rev .Spec .Revision
189
+ }
190
+ }
191
+ allRevisions [rev .Spec .Revision ] = true
192
+ }
193
+ if latestRevision == "" {
194
+ return "" , fmt .Errorf ("no published packages exist; explicit --revision flag is required" )
195
+ }
196
+
197
+ next , err := nextRevisionNumber (latestRevision )
198
+ if err != nil {
199
+ return "" , err
200
+ }
201
+ if _ , ok := allRevisions [next ]; ok {
202
+ return "" , fmt .Errorf ("default revision %q already exists; explicit --revision flag is required" , next )
203
+ }
204
+ return next , err
205
+ }
206
+
207
+ func nextRevisionNumber (latestRevision string ) (string , error ) {
208
+ match , err := regexp .MatchString ("^v[0-9]+$" , latestRevision )
209
+ if err != nil {
210
+ return "" , err
211
+ }
212
+ if ! match {
213
+ return "" , fmt .Errorf ("could not understand format of latest revision %q; explicit --revision flag is required" , latestRevision )
214
+ }
215
+ i , err := strconv .Atoi (latestRevision [1 :])
216
+ if err != nil {
217
+ return "" , err
218
+ }
219
+ i ++
220
+ next := "v" + strconv .Itoa (i )
221
+ return next , nil
222
+ }
0 commit comments