From d3f3577579ed49174a91ac0074c33715cbebf72a Mon Sep 17 00:00:00 2001 From: Chris Tacke Date: Fri, 15 Mar 2024 17:14:56 -0500 Subject: [PATCH 01/15] generate provision command --- .../Current/Device/DeviceProvisionCommand.cs | 33 +++++++++++++++++++ .../Meadow.Cli/Properties/launchSettings.json | 6 +++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/Source/v2/Meadow.Cli/Commands/Current/Device/DeviceProvisionCommand.cs b/Source/v2/Meadow.Cli/Commands/Current/Device/DeviceProvisionCommand.cs index 3f19129c..67902233 100644 --- a/Source/v2/Meadow.Cli/Commands/Current/Device/DeviceProvisionCommand.cs +++ b/Source/v2/Meadow.Cli/Commands/Current/Device/DeviceProvisionCommand.cs @@ -30,6 +30,9 @@ public class DeviceProvisionCommand : BaseDeviceCommand [CommandOption("id", 'i', Description = "The unique ID/serial number of the device to provision. If not provided, it will be queried from the configured device.", IsRequired = false)] public string? SerialNumber { get; set; } + [CommandOption("gen-command", Description = "Generate a provisioning command for the configured device", IsRequired = false)] + public bool GenerateProvisionCommand { get; set; } + public DeviceProvisionCommand(UserService userService, DeviceService deviceService, MeadowConnectionManager connectionManager, ILoggerFactory loggerFactory) : base(connectionManager, loggerFactory) { @@ -37,8 +40,38 @@ public DeviceProvisionCommand(UserService userService, DeviceService deviceServi _userService = userService; } + private async ValueTask OutputProvisionCommand() + { + var device = await GetCurrentDevice(); + + var info = await device.GetDeviceInfo(CancellationToken); + + Logger?.LogInformation(Strings.RequestingDevicePublicKey); + + var publicKey = await device.GetPublicKey(CancellationToken); + publicKey = publicKey.Replace("\r", string.Empty).Replace("\n", string.Empty); + var provisioningID = !string.IsNullOrWhiteSpace(info?.ProcessorId) ? info.ProcessorId : info?.SerialNumber; + + var command = "meadow device provision"; + if (!string.IsNullOrWhiteSpace(OrgId)) + { + command += $" -o {OrgId}"; + } + command += $" -id \"{provisioningID}\" -k \"{publicKey}\""; + Logger?.LogInformation($"Provisioning command is:"); + Logger?.LogInformation(command); + } + protected override async ValueTask ExecuteCommand() { + if (GenerateProvisionCommand) + { + Logger?.LogInformation("Generating a device provisioning command..."); + + await OutputProvisionCommand(); + return; + } + UserOrg? org; try diff --git a/Source/v2/Meadow.Cli/Properties/launchSettings.json b/Source/v2/Meadow.Cli/Properties/launchSettings.json index 750abc6b..c78964a3 100644 --- a/Source/v2/Meadow.Cli/Properties/launchSettings.json +++ b/Source/v2/Meadow.Cli/Properties/launchSettings.json @@ -221,7 +221,11 @@ }, "Device provision": { "commandName": "Project", - "commandLineArgs": "device provision" + "commandLineArgs": "device provision -o christacke6612" + }, + "Device provision command": { + "commandName": "Project", + "commandLineArgs": "device provision --gen-command" }, "Device provision other": { "commandName": "Project", From 04896c7b8392ea40149d5e6499dcfcc2dac7c8da Mon Sep 17 00:00:00 2001 From: Adrian Stevens Date: Fri, 15 Mar 2024 16:07:33 -0700 Subject: [PATCH 02/15] Remove auth from firmware download, minor defensive coding, version bump --- .../Current/Firmware/FirmwareDownloadCommand.cs | 1 + Source/v2/Meadow.Cli/Meadow.CLI.csproj | 2 +- Source/v2/Meadow.Cli/Properties/AssemblyInfo.cs | 2 +- .../v2/Meadow.Cli/Properties/launchSettings.json | 8 ++++---- .../Meadow.Tooling.Core/Package/PackageManager.cs | 15 +++++++++------ 5 files changed, 16 insertions(+), 12 deletions(-) diff --git a/Source/v2/Meadow.Cli/Commands/Current/Firmware/FirmwareDownloadCommand.cs b/Source/v2/Meadow.Cli/Commands/Current/Firmware/FirmwareDownloadCommand.cs index ee97fedf..883f566b 100644 --- a/Source/v2/Meadow.Cli/Commands/Current/Firmware/FirmwareDownloadCommand.cs +++ b/Source/v2/Meadow.Cli/Commands/Current/Firmware/FirmwareDownloadCommand.cs @@ -17,6 +17,7 @@ public FirmwareDownloadCommand( : base(meadowCloudClient, loggerFactory) { _fileManager = fileManager; + RequiresAuthentication = false; } [CommandOption("force", 'f', IsRequired = false)] diff --git a/Source/v2/Meadow.Cli/Meadow.CLI.csproj b/Source/v2/Meadow.Cli/Meadow.CLI.csproj index b17e2437..2181de0c 100644 --- a/Source/v2/Meadow.Cli/Meadow.CLI.csproj +++ b/Source/v2/Meadow.Cli/Meadow.CLI.csproj @@ -10,7 +10,7 @@ Wilderness Labs, Inc Wilderness Labs, Inc true - 2.0.27.0 + 2.0.29.0 AnyCPU http://developer.wildernesslabs.co/Meadow/Meadow.CLI/ https://github.com/WildernessLabs/Meadow.CLI diff --git a/Source/v2/Meadow.Cli/Properties/AssemblyInfo.cs b/Source/v2/Meadow.Cli/Properties/AssemblyInfo.cs index 4abd82f0..d07705c4 100644 --- a/Source/v2/Meadow.Cli/Properties/AssemblyInfo.cs +++ b/Source/v2/Meadow.Cli/Properties/AssemblyInfo.cs @@ -6,5 +6,5 @@ namespace Meadow.CLI; public static class Constants { - public const string CLI_VERSION = "2.0.27.0"; + public const string CLI_VERSION = "2.0.29.0"; } \ No newline at end of file diff --git a/Source/v2/Meadow.Cli/Properties/launchSettings.json b/Source/v2/Meadow.Cli/Properties/launchSettings.json index c78964a3..ead96fa7 100644 --- a/Source/v2/Meadow.Cli/Properties/launchSettings.json +++ b/Source/v2/Meadow.Cli/Properties/launchSettings.json @@ -189,7 +189,7 @@ }, "App Trim": { "commandName": "Project", - "commandLineArgs": "app trim C:\\repos\\wilderness\\Meadow.Core.Samples\\Source\\Blinky\\BlinkyCS --nolink Meadow.Core Meadow.Contracts" + "commandLineArgs": "app trim H:\\WL\\Blinky\\Blinky\\BlinkyCs --nolink BlinkyLib" }, "Dfu Install": { "commandName": "Project", @@ -201,15 +201,15 @@ }, "App Deploy (project folder)": { "commandName": "Project", - "commandLineArgs": "app deploy H:\\WL\\Meadow.ProjectLab\\Source\\ProjectLab_Demo\\bin\\Debug\\netstandard2.1\\postlink_bin" + "commandLineArgs": "app deploy H:\\WL\\Blinky\\Blinky\\BlinkyCs" }, "App Deploy (untrimmed output)": { "commandName": "Project", "commandLineArgs": "app deploy F:\\temp\\MeadowApplication1\\bin\\Debug\\netstandard2.1" }, - "App run": { + "App Run": { "commandName": "Project", - "commandLineArgs": "app run F:\\temp\\MeadowApplication1" + "commandLineArgs": "app run H:\\WL\\Blinky\\Blinky\\BlinkyCs" }, "Flash erase": { "commandName": "Project", diff --git a/Source/v2/Meadow.Tooling.Core/Package/PackageManager.cs b/Source/v2/Meadow.Tooling.Core/Package/PackageManager.cs index 76c3e437..05eb449b 100644 --- a/Source/v2/Meadow.Tooling.Core/Package/PackageManager.cs +++ b/Source/v2/Meadow.Tooling.Core/Package/PackageManager.cs @@ -152,13 +152,16 @@ public Task TrimApplication( .Build(); var opts = deserializer.Deserialize(yaml); - if (opts.Deploy.NoLink != null && opts.Deploy.NoLink.Count > 0) + if (opts != null && opts.Deploy != null) { - noLink = opts.Deploy.NoLink; - } - if (opts.Deploy.IncludePDBs != null) - { - includePdbs = opts.Deploy.IncludePDBs.Value; + if (opts.Deploy.NoLink != null && opts.Deploy.NoLink.Count > 0) + { + noLink = opts.Deploy.NoLink; + } + if (opts.Deploy.IncludePDBs != null) + { + includePdbs = opts.Deploy.IncludePDBs.Value; + } } } From 05fd6fd324af5b4afe93555a42445dcb06f42857 Mon Sep 17 00:00:00 2001 From: Steven Kuhn Date: Mon, 11 Mar 2024 19:40:10 -0500 Subject: [PATCH 03/15] Add support for Meadow.Cloud API keys --- .../Commands/Current/BaseCloudCommand.cs | 40 +++++++++++++++---- .../Cloud/ApiKey/CloudApiKeyCreateCommand.cs | 17 +++----- .../Cloud/ApiKey/CloudApiKeyDeleteCommand.cs | 19 +++------ .../Cloud/ApiKey/CloudApiKeyListCommand.cs | 31 ++++++-------- .../Cloud/ApiKey/CloudApiKeyUpdateCommand.cs | 27 +++++-------- .../Command/CloudCommandPublishCommand.cs | 25 +++++------- .../Package/CloudPackagePublishCommand.cs | 15 ++----- .../Package/CloudPackageUploadCommand.cs | 13 ++---- .../Services/ApiTokenService.cs | 24 +++++------ .../Services/CloudServiceBase.cs | 28 ++++++++----- .../Services/CollectionService.cs | 4 +- .../Services/CommandService.cs | 10 ++--- .../Services/DeviceService.cs | 2 +- .../Services/PackageService.cs | 10 ++--- .../Services/UserService.cs | 8 ++-- 15 files changed, 128 insertions(+), 145 deletions(-) diff --git a/Source/v2/Meadow.Cli/Commands/Current/BaseCloudCommand.cs b/Source/v2/Meadow.Cli/Commands/Current/BaseCloudCommand.cs index 9baed907..5a8c17f7 100644 --- a/Source/v2/Meadow.Cli/Commands/Current/BaseCloudCommand.cs +++ b/Source/v2/Meadow.Cli/Commands/Current/BaseCloudCommand.cs @@ -2,6 +2,7 @@ using Meadow.Cloud.Client; using Meadow.Cloud.Client.Users; using Microsoft.Extensions.Logging; +using System.Net.Http.Headers; namespace Meadow.CLI.Commands.DeviceManagement; @@ -10,6 +11,9 @@ public abstract class BaseCloudCommand : BaseCommand [CommandOption("host", Description = $"The Meadow.Cloud endpoint.", IsRequired = false)] public string Host { get; set; } = DefaultHost; + [CommandOption("apikey", Description = "The API key to use with Meadow.Cloud. Otherwise, use the logged in Wilderness Labs account.", EnvironmentVariable = "MC_APIKEY", IsRequired = false)] + public string? ApiKey { get; set; } + protected const string DefaultHost = Meadow.Cloud.Client.MeadowCloudClient.DefaultHost; protected bool RequiresAuthentication { get; set; } = true; @@ -37,15 +41,22 @@ protected sealed override async ValueTask ExecuteCommand() if (RequiresAuthentication) { - var result = await MeadowCloudClient.Authenticate(CancellationToken); - if (!result) + if (!string.IsNullOrEmpty(ApiKey)) { - throw new CommandException("You must be signed into your Wilderness Labs account to execute this command. Run 'meadow login' to do so."); + MeadowCloudClient.Authorization = new AuthenticationHeaderValue("APIKEY", ApiKey); + } + else + { + var result = await MeadowCloudClient.Authenticate(CancellationToken); + if (!result) + { + throw new CommandException("You must be signed into your Wilderness Labs account to execute this command. Run 'meadow login' to do so."); + } + + // If the user does not yet exist in Meadow.Cloud, this creates them and sets up their initial org + var _ = await MeadowCloudClient.User.GetUser(CancellationToken) + ?? throw new CommandException("There was a problem retrieving your account information."); } - - // If the user does not yet exist in Meadow.Cloud, this creates them and sets up their initial org - var _ = await MeadowCloudClient.User.GetUser(CancellationToken) - ?? throw new CommandException("There was a problem retrieving your account information."); } try @@ -56,6 +67,21 @@ protected sealed override async ValueTask ExecuteCommand() { throw new CommandException("You must be signed into your Wilderness Labs account to execute this command. Run 'meadow login' to do so.", ex); } + catch (MeadowCloudException ex) + { + if (ex.StatusCode == System.Net.HttpStatusCode.Unauthorized) + { + throw new CommandException($"You are not authorized to perform this action. Please check that you have appropriate access" + + (!string.IsNullOrWhiteSpace(ApiKey) ? ", your API key has the correct scope(s)," : "") + " and try again.", ex); + } + + throw new CommandException($@"There was a problem executing the command. Meadow.Cloud returned a non-successful response. + +{(int)ex.StatusCode} {ex.StatusCode} +Response: {(string.IsNullOrWhiteSpace(ex.Response) ? "None" : Environment.NewLine + ex.Response)} + +{ex.StackTrace}", ex); + } } protected async Task GetOrganization(string? orgNameOrId = null, CancellationToken cancellationToken = default) diff --git a/Source/v2/Meadow.Cli/Commands/Current/Cloud/ApiKey/CloudApiKeyCreateCommand.cs b/Source/v2/Meadow.Cli/Commands/Current/Cloud/ApiKey/CloudApiKeyCreateCommand.cs index cdf04f53..478d9d31 100644 --- a/Source/v2/Meadow.Cli/Commands/Current/Cloud/ApiKey/CloudApiKeyCreateCommand.cs +++ b/Source/v2/Meadow.Cli/Commands/Current/Cloud/ApiKey/CloudApiKeyCreateCommand.cs @@ -40,18 +40,11 @@ protected override ValueTask PreAuthenticatedValidation() protected async override ValueTask ExecuteCloudCommand() { - try - { - var request = new CreateApiTokenRequest(Name, Duration, Scopes); - var response = await ApiTokenService.CreateApiToken(request, Host, CancellationToken); + var request = new CreateApiTokenRequest(Name, Duration, Scopes); + var response = await ApiTokenService.CreateApiToken(request, Host, CancellationToken); - Logger.LogInformation($"Your API key '{response.Name}' (expiring {response.ExpiresAt:G} UTC) is:"); - Logger.LogInformation($"\n{response.Token}\n"); - Logger.LogInformation("Make sure to copy this key now as you will not be able to see this again."); - } - catch (MeadowCloudException ex) - { - throw new CommandException($"Create API key command failed: {ex.Message}", innerException: ex); - } + Logger.LogInformation($"Your API key '{response.Name}' (expiring {response.ExpiresAt:G} UTC) is:"); + Logger.LogInformation($"\n{response.Token}\n"); + Logger.LogInformation("Make sure to copy this key now as you will not be able to see this again."); } } diff --git a/Source/v2/Meadow.Cli/Commands/Current/Cloud/ApiKey/CloudApiKeyDeleteCommand.cs b/Source/v2/Meadow.Cli/Commands/Current/Cloud/ApiKey/CloudApiKeyDeleteCommand.cs index 4fa702f4..fc303003 100644 --- a/Source/v2/Meadow.Cli/Commands/Current/Cloud/ApiKey/CloudApiKeyDeleteCommand.cs +++ b/Source/v2/Meadow.Cli/Commands/Current/Cloud/ApiKey/CloudApiKeyDeleteCommand.cs @@ -29,21 +29,14 @@ protected override ValueTask PreAuthenticatedValidation() protected async override ValueTask ExecuteCloudCommand() { - try - { - var getRequest = await ApiTokenService.GetApiTokens(Host, CancellationToken); - var apiKey = getRequest.FirstOrDefault(x => x.Id == NameOrId || string.Equals(x.Name, NameOrId, StringComparison.OrdinalIgnoreCase)); - - if (apiKey == null) - { - throw new CommandException($"API key `{NameOrId}` not found."); - } + var getRequest = await ApiTokenService.GetApiTokens(Host, CancellationToken); + var apiKey = getRequest.FirstOrDefault(x => x.Id == NameOrId || string.Equals(x.Name, NameOrId, StringComparison.OrdinalIgnoreCase)); - await ApiTokenService.DeleteApiToken(apiKey.Id, Host, CancellationToken); - } - catch (MeadowCloudException ex) + if (apiKey == null) { - throw new CommandException($"Create API key command failed: {ex.Message}", innerException: ex); + throw new CommandException($"API key `{NameOrId}` not found."); } + + await ApiTokenService.DeleteApiToken(apiKey.Id, Host, CancellationToken); } } diff --git a/Source/v2/Meadow.Cli/Commands/Current/Cloud/ApiKey/CloudApiKeyListCommand.cs b/Source/v2/Meadow.Cli/Commands/Current/Cloud/ApiKey/CloudApiKeyListCommand.cs index 36df57f3..446fa221 100644 --- a/Source/v2/Meadow.Cli/Commands/Current/Cloud/ApiKey/CloudApiKeyListCommand.cs +++ b/Source/v2/Meadow.Cli/Commands/Current/Cloud/ApiKey/CloudApiKeyListCommand.cs @@ -26,29 +26,22 @@ protected override ValueTask PreAuthenticatedValidation() protected override async ValueTask ExecuteCloudCommand() { - try + var response = await ApiTokenService.GetApiTokens(Host, CancellationToken); + var apiTokens = response.OrderBy(a => a.Name); + + if (!apiTokens.Any()) { - var response = await ApiTokenService.GetApiTokens(Host, CancellationToken); - var apiTokens = response.OrderBy(a => a.Name); - - if (!apiTokens.Any()) - { - Logger.LogInformation("You have no API keys."); - return; - } - - var table = new ConsoleTable("Id", "Name", $"Expires (UTC)", "Scopes"); - foreach (var apiToken in apiTokens) - { - table.AddRow(apiToken.Id, apiToken.Name, $"{apiToken.ExpiresAt:G}", string.Join(", ", apiToken.Scopes.OrderBy(t => t))); - } - - Logger.LogInformation(table); + Logger.LogInformation("You have no API keys."); + return; } - catch (MeadowCloudException ex) + + var table = new ConsoleTable("Id", "Name", $"Expires (UTC)", "Scopes"); + foreach (var apiToken in apiTokens) { - throw new CommandException($"Get API keys command failed: {ex.Message}", innerException: ex); + table.AddRow(apiToken.Id, apiToken.Name, $"{apiToken.ExpiresAt:G}", string.Join(", ", apiToken.Scopes.OrderBy(t => t))); } + + Logger.LogInformation(table); } } diff --git a/Source/v2/Meadow.Cli/Commands/Current/Cloud/ApiKey/CloudApiKeyUpdateCommand.cs b/Source/v2/Meadow.Cli/Commands/Current/Cloud/ApiKey/CloudApiKeyUpdateCommand.cs index f7f60f13..cfb1640e 100644 --- a/Source/v2/Meadow.Cli/Commands/Current/Cloud/ApiKey/CloudApiKeyUpdateCommand.cs +++ b/Source/v2/Meadow.Cli/Commands/Current/Cloud/ApiKey/CloudApiKeyUpdateCommand.cs @@ -35,25 +35,18 @@ protected override ValueTask PreAuthenticatedValidation() protected async override ValueTask ExecuteCloudCommand() { - try - { - var getRequest = await ApiTokenService.GetApiTokens(Host, CancellationToken); - var apiKey = getRequest.FirstOrDefault(x => x.Id == NameOrId || string.Equals(x.Name, NameOrId, StringComparison.OrdinalIgnoreCase)); - - if (apiKey == null) - { - throw new CommandException($"API key `{NameOrId}` not found."); - } - - NewName ??= apiKey.Name; - Scopes ??= apiKey.Scopes; + var getRequest = await ApiTokenService.GetApiTokens(Host, CancellationToken); + var apiKey = getRequest.FirstOrDefault(x => x.Id == NameOrId || string.Equals(x.Name, NameOrId, StringComparison.OrdinalIgnoreCase)); - var updateRequest = new UpdateApiTokenRequest(NewName!, Scopes!); - await ApiTokenService.UpdateApiToken(apiKey.Id, updateRequest, Host, CancellationToken); - } - catch (MeadowCloudException ex) + if (apiKey == null) { - throw new CommandException($"Create API key command failed: {ex.Message}", innerException: ex); + throw new CommandException($"API key `{NameOrId}` not found."); } + + NewName ??= apiKey.Name; + Scopes ??= apiKey.Scopes; + + var updateRequest = new UpdateApiTokenRequest(NewName!, Scopes!); + await ApiTokenService.UpdateApiToken(apiKey.Id, updateRequest, Host, CancellationToken); } } diff --git a/Source/v2/Meadow.Cli/Commands/Current/Cloud/Command/CloudCommandPublishCommand.cs b/Source/v2/Meadow.Cli/Commands/Current/Cloud/Command/CloudCommandPublishCommand.cs index 5d9a3984..923b4ff4 100644 --- a/Source/v2/Meadow.Cli/Commands/Current/Cloud/Command/CloudCommandPublishCommand.cs +++ b/Source/v2/Meadow.Cli/Commands/Current/Cloud/Command/CloudCommandPublishCommand.cs @@ -51,25 +51,18 @@ protected override ValueTask PreAuthenticatedValidation() protected override async ValueTask ExecuteCloudCommand() { - try + if (!string.IsNullOrWhiteSpace(CollectionId)) { - if (!string.IsNullOrWhiteSpace(CollectionId)) - { - await CommandService.PublishCommandForCollection(CollectionId, CommandName, Arguments, (int)QualityOfService, Host, CancellationToken); - } - else if (DeviceIds?.Length > 0) - { - await CommandService.PublishCommandForDevices(DeviceIds, CommandName, Arguments, (int)QualityOfService, Host, CancellationToken); - } - else - { - throw new CommandException("Cannot specify both a collection ID (-c|--collectionId) and list of device IDs (-d|--deviceIds). Only one is allowed."); - } - Logger.LogInformation("Publish command successful."); + await CommandService.PublishCommandForCollection(CollectionId, CommandName, Arguments, (int)QualityOfService, Host, CancellationToken); } - catch (MeadowCloudException ex) + else if (DeviceIds?.Length > 0) { - throw new CommandException($"Publish command failed: {ex.Message}", innerException: ex); + await CommandService.PublishCommandForDevices(DeviceIds, CommandName, Arguments, (int)QualityOfService, Host, CancellationToken); } + else + { + throw new CommandException("Cannot specify both a collection ID (-c|--collectionId) and list of device IDs (-d|--deviceIds). Only one is allowed."); + } + Logger.LogInformation("Publish command successful."); } } diff --git a/Source/v2/Meadow.Cli/Commands/Current/Cloud/Package/CloudPackagePublishCommand.cs b/Source/v2/Meadow.Cli/Commands/Current/Cloud/Package/CloudPackagePublishCommand.cs index 10daeaa9..85460733 100644 --- a/Source/v2/Meadow.Cli/Commands/Current/Cloud/Package/CloudPackagePublishCommand.cs +++ b/Source/v2/Meadow.Cli/Commands/Current/Cloud/Package/CloudPackagePublishCommand.cs @@ -29,16 +29,9 @@ public CloudPackagePublishCommand( protected override async ValueTask ExecuteCloudCommand() { - try - { - Logger.LogInformation($"Publishing package {PackageId} to collection {CollectionId}..."); - - await _packageService.PublishPackage(PackageId, CollectionId, Metadata ?? string.Empty, Host, CancellationToken); - Logger.LogInformation("Publish successful."); - } - catch (MeadowCloudException mex) - { - Logger.LogError($"Publish failed: {mex.Message}"); - } + Logger.LogInformation($"Publishing package {PackageId} to collection {CollectionId}..."); + + await _packageService.PublishPackage(PackageId, CollectionId, Metadata ?? string.Empty, Host, CancellationToken); + Logger.LogInformation("Publish successful."); } } \ No newline at end of file diff --git a/Source/v2/Meadow.Cli/Commands/Current/Cloud/Package/CloudPackageUploadCommand.cs b/Source/v2/Meadow.Cli/Commands/Current/Cloud/Package/CloudPackageUploadCommand.cs index c8d62e0c..523da84d 100644 --- a/Source/v2/Meadow.Cli/Commands/Current/Cloud/Package/CloudPackageUploadCommand.cs +++ b/Source/v2/Meadow.Cli/Commands/Current/Cloud/Package/CloudPackageUploadCommand.cs @@ -42,16 +42,9 @@ protected override async ValueTask ExecuteCloudCommand() if (org == null) { return; } - try - { - Logger.LogInformation($"Uploading package {Path.GetFileName(MpakPath)}..."); + Logger.LogInformation($"Uploading package {Path.GetFileName(MpakPath)}..."); - var package = await _packageService.UploadPackage(MpakPath, org.Id, Description ?? string.Empty, Host, CancellationToken); - Logger.LogInformation($"Upload complete. Package Id: {package.Id}"); - } - catch (MeadowCloudException mex) - { - Logger.LogError($"Upload failed: {mex.Message}"); - } + var package = await _packageService.UploadPackage(MpakPath, org.Id, Description ?? string.Empty, Host, CancellationToken); + Logger.LogInformation($"Upload complete. Package Id: {package.Id}"); } } \ No newline at end of file diff --git a/Source/v2/Meadow.Cloud.Client/Services/ApiTokenService.cs b/Source/v2/Meadow.Cloud.Client/Services/ApiTokenService.cs index 1aa41c76..e544cec6 100644 --- a/Source/v2/Meadow.Cloud.Client/Services/ApiTokenService.cs +++ b/Source/v2/Meadow.Cloud.Client/Services/ApiTokenService.cs @@ -4,14 +4,14 @@ public class ApiTokenService : CloudServiceBase { private static readonly JsonSerializerOptions JsonSerializerOptions = new(JsonSerializerDefaults.Web); - public ApiTokenService(IdentityManager identityManager) : base(identityManager) + public ApiTokenService(IMeadowCloudClient meadowCloudClient) : base(meadowCloudClient) { } - public async Task> GetApiTokens(string host, CancellationToken? cancellationToken) + public async Task> GetApiTokens(string host, CancellationToken cancellationToken = default) { var httpClient = await GetAuthenticatedHttpClient(cancellationToken); - var response = await httpClient.GetAsync($"{host}/api/auth/tokens", cancellationToken ?? CancellationToken.None); + var response = await httpClient.GetAsync($"{host}/api/auth/tokens", cancellationToken); if (!response.IsSuccessStatusCode) { @@ -19,15 +19,15 @@ public async Task> GetApiTokens(string host, Ca throw new MeadowCloudException(response.StatusCode, response: message); } - return await response.Content.ReadFromJsonAsync>(JsonSerializerOptions, cancellationToken ?? CancellationToken.None) + return await response.Content.ReadFromJsonAsync>(JsonSerializerOptions, cancellationToken) ?? Enumerable.Empty(); } - public async Task CreateApiToken(CreateApiTokenRequest request, string host, CancellationToken? cancellationToken) + public async Task CreateApiToken(CreateApiTokenRequest request, string host, CancellationToken cancellationToken = default) { var httpClient = await GetAuthenticatedHttpClient(cancellationToken); var content = new StringContent(JsonSerializer.Serialize(request, JsonSerializerOptions), Encoding.UTF8, "application/json"); - var response = await httpClient.PostAsync($"{host}/api/auth/tokens", content, cancellationToken ?? CancellationToken.None); + var response = await httpClient.PostAsync($"{host}/api/auth/tokens", content, cancellationToken); if (!response.IsSuccessStatusCode) { @@ -35,15 +35,15 @@ public async Task CreateApiToken(CreateApiTokenRequest r throw new MeadowCloudException(response.StatusCode, response: message); } - var result = await response.Content.ReadFromJsonAsync(JsonSerializerOptions, cancellationToken ?? CancellationToken.None); + var result = await response.Content.ReadFromJsonAsync(JsonSerializerOptions, cancellationToken); return result!; } - public async Task UpdateApiToken(string id, UpdateApiTokenRequest request, string host, CancellationToken? cancellationToken) + public async Task UpdateApiToken(string id, UpdateApiTokenRequest request, string host, CancellationToken cancellationToken = default) { var httpClient = await GetAuthenticatedHttpClient(cancellationToken); var content = new StringContent(JsonSerializer.Serialize(request, JsonSerializerOptions), Encoding.UTF8, "application/json"); - var response = await httpClient.PutAsync($"{host}/api/auth/tokens/{id}", content, cancellationToken ?? CancellationToken.None); + var response = await httpClient.PutAsync($"{host}/api/auth/tokens/{id}", content, cancellationToken); if (!response.IsSuccessStatusCode) { @@ -51,14 +51,14 @@ public async Task UpdateApiToken(string id, UpdateApiTok throw new MeadowCloudException(response.StatusCode, response: message); } - var result = await response.Content.ReadFromJsonAsync(JsonSerializerOptions, cancellationToken ?? CancellationToken.None); + var result = await response.Content.ReadFromJsonAsync(JsonSerializerOptions, cancellationToken); return result!; } - public async Task DeleteApiToken(string id, string host, CancellationToken? cancellationToken) + public async Task DeleteApiToken(string id, string host, CancellationToken cancellationToken = default) { var httpClient = await GetAuthenticatedHttpClient(cancellationToken); - var response = await httpClient.DeleteAsync($"{host}/api/auth/tokens/{id}", cancellationToken ?? CancellationToken.None); + var response = await httpClient.DeleteAsync($"{host}/api/auth/tokens/{id}", cancellationToken); if (!response.IsSuccessStatusCode) { diff --git a/Source/v2/Meadow.Cloud.Client/Services/CloudServiceBase.cs b/Source/v2/Meadow.Cloud.Client/Services/CloudServiceBase.cs index 247fb6a0..d06bfafa 100644 --- a/Source/v2/Meadow.Cloud.Client/Services/CloudServiceBase.cs +++ b/Source/v2/Meadow.Cloud.Client/Services/CloudServiceBase.cs @@ -2,24 +2,30 @@ public abstract class CloudServiceBase { - private readonly IdentityManager _identityManager; + private readonly IMeadowCloudClient _meadowCloudClient; - protected CloudServiceBase(IdentityManager identityManager) + protected CloudServiceBase(IMeadowCloudClient meadowCloudClient) { - _identityManager = identityManager; + _meadowCloudClient = meadowCloudClient; } - protected async Task GetAuthenticatedHttpClient(CancellationToken? cancellationToken = null) + protected async Task GetAuthenticatedHttpClient(CancellationToken cancellationToken = default) { - var authToken = await _identityManager.GetAccessToken(cancellationToken ?? CancellationToken.None); - if (string.IsNullOrEmpty(authToken)) + if (_meadowCloudClient.Authorization == null) { - throw new MeadowCloudAuthException(); + var result = await _meadowCloudClient.Authenticate(cancellationToken); + if (!result) + { + throw new MeadowCloudAuthException(); + } } - HttpClient client = new(); - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authToken); - - return client; + return new HttpClient + { + DefaultRequestHeaders = + { + Authorization = _meadowCloudClient.Authorization + } + }; } } diff --git a/Source/v2/Meadow.Cloud.Client/Services/CollectionService.cs b/Source/v2/Meadow.Cloud.Client/Services/CollectionService.cs index ee040342..61a11704 100644 --- a/Source/v2/Meadow.Cloud.Client/Services/CollectionService.cs +++ b/Source/v2/Meadow.Cloud.Client/Services/CollectionService.cs @@ -2,11 +2,11 @@ public class CollectionService : CloudServiceBase { - public CollectionService(IdentityManager identityManager) : base(identityManager) + public CollectionService(IMeadowCloudClient meadowCloudClient) : base(meadowCloudClient) { } - public async Task> GetOrgCollections(string orgId, string host, CancellationToken? cancellationToken) + public async Task> GetOrgCollections(string orgId, string host, CancellationToken cancellationToken = default) { var httpClient = await GetAuthenticatedHttpClient(cancellationToken); diff --git a/Source/v2/Meadow.Cloud.Client/Services/CommandService.cs b/Source/v2/Meadow.Cloud.Client/Services/CommandService.cs index 1d127df9..856e495d 100644 --- a/Source/v2/Meadow.Cloud.Client/Services/CommandService.cs +++ b/Source/v2/Meadow.Cloud.Client/Services/CommandService.cs @@ -2,7 +2,7 @@ public class CommandService : CloudServiceBase { - public CommandService(IdentityManager identityManager) : base(identityManager) + public CommandService(IMeadowCloudClient meadowCloudClient) : base(meadowCloudClient) { } @@ -12,7 +12,7 @@ public async Task PublishCommandForCollection( JsonDocument? arguments = null, int qualityOfService = 0, string? host = null, - CancellationToken? cancellationToken = null) + CancellationToken cancellationToken = default) { var httpClient = await GetAuthenticatedHttpClient(cancellationToken); @@ -23,7 +23,7 @@ public async Task PublishCommandForCollection( qos = qualityOfService }; var content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json"); - var response = await httpClient.PostAsync($"{host}/api/collections/{collectionId}/commands", content, cancellationToken ?? CancellationToken.None); + var response = await httpClient.PostAsync($"{host}/api/collections/{collectionId}/commands", content, cancellationToken); if (!response.IsSuccessStatusCode) { @@ -38,7 +38,7 @@ public async Task PublishCommandForDevices( JsonDocument? arguments = null, int qualityOfService = 0, string? host = null, - CancellationToken? cancellationToken = null) + CancellationToken cancellationToken = default) { var httpClient = await GetAuthenticatedHttpClient(cancellationToken); @@ -50,7 +50,7 @@ public async Task PublishCommandForDevices( qos = qualityOfService }; var content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json"); - var response = await httpClient.PostAsync($"{host}/api/devices/commands", content, cancellationToken ?? CancellationToken.None); + var response = await httpClient.PostAsync($"{host}/api/devices/commands", content, cancellationToken); if (!response.IsSuccessStatusCode) { diff --git a/Source/v2/Meadow.Cloud.Client/Services/DeviceService.cs b/Source/v2/Meadow.Cloud.Client/Services/DeviceService.cs index 2533ca85..1ca82652 100644 --- a/Source/v2/Meadow.Cloud.Client/Services/DeviceService.cs +++ b/Source/v2/Meadow.Cloud.Client/Services/DeviceService.cs @@ -2,7 +2,7 @@ public class DeviceService : CloudServiceBase { - public DeviceService(IdentityManager identityManager) : base(identityManager) + public DeviceService(IMeadowCloudClient meadowCloudClient) : base(meadowCloudClient) { } diff --git a/Source/v2/Meadow.Cloud.Client/Services/PackageService.cs b/Source/v2/Meadow.Cloud.Client/Services/PackageService.cs index 7f22f8f6..a954e2a6 100644 --- a/Source/v2/Meadow.Cloud.Client/Services/PackageService.cs +++ b/Source/v2/Meadow.Cloud.Client/Services/PackageService.cs @@ -7,7 +7,7 @@ public class PackageService : CloudServiceBase { private readonly string _info_json = "info.json"; - public PackageService(IdentityManager identityManager) : base(identityManager) + public PackageService(IMeadowCloudClient meadowCloudClient) : base(meadowCloudClient) { } @@ -16,7 +16,7 @@ public async Task UploadPackage( string orgId, string description, string host, - CancellationToken? cancellationToken = null) + CancellationToken cancellationToken = default) { if (!File.Exists(mpakPath)) { @@ -94,14 +94,14 @@ public async Task PublishPackage( string collectionId, string metadata, string host, - CancellationToken? cancellationToken = null) + CancellationToken cancellationToken = default) { var httpClient = await GetAuthenticatedHttpClient(cancellationToken); var payload = new { metadata, collectionId }; var content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json"); var response = - await httpClient.PostAsync($"{host}/api/packages/{packageId}/publish", content, cancellationToken ?? CancellationToken.None); + await httpClient.PostAsync($"{host}/api/packages/{packageId}/publish", content, cancellationToken); if (!response.IsSuccessStatusCode) { @@ -110,7 +110,7 @@ public async Task PublishPackage( } } - public async Task> GetOrgPackages(string orgId, string host, CancellationToken? cancellationToken = null) + public async Task> GetOrgPackages(string orgId, string host, CancellationToken cancellationToken = default) { var httpClient = await GetAuthenticatedHttpClient(cancellationToken); diff --git a/Source/v2/Meadow.Cloud.Client/Services/UserService.cs b/Source/v2/Meadow.Cloud.Client/Services/UserService.cs index 13ef2c59..2b7004a4 100644 --- a/Source/v2/Meadow.Cloud.Client/Services/UserService.cs +++ b/Source/v2/Meadow.Cloud.Client/Services/UserService.cs @@ -2,15 +2,15 @@ public class UserService : CloudServiceBase { - public UserService(IdentityManager identityManager) : base(identityManager) + public UserService(IMeadowCloudClient meadowCloudClient) : base(meadowCloudClient) { } - public async Task> GetUserOrgs(string host, CancellationToken? cancellationToken = null) + public async Task> GetUserOrgs(string host, CancellationToken cancellationToken = default) { var httpClient = await GetAuthenticatedHttpClient(cancellationToken); - var response = await httpClient.GetAsync($"{host}/api/users/me/orgs", cancellationToken ?? CancellationToken.None); + var response = await httpClient.GetAsync($"{host}/api/users/me/orgs", cancellationToken); if (response.IsSuccessStatusCode) { @@ -23,7 +23,7 @@ public async Task> GetUserOrgs(string host, CancellationToken? can } } - public async Task GetMe(string host, CancellationToken? cancellationToken = null) + public async Task GetMe(string host, CancellationToken cancellationToken = default) { var httpClient = await GetAuthenticatedHttpClient(cancellationToken); From afdcb6263d805fcd4861b52459cc79363548853a Mon Sep 17 00:00:00 2001 From: Steven Kuhn Date: Mon, 11 Mar 2024 20:00:44 -0500 Subject: [PATCH 04/15] Update exception message --- .../Meadow.Cli/Commands/Current/BaseCloudCommand.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Source/v2/Meadow.Cli/Commands/Current/BaseCloudCommand.cs b/Source/v2/Meadow.Cli/Commands/Current/BaseCloudCommand.cs index 5a8c17f7..f76c7f73 100644 --- a/Source/v2/Meadow.Cli/Commands/Current/BaseCloudCommand.cs +++ b/Source/v2/Meadow.Cli/Commands/Current/BaseCloudCommand.cs @@ -3,6 +3,7 @@ using Meadow.Cloud.Client.Users; using Microsoft.Extensions.Logging; using System.Net.Http.Headers; +using System.Text; namespace Meadow.CLI.Commands.DeviceManagement; @@ -71,8 +72,14 @@ protected sealed override async ValueTask ExecuteCommand() { if (ex.StatusCode == System.Net.HttpStatusCode.Unauthorized) { - throw new CommandException($"You are not authorized to perform this action. Please check that you have appropriate access" - + (!string.IsNullOrWhiteSpace(ApiKey) ? ", your API key has the correct scope(s)," : "") + " and try again.", ex); + var sb = new StringBuilder("You are not authorized to perform this action. Please check that you have sufficient access"); + if (!string.IsNullOrWhiteSpace(ApiKey)) + { + sb.Append(", that your API keys is valid with the correct scopes,"); + } + sb.Append(" and try again."); + + throw new CommandException(sb.ToString(), ex); } throw new CommandException($@"There was a problem executing the command. Meadow.Cloud returned a non-successful response. From 3eeef1b3a299cfefa58eb30f7c0d86616c6ff567 Mon Sep 17 00:00:00 2001 From: Steven Kuhn Date: Wed, 13 Mar 2024 10:17:50 -0500 Subject: [PATCH 05/15] Set base address when --host argument is used --- .../Meadow.Cli/Commands/Current/BaseCloudCommand.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Source/v2/Meadow.Cli/Commands/Current/BaseCloudCommand.cs b/Source/v2/Meadow.Cli/Commands/Current/BaseCloudCommand.cs index f76c7f73..96c65d00 100644 --- a/Source/v2/Meadow.Cli/Commands/Current/BaseCloudCommand.cs +++ b/Source/v2/Meadow.Cli/Commands/Current/BaseCloudCommand.cs @@ -38,6 +38,18 @@ protected virtual ValueTask PreAuthenticatedValidation() protected sealed override async ValueTask ExecuteCommand() { + if (!Host.StartsWith("http://", StringComparison.OrdinalIgnoreCase) && !Host.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) + { + throw new CommandException("Host (--host) must be a valid URL that starts with http:// or https://."); + } + + if (!Uri.TryCreate(Host, UriKind.Absolute, out Uri? baseAddress) || baseAddress == null) + { + throw new CommandException("Host (--host) must be a valid URL.", showHelp: true); + } + + MeadowCloudClient.BaseAddress = baseAddress; + await PreAuthenticatedValidation(); if (RequiresAuthentication) From 569090effd82e8afb35de8aeff5514a320f85d83 Mon Sep 17 00:00:00 2001 From: Steven Kuhn Date: Mon, 18 Mar 2024 12:44:00 -0500 Subject: [PATCH 06/15] Do not show help when an invalid --host parameter is given --- Source/v2/Meadow.Cli/Commands/Current/BaseCloudCommand.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/v2/Meadow.Cli/Commands/Current/BaseCloudCommand.cs b/Source/v2/Meadow.Cli/Commands/Current/BaseCloudCommand.cs index 96c65d00..dea7f220 100644 --- a/Source/v2/Meadow.Cli/Commands/Current/BaseCloudCommand.cs +++ b/Source/v2/Meadow.Cli/Commands/Current/BaseCloudCommand.cs @@ -43,9 +43,9 @@ protected sealed override async ValueTask ExecuteCommand() throw new CommandException("Host (--host) must be a valid URL that starts with http:// or https://."); } - if (!Uri.TryCreate(Host, UriKind.Absolute, out Uri? baseAddress) || baseAddress == null) - { - throw new CommandException("Host (--host) must be a valid URL.", showHelp: true); + if (!Uri.TryCreate(Host, UriKind.Absolute, out Uri? baseAddress) || baseAddress == null) + { + throw new CommandException("Host (--host) must be a valid URL."); } MeadowCloudClient.BaseAddress = baseAddress; From 9d496e94df63f2167bf581d09004500df056f280 Mon Sep 17 00:00:00 2001 From: Adrian Stevens Date: Tue, 19 Mar 2024 16:11:46 -0700 Subject: [PATCH 07/15] Validate runtime state for enable, disable, file delete + version bump --- .../Commands/Current/File/FileDeleteCommand.cs | 8 ++++++++ .../Current/Runtime/RuntimeDisableCommand.cs | 15 ++++++++++++--- .../Current/Runtime/RuntimeEnableCommand.cs | 15 ++++++++++++--- Source/v2/Meadow.Cli/Meadow.CLI.csproj | 2 +- Source/v2/Meadow.Cli/Properties/AssemblyInfo.cs | 2 +- 5 files changed, 34 insertions(+), 8 deletions(-) diff --git a/Source/v2/Meadow.Cli/Commands/Current/File/FileDeleteCommand.cs b/Source/v2/Meadow.Cli/Commands/Current/File/FileDeleteCommand.cs index 1d774bea..d6aa6a9c 100644 --- a/Source/v2/Meadow.Cli/Commands/Current/File/FileDeleteCommand.cs +++ b/Source/v2/Meadow.Cli/Commands/Current/File/FileDeleteCommand.cs @@ -21,6 +21,14 @@ protected override async ValueTask ExecuteCommand() var connection = await GetCurrentConnection(); var device = await GetCurrentDevice(); + var state = await device.IsRuntimeEnabled(CancellationToken); + + if (state == true) + { + Logger?.LogInformation($"{Strings.DisablingRuntime}..."); + await device.RuntimeDisable(CancellationToken); + } + // get a list of files in the target folder var folder = Path.GetDirectoryName(MeadowFile)!.Replace(Path.DirectorySeparatorChar, '/'); if (string.IsNullOrWhiteSpace(folder)) diff --git a/Source/v2/Meadow.Cli/Commands/Current/Runtime/RuntimeDisableCommand.cs b/Source/v2/Meadow.Cli/Commands/Current/Runtime/RuntimeDisableCommand.cs index 81d645ed..6adbaa76 100644 --- a/Source/v2/Meadow.Cli/Commands/Current/Runtime/RuntimeDisableCommand.cs +++ b/Source/v2/Meadow.Cli/Commands/Current/Runtime/RuntimeDisableCommand.cs @@ -16,10 +16,19 @@ protected override async ValueTask ExecuteCommand() Logger?.LogInformation($"{Strings.DisablingRuntime}..."); - await device.RuntimeDisable(CancellationToken); - var state = await device.IsRuntimeEnabled(CancellationToken); - Logger?.LogInformation($"Runtime is {(state ? "ENABLED" : "DISABLED")}"); + if (state == false) + { + Logger?.LogInformation("Runtime already disabled"); + } + else + { + await device.RuntimeDisable(CancellationToken); + + state = await device.IsRuntimeEnabled(CancellationToken); + + Logger?.LogInformation($"Runtime is {(state ? "ENABLED" : "DISABLED")}"); + } } } \ No newline at end of file diff --git a/Source/v2/Meadow.Cli/Commands/Current/Runtime/RuntimeEnableCommand.cs b/Source/v2/Meadow.Cli/Commands/Current/Runtime/RuntimeEnableCommand.cs index 58de9f27..ac42aca6 100644 --- a/Source/v2/Meadow.Cli/Commands/Current/Runtime/RuntimeEnableCommand.cs +++ b/Source/v2/Meadow.Cli/Commands/Current/Runtime/RuntimeEnableCommand.cs @@ -16,10 +16,19 @@ protected override async ValueTask ExecuteCommand() Logger?.LogInformation($"{Strings.EnablingRuntime}..."); - await device.RuntimeEnable(CancellationToken); - var state = await device.IsRuntimeEnabled(CancellationToken); - Logger?.LogInformation($"Runtime is {(state ? "ENABLED" : "DISABLED")}"); + if (state == true) + { + Logger?.LogInformation("Runtime already enabled"); + } + else + { + await device.RuntimeEnable(CancellationToken); + + state = await device.IsRuntimeEnabled(CancellationToken); + + Logger?.LogInformation($"Runtime is {(state ? "ENABLED" : "DISABLED")}"); + } } } \ No newline at end of file diff --git a/Source/v2/Meadow.Cli/Meadow.CLI.csproj b/Source/v2/Meadow.Cli/Meadow.CLI.csproj index 2181de0c..7fd839e0 100644 --- a/Source/v2/Meadow.Cli/Meadow.CLI.csproj +++ b/Source/v2/Meadow.Cli/Meadow.CLI.csproj @@ -10,7 +10,7 @@ Wilderness Labs, Inc Wilderness Labs, Inc true - 2.0.29.0 + 2.0.30.0 AnyCPU http://developer.wildernesslabs.co/Meadow/Meadow.CLI/ https://github.com/WildernessLabs/Meadow.CLI diff --git a/Source/v2/Meadow.Cli/Properties/AssemblyInfo.cs b/Source/v2/Meadow.Cli/Properties/AssemblyInfo.cs index d07705c4..4753feb9 100644 --- a/Source/v2/Meadow.Cli/Properties/AssemblyInfo.cs +++ b/Source/v2/Meadow.Cli/Properties/AssemblyInfo.cs @@ -6,5 +6,5 @@ namespace Meadow.CLI; public static class Constants { - public const string CLI_VERSION = "2.0.29.0"; + public const string CLI_VERSION = "2.0.30.0"; } \ No newline at end of file From 67c658752e229a2cbe81353ff76a32284c2a9679 Mon Sep 17 00:00:00 2001 From: Adrian Stevens Date: Tue, 19 Mar 2024 16:21:58 -0700 Subject: [PATCH 08/15] Better messaging if runtime enable/disable fails --- .../Commands/Current/Runtime/RuntimeDisableCommand.cs | 5 +++++ .../Commands/Current/Runtime/RuntimeEnableCommand.cs | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/Source/v2/Meadow.Cli/Commands/Current/Runtime/RuntimeDisableCommand.cs b/Source/v2/Meadow.Cli/Commands/Current/Runtime/RuntimeDisableCommand.cs index 6adbaa76..5af3e51f 100644 --- a/Source/v2/Meadow.Cli/Commands/Current/Runtime/RuntimeDisableCommand.cs +++ b/Source/v2/Meadow.Cli/Commands/Current/Runtime/RuntimeDisableCommand.cs @@ -28,6 +28,11 @@ protected override async ValueTask ExecuteCommand() state = await device.IsRuntimeEnabled(CancellationToken); + if (state == true) + { + Logger?.LogInformation("Disabling runtime failed"); + } + Logger?.LogInformation($"Runtime is {(state ? "ENABLED" : "DISABLED")}"); } } diff --git a/Source/v2/Meadow.Cli/Commands/Current/Runtime/RuntimeEnableCommand.cs b/Source/v2/Meadow.Cli/Commands/Current/Runtime/RuntimeEnableCommand.cs index ac42aca6..a6a578d5 100644 --- a/Source/v2/Meadow.Cli/Commands/Current/Runtime/RuntimeEnableCommand.cs +++ b/Source/v2/Meadow.Cli/Commands/Current/Runtime/RuntimeEnableCommand.cs @@ -28,6 +28,11 @@ protected override async ValueTask ExecuteCommand() state = await device.IsRuntimeEnabled(CancellationToken); + if (state == false) + { + Logger?.LogInformation("Enabling runtime failed"); + } + Logger?.LogInformation($"Runtime is {(state ? "ENABLED" : "DISABLED")}"); } } From 8312308704e1ab6463b82ea8c01fa94be0a4d1df Mon Sep 17 00:00:00 2001 From: Adrian Stevens Date: Tue, 19 Mar 2024 16:25:37 -0700 Subject: [PATCH 09/15] Minor messaging cleanup --- .../Commands/Current/Runtime/RuntimeDisableCommand.cs | 2 +- .../Meadow.Cli/Commands/Current/Runtime/RuntimeEnableCommand.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/v2/Meadow.Cli/Commands/Current/Runtime/RuntimeDisableCommand.cs b/Source/v2/Meadow.Cli/Commands/Current/Runtime/RuntimeDisableCommand.cs index 5af3e51f..fe17cc5e 100644 --- a/Source/v2/Meadow.Cli/Commands/Current/Runtime/RuntimeDisableCommand.cs +++ b/Source/v2/Meadow.Cli/Commands/Current/Runtime/RuntimeDisableCommand.cs @@ -30,7 +30,7 @@ protected override async ValueTask ExecuteCommand() if (state == true) { - Logger?.LogInformation("Disabling runtime failed"); + Logger?.LogError("Failed to disable runtime"); } Logger?.LogInformation($"Runtime is {(state ? "ENABLED" : "DISABLED")}"); diff --git a/Source/v2/Meadow.Cli/Commands/Current/Runtime/RuntimeEnableCommand.cs b/Source/v2/Meadow.Cli/Commands/Current/Runtime/RuntimeEnableCommand.cs index a6a578d5..1b721eee 100644 --- a/Source/v2/Meadow.Cli/Commands/Current/Runtime/RuntimeEnableCommand.cs +++ b/Source/v2/Meadow.Cli/Commands/Current/Runtime/RuntimeEnableCommand.cs @@ -30,7 +30,7 @@ protected override async ValueTask ExecuteCommand() if (state == false) { - Logger?.LogInformation("Enabling runtime failed"); + Logger?.LogError("Failed to enable runtime"); } Logger?.LogInformation($"Runtime is {(state ? "ENABLED" : "DISABLED")}"); From 717097a3f936d36928a3bb9655d74a1a171a1a24 Mon Sep 17 00:00:00 2001 From: Adrian Stevens Date: Tue, 19 Mar 2024 16:49:56 -0700 Subject: [PATCH 10/15] Switch runtime enable/disable validation to throw a CommandException --- .../Commands/Current/Runtime/RuntimeDisableCommand.cs | 2 +- .../Meadow.Cli/Commands/Current/Runtime/RuntimeEnableCommand.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/v2/Meadow.Cli/Commands/Current/Runtime/RuntimeDisableCommand.cs b/Source/v2/Meadow.Cli/Commands/Current/Runtime/RuntimeDisableCommand.cs index fe17cc5e..e54115f6 100644 --- a/Source/v2/Meadow.Cli/Commands/Current/Runtime/RuntimeDisableCommand.cs +++ b/Source/v2/Meadow.Cli/Commands/Current/Runtime/RuntimeDisableCommand.cs @@ -30,7 +30,7 @@ protected override async ValueTask ExecuteCommand() if (state == true) { - Logger?.LogError("Failed to disable runtime"); + throw new CommandException("Failed to disable runtime"); } Logger?.LogInformation($"Runtime is {(state ? "ENABLED" : "DISABLED")}"); diff --git a/Source/v2/Meadow.Cli/Commands/Current/Runtime/RuntimeEnableCommand.cs b/Source/v2/Meadow.Cli/Commands/Current/Runtime/RuntimeEnableCommand.cs index 1b721eee..c28db5af 100644 --- a/Source/v2/Meadow.Cli/Commands/Current/Runtime/RuntimeEnableCommand.cs +++ b/Source/v2/Meadow.Cli/Commands/Current/Runtime/RuntimeEnableCommand.cs @@ -30,7 +30,7 @@ protected override async ValueTask ExecuteCommand() if (state == false) { - Logger?.LogError("Failed to enable runtime"); + throw new CommandException("Failed to enable runtime"); } Logger?.LogInformation($"Runtime is {(state ? "ENABLED" : "DISABLED")}"); From df84006cd3ffdf83cdbb38f85bb7bef132cd399c Mon Sep 17 00:00:00 2001 From: CartBlanche Date: Thu, 21 Mar 2024 19:55:38 +0000 Subject: [PATCH 11/15] Add a 1 second delay, because on VSCode Windows it won't find the port before debugging start. --- Meadow.CLI.Core/DeviceManagement/MeadowDeviceManager.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Meadow.CLI.Core/DeviceManagement/MeadowDeviceManager.cs b/Meadow.CLI.Core/DeviceManagement/MeadowDeviceManager.cs index 9e4eb1ff..53d6d138 100644 --- a/Meadow.CLI.Core/DeviceManagement/MeadowDeviceManager.cs +++ b/Meadow.CLI.Core/DeviceManagement/MeadowDeviceManager.cs @@ -42,6 +42,8 @@ public class MeadowDeviceManager meadow = null; } + await Task.Delay(1000); + logger.LogInformation($"{Environment.NewLine}Connecting to Meadow on {serialPort}{Environment.NewLine}"); var createTask = Task.Run(() => meadow = new MeadowSerialDevice(serialPort, logger)); @@ -56,7 +58,7 @@ public class MeadowDeviceManager } catch (Exception ex) { - logger.LogInformation(ex, "An error occurred while attempting to create Meadow"); + logger.LogInformation(ex, "An error occurred while attempting to create Meadow."); throw; } return null; From 18eac4c40778943766d0d23230cd55e492a32e0b Mon Sep 17 00:00:00 2001 From: CartBlanche Date: Fri, 22 Mar 2024 10:39:26 +0000 Subject: [PATCH 12/15] Remove . from LogInfo to avoid noise. --- Meadow.CLI.Core/DeviceManagement/MeadowDeviceManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Meadow.CLI.Core/DeviceManagement/MeadowDeviceManager.cs b/Meadow.CLI.Core/DeviceManagement/MeadowDeviceManager.cs index 53d6d138..e94f4c67 100644 --- a/Meadow.CLI.Core/DeviceManagement/MeadowDeviceManager.cs +++ b/Meadow.CLI.Core/DeviceManagement/MeadowDeviceManager.cs @@ -58,7 +58,7 @@ public class MeadowDeviceManager } catch (Exception ex) { - logger.LogInformation(ex, "An error occurred while attempting to create Meadow."); + logger.LogInformation(ex, "An error occurred while attempting to create Meadow"); throw; } return null; From f94a5ed34d2608fb8c1ac3110d080f52ba449e6d Mon Sep 17 00:00:00 2001 From: CartBlanche Date: Fri, 22 Mar 2024 10:50:24 +0000 Subject: [PATCH 13/15] Bump to 1.9.4 for VSCode release. --- .github/workflows/dotnet.yml | 4 ++-- Meadow.CLI.Core/Constants.cs | 2 +- Meadow.CLI.Core/Meadow.CLI.Core.6.0.0.csproj | 2 +- Meadow.CLI.Core/Meadow.CLI.Core.Classic.csproj | 2 +- Meadow.CLI.Core/Meadow.CLI.Core.VS2019.csproj | 2 +- Meadow.CLI.Core/Meadow.CLI.Core.csproj | 2 +- Meadow.CLI/Meadow.CLI.Classic.csproj | 2 +- Meadow.CLI/Meadow.CLI.csproj | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index b41a004e..855182ac 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -1,8 +1,8 @@ name: Meadow.CLI Packaging env: - CLI_RELEASE_VERSION_1: 1.9.2.0 + CLI_RELEASE_VERSION_1: 1.9.4.0 CLI_RELEASE_VERSION_2: 2.0.17.0 - IDE_TOOLS_RELEASE_VERSION: 1.9.0 + IDE_TOOLS_RELEASE_VERSION: 1.9.4 MEADOW_OS_VERSION: 1.9.0.0 VS_MAC_2019_VERSION: 8.10 VS_MAC_2022_VERSION: 17.6 diff --git a/Meadow.CLI.Core/Constants.cs b/Meadow.CLI.Core/Constants.cs index 9574de05..2181cc76 100644 --- a/Meadow.CLI.Core/Constants.cs +++ b/Meadow.CLI.Core/Constants.cs @@ -7,7 +7,7 @@ namespace Meadow.CLI.Core { public static class Constants { - public const string CLI_VERSION = "1.9.2.0"; + public const string CLI_VERSION = "1.9.4.0"; public const ushort HCOM_PROTOCOL_PREVIOUS_VERSION_NUMBER = 0x0007; public const ushort HCOM_PROTOCOL_CURRENT_VERSION_NUMBER = 0x0008; // Used for transmission public const string WILDERNESS_LABS_USB_VID = "2E6A"; diff --git a/Meadow.CLI.Core/Meadow.CLI.Core.6.0.0.csproj b/Meadow.CLI.Core/Meadow.CLI.Core.6.0.0.csproj index d3d33e5a..560411ac 100644 --- a/Meadow.CLI.Core/Meadow.CLI.Core.6.0.0.csproj +++ b/Meadow.CLI.Core/Meadow.CLI.Core.6.0.0.csproj @@ -11,7 +11,7 @@ preview enable True - 1.9.2.0 + 1.9.4.0 diff --git a/Meadow.CLI.Core/Meadow.CLI.Core.Classic.csproj b/Meadow.CLI.Core/Meadow.CLI.Core.Classic.csproj index 9f124b3f..b1a86252 100644 --- a/Meadow.CLI.Core/Meadow.CLI.Core.Classic.csproj +++ b/Meadow.CLI.Core/Meadow.CLI.Core.Classic.csproj @@ -11,7 +11,7 @@ preview enable True - 1.9.2.0 + 1.9.4.0 diff --git a/Meadow.CLI.Core/Meadow.CLI.Core.VS2019.csproj b/Meadow.CLI.Core/Meadow.CLI.Core.VS2019.csproj index e20e21ce..fa42c2ad 100644 --- a/Meadow.CLI.Core/Meadow.CLI.Core.VS2019.csproj +++ b/Meadow.CLI.Core/Meadow.CLI.Core.VS2019.csproj @@ -11,7 +11,7 @@ preview enable True - 1.9.2.0 + 1.9.4.0 diff --git a/Meadow.CLI.Core/Meadow.CLI.Core.csproj b/Meadow.CLI.Core/Meadow.CLI.Core.csproj index 92804a69..87a00c67 100644 --- a/Meadow.CLI.Core/Meadow.CLI.Core.csproj +++ b/Meadow.CLI.Core/Meadow.CLI.Core.csproj @@ -11,7 +11,7 @@ preview enable True - 1.9.2.0 + 1.9.4.0 diff --git a/Meadow.CLI/Meadow.CLI.Classic.csproj b/Meadow.CLI/Meadow.CLI.Classic.csproj index 8f0980bc..a6b63123 100644 --- a/Meadow.CLI/Meadow.CLI.Classic.csproj +++ b/Meadow.CLI/Meadow.CLI.Classic.csproj @@ -10,7 +10,7 @@ Peter Moody, Adrian Stevens, Brian Kim, Pete Garafano, Dominique Louis Wilderness Labs, Inc true - 1.9.2.0 + 1.9.4.0 AnyCPU http://developer.wildernesslabs.co/Meadow/Meadow.Foundation/ icon.png diff --git a/Meadow.CLI/Meadow.CLI.csproj b/Meadow.CLI/Meadow.CLI.csproj index 686b2699..02241e2c 100644 --- a/Meadow.CLI/Meadow.CLI.csproj +++ b/Meadow.CLI/Meadow.CLI.csproj @@ -10,7 +10,7 @@ Peter Moody, Adrian Stevens, Brian Kim, Pete Garafano, Dominique Louis Wilderness Labs, Inc true - 1.9.2.0 + 1.9.4.0 AnyCPU http://developer.wildernesslabs.co/Meadow/Meadow.Foundation/ icon.png From fdc946a9e4f9d2f22dae1dce5df2f2edfaf62c5e Mon Sep 17 00:00:00 2001 From: Adrian Stevens Date: Fri, 22 Mar 2024 17:34:16 -0700 Subject: [PATCH 14/15] Version bump and package tags --- Source/v2/Meadow.Cli/Meadow.CLI.csproj | 4 ++-- Source/v2/Meadow.Cli/Properties/AssemblyInfo.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/v2/Meadow.Cli/Meadow.CLI.csproj b/Source/v2/Meadow.Cli/Meadow.CLI.csproj index 7fd839e0..9e4b0197 100644 --- a/Source/v2/Meadow.Cli/Meadow.CLI.csproj +++ b/Source/v2/Meadow.Cli/Meadow.CLI.csproj @@ -10,12 +10,12 @@ Wilderness Labs, Inc Wilderness Labs, Inc true - 2.0.30.0 + 2.0.31.0 AnyCPU http://developer.wildernesslabs.co/Meadow/Meadow.CLI/ https://github.com/WildernessLabs/Meadow.CLI icon.png - Meadow, Meadow.Foundation, Meadow.CLI + Meadow,Meadow.Foundation,Meadow.CLI,CLI,command,line,interface,device,IoT Command-line interface for Meadow false false diff --git a/Source/v2/Meadow.Cli/Properties/AssemblyInfo.cs b/Source/v2/Meadow.Cli/Properties/AssemblyInfo.cs index 4753feb9..95a88769 100644 --- a/Source/v2/Meadow.Cli/Properties/AssemblyInfo.cs +++ b/Source/v2/Meadow.Cli/Properties/AssemblyInfo.cs @@ -6,5 +6,5 @@ namespace Meadow.CLI; public static class Constants { - public const string CLI_VERSION = "2.0.30.0"; + public const string CLI_VERSION = "2.0.31.0"; } \ No newline at end of file From 08f93f986b88208a82c42b79628746df010ca04f Mon Sep 17 00:00:00 2001 From: Adrian Stevens Date: Fri, 22 Mar 2024 17:34:50 -0700 Subject: [PATCH 15/15] Remove unusable arg and return non-zero codes on error --- Source/v2/Meadow.Cli/Program.cs | 41 +++++++++++---------------------- 1 file changed, 13 insertions(+), 28 deletions(-) diff --git a/Source/v2/Meadow.Cli/Program.cs b/Source/v2/Meadow.Cli/Program.cs index 37b75889..3dd2de56 100644 --- a/Source/v2/Meadow.Cli/Program.cs +++ b/Source/v2/Meadow.Cli/Program.cs @@ -18,31 +18,13 @@ public class Program { - public static async Task Main(string[] args) + public static async Task Main(string[] _) { - var logLevel = LogEventLevel.Information; - var logModifier = args.FirstOrDefault(a => a.Contains("-m")) - ?.Count(x => x == 'm') ?? 0; - - logLevel -= logModifier; - if (logLevel < 0) - { - logLevel = 0; - } - - var outputTemplate = logLevel == LogEventLevel.Verbose - ? "[{Timestamp:HH:mm:ss.fff} {Level:u3}] {Message:lj}{NewLine}{Exception}" - : "{Message:lj}{NewLine}{Exception}"; + var outputTemplate = "{Message:lj}{NewLine}{Exception}"; Log.Logger = new LoggerConfiguration().MinimumLevel.Verbose() - .WriteTo.Console(logLevel, outputTemplate) + .WriteTo.Console(LogEventLevel.Information, outputTemplate) .CreateLogger(); - // Log that we're using a log level other than default of Information - if (logLevel != LogEventLevel.Information) - { - Console.WriteLine($"Using log level {logLevel}"); - } - var services = new ServiceCollection(); services.AddLogging( @@ -89,30 +71,33 @@ public static async Task Main(string[] args) var serviceProvider = services.BuildServiceProvider(); + int returnCode; + try { - await new CliApplicationBuilder() + returnCode = await new CliApplicationBuilder() .AddCommandsFromThisAssembly() .UseTypeActivator(serviceProvider.GetService!) .SetExecutableName("meadow") .Build() .RunAsync(); } + catch (CommandException ce) + { + returnCode = ce.ExitCode; + } catch (Exception ex) { Console.WriteLine($"Operation failed: {ex.Message}"); -#if DEBUG - throw; //debug spew for debug builds -#endif + returnCode = 1; } - Environment.Exit(0); - return 0; + return returnCode; } private static void AddCommandsAsServices(IServiceCollection services) { - var assembly = System.Reflection.Assembly.GetEntryAssembly(); //.GetAssembly(typeof(Program)); + var assembly = System.Reflection.Assembly.GetEntryAssembly(); Trace.Assert(assembly != null); var types = assembly?.GetTypes();