J
@atripp
OK, I've tried, but I'm getting nowhere.
I've added a very rudimentary implementation of DynamicListVariableType, based on your code. I've used Inedo.SDK.GetServers(true); rather than linking directly to Otter.Core, but I think that might be irrelevant.
Custom variable types just seem not to work at all.
Any time you add one to the Job (including trying to add the built-in Universal Packages variable type), the front-end just throws an HTTP/500.
My variable type appears in the list, I select it; I give it a variable name and set the list properties (restrict, multi-select, etc); I click Save Variable and an dialog-style iframe pops up with the standard error page. The new variable's JSON is never written to the raft file.
An error occurred in the web application: Value cannot be null. (Parameter 'type')
URL: http://172.31.15.125:8626/jobs/templates/edit-variable?templateId=Default%3A%3AJobTemplate%3A%3AInitialization%2FRun Sysprep for target servers
Referrer: http://172.31.15.125:8626/jobs/templates/edit-variable?templateId=Default%3A%3AJobTemplate%3A%3AInitialization%2FRun Sysprep for target servers
User: Admin
User Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:133.0) Gecko/20100101 Firefox/133.0
Stack trace: at System.ArgumentNullException.Throw(String paramName)
at System.Activator.CreateInstance(Type type, Boolean nonPublic, Boolean wrapExceptions)
at Inedo.Otter.WebApplication.Pages.Jobs.JobTemplates.EditJobTemplateVariablePage.<>c__DisplayClass5_0.<CreateChildControls>b__1()
at Inedo.Web.Controls.ButtonLinks.PostBackButtonLink.Inedo.Web.Controls.ISimpleEventProcessor.ProcessEventAsync(String eventName, String eventArgument)
at Inedo.Web.PageFree.SimplePageBase.ExecutePageLifeCycleAsync()
at Inedo.Web.PageFree.SimplePageBase.ProcessRequestAsync(AhHttpContext context)
at Inedo.Web.AhWebMiddleware.InvokeAsync(HttpContext context)
::HTTP Error on 20/01/2025 18:46:39::
Attaching a debugger traces the error to code which decompiles horribly (and very likely does not match the codebase exactly). I won't paste it all here, but it inside the EditJobTemplateVariablePage, there is a CreateChildControls method, partially containing:
PostBackButtonLink postBackButtonLink = new PostBackButtonLink("Save Variable", delegate {
// ... omitted for brevity
string selectedValue = CS$<>8__locals1.ddlType.SelectedValue;
VariableTemplateType variableTemplateType;
if (!(selectedValue == "Constant"))
{
if (!(selectedValue == "Text"))
{
if (!(selectedValue == "List"))
{
if (!(selectedValue == "Checkbox"))
{
// *** NEXT LINE THROWS... (parameter 'type' cannot be null) ***
variableTemplateType = (VariableTemplateType)Activator.CreateInstance(Type.GetType(CS$<>8__locals1.ddlType.SelectedValue));
}
else
{
variableTemplateType = VariableTemplateType.Checkbox;
}
}
else
{
variableTemplateType = VariableTemplateType.List;
}
}
else
{
variableTemplateType = VariableTemplateType.Text;
}
}
else
{
variableTemplateType = VariableTemplateType.Constant;
}
// ... omitted for brevity
});
It looks like Type.GetType call can't resolve the custom VariableTemplateType class name.
The value of ddlType.SelectedValue looks (at first glance) to match the correct class and assembly name of my type (ServerSelector.ServerListVariableType, ServerSelector).
I've also tried to manually enter the variable definition directly into the raw raft content, but I must be missing something in the syntax, because the result just turns red in the /jobs page (no errors are logged in Diagnostics Centre.).
"JobVariables": [
{
"Name": "TargetServers",
"Description": "Execute against these servers",
"InitialValue": "",
"Type": "ServerSelector.ServerListVariableType, ServerSelector",
"Usage": "Input",
"ListValues": [],
"ListMultiple": true,
"ListRestrict": true
},
...
For what it is worth, my custom class is...
[DisplayName("Specific servers")]
public class ServerListVariableType : DynamicListVariableType
{
[Persistent]
[DisplayName("Include inactive")]
public bool IncludeInactive { get; } = false;
public override async Task<IEnumerable<string>> EnumerateListValuesAsync(VariableTemplateContext context)
{
IEnumerable<string> GetServerNames()
{
foreach (var s in Inedo.SDK.GetServers(IncludeInactive).Select(s => s.Name))
yield return s;
}
return await Task.FromResult(GetServerNames());
}
public override RichDescription GetDescription()
{
return new RichDescription("Allows selection of ",
new Hilite("Servers"),
" outside of Job targeting");
}
}