diff --git a/cli/cli.go b/cli/cli.go index 3f2e4169..e0bb3f75 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -28,6 +28,7 @@ import ( "github.com/arduino/arduino-cloud-cli/cli/credentials" "github.com/arduino/arduino-cloud-cli/cli/dashboard" "github.com/arduino/arduino-cloud-cli/cli/device" + "github.com/arduino/arduino-cloud-cli/cli/folders" "github.com/arduino/arduino-cloud-cli/cli/ota" "github.com/arduino/arduino-cloud-cli/cli/template" "github.com/arduino/arduino-cloud-cli/cli/thing" @@ -67,6 +68,7 @@ func Execute() { cli.AddCommand(dashboard.NewCommand()) cli.AddCommand(ota.NewCommand()) cli.AddCommand(template.NewCommand()) + cli.AddCommand(folders.NewCommand()) if err := cli.Execute(); err != nil { fmt.Fprintln(os.Stderr, err) diff --git a/cli/folders/folder.go b/cli/folders/folder.go new file mode 100644 index 00000000..06a91bec --- /dev/null +++ b/cli/folders/folder.go @@ -0,0 +1,34 @@ +// This file is part of arduino-cloud-cli. +// +// Copyright (C) 2024 ARDUINO SA (http://www.arduino.cc/) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package folders + +import ( + "github.com/spf13/cobra" +) + +func NewCommand() *cobra.Command { + foldersCommand := &cobra.Command{ + Use: "folders", + Short: "Custom folders.", + Long: "Commands to manage folders.", + } + + foldersCommand.AddCommand(initFoldersListCommand()) + + return foldersCommand +} diff --git a/cli/folders/list.go b/cli/folders/list.go new file mode 100644 index 00000000..4e4c4ac9 --- /dev/null +++ b/cli/folders/list.go @@ -0,0 +1,55 @@ +// This file is part of arduino-cloud-cli. +// +// Copyright (C) 2024 ARDUINO SA (http://www.arduino.cc/) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package folders + +import ( + "context" + "fmt" + "os" + + "github.com/arduino/arduino-cli/cli/errorcodes" + "github.com/arduino/arduino-cli/cli/feedback" + "github.com/arduino/arduino-cloud-cli/command/folder" + "github.com/arduino/arduino-cloud-cli/config" + "github.com/spf13/cobra" +) + +func initFoldersListCommand() *cobra.Command { + listCommand := &cobra.Command{ + Use: "list", + Short: "List folders", + Long: "List available folders", + Run: func(cmd *cobra.Command, args []string) { + if err := runFoldersListCommand(); err != nil { + feedback.Errorf("Error during folders list: %v", err) + os.Exit(errorcodes.ErrGeneric) + } + }, + } + + return listCommand +} + +func runFoldersListCommand() error { + cred, err := config.RetrieveCredentials() + if err != nil { + return fmt.Errorf("retrieving credentials: %w", err) + } + ctx := context.Background() + return folder.ListFolders(ctx, cred) +} diff --git a/command/folder/dto.go b/command/folder/dto.go new file mode 100644 index 00000000..077934ae --- /dev/null +++ b/command/folder/dto.go @@ -0,0 +1,57 @@ +package folder + +import ( + "time" + + "github.com/arduino/arduino-cli/table" +) + +type PathNode struct { + // ID of the folder that makes up this path node + FolderId string `json:"folder_id"` + // Name of the folder that makes up this path node + FolderName string `json:"folder_name"` +} + +type Folder struct { + ID string `json:"id"` + Name string `json:"name"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Parent *PathNode `json:"parent,omitempty"` + Path []PathNode `json:"path,omitempty"` +} + +type Folders struct { + Folders []Folder `json:"folders"` +} + +func (r *Folders) Data() interface{} { + return r +} + +func (r *Folders) String() string { + if len(r.Folders) == 0 { + return "" + } + t := table.New() + t.SetHeader("Folder ID", "Name", "Created At", "Updated At", "Parent", "Path") + + // Now print the table + for _, fol := range r.Folders { + parent := "" + if fol.Parent != nil { + parent = fol.Parent.FolderName + } + if fol.Path != nil { + for _, p := range fol.Path { + parent += "> " + p.FolderName + } + } + path := "" + line := []any{fol.ID, fol.Name, fol.CreatedAt.Format(time.RFC3339), fol.UpdatedAt.Format(time.RFC3339), parent, path} + t.AddRow(line...) + } + + return t.Render() +} diff --git a/command/folder/list.go b/command/folder/list.go new file mode 100644 index 00000000..e7e8f0e6 --- /dev/null +++ b/command/folder/list.go @@ -0,0 +1,71 @@ +// This file is part of arduino-cloud-cli. +// +// Copyright (C) 2024 ARDUINO SA (http://www.arduino.cc/) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package folder + +import ( + "context" + + "github.com/arduino/arduino-cli/cli/feedback" + "github.com/arduino/arduino-cloud-cli/config" + "github.com/arduino/arduino-cloud-cli/internal/iot" +) + +func ListFolders(ctx context.Context, cred *config.Credentials) error { + iotClient, err := iot.NewClient(cred) + if err != nil { + return err + } + + fold, err := iotClient.FoldersList(ctx) + if err != nil { + return err + } + + if fold == nil { + feedback.Print("No folders found") + } else { + folders := &Folders{} + for _, f := range fold { + trFolder := Folder{ + ID: f.Id, + Name: f.Name, + CreatedAt: f.CreatedAt, + UpdatedAt: f.UpdatedAt, + } + if f.Parent != nil { + trFolder.Parent = &PathNode{ + FolderId: f.Parent.FolderId, + FolderName: f.Parent.FolderName, + } + } + if f.Path != nil { + trFolder.Path = make([]PathNode, 0, len(f.Path)) + for _, p := range f.Path { + trFolder.Path = append(trFolder.Path, PathNode{ + FolderId: p.FolderId, + FolderName: p.FolderName, + }) + } + } + folders.Folders = append(folders.Folders, trFolder) + } + feedback.PrintResult(folders) + } + + return nil +} diff --git a/go.mod b/go.mod index f29d6e6b..5acfacb4 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,8 @@ module github.com/arduino/arduino-cloud-cli go 1.23.0 +replace github.com/arduino/iot-client-go/v2 => /home/macolomb/CODE/clients-iot-api/generated/iot-client-go + require ( github.com/arduino/arduino-cli v0.0.0-20240927141754-d9dd4ba1ed71 github.com/arduino/go-paths-helper v1.12.1 diff --git a/internal/iot/client.go b/internal/iot/client.go index 788a4e05..cb521ed2 100644 --- a/internal/iot/client.go +++ b/internal/iot/client.go @@ -560,6 +560,22 @@ func (cl *Client) TemplateApply(ctx context.Context, id, thingId, prefix, device return dev, nil } +// List available folders +func (cl *Client) FoldersList(ctx context.Context) ([]iotclient.Folder, error) { + ctx, err := ctxWithToken(ctx, cl.token) + if err != nil { + return nil, err + } + + req := cl.api.FoldersApi.ListFolders(ctx) + folders, _, err := cl.api.FoldersApi.ListFoldersExecute(req) + if err != nil { + err = fmt.Errorf("listing folders: %w", errorDetail(err)) + return nil, err + } + return folders, nil +} + func (cl *Client) setup(client, secret, organizationId string) error { baseURL := GetArduinoAPIBaseURL() @@ -572,7 +588,7 @@ func (cl *Client) setup(client, secret, organizationId string) error { } config.Servers = iotclient.ServerConfigurations{ { - URL: fmt.Sprintf("%s/iot", baseURL), + URL: baseURL, Description: "IoT API endpoint", }, }