diff --git a/backend/windmill-common/src/variables.rs b/backend/windmill-common/src/variables.rs index d103c685ed..9a760c3b60 100644 --- a/backend/windmill-common/src/variables.rs +++ b/backend/windmill-common/src/variables.rs @@ -53,7 +53,24 @@ pub fn get_reserved_variables( flow_id: Option, flow_path: Option, schedule_path: Option, -) -> [ContextualVariable; 11] { +) -> [ContextualVariable; 12] { + let state_path = { + let flow_path = flow_path + .clone() + .unwrap_or_else(|| "NO_FLOW_PATH".to_string()); + let script_path = path.clone().unwrap_or_else(|| "NO_JOB_PATH".to_string()); + let schedule_path = schedule_path + .clone() + .map(|x| format!("/{x}")) + .unwrap_or_else(String::new); + + let script_path = if script_path.ends_with("/") { + "NO_NAME".to_string() + } else { + script_path + }; + format!("{permissioned_as}/{flow_path}/{script_path}{schedule_path}") + }; [ ContextualVariable { name: "WM_WORKSPACE".to_string(), @@ -114,5 +131,10 @@ pub fn get_reserved_variables( value: permissioned_as.to_string(), description: "Fully Qualified (u/g) owner name of executor of the job".to_string(), }, + ContextualVariable { + name: "WM_STATE_PATH".to_string(), + value: state_path, + description: "State resource path unique to a script and its trigger".to_string(), + }, ] } diff --git a/deno-client/mod.ts b/deno-client/mod.ts index 6ec5591bb6..beaee90e69 100644 --- a/deno-client/mod.ts +++ b/deno-client/mod.ts @@ -54,18 +54,11 @@ export async function getResource(path?: string, undefinedIfEmpty?: boolean): Pr } export function getStatePath(): string { - const env_flow_path = Deno.env.get("WM_FLOW_PATH") - const env_job_path = Deno.env.get("WM_JOB_PATH") - const permissioned_as = Deno.env.get("WM_PERMISSIONED_AS") - const flow_path = env_flow_path != undefined && env_flow_path != "" ? env_flow_path : 'NO_FLOW_PATH' - const script_path = env_job_path != undefined && env_job_path != "" ? env_job_path : 'NO_JOB_PATH' - const env_schedule_path = Deno.env.get("WM_SCHEDULE_PATH") - const schedule_path = env_schedule_path != undefined && env_schedule_path != "" ? `/${env_schedule_path}` : '' - - if (script_path.slice(script_path.length - 1) === '/') { - throw Error(`The script path must not end with '/', give a name to your script!`) + const state_path = Deno.env.get("WM_STATE_PATH") + if (state_path === undefined) { + throw Error("State path not set") } - return `${permissioned_as}/${flow_path}/${script_path}${schedule_path}` + return state_path } /** diff --git a/go-client/windmill.go b/go-client/windmill.go index 758d71a41e..a2cfa28c82 100644 --- a/go-client/windmill.go +++ b/go-client/windmill.go @@ -2,6 +2,7 @@ package windmill import ( "context" + "errors" "fmt" "net/http" "os" @@ -9,10 +10,6 @@ import ( api "github.com/windmill-labs/windmill-go-client/api" ) -func hello_world() { - fmt.Println("Windmill") -} - type ClientWithWorkspace struct { Client *api.ClientWithResponses Workspace string @@ -47,6 +44,9 @@ func GetVariable(path string) (string, error) { res, err := client.Client.GetVariableWithResponse(context.Background(), client.Workspace, path, &api.GetVariableParams{ DecryptSecret: newBool(true), }) + if res.StatusCode()/100 != 2 { + return "", errors.New(string(res.Body)) + } if err != nil { return "", err } @@ -59,8 +59,68 @@ func GetResource(path string) (interface{}, error) { return nil, err } res, err := client.Client.GetResourceWithResponse(context.Background(), client.Workspace, path) + if res.StatusCode()/100 != 2 { + return nil, errors.New(string(res.Body)) + } if err != nil { return nil, err } return *res.JSON200.Value, nil } + +func SetResource(path string, value interface{}) error { + client, err := GetClient() + if err != nil { + return err + } + res, err := client.Client.UpdateResourceWithResponse(context.Background(), client.Workspace, path, api.EditResource{Value: &value}) + if err != nil { + return err + } + if res.StatusCode()/100 != 2 { + return errors.New(string(res.Body)) + } + return nil +} + +func SetVariable(path string, value string) error { + client, err := GetClient() + if err != nil { + return err + } + res, err := client.Client.UpdateVariableWithResponse(context.Background(), client.Workspace, path, api.EditVariable{Value: &value}) + if err != nil { + return err + } + if res.StatusCode()/100 != 2 { + return errors.New(string(res.Body)) + } + return nil +} + +func GetStatePath() string { + return os.Getenv("WM_STATE_PATH") +} + +func GetState() (interface{}, error) { + return GetResource(GetStatePath()) +} + +func SetState(state interface{}) error { + err := SetResource(GetStatePath(), state) + if err != nil { + client, err := GetClient() + if err != nil { + return err + } + res, err := client.Client.CreateResourceWithResponse(context.Background(), client.Workspace, api.CreateResource{Value: &state}) + if err != nil { + return err + } + if res.StatusCode()/100 != 2 { + return errors.New(string(res.Body)) + } + return nil + } + return nil +} diff --git a/python-client/wmill/wmill/client.py b/python-client/wmill/wmill/client.py index 474e6edf04..249ceed5a2 100644 --- a/python-client/wmill/wmill/client.py +++ b/python-client/wmill/wmill/client.py @@ -53,7 +53,9 @@ class JobStatus(Enum): _client: "AuthenticatedClient | None" = None -def create_client(base_url: "str | None" = None, token: "str | None" = None) -> AuthenticatedClient: +def create_client( + base_url: "str | None" = None, token: "str | None" = None +) -> AuthenticatedClient: env_base_url = os.environ.get("BASE_INTERNAL_URL") if env_base_url is not None: @@ -63,7 +65,9 @@ def create_client(base_url: "str | None" = None, token: "str | None" = None) -> token_: str = token or os.environ.get("WM_TOKEN") or "" global _client if _client is None: - _client = AuthenticatedClient(base_url=base_url_, token=token_, timeout=30, verify_ssl=False) + _client = AuthenticatedClient( + base_url=base_url_, token=token_, timeout=30, verify_ssl=False + ) return _client @@ -78,7 +82,9 @@ def get_version() -> str: """ Returns the current version of the backend """ - return backend_version.sync_detailed(client=create_client()).content.decode("us-ascii") + return backend_version.sync_detailed(client=create_client()).content.decode( + "us-ascii" + ) def run_script_async( @@ -99,7 +105,9 @@ def run_script_async( ).content.decode("us-ascii") -def run_script_sync(hash: str, args: Dict[str, Any] = {}, verbose: bool = False) -> Dict[str, Any]: +def run_script_sync( + hash: str, args: Dict[str, Any] = {}, verbose: bool = False +) -> Dict[str, Any]: """ Run a script, wait for it to complete and return the result of the launched script """ @@ -120,7 +128,9 @@ def get_job_status(job_id: str) -> JobStatus: """ Returns the status of a queued or completed job """ - res = get_job.sync_detailed(client=create_client(), workspace=get_workspace(), id=job_id).parsed + res = get_job.sync_detailed( + client=create_client(), workspace=get_workspace(), id=job_id + ).parsed if not res: raise Exception(f"Job {job_id} not found") elif not res.type: @@ -140,7 +150,9 @@ def get_result(job_id: str) -> Dict[str, Any]: """ Returns the result of a completed job """ - res = get_completed_job.sync_detailed(client=create_client(), workspace=get_workspace(), id=job_id).parsed + res = get_completed_job.sync_detailed( + client=create_client(), workspace=get_workspace(), id=job_id + ).parsed if not res: raise Exception(f"Job {job_id} not found") if not res.result: @@ -154,12 +166,16 @@ def get_resource(path: str | None = None, none_if_undefined: bool = False) -> An Returns the resource at a given path """ path = path or get_state_path() - parsed = get_resource_api.sync_detailed(workspace=get_workspace(), path=path, client=create_client()).parsed + parsed = get_resource_api.sync_detailed( + workspace=get_workspace(), path=path, client=create_client() + ).parsed if parsed is None: if none_if_undefined: return None else: - raise Exception(f"Resource at path {path} does not exist or you do not have read permissions on it") + raise Exception( + f"Resource at path {path} does not exist or you do not have read permissions on it" + ) if isinstance(parsed.value, Unset): return None @@ -175,18 +191,24 @@ def get_state() -> Any: return get_resource(None, True) -def set_resource(value: Any, path: str | None = None, resource_type: str = "state") -> None: +def set_resource( + value: Any, path: str | None = None, resource_type: str = "state" +) -> None: """ Set the resource at a given path as a string, creating it if it does not exist """ path = path or get_state_path() workspace = get_workspace() client = create_client() - if not exists_resource.sync_detailed(workspace=workspace, path=path, client=client).parsed: + if not exists_resource.sync_detailed( + workspace=workspace, path=path, client=client + ).parsed: create_resource.sync_detailed( workspace=workspace, client=client, - json_body=CreateResourceJsonBody(path=path, value=value, resource_type=resource_type), + json_body=CreateResourceJsonBody( + path=path, value=value, resource_type=resource_type + ), ) else: update_resource.sync_detailed( @@ -208,9 +230,13 @@ def get_variable(path: str) -> str: """ Returns the variable at a given path as a string """ - res = get_variable_api.sync_detailed(workspace=get_workspace(), path=path, client=create_client()).parsed + res = get_variable_api.sync_detailed( + workspace=get_workspace(), path=path, client=create_client() + ).parsed if res is None: - raise Exception(f"Variable at path {path} does not exist or you do not have read permissions on it") + raise Exception( + f"Variable at path {path} does not exist or you do not have read permissions on it" + ) return res.value # type: ignore @@ -221,11 +247,15 @@ def set_variable(path: str, value: str) -> None: """ workspace = get_workspace() client = create_client() - if not exists_variable.sync_detailed(workspace=workspace, path=path, client=client).parsed: + if not exists_variable.sync_detailed( + workspace=workspace, path=path, client=client + ).parsed: create_variable.sync_detailed( workspace=workspace, client=client, - json_body=CreateVariableJsonBody(path=path, value=value, is_secret=False, description=""), + json_body=CreateVariableJsonBody( + path=path, value=value, is_secret=False, description="" + ), ) else: update_variable.sync_detailed( @@ -237,19 +267,10 @@ def set_variable(path: str, value: str) -> None: def get_state_path() -> str: - """ - Get a stable path for next execution of the script to point to the same resource - """ - permissioned_as = os.environ.get("WM_PERMISSIONED_AS") - flow_path = os.environ.get("WM_FLOW_PATH") or "NO_FLOW_PATH" - script_path = os.environ.get("WM_JOB_PATH") or "NO_JOB_PATH" - env_schedule_path = os.environ.get("WM_SCHEDULE_PATH") - schedule_path = "" if env_schedule_path is None or env_schedule_path == "" else f"/{env_schedule_path}" - - if script_path.endswith("/"): - raise Exception("The script path must not end with '/', give a name to your script!") - - return f"{permissioned_as}/{flow_path}/{script_path}{schedule_path}" + state_path = os.environ.get("WM_STATE_PATH") + if state_path is None: + raise Exception("State path not found") + return state_path def _transform_leaves(d: Dict[str, Any]) -> Dict[str, Any]: