-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'develop' into warnings
- Loading branch information
Showing
13 changed files
with
521 additions
and
16 deletions.
There are no files selected for viewing
73 changes: 73 additions & 0 deletions
73
Source/v2/Meadow.CLI/Commands/Current/Cloud/ApiKey/CloudApiKeyCreateCommand.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
using CliFx.Attributes; | ||
using CliFx.Exceptions; | ||
using Meadow.Cloud; | ||
using Meadow.Cloud.Identity; | ||
using Microsoft.Extensions.Logging; | ||
|
||
namespace Meadow.CLI.Commands.DeviceManagement; | ||
|
||
[Command("cloud apikey create", Description = "Create a Meadow.Cloud API key")] | ||
public class CloudApiKeyCreateCommand : BaseCloudCommand<CloudApiKeyCreateCommand> | ||
{ | ||
[CommandParameter(0, Description = "The name of the API key", IsRequired = true, Name = "NAME")] | ||
public string? Name { get; set; } | ||
|
||
[CommandOption("duration", 'd', Description = "The duration of the API key, in days", IsRequired = true)] | ||
public int Duration { get; set; } | ||
|
||
[CommandOption("scopes", 's', Description = "The list of scopes (permissions) to grant the API key", IsRequired = true)] | ||
public string[]? Scopes { get; set; } | ||
|
||
[CommandOption("host", Description = $"Optionally set a host (default is {DefaultHost})")] | ||
public string? Host { get; set; } | ||
|
||
private ApiTokenService ApiTokenService { get; } | ||
|
||
public CloudApiKeyCreateCommand( | ||
ApiTokenService apiTokenService, | ||
CollectionService collectionService, | ||
DeviceService deviceService, | ||
IdentityManager identityManager, | ||
UserService userService, | ||
ILoggerFactory? loggerFactory) | ||
: base(identityManager, userService, deviceService, collectionService, loggerFactory) | ||
{ | ||
ApiTokenService = apiTokenService; | ||
} | ||
|
||
protected async override ValueTask ExecuteCommand() | ||
{ | ||
if (Duration < 1 || Duration > 90) | ||
{ | ||
throw new CommandException("Duration (-d|--duration) must be between 1 and 90 days.", showHelp: true); | ||
} | ||
|
||
Host ??= DefaultHost; | ||
|
||
Logger?.LogInformation($"Creating an API key on Meadow.Cloud{(Host != DefaultHost ? $" ({Host.ToLowerInvariant()})" : string.Empty)}..."); | ||
|
||
var token = await IdentityManager.GetAccessToken(CancellationToken); | ||
if (string.IsNullOrWhiteSpace(token)) | ||
{ | ||
throw new CommandException("You must be signed into Meadow.Cloud to execute this command. Run 'meadow cloud login' to do so."); | ||
} | ||
|
||
try | ||
{ | ||
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 (MeadowCloudAuthException ex) | ||
{ | ||
throw new CommandException("You must be signed in to execute this command.", innerException: ex); | ||
} | ||
catch (MeadowCloudException ex) | ||
{ | ||
throw new CommandException($"Create API key command failed: {ex.Message}", innerException: ex); | ||
} | ||
} | ||
} |
65 changes: 65 additions & 0 deletions
65
Source/v2/Meadow.CLI/Commands/Current/Cloud/ApiKey/CloudApiKeyDeleteCommand.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
using CliFx.Attributes; | ||
using CliFx.Exceptions; | ||
using Meadow.Cloud; | ||
using Meadow.Cloud.Identity; | ||
using Microsoft.Extensions.Logging; | ||
|
||
namespace Meadow.CLI.Commands.DeviceManagement; | ||
|
||
[Command("cloud apikey delete", Description = "Delete a Meadow.Cloud API key")] | ||
public class CloudApiKeyDeleteCommand : BaseCloudCommand<CloudApiKeyDeleteCommand> | ||
{ | ||
[CommandParameter(0, Description = "The name or ID of the API key", IsRequired = true, Name = "NAME_OR_ID")] | ||
public string? NameOrId { get; set; } | ||
|
||
[CommandOption("host", Description = $"Optionally set a host (default is {DefaultHost})", IsRequired = false)] | ||
public string? Host { get; set; } | ||
|
||
private ApiTokenService ApiTokenService { get; } | ||
|
||
public CloudApiKeyDeleteCommand( | ||
ApiTokenService apiTokenService, | ||
CollectionService collectionService, | ||
DeviceService deviceService, | ||
IdentityManager identityManager, | ||
UserService userService, | ||
ILoggerFactory? loggerFactory) | ||
: base(identityManager, userService, deviceService, collectionService, loggerFactory) | ||
{ | ||
ApiTokenService = apiTokenService; | ||
} | ||
|
||
protected async override ValueTask ExecuteCommand() | ||
{ | ||
Host ??= DefaultHost; | ||
|
||
Logger?.LogInformation($"Deleting API key `{NameOrId}` on Meadow.Cloud{(Host != DefaultHost ? $" ({Host.ToLowerInvariant()})" : string.Empty)}..."); | ||
|
||
var token = await IdentityManager.GetAccessToken(CancellationToken); | ||
if (string.IsNullOrWhiteSpace(token)) | ||
{ | ||
throw new CommandException("You must be signed into Meadow.Cloud to execute this command. Run 'meadow cloud login' to do so."); | ||
} | ||
|
||
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."); | ||
} | ||
|
||
await ApiTokenService.DeleteApiToken(apiKey.Id, Host, CancellationToken); | ||
} | ||
catch (MeadowCloudAuthException ex) | ||
{ | ||
throw new CommandException("You must be signed in to execute this command.", innerException: ex); | ||
} | ||
catch (MeadowCloudException ex) | ||
{ | ||
throw new CommandException($"Create API key command failed: {ex.Message}", innerException: ex); | ||
} | ||
} | ||
} |
71 changes: 71 additions & 0 deletions
71
Source/v2/Meadow.CLI/Commands/Current/Cloud/ApiKey/CloudApiKeyListCommand.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
using CliFx.Attributes; | ||
using CliFx.Exceptions; | ||
using Meadow.Cloud; | ||
using Meadow.Cloud.Identity; | ||
using Microsoft.Extensions.Logging; | ||
|
||
namespace Meadow.CLI.Commands.DeviceManagement; | ||
|
||
[Command("cloud apikey list", Description = "List your Meadow.Cloud API keys")] | ||
public class CloudApiKeyListCommand : BaseCloudCommand<CloudApiKeyListCommand> | ||
{ | ||
[CommandOption("host", Description = $"Optionally set a host (default is {DefaultHost})", IsRequired = false)] | ||
public string? Host { get; set; } | ||
|
||
private ApiTokenService ApiTokenService { get; } | ||
|
||
public CloudApiKeyListCommand( | ||
ApiTokenService apiTokenService, | ||
CollectionService collectionService, | ||
DeviceService deviceService, | ||
IdentityManager identityManager, | ||
UserService userService, | ||
ILoggerFactory? loggerFactory) | ||
: base(identityManager, userService, deviceService, collectionService, loggerFactory) | ||
{ | ||
ApiTokenService = apiTokenService; | ||
} | ||
|
||
protected override async ValueTask ExecuteCommand() | ||
{ | ||
Host ??= DefaultHost; | ||
|
||
Logger?.LogInformation($"Retrieving your API keys from Meadow.Cloud{(Host != DefaultHost ? $" ({Host.ToLowerInvariant()})" : string.Empty)}..."); | ||
|
||
var token = await IdentityManager.GetAccessToken(CancellationToken); | ||
if (string.IsNullOrWhiteSpace(token)) | ||
{ | ||
throw new CommandException("You must be signed into Meadow.Cloud to execute this command. Run 'meadow cloud login' to do so."); | ||
} | ||
|
||
try | ||
{ | ||
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); | ||
} | ||
catch (MeadowCloudAuthException ex) | ||
{ | ||
throw new CommandException("You must be signed in to execute this command.", innerException: ex); | ||
} | ||
catch (MeadowCloudException ex) | ||
{ | ||
throw new CommandException($"Get API keys command failed: {ex.Message}", innerException: ex); | ||
} | ||
} | ||
} | ||
|
||
|
75 changes: 75 additions & 0 deletions
75
Source/v2/Meadow.CLI/Commands/Current/Cloud/ApiKey/CloudApiKeyUpdateCommand.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
using CliFx.Attributes; | ||
using CliFx.Exceptions; | ||
using Meadow.Cloud; | ||
using Meadow.Cloud.Identity; | ||
using Microsoft.Extensions.Logging; | ||
|
||
namespace Meadow.CLI.Commands.DeviceManagement; | ||
|
||
[Command("cloud apikey update", Description = "Update a Meadow.Cloud API key")] | ||
public class CloudApiKeyUpdateCommand : BaseCloudCommand<CloudApiKeyUpdateCommand> | ||
{ | ||
[CommandParameter(0, Description = "The name or ID of the API key", IsRequired = true, Name = "NAME_OR_ID")] | ||
public string? NameOrId { get; set; } | ||
|
||
[CommandOption("name", 'n', Description = "The new name to use for the API key")] | ||
public string? NewName { get; set; } | ||
|
||
[CommandOption("scopes", 's', Description = "The list of scopes (permissions) to grant the API key")] | ||
public string[]? Scopes { get; set; } | ||
|
||
[CommandOption("host", Description = $"Optionally set a host (default is {DefaultHost})", IsRequired = false)] | ||
public string? Host { get; set; } | ||
|
||
private ApiTokenService ApiTokenService { get; } | ||
|
||
public CloudApiKeyUpdateCommand( | ||
ApiTokenService apiTokenService, | ||
CollectionService collectionService, | ||
DeviceService deviceService, | ||
IdentityManager identityManager, | ||
UserService userService, | ||
ILoggerFactory? loggerFactory) | ||
: base(identityManager, userService, deviceService, collectionService, loggerFactory) | ||
{ | ||
ApiTokenService = apiTokenService; | ||
} | ||
|
||
protected async override ValueTask ExecuteCommand() | ||
{ | ||
Host ??= DefaultHost; | ||
|
||
Logger?.LogInformation($"Updating API key `{NameOrId}` on Meadow.Cloud{(Host != DefaultHost ? $" ({Host.ToLowerInvariant()})" : string.Empty)}..."); | ||
|
||
var token = await IdentityManager.GetAccessToken(CancellationToken); | ||
if (string.IsNullOrWhiteSpace(token)) | ||
{ | ||
throw new CommandException("You must be signed into Meadow.Cloud to execute this command. Run 'meadow cloud login' to do so."); | ||
} | ||
|
||
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 updateRequest = new UpdateApiTokenRequest(NewName!, Scopes!); | ||
await ApiTokenService.UpdateApiToken(apiKey.Id, updateRequest, Host, CancellationToken); | ||
} | ||
catch (MeadowCloudAuthException ex) | ||
{ | ||
throw new CommandException("You must be signed in to execute this command.", innerException: ex); | ||
} | ||
catch (MeadowCloudException ex) | ||
{ | ||
throw new CommandException($"Create API key command failed: {ex.Message}", innerException: ex); | ||
} | ||
} | ||
} |
107 changes: 107 additions & 0 deletions
107
Source/v2/Meadow.CLI/Commands/Current/Cloud/ConsoleTable.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
using System.Text; | ||
|
||
namespace Meadow.CLI; | ||
|
||
public class ConsoleTable | ||
{ | ||
private readonly string[] _columns; | ||
private IList<string[]> _rows; | ||
|
||
public ConsoleTable(params string[] columns) | ||
{ | ||
_columns = columns; | ||
_rows = new List<string[]>(); | ||
} | ||
|
||
public void AddRow(params object[] values) | ||
{ | ||
if (values.Length != _columns.Length) | ||
{ | ||
throw new InvalidOperationException("The number of values for the given row does not match the number of columns."); | ||
} | ||
|
||
_rows.Add(values.Select(v => Convert.ToString(v) ?? string.Empty).ToArray()); | ||
} | ||
|
||
public static implicit operator string(ConsoleTable table) => table.Render(); | ||
|
||
public string Render() | ||
{ | ||
var maxWidths = new int[_columns.Length]; | ||
for (var i = 0; i < _columns.Length; i++) | ||
{ | ||
maxWidths[i] = _columns[i].Length; | ||
} | ||
|
||
for (var i = 0; i < _rows.Count; i++) | ||
{ | ||
for (var j = 0; j < _rows[i].Length; j++) | ||
{ | ||
maxWidths[j] = Math.Max(maxWidths[j], _rows[i][j].Length); | ||
} | ||
} | ||
|
||
var sb = new StringBuilder(); | ||
|
||
// Divider | ||
sb.AppendLine(); | ||
for (var i = 0; i < _columns.Length; i++) | ||
{ | ||
sb.Append(new string('-', maxWidths[i])); | ||
if (i < _columns.Length - 1) | ||
{ | ||
sb.Append("-+-"); | ||
} | ||
} | ||
|
||
// Header | ||
sb.AppendLine(); | ||
for (var i = 0; i < _columns.Length; i++) | ||
{ | ||
sb.Append(_columns[i].PadRight(maxWidths[i])); | ||
if (i < _columns.Length - 1) | ||
{ | ||
sb.Append(" | "); | ||
} | ||
} | ||
|
||
// Divider | ||
sb.AppendLine(); | ||
for (var i = 0; i < _columns.Length; i++) | ||
{ | ||
sb.Append(new string('-', maxWidths[i])); | ||
if (i < _columns.Length - 1) | ||
{ | ||
sb.Append("-|-"); | ||
} | ||
} | ||
|
||
// Rows | ||
for (var i = 0; i < _rows.Count; i++) | ||
{ | ||
sb.AppendLine(); | ||
for (var j = 0; j < _rows[i].Length; j++) | ||
{ | ||
sb.Append(_rows[i][j].PadRight(maxWidths[j])); | ||
if (j < _rows[i].Length - 1) | ||
{ | ||
sb.Append(" | "); | ||
} | ||
} | ||
} | ||
|
||
// Divider | ||
sb.AppendLine(); | ||
for (var i = 0; i < _columns.Length; i++) | ||
{ | ||
sb.Append(new string('-', maxWidths[i])); | ||
if (i < _columns.Length - 1) | ||
{ | ||
sb.Append("-+-"); | ||
} | ||
} | ||
|
||
sb.AppendLine(); | ||
return sb.ToString(); | ||
} | ||
} |
Oops, something went wrong.