Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
79b1a2a
Add notifications onto CR Event (#736)
JoeBatt1989 Apr 11, 2023
5cf5c83
squashed commits
neildsouth Apr 12, 2023
1c86cef
Merge pull request #734 from Project-MONAI/AC-1913-nds
neildsouth Apr 12, 2023
aa3f7a9
squash commits
neildsouth Apr 14, 2023
ff0c754
Merge branch 'develop' into AC-1914
neildsouth Apr 14, 2023
e972799
Merge pull request #743 from Project-MONAI/AC-1914
neildsouth Apr 14, 2023
5297c24
Notifications bug (#751)
JoeBatt1989 Apr 17, 2023
0afb899
AC-1714 monai update task manager plugin with delete end point (#739)
lillie-dae Apr 17, 2023
f4c4701
added endpoint
neildsouth Apr 17, 2023
69e94bb
merge in develop
neildsouth Apr 17, 2023
37401a7
adding more tests
neildsouth Apr 17, 2023
c2d6ccb
Merge pull request #752 from Project-MONAI/AC-1979
neildsouth Apr 18, 2023
4165c41
adding default podPriorityClassName
neildsouth Apr 18, 2023
176efb7
AC-1989 validate priority
neildsouth Apr 18, 2023
e37bfe4
adding loggin to see whats posted to Argo template endpoint
neildsouth Apr 19, 2023
5f58fa1
Merge pull request #753 from Project-MONAI/AC-2022
neildsouth Apr 19, 2023
3c79f28
merge in develop
neildsouth Apr 19, 2023
9ce57b6
Merge pull request #754 from Project-MONAI/Nds-Add-Debug-logging-argo…
neildsouth Apr 19, 2023
6ce8360
Prerelease 2023.04.24.1 - merge in main - developer (#756)
lillie-dae Apr 24, 2023
c748cbd
added endpoint
neildsouth Apr 17, 2023
bcf92fa
rebase on main
lillie-dae Apr 24, 2023
0c3cc3b
rebase on main
JoeBatt1989 Apr 17, 2023
fa743dc
AC-1714 monai update task manager plugin with delete end point (#739)
lillie-dae Apr 17, 2023
1c04999
adding more tests
neildsouth Apr 17, 2023
05f8258
adding loggin to see whats posted to Argo template endpoint
neildsouth Apr 19, 2023
7d09099
adding default podPriorityClassName
neildsouth Apr 18, 2023
4d7d496
AC-1989 validate priority
neildsouth Apr 18, 2023
bb2e23c
Prerelease 2023.04.24.1 - merge in main - developer (#756)
lillie-dae Apr 24, 2023
01fbb01
merge in main
lillie-dae Apr 24, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions guidelines/mwm-developer-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,17 @@ Note. if you already have docker container for Minio Rabbit etc running Stop the
- `kubectl apply -n argo -f https://raw.githubusercontent.com/argoproj/argo-workflows/master/manifests/quick-start-postgres.yaml`
- `kubectl config set-context --current --namespace=argo`

To disable argo authentication run

kubectl patch deployment \
argo-server \
--namespace argo \
--type='json' \
-p='[{"op": "replace", "path": "/spec/template/spec/containers/0/args", "value": [
"server",
"--auth-mode=server"
]}]'

Note. below Im using bash as its my preferred option, But if you to are using bash and your on windows (wsl2) you MUST make sure you windows .kube/config is also pointing to the same K8's cluster, this is because the code running in vs will look in there for the context to write k8's secrets too!

now in a bash window (can be cmd or powershell)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
//
// Copyright 2023 Guy’s and St Thomas’ NHS Foundation Trust
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/*
* Copyright 2022 MONAI Consortium
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Monai.Deploy.WorkflowManager.TaskManager.API.Models;
using Mongo.Migration.Migrations.Document;
using MongoDB.Bson;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
//
// Copyright 2023 Guy’s and St Thomas’ NHS Foundation Trust
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// using Monai.Deploy.WorkflowManager.TaskManager.API.Models;
/*
* Copyright 2022 MONAI Consortium
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

using Monai.Deploy.WorkflowManager.TaskManager.API.Models;
using Mongo.Migration.Migrations.Document;
using MongoDB.Bson;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,13 @@ private void Initialize()
_workflowName = Event.TaskPluginArguments[Keys.WorkflowName];
}

if (Event.TaskPluginArguments.ContainsKey(Keys.Notifications))
if (Event.TaskPluginArguments.ContainsKey(Keys.Notifications) && Boolean.TryParse(Event.TaskPluginArguments[Keys.Notifications], out var result))
{
_notifications = Boolean.TryParse(Event.TaskPluginArguments[Keys.Notifications], out bool result);
_notifications = result;
}
else
{
_notifications = true;
}

if (Event.TaskPluginArguments.ContainsKey(Keys.ReviewedExecutionId))
Expand Down
215 changes: 107 additions & 108 deletions src/TaskManager/Plug-ins/Argo/ArgoClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,37 +22,9 @@

namespace Monai.Deploy.WorkflowManager.TaskManager.Argo
{
public interface IArgoClient
public class ArgoClient : BaseArgoClient, IArgoClient
{
Task<Workflow> Argo_CreateWorkflowAsync(string argoNamespace, WorkflowCreateRequest body, CancellationToken cancellationToken);

Task<Workflow> Argo_GetWorkflowAsync(string argoNamespace, string name, string getOptions_resourceVersion, string fields, CancellationToken cancellationToken);

Task<WorkflowTemplate> Argo_GetWorkflowTemplateAsync(string argoNamespace, string name, string getOptions_resourceVersion);

Task<Workflow> Argo_StopWorkflowAsync(string argoNamespace, string name, WorkflowStopRequest body);

Task<Workflow> Argo_TerminateWorkflowAsync(string argoNamespace, string name, WorkflowTerminateRequest body);

Task<Version?> Argo_GetVersionAsync();

Task<string?> Argo_Get_WorkflowLogsAsync(string argoNamespace, string name, string podName, string logOptions_container);

Task<WorkflowTemplate> Argo_CreateWorkflowTemplateAsync(string argoNamespace, WorkflowTemplateCreateRequest body, CancellationToken cancellationToken);
}

public class ArgoClient : IArgoClient
{
private readonly HttpClient _httpClient;

public string BaseUrl { get; set; } = "http://localhost:2746";

private string FormattedBaseUrl { get { return BaseUrl != null ? BaseUrl.TrimEnd('/') : ""; } }

public ArgoClient(HttpClient httpClient)
{
_httpClient = httpClient;
}
public ArgoClient(HttpClient httpClient) : base(httpClient) { }

public async Task<Workflow> Argo_CreateWorkflowAsync(string argoNamespace, WorkflowCreateRequest body, CancellationToken cancellationToken)
{
Expand Down Expand Up @@ -164,27 +136,113 @@ public async Task<WorkflowTemplate> Argo_GetWorkflowTemplateAsync(string argoNam
urlBuilder.Length--;
return await GetRequest<string>(urlBuilder, true).ConfigureAwait(false);
}
private async Task<T> SendRequest<T>(StringContent stringContent, StringBuilder urlBuilder, string Method, CancellationToken cancellationToken)

/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
/// <returns>A successful response.</returns>
/// <exception cref="ApiException">A server side error occurred.</exception>
public virtual async Task<WorkflowTemplate> Argo_CreateWorkflowTemplateAsync(string argoNamespace, WorkflowTemplateCreateRequest body, CancellationToken cancellationToken)
{
Guard.Against.NullOrWhiteSpace(argoNamespace);
Guard.Against.Null(body);

var urlBuilder = new StringBuilder();
urlBuilder.Append(CultureInfo.InvariantCulture, $"{FormattedBaseUrl}/api/v1/workflow-templates/{argoNamespace}");

var method = "POST";
var content = new StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body));

Check warning

Code scanning / CodeQL

Cross-site scripting

[User-provided value](1) flows to here and is written to HTML or JavaScript.
return await SendRequest<WorkflowTemplate>(content, urlBuilder, method, cancellationToken).ConfigureAwait(false);
}

/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
/// <returns>A successful response.</returns>
/// <exception cref="ApiException">A server side error occurred.</exception>
public virtual async Task<bool> Argo_DeleteWorkflowTemplateAsync(string argoNamespace, string templateName, CancellationToken cancellationToken)
{
using (var request = new HttpRequestMessage())
Guard.Against.NullOrWhiteSpace(argoNamespace);

var urlBuilder = new StringBuilder();
urlBuilder.Append(CultureInfo.InvariantCulture, $"{FormattedBaseUrl}/api/v1/workflow-templates/{argoNamespace}/{templateName}");

var method = "DELETE";
var response = await HttpClient.SendAsync(new HttpRequestMessage(new HttpMethod(method), urlBuilder.ToString()), cancellationToken).ConfigureAwait(false);
return (int)response.StatusCode == 200;
}

public static string ConvertToString(object value, CultureInfo cultureInfo)
{
if (value == null)
{
stringContent.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json");
request.Content = stringContent;
request.Method = new HttpMethod(Method);
request.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json"));
request.RequestUri = new Uri(urlBuilder.ToString(), UriKind.RelativeOrAbsolute);
return "";
}

HttpResponseMessage? response = null;
try
if (value is Enum)
{
var name = Enum.GetName(value.GetType(), value);
if (name != null)
{
response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
var field = System.Reflection.IntrospectionExtensions.GetTypeInfo(value.GetType()).GetDeclaredField(name);
if (field != null)
{
if (System.Reflection.CustomAttributeExtensions.GetCustomAttribute(field, typeof(System.Runtime.Serialization.EnumMemberAttribute)) is System.Runtime.Serialization.EnumMemberAttribute attribute)
{
return attribute.Value ?? name;
}
}

var converted = Convert.ToString(Convert.ChangeType(value, Enum.GetUnderlyingType(value.GetType()), cultureInfo));
return converted ?? string.Empty;
}
catch (Exception ex)
}
else if (value is bool boolean)
{
return Convert.ToString(boolean, cultureInfo).ToLowerInvariant();
}
else if (value is byte[] v)
{
return Convert.ToBase64String(v);
}
else if (value.GetType().IsArray)
{
var array = Enumerable.OfType<object>((Array)value);
return string.Join(",", Enumerable.Select(array, o => ConvertToString(o, cultureInfo)));
}

var result = Convert.ToString(value, cultureInfo);
return result ?? "";
}
}

/// <summary>
/// <see cref="BaseArgoClient"/> generic functions relating to argo requests
/// </summary>
public class BaseArgoClient
{
public string BaseUrl { get; set; } = "http://localhost:2746";

protected string FormattedBaseUrl { get { return BaseUrl != null ? BaseUrl.TrimEnd('/') : ""; } }

protected readonly HttpClient HttpClient;

public BaseArgoClient(HttpClient httpClient)
{
HttpClient = httpClient;
}

protected async Task<T> SendRequest<T>(StringContent stringContent, StringBuilder urlBuilder, string method, CancellationToken cancellationToken)
{
using (var request = new HttpRequestMessage())
{
if (stringContent is not null)
{
var mess = ex.Message;
throw;
stringContent.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json");
request.Content = stringContent;
}
request.Method = new HttpMethod(method);
request.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json"));
request.RequestUri = new Uri(urlBuilder.ToString(), UriKind.RelativeOrAbsolute);

HttpResponseMessage? response = null;
response = await HttpClient.SendAsync(request, HttpCompletionOption.ResponseContentRead, cancellationToken).ConfigureAwait(false);

try
{
Expand Down Expand Up @@ -222,7 +280,7 @@ private async Task<T> SendRequest<T>(StringContent stringContent, StringBuilder
}
}

private async Task<T?> GetRequest<T>(StringBuilder urlBuilder, bool isLogs = false)
protected async Task<T?> GetRequest<T>(StringBuilder urlBuilder, bool isLogs = false)
{

using (var request = new HttpRequestMessage())
Expand All @@ -231,7 +289,7 @@ private async Task<T> SendRequest<T>(StringContent stringContent, StringBuilder
request.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json"));
request.RequestUri = new Uri(urlBuilder.ToString(), UriKind.RelativeOrAbsolute);

var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
var response = await HttpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
try
{
var headers_ = Enumerable.ToDictionary(response.Headers, h_ => h_.Key, h_ => h_.Value);
Expand Down Expand Up @@ -271,10 +329,9 @@ private async Task<T> SendRequest<T>(StringContent stringContent, StringBuilder
}
}


protected virtual async Task<ObjectResponseResult<T?>> ReadObjectResponseAsync<T>(HttpResponseMessage response, IReadOnlyDictionary<string, IEnumerable<string>> headers, bool isLogs = false)
{
if (response == null || response.Content == null)
if (response == null || response.Content == null || response.Content.GetType().Name == "EmptyContent")
{
return new ObjectResponseResult<T?>(default, string.Empty);
}
Expand Down Expand Up @@ -306,7 +363,7 @@ private async Task<T> SendRequest<T>(StringContent stringContent, StringBuilder
}
}

protected static string DecodeLogs(string logInput)
public static string DecodeLogs(string logInput)
{
var rows = logInput.Split(new String[] { "\n" }, StringSplitOptions.None);
var jsonBody = $"[{string.Join(",", rows)}]";
Expand Down Expand Up @@ -348,23 +405,7 @@ protected virtual async Task<ObjectResponseResult<string>> ReadLogResponseAsync(
}
}

/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
/// <returns>A successful response.</returns>
/// <exception cref="ApiException">A server side error occurred.</exception>
public virtual async Task<WorkflowTemplate> Argo_CreateWorkflowTemplateAsync(string argoNamespace, WorkflowTemplateCreateRequest body, CancellationToken cancellationToken)
{
Guard.Against.NullOrWhiteSpace(argoNamespace);
Guard.Against.Null(body);

var urlBuilder = new StringBuilder();
urlBuilder.Append(CultureInfo.InvariantCulture, $"{FormattedBaseUrl}/api/v1/workflow-templates/{argoNamespace}");

var Method = "POST";
var content = new StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body));
return await SendRequest<WorkflowTemplate>(content, urlBuilder, Method, cancellationToken).ConfigureAwait(false);
}

protected struct ObjectResponseResult<T>
protected readonly struct ObjectResponseResult<T>
{
public ObjectResponseResult(T responseObject, string responseText)
{
Expand All @@ -377,61 +418,19 @@ public ObjectResponseResult(T responseObject, string responseText)
public string Text { get; }
}

private string ConvertToString(object value, CultureInfo cultureInfo)
{
if (value == null)
{
return "";
}

if (value is Enum)
{
var name = Enum.GetName(value.GetType(), value);
if (name != null)
{
var field = System.Reflection.IntrospectionExtensions.GetTypeInfo(value.GetType()).GetDeclaredField(name);
if (field != null)
{
if (System.Reflection.CustomAttributeExtensions.GetCustomAttribute(field, typeof(System.Runtime.Serialization.EnumMemberAttribute)) is System.Runtime.Serialization.EnumMemberAttribute attribute)
{
return attribute.Value ?? name;
}
}

var converted = Convert.ToString(Convert.ChangeType(value, Enum.GetUnderlyingType(value.GetType()), cultureInfo));
return converted ?? string.Empty;
}
}
else if (value is bool boolean)
{
return Convert.ToString(boolean, cultureInfo).ToLowerInvariant();
}
else if (value is byte[] v)
{
return Convert.ToBase64String(v);
}
else if (value.GetType().IsArray)
{
var array = Enumerable.OfType<object>((Array)value);
return string.Join(",", Enumerable.Select(array, o => ConvertToString(o, cultureInfo)));
}

var result = Convert.ToString(value, cultureInfo);
return result ?? "";
}
class ArgoLogEntry
{
public string Content { get; set; } = "";

public string PodName { get; set; } = "";
}

class ArgoLogEntryResult
{
public ArgoLogEntry Result { get; set; } = new ArgoLogEntry();
}
}


public class Version
{
[Newtonsoft.Json.JsonProperty("buildDate", Required = Newtonsoft.Json.Required.Always)]
Expand Down
Loading