Skip to content

Commit

Permalink
Merge pull request #543 from WildernessLabs/develop
Browse files Browse the repository at this point in the history
Merge to main for 1.9.4 extension release.
  • Loading branch information
adrianstevens authored Mar 23, 2024
2 parents 602c2f6 + 7839d5f commit ce5eae7
Show file tree
Hide file tree
Showing 34 changed files with 268 additions and 202 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion Meadow.CLI.Core/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
2 changes: 2 additions & 0 deletions Meadow.CLI.Core/DeviceManagement/MeadowDeviceManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
2 changes: 1 addition & 1 deletion Meadow.CLI.Core/Meadow.CLI.Core.6.0.0.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Version>1.9.2.0</Version>
<Version>1.9.4.0</Version>
</PropertyGroup>

<PropertyGroup>
Expand Down
2 changes: 1 addition & 1 deletion Meadow.CLI.Core/Meadow.CLI.Core.Classic.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Version>1.9.2.0</Version>
<Version>1.9.4.0</Version>
</PropertyGroup>

<PropertyGroup>
Expand Down
2 changes: 1 addition & 1 deletion Meadow.CLI.Core/Meadow.CLI.Core.VS2019.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Version>1.9.2.0</Version>
<Version>1.9.4.0</Version>
</PropertyGroup>

<PropertyGroup>
Expand Down
2 changes: 1 addition & 1 deletion Meadow.CLI.Core/Meadow.CLI.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Version>1.9.2.0</Version>
<Version>1.9.4.0</Version>
</PropertyGroup>

<PropertyGroup>
Expand Down
2 changes: 1 addition & 1 deletion Meadow.CLI/Meadow.CLI.Classic.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<Authors>Peter Moody, Adrian Stevens, Brian Kim, Pete Garafano, Dominique Louis</Authors>
<Company>Wilderness Labs, Inc</Company>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageVersion>1.9.2.0</PackageVersion>
<PackageVersion>1.9.4.0</PackageVersion>
<Platforms>AnyCPU</Platforms>
<PackageProjectUrl>http://developer.wildernesslabs.co/Meadow/Meadow.Foundation/</PackageProjectUrl>
<PackageIcon>icon.png</PackageIcon>
Expand Down
2 changes: 1 addition & 1 deletion Meadow.CLI/Meadow.CLI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<Authors>Peter Moody, Adrian Stevens, Brian Kim, Pete Garafano, Dominique Louis</Authors>
<Company>Wilderness Labs, Inc</Company>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageVersion>1.9.2.0</PackageVersion>
<PackageVersion>1.9.4.0</PackageVersion>
<Platforms>AnyCPU</Platforms>
<PackageProjectUrl>http://developer.wildernesslabs.co/Meadow/Meadow.Foundation/</PackageProjectUrl>
<PackageIcon>icon.png</PackageIcon>
Expand Down
59 changes: 52 additions & 7 deletions Source/v2/Meadow.Cli/Commands/Current/BaseCloudCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using Meadow.Cloud.Client;
using Meadow.Cloud.Client.Users;
using Microsoft.Extensions.Logging;
using System.Net.Http.Headers;
using System.Text;

namespace Meadow.CLI.Commands.DeviceManagement;

Expand All @@ -10,6 +12,9 @@ public abstract class BaseCloudCommand<T> : BaseCommand<T>
[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;
Expand All @@ -33,19 +38,38 @@ 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.");
}

MeadowCloudClient.BaseAddress = baseAddress;

await PreAuthenticatedValidation();

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
Expand All @@ -56,6 +80,27 @@ 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)
{
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.
{(int)ex.StatusCode} {ex.StatusCode}
Response: {(string.IsNullOrWhiteSpace(ex.Response) ? "None" : Environment.NewLine + ex.Response)}
{ex.StackTrace}", ex);
}
}

protected async Task<GetOrganizationResponse?> GetOrganization(string? orgNameOrId = null, CancellationToken cancellationToken = default)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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}");
}
}
Loading

0 comments on commit ce5eae7

Please sign in to comment.