From 2bfa72f48f302c2b1956539c75eac0e7a5a2e35b Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Sun, 7 Mar 2021 11:46:21 +0100 Subject: [PATCH] feat: mv_createjob macro for creating a viya job --- all.sas | 309 ++++++++++++++++++++++++++++++++++- viya/mv_createjob.sas | 307 ++++++++++++++++++++++++++++++++++ viya/mv_createwebservice.sas | 2 +- 3 files changed, 616 insertions(+), 2 deletions(-) create mode 100644 viya/mv_createjob.sas diff --git a/all.sas b/all.sas index 74591ad..106d858 100644 --- a/all.sas +++ b/all.sas @@ -10838,7 +10838,314 @@ options noquotelenmax; libname &libref1 clear; %end; %mend;/** - @file mv_createwebservice.sas + @file + @brief Creates a Viya Job + @details + Code is passed in as one or more filerefs. + + %* Step 1 - compile macros ; + filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas"; + %inc mc; + + %* Step 2 - Create some SAS code and add it to a job; + filename ft15f001 temp; + parmcards4; + data some_code; + set sashelp.class; + run; + ;;;; + %mv_createjob(path=/Public/app/sasjstemp/jobs/myjobs,name=myjob) + + The path to the job will then be shown in the log, eg as follows: + + ![viya job location](https://i.imgur.com/XRUDHgA.png) + + +

SAS Macros

+ @li mp_abort.sas + @li mv_createfolder.sas + @li mf_getuniquelibref.sas + @li mf_getuniquefileref.sas + @li mf_getplatform.sas + @li mf_isblank.sas + @li mv_deletejes.sas + + @param path= The full path (on SAS Drive) where the job will be created + @param name= The name of the job + @param desc= The description of the job + @param precode= Space separated list of filerefs, pointing to the code that + needs to be attached to the beginning of the job + @param code= Fileref(s) of the actual code to be added + @param access_token_var= The global macro variable to contain the access token + @param grant_type= valid values are "password" or "authorization_code" (unquoted). + The default is authorization_code. + @param replace= select NO to avoid replacing any existing job in that location + @param contextname= Choose a specific context on which to run the Job. Leave + blank to use the default context. From Viya 3.5 it is possible to configure + a shared context - see + https://go.documentation.sas.com/?docsetId=calcontexts&docsetTarget=n1hjn8eobk5pyhn1wg3ja0drdl6h.htm&docsetVersion=3.5&locale=en + + @version VIYA V.03.04 + @author [Allan Bowe](https://www.linkedin.com/in/allanbowe) + +**/ + +%macro mv_createjob(path= + ,name= + ,desc=Created by the mv_createjob.sas macro + ,precode= + ,code=ft15f001 + ,access_token_var=ACCESS_TOKEN + ,grant_type=sas_services + ,replace=YES + ,debug=0 + ,contextname= + ); +%local oauth_bearer; +%if &grant_type=detect %then %do; + %if %symexist(&access_token_var) %then %let grant_type=authorization_code; + %else %let grant_type=sas_services; +%end; +%if &grant_type=sas_services %then %do; + %let oauth_bearer=oauth_bearer=sas_services; + %let &access_token_var=; +%end; +%put &sysmacroname: grant_type=&grant_type; + +/* initial validation checking */ +%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password + and &grant_type ne sas_services + ) + ,mac=&sysmacroname + ,msg=%str(Invalid value for grant_type: &grant_type) +) +%mp_abort(iftrue=(%mf_isblank(&path)=1) + ,mac=&sysmacroname + ,msg=%str(path value must be provided) +) +%mp_abort(iftrue=(%length(&path)=1) + ,mac=&sysmacroname + ,msg=%str(path value must be provided) +) +%mp_abort(iftrue=(%mf_isblank(&name)=1) + ,mac=&sysmacroname + ,msg=%str(name value must be provided) +) + +options noquotelenmax; + +* remove any trailing slash ; +%if "%substr(&path,%length(&path),1)" = "/" %then + %let path=%substr(&path,1,%length(&path)-1); + +/* ensure folder exists */ +%put &sysmacroname: Path &path being checked / created; +%mv_createfolder(path=&path) + +%local base_uri; /* location of rest apis */ +%let base_uri=%mf_getplatform(VIYARESTAPI); + +/* fetching folder details for provided path */ +%local fname1; +%let fname1=%mf_getuniquefileref(); +proc http method='GET' out=&fname1 &oauth_bearer + url="&base_uri/folders/folders/@item?path=&path"; +%if &grant_type=authorization_code %then %do; + headers "Authorization"="Bearer &&&access_token_var"; +%end; +run; +%if &debug %then %do; + data _null_; + infile &fname1; + input; + putlog _infile_; + run; +%end; +%mp_abort(iftrue=(&SYS_PROCHTTP_STATUS_CODE ne 200) + ,mac=&sysmacroname + ,msg=%str(&SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE) +) + +/* path exists. Grab follow on link to check members */ +%local libref1; +%let libref1=%mf_getuniquelibref(); +libname &libref1 JSON fileref=&fname1; + +data _null_; + set &libref1..links; + if rel='members' then call symputx('membercheck',quote("&base_uri"!!trim(href)),'l'); + else if rel='self' then call symputx('parentFolderUri',href,'l'); +run; +data _null_; + set &libref1..root; + call symputx('folderid',id,'l'); +run; +%local fname2; +%let fname2=%mf_getuniquefileref(); +proc http method='GET' + out=&fname2 + &oauth_bearer + url=%unquote(%superq(membercheck)); + headers + %if &grant_type=authorization_code %then %do; + "Authorization"="Bearer &&&access_token_var" + %end; + 'Accept'='application/vnd.sas.collection+json' + 'Accept-Language'='string'; +%if &debug=1 %then %do; + debug level = 3; +%end; +run; +/*data _null_;infile &fname2;input;putlog _infile_;run;*/ +%mp_abort(iftrue=(&SYS_PROCHTTP_STATUS_CODE ne 200) + ,mac=&sysmacroname + ,msg=%str(&SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE) +) + +%if %upcase(&replace)=YES %then %do; + %mv_deletejes(path=&path, name=&name) +%end; +%else %do; + /* check that job does not already exist in that folder */ + %local libref2; + %let libref2=%mf_getuniquelibref(); + libname &libref2 JSON fileref=&fname2; + %local exists; %let exists=0; + data _null_; + set &libref2..items; + if contenttype='jobDefinition' and upcase(name)="%upcase(&name)" then + call symputx('exists',1,'l'); + run; + %mp_abort(iftrue=(&exists=1) + ,mac=&sysmacroname + ,msg=%str(Job &name already exists in &path) + ) + libname &libref2 clear; +%end; + +/* set up the body of the request to create the service */ +%local fname3; +%let fname3=%mf_getuniquefileref(); +data _null_; + file &fname3 TERMSTR=' '; + length string $32767; + string=cats('{"version": 0,"name":"' + ,"&name" + ,'","type":"Compute","parameters":[{"name":"_addjesbeginendmacros"' + ,',"type":"CHARACTER","defaultValue":"false"}'); + context=quote(cats(symget('contextname'))); + if context ne '""' then do; + string=cats(string,',{"version": 1,"name": "_contextName","defaultValue":' + ,context,',"type":"CHARACTER","label":"Context Name","required": false}'); + end; + string=cats(string,'],"code":"'); + put string; +run; + + +/* insert the code, escaping double quotes and carriage returns */ +%local x fref freflist; +%let freflist= &precode &code ; +%do x=1 %to %sysfunc(countw(&freflist)); + %let fref=%scan(&freflist,&x); + %put &sysmacroname: adding &fref; + data _null_; + length filein 8 fileid 8; + filein = fopen("&fref","I",1,"B"); + fileid = fopen("&fname3","A",1,"B"); + rec = "20"x; + do while(fread(filein)=0); + rc = fget(filein,rec,1); + if rec='"' then do; + rc =fput(fileid,'\');rc =fwrite(fileid); + rc =fput(fileid,'"');rc =fwrite(fileid); + end; + else if rec='0A'x then do; + rc =fput(fileid,'\');rc =fwrite(fileid); + rc =fput(fileid,'r');rc =fwrite(fileid); + end; + else if rec='0D'x then do; + rc =fput(fileid,'\');rc =fwrite(fileid); + rc =fput(fileid,'n');rc =fwrite(fileid); + end; + else if rec='09'x then do; + rc =fput(fileid,'\');rc =fwrite(fileid); + rc =fput(fileid,'t');rc =fwrite(fileid); + end; + else if rec='5C'x then do; + rc =fput(fileid,'\');rc =fwrite(fileid); + rc =fput(fileid,'\');rc =fwrite(fileid); + end; + else do; + rc =fput(fileid,rec); + rc =fwrite(fileid); + end; + end; + rc=fclose(filein); + rc=fclose(fileid); + run; +%end; + +/* finish off the body of the code file loaded to JES */ +data _null_; + file &fname3 mod TERMSTR=' '; + put '"}'; +run; + +/* now we can create the job!! */ +%local fname4; +%let fname4=%mf_getuniquefileref(); +proc http method='POST' + in=&fname3 + out=&fname4 + &oauth_bearer + url="&base_uri/jobDefinitions/definitions?parentFolderUri=&parentFolderUri"; + headers 'Content-Type'='application/vnd.sas.job.definition+json' + %if &grant_type=authorization_code %then %do; + "Authorization"="Bearer &&&access_token_var" + %end; + "Accept"="application/vnd.sas.job.definition+json"; +%if &debug=1 %then %do; + debug level = 3; +%end; +run; +/*data _null_;infile &fname4;input;putlog _infile_;run;*/ +%mp_abort(iftrue=(&SYS_PROCHTTP_STATUS_CODE ne 201) + ,mac=&sysmacroname + ,msg=%str(&SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE) +) +/* clear refs */ +filename &fname1 clear; +filename &fname2 clear; +filename &fname3 clear; +filename &fname4 clear; +libname &libref1 clear; + +/* get the url so we can give a helpful log message */ +%local url; +data _null_; + if symexist('_baseurl') then do; + url=symget('_baseurl'); + if subpad(url,length(url)-9,9)='SASStudio' + then url=substr(url,1,length(url)-11); + else url="&systcpiphostname"; + end; + else url="&systcpiphostname"; + call symputx('url',url); +run; + + +%put &sysmacroname: Job &name successfully created in &path; +%put &sysmacroname:; +%put &sysmacroname: Check it out here:; +%put &sysmacroname:;%put; +%put &url/SASJobExecution?_PROGRAM=&path/&name;%put; +%put &sysmacroname:; +%put &sysmacroname:; + +%mend; +/** + @file @brief Creates a JobExecution web service if it doesn't already exist @details Code is passed in as one or more filerefs. diff --git a/viya/mv_createjob.sas b/viya/mv_createjob.sas new file mode 100644 index 0000000..e3eba3a --- /dev/null +++ b/viya/mv_createjob.sas @@ -0,0 +1,307 @@ +/** + @file + @brief Creates a Viya Job + @details + Code is passed in as one or more filerefs. + + %* Step 1 - compile macros ; + filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas"; + %inc mc; + + %* Step 2 - Create some SAS code and add it to a job; + filename ft15f001 temp; + parmcards4; + data some_code; + set sashelp.class; + run; + ;;;; + %mv_createjob(path=/Public/app/sasjstemp/jobs/myjobs,name=myjob) + + The path to the job will then be shown in the log, eg as follows: + + ![viya job location](https://i.imgur.com/XRUDHgA.png) + + +

SAS Macros

+ @li mp_abort.sas + @li mv_createfolder.sas + @li mf_getuniquelibref.sas + @li mf_getuniquefileref.sas + @li mf_getplatform.sas + @li mf_isblank.sas + @li mv_deletejes.sas + + @param path= The full path (on SAS Drive) where the job will be created + @param name= The name of the job + @param desc= The description of the job + @param precode= Space separated list of filerefs, pointing to the code that + needs to be attached to the beginning of the job + @param code= Fileref(s) of the actual code to be added + @param access_token_var= The global macro variable to contain the access token + @param grant_type= valid values are "password" or "authorization_code" (unquoted). + The default is authorization_code. + @param replace= select NO to avoid replacing any existing job in that location + @param contextname= Choose a specific context on which to run the Job. Leave + blank to use the default context. From Viya 3.5 it is possible to configure + a shared context - see + https://go.documentation.sas.com/?docsetId=calcontexts&docsetTarget=n1hjn8eobk5pyhn1wg3ja0drdl6h.htm&docsetVersion=3.5&locale=en + + @version VIYA V.03.04 + @author [Allan Bowe](https://www.linkedin.com/in/allanbowe) + +**/ + +%macro mv_createjob(path= + ,name= + ,desc=Created by the mv_createjob.sas macro + ,precode= + ,code=ft15f001 + ,access_token_var=ACCESS_TOKEN + ,grant_type=sas_services + ,replace=YES + ,debug=0 + ,contextname= + ); +%local oauth_bearer; +%if &grant_type=detect %then %do; + %if %symexist(&access_token_var) %then %let grant_type=authorization_code; + %else %let grant_type=sas_services; +%end; +%if &grant_type=sas_services %then %do; + %let oauth_bearer=oauth_bearer=sas_services; + %let &access_token_var=; +%end; +%put &sysmacroname: grant_type=&grant_type; + +/* initial validation checking */ +%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password + and &grant_type ne sas_services + ) + ,mac=&sysmacroname + ,msg=%str(Invalid value for grant_type: &grant_type) +) +%mp_abort(iftrue=(%mf_isblank(&path)=1) + ,mac=&sysmacroname + ,msg=%str(path value must be provided) +) +%mp_abort(iftrue=(%length(&path)=1) + ,mac=&sysmacroname + ,msg=%str(path value must be provided) +) +%mp_abort(iftrue=(%mf_isblank(&name)=1) + ,mac=&sysmacroname + ,msg=%str(name value must be provided) +) + +options noquotelenmax; + +* remove any trailing slash ; +%if "%substr(&path,%length(&path),1)" = "/" %then + %let path=%substr(&path,1,%length(&path)-1); + +/* ensure folder exists */ +%put &sysmacroname: Path &path being checked / created; +%mv_createfolder(path=&path) + +%local base_uri; /* location of rest apis */ +%let base_uri=%mf_getplatform(VIYARESTAPI); + +/* fetching folder details for provided path */ +%local fname1; +%let fname1=%mf_getuniquefileref(); +proc http method='GET' out=&fname1 &oauth_bearer + url="&base_uri/folders/folders/@item?path=&path"; +%if &grant_type=authorization_code %then %do; + headers "Authorization"="Bearer &&&access_token_var"; +%end; +run; +%if &debug %then %do; + data _null_; + infile &fname1; + input; + putlog _infile_; + run; +%end; +%mp_abort(iftrue=(&SYS_PROCHTTP_STATUS_CODE ne 200) + ,mac=&sysmacroname + ,msg=%str(&SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE) +) + +/* path exists. Grab follow on link to check members */ +%local libref1; +%let libref1=%mf_getuniquelibref(); +libname &libref1 JSON fileref=&fname1; + +data _null_; + set &libref1..links; + if rel='members' then call symputx('membercheck',quote("&base_uri"!!trim(href)),'l'); + else if rel='self' then call symputx('parentFolderUri',href,'l'); +run; +data _null_; + set &libref1..root; + call symputx('folderid',id,'l'); +run; +%local fname2; +%let fname2=%mf_getuniquefileref(); +proc http method='GET' + out=&fname2 + &oauth_bearer + url=%unquote(%superq(membercheck)); + headers + %if &grant_type=authorization_code %then %do; + "Authorization"="Bearer &&&access_token_var" + %end; + 'Accept'='application/vnd.sas.collection+json' + 'Accept-Language'='string'; +%if &debug=1 %then %do; + debug level = 3; +%end; +run; +/*data _null_;infile &fname2;input;putlog _infile_;run;*/ +%mp_abort(iftrue=(&SYS_PROCHTTP_STATUS_CODE ne 200) + ,mac=&sysmacroname + ,msg=%str(&SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE) +) + +%if %upcase(&replace)=YES %then %do; + %mv_deletejes(path=&path, name=&name) +%end; +%else %do; + /* check that job does not already exist in that folder */ + %local libref2; + %let libref2=%mf_getuniquelibref(); + libname &libref2 JSON fileref=&fname2; + %local exists; %let exists=0; + data _null_; + set &libref2..items; + if contenttype='jobDefinition' and upcase(name)="%upcase(&name)" then + call symputx('exists',1,'l'); + run; + %mp_abort(iftrue=(&exists=1) + ,mac=&sysmacroname + ,msg=%str(Job &name already exists in &path) + ) + libname &libref2 clear; +%end; + +/* set up the body of the request to create the service */ +%local fname3; +%let fname3=%mf_getuniquefileref(); +data _null_; + file &fname3 TERMSTR=' '; + length string $32767; + string=cats('{"version": 0,"name":"' + ,"&name" + ,'","type":"Compute","parameters":[{"name":"_addjesbeginendmacros"' + ,',"type":"CHARACTER","defaultValue":"false"}'); + context=quote(cats(symget('contextname'))); + if context ne '""' then do; + string=cats(string,',{"version": 1,"name": "_contextName","defaultValue":' + ,context,',"type":"CHARACTER","label":"Context Name","required": false}'); + end; + string=cats(string,'],"code":"'); + put string; +run; + + +/* insert the code, escaping double quotes and carriage returns */ +%local x fref freflist; +%let freflist= &precode &code ; +%do x=1 %to %sysfunc(countw(&freflist)); + %let fref=%scan(&freflist,&x); + %put &sysmacroname: adding &fref; + data _null_; + length filein 8 fileid 8; + filein = fopen("&fref","I",1,"B"); + fileid = fopen("&fname3","A",1,"B"); + rec = "20"x; + do while(fread(filein)=0); + rc = fget(filein,rec,1); + if rec='"' then do; + rc =fput(fileid,'\');rc =fwrite(fileid); + rc =fput(fileid,'"');rc =fwrite(fileid); + end; + else if rec='0A'x then do; + rc =fput(fileid,'\');rc =fwrite(fileid); + rc =fput(fileid,'r');rc =fwrite(fileid); + end; + else if rec='0D'x then do; + rc =fput(fileid,'\');rc =fwrite(fileid); + rc =fput(fileid,'n');rc =fwrite(fileid); + end; + else if rec='09'x then do; + rc =fput(fileid,'\');rc =fwrite(fileid); + rc =fput(fileid,'t');rc =fwrite(fileid); + end; + else if rec='5C'x then do; + rc =fput(fileid,'\');rc =fwrite(fileid); + rc =fput(fileid,'\');rc =fwrite(fileid); + end; + else do; + rc =fput(fileid,rec); + rc =fwrite(fileid); + end; + end; + rc=fclose(filein); + rc=fclose(fileid); + run; +%end; + +/* finish off the body of the code file loaded to JES */ +data _null_; + file &fname3 mod TERMSTR=' '; + put '"}'; +run; + +/* now we can create the job!! */ +%local fname4; +%let fname4=%mf_getuniquefileref(); +proc http method='POST' + in=&fname3 + out=&fname4 + &oauth_bearer + url="&base_uri/jobDefinitions/definitions?parentFolderUri=&parentFolderUri"; + headers 'Content-Type'='application/vnd.sas.job.definition+json' + %if &grant_type=authorization_code %then %do; + "Authorization"="Bearer &&&access_token_var" + %end; + "Accept"="application/vnd.sas.job.definition+json"; +%if &debug=1 %then %do; + debug level = 3; +%end; +run; +/*data _null_;infile &fname4;input;putlog _infile_;run;*/ +%mp_abort(iftrue=(&SYS_PROCHTTP_STATUS_CODE ne 201) + ,mac=&sysmacroname + ,msg=%str(&SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE) +) +/* clear refs */ +filename &fname1 clear; +filename &fname2 clear; +filename &fname3 clear; +filename &fname4 clear; +libname &libref1 clear; + +/* get the url so we can give a helpful log message */ +%local url; +data _null_; + if symexist('_baseurl') then do; + url=symget('_baseurl'); + if subpad(url,length(url)-9,9)='SASStudio' + then url=substr(url,1,length(url)-11); + else url="&systcpiphostname"; + end; + else url="&systcpiphostname"; + call symputx('url',url); +run; + + +%put &sysmacroname: Job &name successfully created in &path; +%put &sysmacroname:; +%put &sysmacroname: Check it out here:; +%put &sysmacroname:;%put; +%put &url/SASJobExecution?_PROGRAM=&path/&name;%put; +%put &sysmacroname:; +%put &sysmacroname:; + +%mend; diff --git a/viya/mv_createwebservice.sas b/viya/mv_createwebservice.sas index bc4f0e9..3494d36 100644 --- a/viya/mv_createwebservice.sas +++ b/viya/mv_createwebservice.sas @@ -1,5 +1,5 @@ /** - @file mv_createwebservice.sas + @file @brief Creates a JobExecution web service if it doesn't already exist @details Code is passed in as one or more filerefs.