Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
386740b
[skip changelog] Add DiscoveryManager to PackageManager
silvanocerza Aug 9, 2021
ebca55f
Add loading of PluggableDiscoveries when loading a platform release
silvanocerza Jun 23, 2021
7cf3215
Added compatibility layer for non-pluggable platforms
cmaglie Jun 29, 2021
f442b1a
Implemented board list with discoveries
silvanocerza Aug 9, 2021
dd6a8fc
Implemented discovery loading after initialization
cmaglie Jun 29, 2021
f07775a
Implemented board watch with discoveries
silvanocerza Aug 9, 2021
e80869b
Fix load discoveries tests
silvanocerza Jun 29, 2021
23bab36
Fix some issues with board list watcher
silvanocerza Jun 30, 2021
3b4bd38
Fix FindToolsRequiredFromPlatformRelease not returning discoveries
silvanocerza Jun 30, 2021
47b84f5
Enhanced handling of some discoveries states
silvanocerza Jul 2, 2021
b32749f
Fix PackageManager reset
silvanocerza Jul 6, 2021
0d2add4
Add function to convert discovery.Port to rpc.Port
silvanocerza Aug 9, 2021
8bb84b2
Moved reference argument parsing to new package
silvanocerza Jul 6, 2021
3ff8287
Fix functions docstrings
silvanocerza Jul 8, 2021
ec3dfbc
Remove duplicated code to initialize Sketch path
silvanocerza Aug 9, 2021
fb934e5
Add property conversion for platform not supporting pluggable discovery
silvanocerza Jul 15, 2021
38c4325
Fix board list watch not working
silvanocerza Jul 15, 2021
a3007fe
Fix crash when converting Port to rpc struct
silvanocerza Jul 20, 2021
2d69063
Add generic Port argument
silvanocerza Jul 15, 2021
7e6dfca
Change gRPC upload functions to use new Port message
silvanocerza Aug 9, 2021
e6c33ea
Add support for upload user fields
silvanocerza Aug 9, 2021
4a586d9
Fix upload unit tests
silvanocerza Jul 20, 2021
67ae164
Fix code naming issues
silvanocerza Jul 20, 2021
ddf1603
Added builtin:mdns-discovery
cmaglie Jul 21, 2021
ea61dca
Do not panic if discovery tool is not installed
cmaglie Jul 21, 2021
d255c3d
Implemented port/protocol detection at CLI startup time
silvanocerza Aug 9, 2021
18777b2
Perform 1200bps-touch only on serial ports
cmaglie Jul 21, 2021
709db6f
Added missing properties for pluggable upload
cmaglie Jul 21, 2021
322ce23
Correctly implemented 'board list' timeout option
silvanocerza Aug 9, 2021
4fc7857
Updated mdns-discovery to 0.9.2
cmaglie Jul 22, 2021
77a40fd
Add documentation
silvanocerza Aug 10, 2021
f8ac21d
Add board properties to board list command and gRPC function
silvanocerza Aug 9, 2021
5ecf4d8
Fix documentation and code comments
silvanocerza Aug 10, 2021
c8fb6e4
Fix crash when attempting upload without specifying port address
silvanocerza Jul 23, 2021
2a7d3d2
Fix unit tests
silvanocerza Jul 23, 2021
015aea3
Update go-properties-orderedmap to fix discovery properties issues
silvanocerza Aug 11, 2021
33f8848
Fix more documentation
silvanocerza Jul 26, 2021
bd68d5e
Clarify pluggable discovery specification
silvanocerza Jul 26, 2021
69c6812
More documentation fixes
silvanocerza Jul 27, 2021
aec9695
Add upload_port properties docs in platform specification
silvanocerza Jul 27, 2021
ed318af
Change links from pluggable discovery RFC to official docs
silvanocerza Jul 27, 2021
95b69fa
Add more upload mock integration tests
silvanocerza Jul 27, 2021
3e3fcd2
Fix integration tests
silvanocerza Jul 28, 2021
b7d594f
Change property to declare pluggable discoveries
silvanocerza Aug 10, 2021
763ce6f
Change property to declare pluggable discoveries
silvanocerza Jul 28, 2021
ae3a497
Fix documentation
silvanocerza Jul 28, 2021
3f484ed
Fix loading of platform not supporting pluggable discovery
silvanocerza Jul 28, 2021
be9e879
Fix more documentation
silvanocerza Jul 29, 2021
225c123
Add pluggable discovery states documentation
silvanocerza Aug 2, 2021
569429d
Enhanced handling of pluggable discoveries states
silvanocerza Aug 9, 2021
a0f2aee
Discoveries processes are now killed if the HELLO command fails
silvanocerza Aug 3, 2021
2624785
Add pluggable discovery logging
silvanocerza Aug 4, 2021
2a555ff
Enhanced handling of failing pluggable discoveries
silvanocerza Aug 4, 2021
71dcc0e
Fix pluggable discoveries parallelization
silvanocerza Aug 9, 2021
a0a6371
Discoveries event channels are now created when start sync is called
silvanocerza Aug 9, 2021
cfc7aa4
Cached ports are now reset on discovery stop
silvanocerza Aug 9, 2021
2a34f7d
Renamed ListSync methods to ListCachedPorts
silvanocerza Aug 9, 2021
85607d6
Pluggable discovery upload user fields are now limited to 50 chars
silvanocerza Aug 9, 2021
a5be354
Fix i18n strings
silvanocerza Aug 9, 2021
e68e4e0
Fix failing integration tests
silvanocerza Aug 10, 2021
eef60aa
Fix i18n data
silvanocerza Aug 10, 2021
f8814bc
Fix integration tests again
silvanocerza Aug 10, 2021
5aa1f1d
[skip changelog] Internationalize strings added for pluggable discove…
per1234 Aug 10, 2021
9941402
Update docs/pluggable-discovery-specification.md
silvanocerza Aug 10, 2021
53e1d25
Fix failing workflows
silvanocerza Aug 11, 2021
9a355b7
Updated upload-mock tests for generation
cmaglie Aug 12, 2021
41db8b5
Added a lot of mock upload test (also with programmer option)
cmaglie Aug 16, 2021
b322874
test_upload_mock: Handle '{' and '}' in recipes
cmaglie Aug 16, 2021
d7a576c
network ota: autoconvert network_patter from legacy
cmaglie Aug 17, 2021
3c10109
Automatically add port detection properties for network discovery
cmaglie Aug 19, 2021
e6ee163
Slightly improved 'board list' text output
cmaglie Aug 20, 2021
75c7fef
Default 'board list' timeout to 1s
cmaglie Aug 20, 2021
c100e14
Added some code review fixes
cmaglie Aug 23, 2021
40943b1
Added unit test for legacy-package conversion to pluggable discovery
cmaglie Aug 23, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add support for upload user fields
  • Loading branch information
silvanocerza authored and cmaglie committed Aug 19, 2021
commit e6c33ea4d3685dbb1bd32d7b54981db6100885d5
51 changes: 51 additions & 0 deletions cli/arguments/user_fields.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// This file is part of arduino-cli.
//
// Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
//
// This software is released under the GNU General Public License version 3,
// which covers the main part of arduino-cli.
// The terms of this license can be found at:
// https://www.gnu.org/licenses/gpl-3.0.en.html
//
// You can be released from the requirements of the above licenses by purchasing
// a commercial license. Buying such a license is mandatory if you want to
// modify or otherwise use the software for commercial activities involving the
// Arduino software without disclosing the source code of your own applications.
// To purchase a commercial license, send an email to license@arduino.cc.

package arguments

import (
"bufio"
"fmt"
"os"

"github.com/arduino/arduino-cli/cli/feedback"
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
"golang.org/x/crypto/ssh/terminal"
)

// AskForUserFields prompts the user to input the provided user fields.
// If there is an error reading input it panics.
func AskForUserFields(userFields []*rpc.UserField) map[string]string {
writer := feedback.OutputWriter()
fields := map[string]string{}
reader := bufio.NewReader(os.Stdin)
for _, f := range userFields {
fmt.Fprintf(writer, "%s: ", f.Label)
var value []byte
var err error
if f.Secret {
value, err = terminal.ReadPassword(int(os.Stdin.Fd()))
} else {
value, err = reader.ReadBytes('\n')
}
if err != nil {
panic(err)
}
fields[f.Name] = string(value)
}
fmt.Fprintln(writer, "")

return fields
}
23 changes: 20 additions & 3 deletions cli/compile/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,16 +190,33 @@ func run(cmd *cobra.Command, args []string) {
}

if err == nil && uploadAfterCompile {

var sk *sketch.Sketch
sk, err = sketch.New(sketchPath)
if err != nil {

feedback.Errorf("Error during Upload: %v", err)
os.Exit(errorcodes.ErrGeneric)
}
var discoveryPort *discovery.Port
discoveryPort, err = port.GetPort(inst, sk)
if err != nil {
feedback.Errorf("Error during Upload: %v", err)
os.Exit(errorcodes.ErrGeneric)
}

userFieldRes, err := upload.SupportedUserFields(context.Background(), &rpc.SupportedUserFieldsRequest{
Instance: inst,
Fqbn: fqbn,
Protocol: discoveryPort.Protocol,
})
if err != nil {
feedback.Errorf("Error during Upload: %v", err)
os.Exit(errorcodes.ErrGeneric)
}

fields := map[string]string{}
if len(userFieldRes.UserFields) > 0 {
feedback.Printf("Uploading to specified board using %s protocol requires the following info:", discoveryPort.Protocol)
fields = arguments.AskForUserFields(userFieldRes.UserFields)
}

uploadRequest := &rpc.UploadRequest{
Expand All @@ -211,8 +228,8 @@ func run(cmd *cobra.Command, args []string) {
Verify: verify,
ImportDir: buildPath,
Programmer: programmer,
UserFields: fields,
}
var err error
if output.OutputFormat == "json" {
// TODO: do not print upload output in json mode
uploadOut := new(bytes.Buffer)
Expand Down
17 changes: 17 additions & 0 deletions cli/upload/upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,22 @@ func run(command *cobra.Command, args []string) {
os.Exit(errorcodes.ErrGeneric)
}

userFieldRes, err := upload.SupportedUserFields(context.Background(), &rpc.SupportedUserFieldsRequest{
Instance: instance,
Fqbn: fqbn,
Protocol: discoveryPort.Protocol,
})
if err != nil {
feedback.Errorf("Error during Upload: %v", err)
os.Exit(errorcodes.ErrGeneric)
}

fields := map[string]string{}
if len(userFieldRes.UserFields) > 0 {
feedback.Printf("Uploading to specified board using %s protocol requires the following info:", discoveryPort.Protocol)
fields = arguments.AskForUserFields(userFieldRes.UserFields)
}

if _, err := upload.Upload(context.Background(), &rpc.UploadRequest{
Instance: instance,
Fqbn: fqbn,
Expand All @@ -113,6 +129,7 @@ func run(command *cobra.Command, args []string) {
ImportDir: importDir,
Programmer: programmer,
DryRun: dryRun,
UserFields: fields,
}, os.Stdout, os.Stderr); err != nil {
feedback.Errorf(tr("Error during Upload: %v"), err)
os.Exit(errorcodes.ErrGeneric)
Expand Down
1 change: 1 addition & 0 deletions commands/upload/burnbootloader.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ func BurnBootloader(ctx context.Context, req *rpc.BurnBootloaderRequest, outStre
outStream,
errStream,
req.GetDryRun(),
map[string]string{}, // User fields
)
if err != nil {
return nil, err
Expand Down
84 changes: 83 additions & 1 deletion commands/upload/upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"fmt"
"io"
"path/filepath"
"strconv"
"strings"

"github.com/arduino/arduino-cli/arduino/cores"
Expand All @@ -39,6 +40,43 @@ import (

var tr = i18n.Tr

// SupportedUserFields returns a SupportedUserFieldsResponse containing all the UserFields supported
// by the upload tools needed by the board using the protocol specified in SupportedUserFieldsRequest.
func SupportedUserFields(ctx context.Context, req *rpc.SupportedUserFieldsRequest) (*rpc.SupportedUserFieldsResponse, error) {
if req.Protocol == "" {
return nil, fmt.Errorf("missing protocol")
}

pm := commands.GetPackageManager(req.GetInstance().GetId())
if pm == nil {
return nil, fmt.Errorf("invalid instance")
}

fqbn, err := cores.ParseFQBN(req.GetFqbn())
if err != nil {
return nil, fmt.Errorf("parsing fqbn: %s", err)
}

_, platformRelease, board, _, _, err := pm.ResolveFQBN(fqbn)
if err != nil {
return nil, fmt.Errorf("loading board data: %s", err)
}

toolId, err := getToolId(board.Properties, "upload", req.Protocol)
if err != nil {
return nil, err
}

userFields, err := getUserFields(toolId, platformRelease)
if err != nil {
return nil, err
}

return &rpc.SupportedUserFieldsResponse{
UserFields: userFields,
}, nil
}

// getToolId returns the ID of the tool that supports the action and protocol combination by searching in props.
// Returns error if tool cannot be found.
func getToolId(props *properties.Map, action, protocol string) (string, error) {
Expand All @@ -55,6 +93,35 @@ func getToolId(props *properties.Map, action, protocol string) (string, error) {
return "", fmt.Errorf("cannot find tool: undefined '%s' property", toolProperty)
}

// getUserFields return all user fields supported by the tools specified.
// Returns error only in case the secret property is not a valid boolean.
func getUserFields(toolId string, platformRelease *cores.PlatformRelease) ([]*rpc.UserField, error) {
userFields := []*rpc.UserField{}
fields := platformRelease.Properties.SubTree(fmt.Sprintf("tools.%s.upload.field", toolId))
keys := fields.FirstLevelKeys()

for _, key := range keys {
value := fields.Get(key)
secretProp := fmt.Sprintf("%s.secret", key)
secret, ok := fields.GetOk(secretProp)
if !ok {
secret = "false"
}
isSecret, err := strconv.ParseBool(secret)
if err != nil {
return nil, fmt.Errorf(`parsing "tools.%s.upload.field.%s.secret", property is not a boolean`, toolId, key)
}
userFields = append(userFields, &rpc.UserField{
ToolId: toolId,
Name: key,
Label: value,
Secret: isSecret,
})
}

return userFields, nil
}

// Upload FIXMEDOC
func Upload(ctx context.Context, req *rpc.UploadRequest, outStream io.Writer, errStream io.Writer) (*rpc.UploadResponse, error) {
logrus.Tracef("Upload %s on %s started", req.GetSketchPath(), req.GetFqbn())
Expand Down Expand Up @@ -83,6 +150,7 @@ func Upload(ctx context.Context, req *rpc.UploadRequest, outStream io.Writer, er
outStream,
errStream,
req.GetDryRun(),
req.GetUserFields(),
)
if err != nil {
return nil, err
Expand All @@ -107,6 +175,7 @@ func UsingProgrammer(ctx context.Context, req *rpc.UploadUsingProgrammerRequest,
Programmer: req.GetProgrammer(),
Verbose: req.GetVerbose(),
Verify: req.GetVerify(),
UserFields: req.GetUserFields(),
}, outStream, errStream)
return &rpc.UploadUsingProgrammerResponse{}, err
}
Expand All @@ -117,7 +186,7 @@ func runProgramAction(pm *packagemanager.PackageManager,
programmerID string,
verbose, verify, burnBootloader bool,
outStream, errStream io.Writer,
dryRun bool) error {
dryRun bool, userFields map[string]string) error {

if burnBootloader && programmerID == "" {
return fmt.Errorf(tr("no programmer specified for burning bootloader"))
Expand Down Expand Up @@ -231,6 +300,14 @@ func runProgramAction(pm *packagemanager.PackageManager,
}
}

// Certain tools require the user to provide custom fields at run time,
// if they've been provided set them
// For more info:
// https://github.com/arduino/tooling-rfcs/blob/main/RFCs/0002-pluggable-discovery.md#user-provided-fields
for name, value := range userFields {
uploadProperties.Set(fmt.Sprintf("%s.field.%s", action, name), value)
}

if !uploadProperties.ContainsKey("upload.protocol") && programmer == nil {
return fmt.Errorf(tr("a programmer is required to upload for this board"))
}
Expand Down Expand Up @@ -367,6 +444,11 @@ func runProgramAction(pm *packagemanager.PackageManager,
}
}

// Get Port properties gathered using Pluggable Discovery
for prop, value := range actualPort.Properties {
uploadProperties.Set(fmt.Sprintf("upload.port.properties.%s", prop), value)
}

// Run recipes for upload
if burnBootloader {
if err := runTool("erase.pattern", uploadProperties, outStream, errStream, verbose, dryRun); err != nil {
Expand Down
37 changes: 37 additions & 0 deletions commands/upload/upload_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,3 +266,40 @@ upload.tool.network=arduino_ota`))
require.NoError(t, err)
require.Equal(t, "avrdude", toolId)
}

func TestGetUserFields(t *testing.T) {
platformRelease := &cores.PlatformRelease{}

props, err := properties.LoadFromBytes([]byte(`
tools.avrdude.upload.field.username=Username
tools.avrdude.upload.field.password=Password
tools.avrdude.upload.field.password.secret=true
tools.arduino_ota.upload.field.username=Username
tools.arduino_ota.upload.field.password=Password
tools.arduino_ota.upload.field.password.secret=true`))
require.NoError(t, err)

platformRelease.Properties = props

userFields, err := getUserFields("avrdude", platformRelease)
require.NoError(t, err)
require.Len(t, userFields, 2)
require.Equal(t, userFields[0].ToolId, "avrdude")
require.Equal(t, userFields[0].Name, "username")
require.Equal(t, userFields[0].Label, "Username")
require.False(t, userFields[0].Secret)
require.Equal(t, userFields[1].ToolId, "avrdude")
require.Equal(t, userFields[1].Name, "password")
require.Equal(t, userFields[1].Label, "Password")
require.True(t, userFields[1].Secret)

props, err = properties.LoadFromBytes([]byte(`
tools.arduino_ota.upload.field.password=Password
tools.arduino_ota.upload.field.password.secret=THIS_IS_NOT_A_BOOLEAN`))
require.NoError(t, err)
platformRelease.Properties = props

userFields, err = getUserFields("arduino_ota", platformRelease)
require.Nil(t, userFields)
require.EqualError(t, err, `parsing "tools.arduino_ota.upload.field.password.secret", property is not a boolean`)
}
Loading