Skip to content

Commit

Permalink
Merge pull request #18172 from jmchilton/file_source_templates_2
Browse files Browse the repository at this point in the history
Harden User Object Store and File Source Creation
  • Loading branch information
jdavcs authored May 20, 2024
2 parents 9b6268b + 2a92bc1 commit 177afd4
Show file tree
Hide file tree
Showing 20 changed files with 626 additions and 115 deletions.
3 changes: 3 additions & 0 deletions client/src/api/configTemplates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ export type VariableValueType = (string | boolean | number) | undefined;
export type VariableData = { [key: string]: VariableValueType };
export type SecretData = { [key: string]: string };

export type PluginAspectStatus = components["schemas"]["PluginAspectStatus"];
export type PluginStatus = components["schemas"]["PluginStatus"];

export interface TemplateSummary {
description: string | null;
hidden?: boolean;
Expand Down
80 changes: 80 additions & 0 deletions client/src/api/schema/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,10 @@ export interface paths {
/** Create a user-bound file source. */
post: operations["file_sources__create_instance"];
};
"/api/file_source_instances/test": {
/** Test payload for creating user-bound file source. */
post: operations["file_sources__test_new_instance_configuration"];
};
"/api/file_source_instances/{user_file_source_id}": {
/** Get a list of persisted file source instances defined by the requesting user. */
get: operations["file_sources__instances_get"];
Expand Down Expand Up @@ -1269,6 +1273,10 @@ export interface paths {
/** Create a user-bound object store. */
post: operations["object_stores__create_instance"];
};
"/api/object_store_instances/test": {
/** Test payload for creating user-bound object store. */
post: operations["object_stores__test_new_instance_configuration"];
};
"/api/object_store_instances/{user_object_store_id}": {
/** Get a persisted object store instances owned by the requesting user. */
get: operations["object_stores__instances_get"];
Expand Down Expand Up @@ -10455,12 +10463,28 @@ export interface components {
* @enum {string}
*/
PersonalNotificationCategory: "message" | "new_shared_item";
/** PluginAspectStatus */
PluginAspectStatus: {
/** Message */
message: string;
/**
* State
* @enum {string}
*/
state: "ok" | "not_ok" | "unknown";
};
/**
* PluginKind
* @description Enum to distinguish between different kinds or categories of plugins.
* @enum {string}
*/
PluginKind: "rfs" | "drs" | "rdm" | "stock";
/** PluginStatus */
PluginStatus: {
connection?: components["schemas"]["PluginAspectStatus"] | null;
template_definition: components["schemas"]["PluginAspectStatus"];
template_settings?: components["schemas"]["PluginAspectStatus"] | null;
};
/** Position */
Position: {
/** Left */
Expand Down Expand Up @@ -15003,6 +15027,34 @@ export interface operations {
};
};
};
file_sources__test_new_instance_configuration: {
/** Test payload for creating user-bound file source. */
parameters?: {
/** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */
header?: {
"run-as"?: string | null;
};
};
requestBody: {
content: {
"application/json": components["schemas"]["CreateInstancePayload"];
};
};
responses: {
/** @description Successful Response */
200: {
content: {
"application/json": components["schemas"]["PluginStatus"];
};
};
/** @description Validation Error */
422: {
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
file_sources__instances_get: {
/** Get a list of persisted file source instances defined by the requesting user. */
parameters: {
Expand Down Expand Up @@ -20880,6 +20932,34 @@ export interface operations {
};
};
};
object_stores__test_new_instance_configuration: {
/** Test payload for creating user-bound object store. */
parameters?: {
/** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */
header?: {
"run-as"?: string | null;
};
};
requestBody: {
content: {
"application/json": components["schemas"]["CreateInstancePayload"];
};
};
responses: {
/** @description Successful Response */
200: {
content: {
"application/json": components["schemas"]["PluginStatus"];
};
};
/** @description Validation Error */
422: {
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
object_stores__instances_get: {
/** Get a persisted object store instances owned by the requesting user. */
parameters: {
Expand Down
12 changes: 12 additions & 0 deletions client/src/components/ConfigTemplates/formUtil.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type {
Instance,
PluginStatus,
SecretData,
TemplateSecret,
TemplateSummary,
Expand Down Expand Up @@ -243,3 +244,14 @@ export function upgradeFormDataToPayload(template: TemplateSummary, formData: an
};
return payload;
}

export function pluginStatusToErrorMessage(pluginStatus: PluginStatus): string | null {
if (pluginStatus.template_definition.state == "not_ok") {
return pluginStatus.template_definition.message;
} else if (pluginStatus.template_settings?.state == "not_ok") {
return pluginStatus.template_settings.message;
} else if (pluginStatus.connection?.state == "not_ok") {
return pluginStatus.connection.message;
}
return null;
}
14 changes: 12 additions & 2 deletions client/src/components/FileSources/Instances/CreateForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@ import { BAlert } from "bootstrap-vue";
import { computed, ref } from "vue";
import type { FileSourceTemplateSummary, UserFileSourceModel } from "@/api/fileSources";
import { createFormDataToPayload, createTemplateForm } from "@/components/ConfigTemplates/formUtil";
import {
createFormDataToPayload,
createTemplateForm,
pluginStatusToErrorMessage,
} from "@/components/ConfigTemplates/formUtil";
import { errorMessageAsString } from "@/utils/simple-error";
import { create } from "./services";
import { create, test } from "./services";
import InstanceForm from "@/components/ConfigTemplates/InstanceForm.vue";
Expand All @@ -25,6 +29,12 @@ const inputs = computed(() => {
async function onSubmit(formData: any) {
const payload = createFormDataToPayload(props.template, formData);
const { data: pluginStatus } = await test(payload);
const testError = pluginStatusToErrorMessage(pluginStatus);
if (testError) {
error.value = testError;
return;
}
try {
const { data: fileSource } = await create(payload);
emit("created", fileSource);
Expand Down
1 change: 1 addition & 0 deletions client/src/components/FileSources/Instances/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { UserFileSourceModel } from "@/api/fileSources";
import { fetcher } from "@/api/schema/fetcher";

export const create = fetcher.path("/api/file_source_instances").method("post").create();
export const test = fetcher.path("/api/file_source_instances/test").method("post").create();
export const update = fetcher.path("/api/file_source_instances/{user_file_source_id}").method("put").create();

export async function hide(instance: UserFileSourceModel) {
Expand Down
18 changes: 18 additions & 0 deletions client/src/components/ObjectStore/Instances/CreateForm.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { mount } from "@vue/test-utils";
import flushPromises from "flush-promises";
import { getLocalVue } from "tests/jest/helpers";

import { PluginStatus } from "@/api/configTemplates";
import { mockFetcher } from "@/api/schema/__mocks__";
import type { ObjectStoreTemplateSummary } from "@/components/ObjectStore/Templates/types";

Expand Down Expand Up @@ -57,6 +58,21 @@ const SIMPLE_TEMPLATE: ObjectStoreTemplateSummary = {
badges: [],
};

const FAKE_PLUGIN_STATUS: PluginStatus = {
template_definition: {
state: "ok",
message: "ok",
},
template_settings: {
state: "ok",
message: "ok",
},
connection: {
state: "ok",
message: "ok",
},
};

describe("CreateForm", () => {
it("should render a form with admin markdown converted to HTML in help", async () => {
const wrapper = mount(CreateForm, {
Expand All @@ -83,6 +99,7 @@ describe("CreateForm", () => {
},
localVue,
});
mockFetcher.path("/api/object_store_instances/test").method("post").mock({ data: FAKE_PLUGIN_STATUS });
mockFetcher.path("/api/object_store_instances").method("post").mock({ data: FAKE_OBJECT_STORE });
await flushPromises();
const nameForElement = wrapper.find("#form-element-_meta_name");
Expand All @@ -102,6 +119,7 @@ describe("CreateForm", () => {
},
localVue,
});
mockFetcher.path("/api/object_store_instances/test").method("post").mock({ data: FAKE_PLUGIN_STATUS });
mockFetcher
.path("/api/object_store_instances")
.method("post")
Expand Down
15 changes: 13 additions & 2 deletions client/src/components/ObjectStore/Instances/CreateForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@
import { BAlert } from "bootstrap-vue";
import { computed, ref } from "vue";
import { createFormDataToPayload, createTemplateForm, type FormEntry } from "@/components/ConfigTemplates/formUtil";
import {
createFormDataToPayload,
createTemplateForm,
type FormEntry,
pluginStatusToErrorMessage,
} from "@/components/ConfigTemplates/formUtil";
import type { UserConcreteObjectStore } from "@/components/ObjectStore/Instances/types";
import type { ObjectStoreTemplateSummary } from "@/components/ObjectStore/Templates/types";
import { errorMessageAsString } from "@/utils/simple-error";
import { create } from "./services";
import { create, test } from "./services";
import InstanceForm from "@/components/ConfigTemplates/InstanceForm.vue";
Expand All @@ -26,6 +31,12 @@ const inputs = computed<Array<FormEntry>>(() => {
async function onSubmit(formData: any) {
const payload = createFormDataToPayload(props.template, formData);
const { data: pluginStatus } = await test(payload);
const testError = pluginStatusToErrorMessage(pluginStatus);
if (testError) {
error.value = testError;
return;
}
try {
const { data: objectStore } = await create(payload);
emit("created", objectStore);
Expand Down
1 change: 1 addition & 0 deletions client/src/components/ObjectStore/Instances/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { fetcher } from "@/api/schema/fetcher";
import type { UserConcreteObjectStore } from "./types";

export const create = fetcher.path("/api/object_store_instances").method("post").create();
export const test = fetcher.path("/api/object_store_instances/test").method("post").create();
export const update = fetcher.path("/api/object_store_instances/{user_object_store_id}").method("put").create();

export async function hide(instance: UserConcreteObjectStore) {
Expand Down
9 changes: 8 additions & 1 deletion lib/galaxy/managers/_config_templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
secrets_as_dict,
SecretsDict,
Template,
TemplateEnvironmentEntry,
TemplateEnvironmentSecret,
TemplateEnvironmentVariable,
TemplateVariableValueType,
Expand Down Expand Up @@ -102,8 +103,14 @@ def recover_secrets(
def prepare_environment(
configuration_template: HasConfigEnvironment, vault: Vault, app_config: UsesTemplatesAppConfig
) -> EnvironmentDict:
return prepare_environment_from_root(configuration_template.template_environment.root, vault, app_config)


def prepare_environment_from_root(
root: Optional[List[TemplateEnvironmentEntry]], vault: Vault, app_config: UsesTemplatesAppConfig
):
environment: EnvironmentDict = {}
for environment_entry in configuration_template.template_environment.root:
for environment_entry in root or []:
e_type = environment_entry.type
e_name = environment_entry.name
if e_type == "secret":
Expand Down
Loading

0 comments on commit 177afd4

Please sign in to comment.