Skip to content

Commit

Permalink
Use production genepool (#272)
Browse files Browse the repository at this point in the history
* use production genepool
use production genepool as default and added
environment variables to use staging genepool if required for testing.
* fix check issue
* fix launch.settings login/logoff
* print all organizations in key result
output of all organizations even if currently technical not possible to have
multiple orgs for API keys. Fixes also wording to US english.
  • Loading branch information
fw2568 authored Nov 5, 2024
1 parent c24cef6 commit 6bef537
Show file tree
Hide file tree
Showing 9 changed files with 112 additions and 95 deletions.
1 change: 1 addition & 0 deletions src/apps/src/Eryph-zero/HostVmHostAgentModuleExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Threading.Tasks;
using Dbosoft.Hosuto.Modules.Hosting;
using Eryph.Modules.VmHostAgent;
using Eryph.Modules.VmHostAgent.Genetics;
using Microsoft.Extensions.DependencyInjection;
using SimpleInjector;

Expand Down
50 changes: 33 additions & 17 deletions src/apps/src/Eryph-zero/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
using Serilog.Extensions.Logging;
using Serilog.Sinks.SystemConsole.Themes;
using Serilog.Templates;
using Serilog.Templates.Themes;
using SimpleInjector;
using SimpleInjector.Lifestyles;
using Spectre.Console;
Expand All @@ -61,9 +60,24 @@ namespace Eryph.Runtime.Zero;

internal static class Program
{
private static GenepoolSettings _genepoolSettings = GenePoolConstants.ProductionGenepool;

private static async Task<int> Main(string[] args)
{
IdentityModelEventSource.ShowPII = true;
IdentityModelEventSource.ShowPII = true;

// we use environment variables here as it is only used for testing and packer is using same variables
var stagingAuthority = Environment.GetEnvironmentVariable("ERYPH_GENEPOOL_AUTHORITY") == "staging";

if (stagingAuthority)
{
_genepoolSettings = GenePoolConstants.StagingGenepool;
var overwriteGenepoolApi = Environment.GetEnvironmentVariable("ERYPH_GENEPOOL_API");
if(!string.IsNullOrWhiteSpace(overwriteGenepoolApi))
_genepoolSettings = _genepoolSettings with { ApiEndpoint = new Uri(overwriteGenepoolApi) };

}


var rootCommand = new RootCommand();
var debugWaitOption = new System.CommandLine.Option<bool>(name: "--debuggerWait",
Expand Down Expand Up @@ -134,20 +148,21 @@ private static async Task<int> Main(string[] args)
nonInteractiveOption, noCurrentConfigCheckOption);
agentSettingsCommand.AddCommand(importAgentSettingsCommand);

var loginCommand = new Command("login");
rootCommand.AddCommand(loginCommand);
loginCommand.SetHandler(Login);

var logoutCommand = new Command("logout");
rootCommand.AddCommand(logoutCommand);
logoutCommand.SetHandler(Logout);

var genePoolCommand = new Command("genepool");
rootCommand.AddCommand(genePoolCommand);

var genePoolInfoCommand = new Command("info");
genePoolCommand.AddCommand(genePoolInfoCommand);
genePoolInfoCommand.SetHandler(GetGenePoolInfo);
genePoolInfoCommand.SetHandler(() => GetGenePoolInfo(_genepoolSettings));

var loginCommand = new Command("login");
genePoolCommand.AddCommand(loginCommand);
loginCommand.SetHandler(() => Login(_genepoolSettings));

var logoutCommand = new Command("logout");
genePoolCommand.AddCommand(logoutCommand);
logoutCommand.SetHandler(() => Logout(_genepoolSettings));


var networksCommand = new Command("networks");
rootCommand.AddCommand(networksCommand);
Expand Down Expand Up @@ -300,6 +315,7 @@ private static async Task<int> Run(string[] args)
.AddComputeApiModule()
.AddIdentityModule(container)
.ConfigureServices(c => c.AddSingleton(_ => container.GetInstance<IEndpointResolver>()))
.ConfigureServices(c => c.AddSingleton(_genepoolSettings))
.ConfigureHostOptions(cfg => cfg.ShutdownTimeout = new TimeSpan(0, 0, 15))
// The logger must not be disposed here as it is injected into multiple modules.
// Serilog requires a single logger instance for synchronization.
Expand Down Expand Up @@ -916,27 +932,27 @@ from _ in VmHostAgentConfigurationUpdate<SimpleConsoleRuntime>.updateConfig(
select unit,
SimpleConsoleRuntime.New());

private static Task<int> Login() =>
private static Task<int> Login(GenepoolSettings genepoolSettings) =>
RunAsAdmin(
from _ in unitEff
let genePoolApiStore = new ZeroGenePoolApiKeyStore()
from __ in GenePoolCli<SimpleConsoleRuntime>.login(genePoolApiStore)
from __ in GenePoolCli<SimpleConsoleRuntime>.login(genePoolApiStore, genepoolSettings)
select unit,
SimpleConsoleRuntime.New());

private static Task<int> GetGenePoolInfo() =>
private static Task<int> GetGenePoolInfo(GenepoolSettings genepoolSettings) =>
RunAsAdmin(
from _ in unitEff
let genePoolApiStore = new ZeroGenePoolApiKeyStore()
from __ in GenePoolCli<SimpleConsoleRuntime>.getApiKeyStatus(genePoolApiStore)
from __ in GenePoolCli<SimpleConsoleRuntime>.getApiKeyStatus(genePoolApiStore, genepoolSettings)
select unit,
SimpleConsoleRuntime.New());

private static Task<int> Logout() =>
private static Task<int> Logout(GenepoolSettings genepoolSettings) =>
RunAsAdmin(
from _ in unitEff
let genePoolApiStore = new ZeroGenePoolApiKeyStore()
from __ in GenePoolCli<SimpleConsoleRuntime>.logout(genePoolApiStore)
from __ in GenePoolCli<SimpleConsoleRuntime>.logout(genePoolApiStore, genepoolSettings)
select unit,
SimpleConsoleRuntime.New());

Expand Down
7 changes: 4 additions & 3 deletions src/apps/src/Eryph-zero/Properties/launchSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"commandName": "Project",
"commandLineArgs": "run",
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Development"
"DOTNET_ENVIRONMENT": "Development",
"ERYPH_GENEPOOL_AUTHORITY": "staging_off"
},
"remoteDebugEnabled": true
},
Expand Down Expand Up @@ -58,15 +59,15 @@
},
"login": {
"commandName": "Project",
"commandLineArgs": "login",
"commandLineArgs": "genepool login",
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Development"
},
"remoteDebugEnabled": false
},
"logout": {
"commandName": "Project",
"commandLineArgs": "logout",
"commandLineArgs": "genepool logout",
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Development"
},
Expand Down
84 changes: 40 additions & 44 deletions src/modules/src/Eryph.Modules.VmHostAgent/Genetics/GenePoolCli.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Azure.Core;
using Eryph.AnsiConsole.Sys;
using Eryph.ConfigModel;
using Eryph.Core.Sys;
using Eryph.GenePool.Client;
using Eryph.GenePool.Client.Credentials;
using Eryph.GenePool.Client.Requests;
using Eryph.GenePool.Model;
using Eryph.GenePool.Model.Requests.User;
using Eryph.GenePool.Model.Responses;
using Eryph.VmManagement.Sys;
using LanguageExt;
Expand All @@ -35,12 +31,12 @@ public static class GenePoolCli<RT> where RT : struct,
HasFile<RT>,
HasHardwareId<RT>
{
public static Aff<RT, Unit> login(IGenePoolApiKeyStore apiKeyStore) =>
public static Aff<RT, Unit> login(IGenePoolApiKeyStore apiKeyStore, GenepoolSettings genepoolSettings) =>
from _1 in AnsiConsole<RT>.write(new Rows(
new Text("We will create a dedicated API key for this machine which grants read-only access to the genepool."),
new Text("You will need to authenticate with your personal credentials."),
Text.Empty))
from existingApiKey in apiKeyStore.GetApiKey(GenePoolConstants.EryphGenePool.Name)
from existingApiKey in apiKeyStore.GetApiKey(genepoolSettings.Name)
.ToAff(identity)
from shouldContinue in existingApiKey.Match(
Some: apiKey =>
Expand All @@ -50,38 +46,38 @@ from _ in AnsiConsole<RT>.markupLine(
from shouldOverwrite in AnsiConsole<RT>.confirm("Overwrite API key?", false)
select shouldOverwrite,
None: () => SuccessAff<RT, bool>(true))
from _2 in shouldContinue ? createApiKey(apiKeyStore) : unitEff
from _2 in shouldContinue ? createApiKey(apiKeyStore, genepoolSettings) : unitEff
select unit;

public static Aff<RT, Unit> getApiKeyStatus(IGenePoolApiKeyStore genePoolApiStore) =>
public static Aff<RT, Unit> getApiKeyStatus(IGenePoolApiKeyStore genePoolApiStore, GenepoolSettings genepoolSettings) =>
from apiKeys in genePoolApiStore.GetApiKeys().ToAff(identity)
let apiKey = apiKeys.Find(GenePoolConstants.EryphGenePool.Name)
let apiKey = apiKeys.Find(genepoolSettings.Name)
from _ in apiKey.Match(
Some: key =>
from _1 in AnsiConsole<RT>.writeLine("The following gene pool API key is configured:")
from _2 in AnsiConsole<RT>.write(printApiKey(key))
from _3 in validateApiKey(key)
from _3 in validateApiKey(key, genepoolSettings)
select unit,
None: () => AnsiConsole<RT>.writeLine("No gene pool API key is configured."))
select unit;

public static Aff<RT, Unit> logout(IGenePoolApiKeyStore keyStore) =>
public static Aff<RT, Unit> logout(IGenePoolApiKeyStore keyStore, GenepoolSettings genepoolSettings) =>
from apiKeys in keyStore.GetApiKeys().ToAff(identity)
let apiKey = apiKeys.Find(GenePoolConstants.EryphGenePool.Name)
let apiKey = apiKeys.Find(genepoolSettings.Name)
from _ in apiKey.Match(
Some: key =>
from _1 in AnsiConsole<RT>.writeLine("The following gene pool API key is configured:")
from _2 in AnsiConsole<RT>.write(printApiKey(key))
from shouldRemove in AnsiConsole<RT>.confirm("Remove API key?", false)
from _3 in shouldRemove
? removeApiKey(keyStore, GenePoolConstants.EryphGenePool.Name, key)
? removeApiKey(keyStore, genepoolSettings, key)
: unitEff
select unit,
None: () => AnsiConsole<RT>.writeLine("No gene pool API key is configured."))
select unit;

private static Aff<RT, Unit> createApiKey(
IGenePoolApiKeyStore apiKeyStore) =>
IGenePoolApiKeyStore apiKeyStore, GenepoolSettings genepoolSettings) =>
from orgName in AnsiConsole<RT>.prompt(
"Enter your organization:",
OrganizationName.NewValidation)
Expand All @@ -91,13 +87,12 @@ from apiKeyName in AnsiConsole<RT>.prompt(
ApiKeyName.NewValidation,
$"eryph-zero-{hostname.ToLowerInvariant()}")
from cancelToken in cancelToken<RT>()
from client in createGenePoolClient()
from client in createInteractiveGenePoolClient(genepoolSettings)
from stopSpinner in AnsiConsole<RT>.startSpinner("Creating API key...")
from result in Aff(async () =>
{
var response = await client.GetOrganizationClient(orgName)
// TODO Remove Org.Read permission when the /me endpoint is available
.CreateApiKeyAsync(apiKeyName, ["Geneset.Read", "Org.Read"], cancellationToken: cancelToken);
.CreateApiKeyAsync(apiKeyName, ["Geneset.Read"], cancellationToken: cancelToken);
return Optional(response);
}) | @catch(e => stopSpinner.Bind(_ => FailAff<RT, Option<ApiKeySecretResponse>>(e)))
from _1 in stopSpinner
Expand All @@ -109,40 +104,41 @@ from validResult in result.ToAff(Error.New("The gene pool API key was not create
Organization = validResult.Organization,
Secret = validResult.Secret,
}
from _2 in apiKeyStore.SaveApiKey(GenePoolConstants.EryphGenePool.Name, apiKey).ToAff(identity)
from _2 in apiKeyStore.SaveApiKey(genepoolSettings.Name, apiKey).ToAff(identity)
from _3 in AnsiConsole<RT>.writeLine("The gene pool API key was successfully created.")
select unit;

private static Aff<RT, Unit> validateApiKey(GenePoolApiKey apiKey) =>
from keyResponse in getApiKeyFromPool(apiKey).Map(Optional)
private static Aff<RT, Unit> validateApiKey(GenePoolApiKey apiKey, GenepoolSettings genepoolSettings) =>
from userResponse in getApiKeyUser(apiKey, genepoolSettings).Map(Optional)
| @catch(ex => ex is ErrorResponseException { Response.StatusCode: HttpStatusCode.Unauthorized },
Option<ApiKeyResponse>.None)
Option<GetMeResponse>.None)
| @catch(_ => true, e => Error.New("Could not validate the gene pool API key.", e))
from _ in keyResponse.Match(
from _ in userResponse.Match(
Some: response =>
from _ in AnsiConsole<RT>.write(new Rows(
new Markup("The gene pool API key is valid. The following information was returned by the gene pool:"),
new Grid()
.AddColumn()
.AddColumn()
.AddRow(new Text("Key ID:"), new Text(response.KeyId))
.AddRow(new Text("Key Name:"), new Text(response.Name))
.AddRow(new Text("Organisation:"), new Text(response.Organization.Name))))
.AddRow(new Text("Key ID:"), new Text(response.Id))
.AddRow(new Text("Key Name:"), new Text(response.DisplayName ?? ""))
.AddRow(new Text(response.GenepoolOrgs?.Length != 1 ? "Organizations:": "Organization:"),
new Text(string.Join(',',(response.GenepoolOrgs ?? []).Select(x=>x.Name))))))
select unit,
None: () =>
from _ in AnsiConsole<RT>.writeLine("The gene pool API key is invalid.")
select unit)
select unit;

private static Aff<RT, ApiKeyResponse> getApiKeyFromPool(GenePoolApiKey apiKey) =>
private static Aff<RT, GetMeResponse> getApiKeyUser(GenePoolApiKey apiKey, GenepoolSettings genepoolSettings) =>
from cancelToken in cancelToken<RT>()
from applicationId in ApplicationInfo<RT>.applicationId()
from hardwareId in HardwareId<RT>.hashedHardwareId()
from stopSpinner in AnsiConsole<RT>.startSpinner("Validating API key...")
from response in Aff(async () =>
{
var client = new GenePoolClient(
GenePoolConstants.EryphGenePool.ApiEndpoint,
genepoolSettings.ApiEndpoint,
new ApiKeyCredential(apiKey.Secret),
new GenePoolClientOptions()
{
Expand All @@ -152,24 +148,24 @@ from response in Aff(async () =>
},
HardwareId = hardwareId,
});
var response = await client.GetApiKeyClient(apiKey.Organization, apiKey.Id)
.GetAsync(cancellationToken: cancelToken);
var response = await client.GetUserClient()
.GetAsync(new GetUserRequestOptions{Expand = new ExpandFromUser{ GenepoolOrgs = true}}, cancellationToken: cancelToken);

return response;
}) | @catch(e => stopSpinner.Bind(_ => FailAff<RT, ApiKeyResponse>(e)))
}) | @catch(e => stopSpinner.Bind(_ => FailAff<RT, GetMeResponse>(e)))
from _ in stopSpinner
select response;

private static Aff<RT, Unit> removeApiKey(
IGenePoolApiKeyStore apiKeyStore,
string genePoolName,
GenepoolSettings genepoolSettings,
GenePoolApiKey apiKey) =>
from _1 in AnsiConsole<RT>.writeLine(
"Do you want to delete the API key from the remote gene pool? "
+ "You will need to authenticate with the gene pool.")
from shouldDelete in AnsiConsole<RT>.confirm("Delete gene pool API key from gene pool?", false)
from shouldContinue in shouldDelete
? deleteApiKeyFromPool(apiKey).Map(_ => true)
? deleteApiKeyFromPool(apiKey, genepoolSettings).Map(_ => true)
| @catch(e =>
from _1 in AnsiConsole<RT>.write(new Rows(
new Markup("[red]Could not delete the API key from the remote gene pool.[/]"),
Expand All @@ -178,13 +174,13 @@ from shouldContinue in AnsiConsole<RT>.confirm("Remove the gene pool API key fro
select shouldContinue)
: SuccessAff<RT, bool>(true)
from _2 in shouldContinue
? apiKeyStore.RemoveApiKey(genePoolName).ToAff(identity)
? apiKeyStore.RemoveApiKey(genepoolSettings.Name).ToAff(identity)
: unitEff
from _5 in AnsiConsole<RT>.writeLine("The gene pool API key was successfully removed.")
select unit;

private static Aff<RT, Unit> deleteApiKeyFromPool(GenePoolApiKey apiKey) =>
from client in createGenePoolClient()
private static Aff<RT, Unit> deleteApiKeyFromPool(GenePoolApiKey apiKey, GenepoolSettings genepoolSettings) =>
from client in createInteractiveGenePoolClient(genepoolSettings)
from cancelToken in cancelToken<RT>()
from stopSpinner in AnsiConsole<RT>.startSpinner("Deleting API key...")
from _1 in Aff(async () =>
Expand All @@ -196,12 +192,12 @@ await client.GetApiKeyClient(apiKey.Organization, apiKey.Id)
from _2 in stopSpinner
select unit;

private static Aff<RT, GenePoolClient> createGenePoolClient() =>
private static Aff<RT, GenePoolClient> createInteractiveGenePoolClient(GenepoolSettings genepoolSettings) =>
from cancelToken in cancelToken<RT>()
from applicationId in ApplicationInfo<RT>.applicationId()
from hardwareId in HardwareId<RT>.hashedHardwareId()
from stopSpinner in AnsiConsole<RT>.startSpinner("Authenticating with the gene pool. Please check your browser.")
let options = new GenePoolClientOptions()
let options = new GenePoolClientOptions(GenePoolClientOptions.ServiceVersion.V1, genepoolSettings.ApiEndpoint.ToString(), genepoolSettings.IsStaging)
{
Diagnostics =
{
Expand All @@ -213,8 +209,8 @@ from result in Aff(async () =>
{
var credentialOptions = new B2CInteractiveBrowserCredentialOptions
{
ClientId = GenePoolConstants.EryphGenePool.AuthClientId,
AuthorityUri = GenePoolConstants.EryphGenePool.AuthEndpoint.AbsoluteUri,
ClientId = genepoolSettings.AuthClientId,
AuthorityUri = genepoolSettings.AuthEndpoint.AbsoluteUri,
BrowserCustomization = new BrowserCustomizationOptions
{
UseEmbeddedWebView = false,
Expand All @@ -224,7 +220,7 @@ from result in Aff(async () =>
var credential = new B2CInteractiveBrowserCredential(credentialOptions);
await credential.AuthenticateAsync(new TokenRequestContext());

return new GenePoolClient(GenePoolConstants.EryphGenePool.ApiEndpoint, credential, options);
return new GenePoolClient(genepoolSettings.ApiEndpoint, credential, options);
}) | @catch(e => stopSpinner.Bind(_ => FailAff<RT, GenePoolClient>(e)))
from _ in stopSpinner
from __ in AnsiConsole<RT>.writeLine("Successfully authenticated with the gene pool.")
Expand All @@ -236,5 +232,5 @@ private static IRenderable printApiKey(GenePoolApiKey apiKey) =>
.AddColumn()
.AddRow(new Text("Key ID:"), new Text(apiKey.Id))
.AddRow(new Text("Key Name:"), new Text(apiKey.Name))
.AddRow(new Text("Organisation:"), new Text(apiKey.Organization));
.AddRow(new Text("Organization:"), new Text(apiKey.Organization));
}
Loading

0 comments on commit 6bef537

Please sign in to comment.