@@ -2,17 +2,20 @@ package list
22
33import (
44 "context"
5+ "errors"
56 "fmt"
7+ "strconv"
68 "time"
79
810 "github.com/stackitcloud/stackit-cli/internal/pkg/types"
911
1012 "github.com/spf13/cobra"
13+ "github.com/stackitcloud/stackit-sdk-go/core/experimental/paginate"
1114 resourcemanager "github.com/stackitcloud/stackit-sdk-go/services/resourcemanager/v0api"
1215
1316 "github.com/stackitcloud/stackit-cli/internal/pkg/args"
1417 "github.com/stackitcloud/stackit-cli/internal/pkg/auth"
15- "github.com/stackitcloud/stackit-cli/internal/pkg/errors"
18+ internalerrors "github.com/stackitcloud/stackit-cli/internal/pkg/errors"
1619 "github.com/stackitcloud/stackit-cli/internal/pkg/examples"
1720 "github.com/stackitcloud/stackit-cli/internal/pkg/flags"
1821 "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
@@ -95,51 +98,42 @@ func configureFlags(cmd *cobra.Command) {
9598 cmd .Flags ().String (memberFlag , "" , "Filter by member. The list of folders of which the member is part of will be shown" )
9699 cmd .Flags ().String (creationTimeAfterFlag , "" , "Filter by creation timestamp, in a date-time with the RFC3339 layout format, e.g. 2023-01-01T00:00:00Z. The list of folders that were created after the given timestamp will be shown" )
97100 cmd .Flags ().Int64 (limitFlag , 0 , "Maximum number of entries to list" )
98- cmd .Flags ().Int64 (pageSizeFlag , pageSizeDefault , "Number of items fetched in each API call. Does not affect the number of items in the command output" )
99101}
100102
101103func parseInput (p * print.Printer , cmd * cobra.Command , _ []string ) (* inputModel , error ) {
102104 globalFlags := globalflags .Parse (p , cmd )
103105
104106 creationTimeAfter , err := flags .FlagToDateTimePointer (p , cmd , creationTimeAfterFlag , creationTimeAfterFormat )
105107 if err != nil {
106- return nil , & errors .FlagValidationError {
108+ return nil , & internalerrors .FlagValidationError {
107109 Flag : creationTimeAfterFlag ,
108110 Details : err .Error (),
109111 }
110112 }
111113
112114 limit := flags .FlagToInt64Pointer (p , cmd , limitFlag )
113115 if limit != nil && * limit < 1 {
114- return nil , & errors .FlagValidationError {
116+ return nil , & internalerrors .FlagValidationError {
115117 Flag : limitFlag ,
116118 Details : "must be greater than 0" ,
117119 }
118120 }
119121
120- pageSize := flags .FlagWithDefaultToInt64Value (p , cmd , pageSizeFlag )
121- if pageSize < 1 {
122- return nil , & errors.FlagValidationError {
123- Flag : pageSizeFlag ,
124- Details : "must be greater than 0" ,
125- }
126- }
127-
128122 model := inputModel {
129123 GlobalFlagModel : globalFlags ,
130124 ParentId : flags .FlagToStringPointer (p , cmd , parentIdFlag ),
131125 ProjectIdLike : flags .FlagToStringSliceValue (p , cmd , projectIdLikeFlag ),
132126 Member : flags .FlagToStringPointer (p , cmd , memberFlag ),
133127 CreationTimeAfter : creationTimeAfter ,
134128 Limit : limit ,
135- PageSize : pageSize ,
129+ PageSize : pageSizeDefault ,
136130 }
137131
138132 p .DebugInputModel (model )
139133 return & model , nil
140134}
141135
142- func buildRequest (ctx context.Context , model * inputModel , apiClient resourceManagerClient , offset int ) (resourcemanager.ApiListFoldersRequest , error ) {
136+ func buildRequest (ctx context.Context , model * inputModel , apiClient resourcemanager. DefaultAPI ) (resourcemanager.ApiListFoldersRequest , error ) {
143137 req := apiClient .ListFolders (ctx )
144138 if model .ParentId != nil {
145139 req = req .ContainerParentId (* model .ParentId )
@@ -150,6 +144,9 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient resourceMana
150144 if model .CreationTimeAfter != nil {
151145 req = req .CreationTimeStart (* model .CreationTimeAfter )
152146 }
147+ if model .ProjectIdLike != nil {
148+ req = req .ContainerIds (model .ProjectIdLike )
149+ }
153150
154151 if model .ParentId == nil && model .ProjectIdLike == nil && model .Member == nil {
155152 email , err := auth .GetAuthEmail ()
@@ -159,49 +156,28 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient resourceMana
159156 req = req .Member (email )
160157 }
161158 req = req .Limit (float32 (model .PageSize ))
162- req = req .Offset (float32 (offset ))
163159 return req , nil
164160}
165161
166- type resourceManagerClient interface {
167- ListFolders (ctx context.Context ) resourcemanager.ApiListFoldersRequest
168- }
162+ func fetchFolders (ctx context.Context , model * inputModel , apiClient resourcemanager.DefaultAPI ) ([]resourcemanager.ListFoldersResponseItemsInner , error ) {
163+ opts := []paginate.Option {paginate .WithPageSize (pageSizeDefault )}
164+ if model .Limit != nil {
165+ opts = append (opts , paginate .WithLimit (int (* model .Limit )))
166+ }
169167
170- func fetchFolders (ctx context. Context , model * inputModel , apiClient resourceManagerClient ) ([]resourcemanager. ListFoldersResponseItemsInner , error ) {
171- if model . Limit != nil && * model . Limit < model . PageSize {
172- model . PageSize = * model . Limit
168+ req , err := buildRequest (ctx , model , apiClient )
169+ if err != nil {
170+ return nil , fmt . Errorf ( "failed to build request: %w" , err )
173171 }
174172
175- offset := 0
176- folders := []resourcemanager.ListFoldersResponseItemsInner {}
177- for {
178- // Call API
179- req , err := buildRequest (ctx , model , apiClient , offset )
180- if err != nil {
181- return nil , fmt .Errorf ("build list folders request: %w" , err )
182- }
183- resp , err := req .Execute ()
173+ items := make ([]resourcemanager.ListFoldersResponseItemsInner , 0 )
174+ for item , err := range paginate .Items (& listFoldersAdapter {request : req }, opts ... ) {
184175 if err != nil {
185- return nil , fmt .Errorf ("get folders: %w" , err )
186- }
187- respFolders := resp .GetItems ()
188- if len (respFolders ) == 0 {
189- break
190- }
191- folders = append (folders , respFolders ... )
192- // Stop if no more pages
193- if len (respFolders ) < int (model .PageSize ) {
194- break
195- }
196-
197- // Stop and truncate if limit is reached
198- if model .Limit != nil && len (folders ) >= int (* model .Limit ) {
199- folders = folders [:* model .Limit ]
200- break
176+ return nil , fmt .Errorf ("list items: %w" , err )
201177 }
202- offset += int ( model . PageSize )
178+ items = append ( items , item )
203179 }
204- return folders , nil
180+ return items , nil
205181}
206182
207183func outputResult (p * print.Printer , outputFormat string , folders []resourcemanager.ListFoldersResponseItemsInner ) error {
@@ -212,9 +188,7 @@ func outputResult(p *print.Printer, outputFormat string, folders []resourcemanag
212188 }
213189 table := tables .NewTable ()
214190 table .SetHeader ("ID" , "NAME" , "PARENT ID" )
215- for i := range folders {
216- f := folders [i ]
217-
191+ for _ , f := range folders {
218192 table .AddRow (
219193 f .FolderId ,
220194 f .Name ,
@@ -230,3 +204,67 @@ func outputResult(p *print.Printer, outputFormat string, folders []resourcemanag
230204 return nil
231205 })
232206}
207+
208+ var (
209+ _ paginate.Request [* listFoldersAdapter , listFoldersAdapterResponse ] = (* listFoldersAdapter )(nil )
210+ _ paginate.Response [resourcemanager.ListFoldersResponseItemsInner ] = listFoldersAdapterResponse {}
211+ )
212+
213+ // listFoldersAdapter maps the token-based paginate API onto the
214+ // offset/limit-based ListFolders endpoint. The page token is the
215+ // decimal offset of the next page.
216+ type listFoldersAdapter struct {
217+ request resourcemanager.ApiListFoldersRequest
218+ pageSize int32
219+ offset int64
220+ err error
221+ }
222+
223+ func (a * listFoldersAdapter ) PageSize (pageSize int32 ) * listFoldersAdapter {
224+ a .pageSize = pageSize
225+ a .request = a .request .Limit (float32 (pageSize ))
226+ return a
227+ }
228+
229+ func (a * listFoldersAdapter ) PageToken (pageToken string ) * listFoldersAdapter {
230+ offset , err := strconv .ParseInt (pageToken , 10 , 64 )
231+ if err != nil || offset < 0 {
232+ a .err = fmt .Errorf ("invalid page token %q" , pageToken )
233+ return a
234+ }
235+ a .offset = offset
236+ a .request = a .request .Offset (float32 (offset ))
237+ return a
238+ }
239+
240+ func (a * listFoldersAdapter ) Execute () (listFoldersAdapterResponse , error ) {
241+ if a .err != nil {
242+ return listFoldersAdapterResponse {}, a .err
243+ }
244+ resp , err := a .request .Execute ()
245+ if err != nil {
246+ return listFoldersAdapterResponse {}, err
247+ }
248+ if resp == nil {
249+ return listFoldersAdapterResponse {}, errors .New ("empty response from API" )
250+ }
251+ return listFoldersAdapterResponse {
252+ ListFoldersResponse : resp ,
253+ offset : a .offset ,
254+ pageSize : a .pageSize ,
255+ }, nil
256+ }
257+
258+ type listFoldersAdapterResponse struct {
259+ * resourcemanager.ListFoldersResponse
260+ offset int64
261+ pageSize int32
262+ }
263+
264+ func (r listFoldersAdapterResponse ) GetNextPageToken () string {
265+ n := int64 (len (r .GetItems ()))
266+ if n == 0 || n < int64 (r .pageSize ) {
267+ return ""
268+ }
269+ return strconv .FormatInt (r .offset + n , 10 )
270+ }
0 commit comments