1
0
mirror of https://github.com/sasjs/core.git synced 2025-12-22 02:31:19 +00:00

Compare commits

..

20 Commits

Author SHA1 Message Date
Allan Bowe
0749ea0819 Merge pull request #179 from sasjs/173-disable-dependence-on-io-(gsub)-package
New `mp_replace()` macro to find & replace in text files
2022-03-03 15:19:04 +02:00
munja
e09a39e748 fix: tests 2022-03-03 13:18:11 +00:00
Allan Bowe
20dcefaefd Merge pull request #180 from sasjs/all-contributors/add-yabwon
docs: add yabwon as a contributor for code
2022-03-03 15:17:16 +02:00
allcontributors[bot]
4c8347516a docs: update .all-contributorsrc [skip ci] 2022-03-03 13:16:32 +00:00
allcontributors[bot]
e497d226a0 docs: update README.md [skip ci] 2022-03-03 13:16:31 +00:00
munja
ccf8f1acc0 fix: adding test and updating documentation 2022-03-03 12:46:00 +00:00
munja
fe9a2ed979 feat: mp_replace macro, credit Bartosz 2022-03-03 10:30:07 +00:00
munja
078815e83e chore: stub 2022-03-02 21:33:41 +00:00
Allan Bowe
bb80c7af5a chore: adding funding.yml 2022-03-01 19:24:27 +00:00
Allan Bowe
842662aae1 Merge pull request #172 from sasjs/serverupdates
fix: updating mp_streamfile for sasjs/server compatibility
2022-02-28 23:21:48 +02:00
munja
876fac2332 feat: several macros for working with the sasjs/server apis 2022-02-28 21:17:38 +00:00
Allan Bowe
427deca350 Merge pull request #178 from sasjs/allanbowe/enable-consul-token-as-177
feat: adding consul_token option as parameter in mv_registerclient.
2022-02-24 23:20:03 +02:00
Allan Bowe
07bde4b25c feat: adding consul_token option as parameter in mv_registerclient. Closes #177 2022-02-24 21:16:23 +00:00
Allan Bowe
80b06af581 Merge pull request #176 from sasjs/streamserver
feat: adding SASjs server support to mp_streamfile.sas
2022-02-23 19:02:37 +02:00
Allan Bowe
3c026811e9 feat: adding SASjs server support to mp_streamfile.sas 2022-02-23 17:01:50 +00:00
Allan Bowe
cf547ce7e4 Merge pull request #175 from sasjs/authbranch
fix: tidyup of mm_getauthinfo.sas
2022-02-23 11:43:33 +02:00
Allan Bowe
6952c79899 fix: tidyup of mm_getauthinfo.sas 2022-02-23 09:42:47 +00:00
Allan Bowe
09e3f63da7 Merge pull request #174 from sasjs/stpabortissue
fix: missing dependency in mm_createstp
2022-02-22 13:47:05 +02:00
Allan Bowe
d6956f4122 fix: missing dependency in mm_createstp 2022-02-22 11:46:31 +00:00
munja
badf5b5761 fix: updating mp_streamfile for sasjs/server compatibility 2022-02-18 22:22:52 +00:00
22 changed files with 1175 additions and 233 deletions

View File

@@ -117,6 +117,15 @@
"contributions": [ "contributions": [
"bug" "bug"
] ]
},
{
"login": "yabwon",
"name": "Bart Jablonski",
"avatar_url": "https://avatars.githubusercontent.com/u/9314894?v=4",
"profile": "https://github.com/yabwon",
"contributions": [
"code"
]
} }
], ],
"contributorsPerLine": 7, "contributorsPerLine": 7,

3
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,3 @@
# These are supported funding model platforms
github: [sasjs]

View File

@@ -47,7 +47,7 @@ Documentation: https://core.sasjs.io
This library will not be used for storing data entries (such as formats or datalines). Where this becomes necessary in the future, a new repo will be created, in order to keep the NPM bundle size down (for the benefit of those looking to embed purely macros in their applications). This library will not be used for storing data entries (such as formats or datalines). Where this becomes necessary in the future, a new repo will be created, in order to keep the NPM bundle size down (for the benefit of those looking to embed purely macros in their applications).
#### FCMP library (All Platforms) ### FCMP library (All Platforms)
- Function and macro names are identical, except for special cases - Function and macro names are identical, except for special cases
- Prefixes: _mcf_ - Prefixes: _mcf_
@@ -217,10 +217,9 @@ If you find this library useful, please leave a [star](https://github.com/sasjs/
![](https://starchart.cc/sasjs/core.svg) ![](https://starchart.cc/sasjs/core.svg)
## Contributors ✨ ## Contributors ✨
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section --> <!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-10-orange.svg?style=flat-square)](#contributors-) [![All Contributors](https://img.shields.io/badge/all_contributors-11-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END --> <!-- ALL-CONTRIBUTORS-BADGE:END -->
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
@@ -241,6 +240,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<td align="center"><a href="https://github.com/kkchandok"><img src="https://avatars.githubusercontent.com/u/46090627?v=4?s=100" width="100px;" alt=""/><br /><sub><b>kkchandok</b></sub></a><br /><a href="#ideas-kkchandok" title="Ideas, Planning, & Feedback">🤔</a></td> <td align="center"><a href="https://github.com/kkchandok"><img src="https://avatars.githubusercontent.com/u/46090627?v=4?s=100" width="100px;" alt=""/><br /><sub><b>kkchandok</b></sub></a><br /><a href="#ideas-kkchandok" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="https://github.com/VladislavParhomchik"><img src="https://avatars.githubusercontent.com/u/83717836?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Vladislav Parhomchik</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=VladislavParhomchik" title="Tests">⚠️</a> <a href="https://github.com/sasjs/core/pulls?q=is%3Apr+reviewed-by%3AVladislavParhomchik" title="Reviewed Pull Requests">👀</a></td> <td align="center"><a href="https://github.com/VladislavParhomchik"><img src="https://avatars.githubusercontent.com/u/83717836?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Vladislav Parhomchik</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=VladislavParhomchik" title="Tests">⚠️</a> <a href="https://github.com/sasjs/core/pulls?q=is%3Apr+reviewed-by%3AVladislavParhomchik" title="Reviewed Pull Requests">👀</a></td>
<td align="center"><a href="https://github.com/vznesh"><img src="https://avatars.githubusercontent.com/u/28916792?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Vignesh T.</b></sub></a><br /><a href="https://github.com/sasjs/core/issues?q=author%3Avznesh" title="Bug reports">🐛</a></td> <td align="center"><a href="https://github.com/vznesh"><img src="https://avatars.githubusercontent.com/u/28916792?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Vignesh T.</b></sub></a><br /><a href="https://github.com/sasjs/core/issues?q=author%3Avznesh" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/yabwon"><img src="https://avatars.githubusercontent.com/u/9314894?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Bart Jablonski</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=yabwon" title="Code">💻</a></td>
</tr> </tr>
</table> </table>

599
all.sas
View File

@@ -3116,7 +3116,8 @@ run;
outds=work.test_results outds=work.test_results
)/*/STORE SOURCE*/; )/*/STORE SOURCE*/;
%local ds test_result test_comments del add mod ilist; %local ds test_result test_comments del add mod ilist;
%let ilist=%upcase(&sasjs_prefix._FUNCTIONS &ignorelist); %let ilist=%upcase(&sasjs_prefix._FUNCTIONS SYS_PROCHTTP_STATUS_CODE
SYS_PROCHTTP_STATUS_CODE SYS_PROCHTTP_STATUS_PHRASE &ignorelist);
/** /**
* this sets up the global vars, it will also enter STRICT mode. If this * this sets up the global vars, it will also enter STRICT mode. If this
@@ -3131,7 +3132,7 @@ run;
create table &scopeds as create table &scopeds as
select name,offset,value select name,offset,value
from dictionary.macros from dictionary.macros
where scope="&scope" and name not in (%mf_getquotedstr(&ilist)) where scope="&scope" and upcase(name) not in (%mf_getquotedstr(&ilist))
order by name,offset; order by name,offset;
%end; %end;
%else %if &action=COMPARE %then %do; %else %if &action=COMPARE %then %do;
@@ -3183,7 +3184,8 @@ run;
drop table &ds; drop table &ds;
%end; %end;
%mend mp_assertscope;/** %mend mp_assertscope;
/**
@file @file
@brief Convert a file to/from base64 format @brief Convert a file to/from base64 format
@details Creates a new version of a file either encoded or decoded using @details Creates a new version of a file either encoded or decoded using
@@ -8872,7 +8874,8 @@ options ibufsize=&ibufsize;
%mend mp_loadformat;/** %mend mp_loadformat;/**
@file @file
@brief Mechanism for locking tables to prevent parallel modifications @brief Mechanism for locking tables to prevent parallel modifications
@details Uses a control table to enable ANY table to be locked for updates. @details Uses a control table to enable ANY table to be locked for updates
(not just SAS datasets).
Only useful if every update uses the macro! Used heavily within Only useful if every update uses the macro! Used heavily within
[Data Controller for SAS](https://datacontroller.io). [Data Controller for SAS](https://datacontroller.io).
@@ -8886,7 +8889,7 @@ options ibufsize=&ibufsize;
length is 200 characters. length is 200 characters.
@param [out] ctl_ds= (0) The control table which controls the actual locking. @param [out] ctl_ds= (0) The control table which controls the actual locking.
Should already be assigned and available. The definition is available by Should already be assigned and available. The definition is available by
running mp_coretable.sas as follows: `%mp_coretable(LOCKTABLE)`. running the mddl_dc_locktable.sas macro.
@param [in] loops= (25) Number of times to check for a lock. @param [in] loops= (25) Number of times to check for a lock.
@param [in] loop_secs= (1) Seconds to wait between each lock attempt @param [in] loop_secs= (1) Seconds to wait between each lock attempt
@@ -9594,6 +9597,155 @@ insert into &outds select distinct * from &append_ds;
%end; %end;
%mend mp_recursivejoin; %mend mp_recursivejoin;
/**
@file
@brief Performs a text substitution on a file
@details Performs a find and replace on a file, either in place or to a new
file. Can be used on files where lines are longer than 32767.
Works by reading in the file byte by byte, then marking the beginning and end
of each matched string, before finally doing the replace.
Full credit for this highly efficient and syntactically satisfying SAS logic
goes to [Bartosz Jabłoński](https://www.linkedin.com/in/yabwon), founder of
the [SAS Packages](https://github.com/yabwon/SAS_PACKAGES) framework.
Usage:
%let file="%sysfunc(pathname(work))/file.txt";
%let str=replace/me;
%let rep=with/this;
data _null_;
file &file;
put 'blahblah';
put "blahblah&str.blah";
put 'blahblahblah';
run;
%mp_replace(&file, findvar=str, replacevar=rep)
data _null_;
infile &file;
input;
list;
run;
Note - if you are running a version of SAS that will allow the io package in
LUA, you can also use this macro: mp_gsubfile.sas
@param infile The QUOTED path to the file on which to perform the substitution
@param findvar= Macro variable NAME containing the string to search for
@param replacevar= Macro variable NAME containing the replacement string
@param outfile= (0) Optional QUOTED path to an the adjusted output file (to
avoid overwriting the first file).
<h4> SAS Macros </h4>
@li mf_getuniquefileref.sas
@li mf_getuniquename.sas
<h4> Related Macros </h4>
@li mp_gsubfile.test.sas
@version 9.4
@author Bartosz Jabłoński
@author Allan Bowe
**/
%macro mp_replace(infile,
findvar=,
replacevar=,
outfile=0
)/*/STORE SOURCE*/;
%local inref dttm ds1;
%let inref=%mf_getuniquefileref();
%let outref=%mf_getuniquefileref();
%if &outfile=0 %then %let outfile=&infile;
%let ds1=%mf_getuniquename(prefix=allchars);
%let ds2=%mf_getuniquename(prefix=startmark);
/* START */
%let dttm=%sysfunc(datetime());
filename &inref &infile lrecl=1 recfm=n;
data &ds1;
infile &inref;
input sourcechar $ 1. @@;
format sourcechar hex2.;
run;
data &ds2;
/* set find string to length in bytes to cover trailing spaces */
length string $ %length(%superq(&findvar));
string =symget("&findvar");
drop string;
firstchar=char(string,1);
findlen=lengthm(string); /* <- for trailing bytes */
do _N_=1 to nobs;
set &ds1 nobs=nobs point=_N_;
if sourcechar=firstchar then do;
pos=1;
s=0;
do point=_N_ to min(_N_ + findlen -1,nobs);
set &ds1 point=point;
if sourcechar=char(string, pos) then s + 1;
else goto _leave_;
pos+1;
end;
_leave_:
if s=findlen then do;
START =_N_;
_N_ =_N_+ s - 1;
STOP =_N_;
output;
end;
end;
end;
stop;
keep START STOP;
run;
data &ds1;
declare hash HS(dataset:"&ds2(keep=start)");
HS.defineKey("start");
HS.defineDone();
declare hash HE(dataset:"&ds2(keep=stop)");
HE.defineKey("stop");
HE.defineDone();
do until(eof);
set &ds1 end=eof curobs =n;
start = ^HS.check(key:n);
stop = ^HE.check(key:n);
length strt $ 1;
strt =put(start,best. -L);
retain out 1;
if out then output;
if start then out=0;
if stop then out=1;
end;
stop;
keep sourcechar strt;
run;
filename &outref &outfile recfm=n;
data _null_;
length replace $ %length(%superq(&replacevar));
replace=symget("&replacevar");
file &outref;
do until(eof);
set &ds1 end=eof;
if strt ="1" then put replace char.;
else put sourcechar char1.;
end;
stop;
run;
/* END */
%put &sysmacroname took %sysevalf(%sysfunc(datetime())-&dttm) seconds to run;
%mend mp_replace;
/** /**
@file @file
@brief Reset when an err condition occurs @brief Reset when an err condition occurs
@@ -11261,7 +11413,7 @@ create table &outds as
%mp_streamfile(contenttype=csv,inloc=/some/where.txt,outname=myfile.txt) %mp_streamfile(contenttype=csv,inloc=/some/where.txt,outname=myfile.txt)
@param [in] contenttype= (TEXTS) Either TEXT, ZIP, CSV, EXCEL @param [in] contenttype= (TEXT) Either TEXT, ZIP, CSV, EXCEL
@param [in] inloc= /path/to/file.ext to be sent @param [in] inloc= /path/to/file.ext to be sent
@param [in] inref= fileref of file to be sent (if provided, overrides `inloc`) @param [in] inref= fileref of file to be sent (if provided, overrides `inloc`)
@param [in] iftrue= (1=1) Provide a condition under which to execute. @param [in] iftrue= (1=1) Provide a condition under which to execute.
@@ -11271,6 +11423,7 @@ create table &outds as
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li mf_getplatform.sas @li mf_getplatform.sas
@li mfs_httpheader.sas
@li mp_binarycopy.sas @li mp_binarycopy.sas
@author Allan Bowe @author Allan Bowe
@@ -11304,7 +11457,7 @@ data _null_;
run; run;
%if &contentype=CSV %then %do; %if &contentype=CSV %then %do;
%if (&platform=SASMETA and &streamweb=1) or &platform=SASJS %then %do; %if (&platform=SASMETA and &streamweb=1) %then %do;
data _null_; data _null_;
rc=stpsrv_header('Content-type','application/csv'); rc=stpsrv_header('Content-type','application/csv');
rc=stpsrv_header('Content-disposition',"attachment; filename=&outname"); rc=stpsrv_header('Content-disposition',"attachment; filename=&outname");
@@ -11315,10 +11468,14 @@ run;
contenttype='application/csv' contenttype='application/csv'
contentdisp="attachment; filename=&outname"; contentdisp="attachment; filename=&outname";
%end; %end;
%else %if &platform=SASJS %then %do;
%mfs_httpheader(Content-type,application/csv)
%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))
%end;
%end; %end;
%else %if &contentype=EXCEL %then %do; %else %if &contentype=EXCEL %then %do;
/* suitable for XLS format */ /* suitable for XLS format */
%if (&platform=SASMETA and &streamweb=1) or &platform=SASJS %then %do; %if (&platform=SASMETA and &streamweb=1) %then %do;
data _null_; data _null_;
rc=stpsrv_header('Content-type','application/vnd.ms-excel'); rc=stpsrv_header('Content-type','application/vnd.ms-excel');
rc=stpsrv_header('Content-disposition',"attachment; filename=&outname"); rc=stpsrv_header('Content-disposition',"attachment; filename=&outname");
@@ -11329,9 +11486,13 @@ run;
contenttype='application/vnd.ms-excel' contenttype='application/vnd.ms-excel'
contentdisp="attachment; filename=&outname"; contentdisp="attachment; filename=&outname";
%end; %end;
%else %if &platform=SASJS %then %do;
%mfs_httpheader(Content-type,application/vnd.ms-excel)
%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))
%end;
%end; %end;
%else %if &contentype=GIF or &contentype=JPEG or &contentype=PNG %then %do; %else %if &contentype=GIF or &contentype=JPEG or &contentype=PNG %then %do;
%if (&platform=SASMETA and &streamweb=1) or &platform=SASJS %then %do; %if (&platform=SASMETA and &streamweb=1) %then %do;
data _null_; data _null_;
rc=stpsrv_header('Content-type',"image/%lowcase(&contenttype)"); rc=stpsrv_header('Content-type',"image/%lowcase(&contenttype)");
run; run;
@@ -11340,15 +11501,21 @@ run;
filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" filename &outref filesrvc parenturi="&SYS_JES_JOB_URI"
contenttype="image/%lowcase(&contenttype)"; contenttype="image/%lowcase(&contenttype)";
%end; %end;
%else %if &platform=SASJS %then %do;
%mfs_httpheader(Content-type,image/%lowcase(&contenttype))
%end;
%end; %end;
%else %if &contentype=HTML %then %do; %else %if &contentype=HTML %then %do;
%if &platform=SASVIYA %then %do; %if &platform=SASVIYA %then %do;
filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name="_webout.json" filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name="_webout.json"
contenttype="text/html"; contenttype="text/html";
%end; %end;
%else %if &platform=SASJS %then %do;
%mfs_httpheader(Content-type,text/html)
%end;
%end; %end;
%else %if &contentype=TEXT %then %do; %else %if &contentype=TEXT %then %do;
%if (&platform=SASMETA and &streamweb=1) or &platform=SASJS %then %do; %if (&platform=SASMETA and &streamweb=1) %then %do;
data _null_; data _null_;
rc=stpsrv_header('Content-type','application/text'); rc=stpsrv_header('Content-type','application/text');
rc=stpsrv_header('Content-disposition',"attachment; filename=&outname"); rc=stpsrv_header('Content-disposition',"attachment; filename=&outname");
@@ -11359,9 +11526,13 @@ run;
contenttype='application/text' contenttype='application/text'
contentdisp="attachment; filename=&outname"; contentdisp="attachment; filename=&outname";
%end; %end;
%else %if &platform=SASJS %then %do;
%mfs_httpheader(Content-type,application/text)
%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))
%end;
%end; %end;
%else %if &contentype=WOFF or &contentype=WOFF2 or &contentype=TTF %then %do; %else %if &contentype=WOFF or &contentype=WOFF2 or &contentype=TTF %then %do;
%if (&platform=SASMETA and &streamweb=1) or &platform=SASJS %then %do; %if (&platform=SASMETA and &streamweb=1) %then %do;
data _null_; data _null_;
rc=stpsrv_header('Content-type',"font/%lowcase(&contenttype)"); rc=stpsrv_header('Content-type',"font/%lowcase(&contenttype)");
run; run;
@@ -11370,9 +11541,12 @@ run;
filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" filename &outref filesrvc parenturi="&SYS_JES_JOB_URI"
contenttype="font/%lowcase(&contenttype)"; contenttype="font/%lowcase(&contenttype)";
%end; %end;
%else %if &platform=SASJS %then %do;
%mfs_httpheader(Content-type,font/%lowcase(&contenttype))
%end;
%end; %end;
%else %if &contentype=XLSX %then %do; %else %if &contentype=XLSX %then %do;
%if (&platform=SASMETA and &streamweb=1) or &platform=SASJS %then %do; %if (&platform=SASMETA and &streamweb=1) %then %do;
data _null_; data _null_;
rc=stpsrv_header('Content-type', rc=stpsrv_header('Content-type',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
@@ -11385,9 +11559,15 @@ run;
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
contentdisp="attachment; filename=&outname"; contentdisp="attachment; filename=&outname";
%end; %end;
%else %if &platform=SASJS %then %do;
%mfs_httpheader(Content-type
,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
)
%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))
%end;
%end; %end;
%else %if &contentype=ZIP %then %do; %else %if &contentype=ZIP %then %do;
%if (&platform=SASMETA and &streamweb=1) or &platform=SASJS %then %do; %if (&platform=SASMETA and &streamweb=1) %then %do;
data _null_; data _null_;
rc=stpsrv_header('Content-type','application/zip'); rc=stpsrv_header('Content-type','application/zip');
rc=stpsrv_header('Content-disposition',"attachment; filename=&outname"); rc=stpsrv_header('Content-disposition',"attachment; filename=&outname");
@@ -11398,6 +11578,10 @@ run;
contenttype='application/zip' contenttype='application/zip'
contentdisp="attachment; filename=&outname"; contentdisp="attachment; filename=&outname";
%end; %end;
%else %if &platform=SASJS %then %do;
%mfs_httpheader(Content-type,application/zip)
%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))
%end;
%end; %end;
%else %do; %else %do;
%put %str(ERR)OR: Content Type &contenttype NOT SUPPORTED by &sysmacroname!; %put %str(ERR)OR: Content Type &contenttype NOT SUPPORTED by &sysmacroname!;
@@ -13134,6 +13318,12 @@ run;
/* now try and assign it */ /* now try and assign it */
if libname("&libref",,'meta',cats('liburi="',liburi,'";')) ne 0 then do; if libname("&libref",,'meta',cats('liburi="',liburi,'";')) ne 0 then do;
putlog "&libref could not be assigned"; putlog "&libref could not be assigned";
putlog liburi=;
/**
* Fetch the system message for display in the abort modal. This is
* not always helpful though. One example, previously received:
* NOTE: Libref XX refers to the same library metadata as libref XX.
*/
call symputx('msg',sysmsg(),'l'); call symputx('msg',sysmsg(),'l');
if "&mabort"='HARD' then call symputx('mp_abort',1,'l'); if "&mabort"='HARD' then call symputx('mp_abort',1,'l');
end; end;
@@ -13155,7 +13345,7 @@ run;
%if &mp_abort=1 %then %do; %if &mp_abort=1 %then %do;
%mp_abort(iftrue= (&mp_abort=1) %mp_abort(iftrue= (&mp_abort=1)
,mac=&sysmacroname ,mac=mm_assignlib.sas
,msg=&msg ,msg=&msg
) )
%return; %return;
@@ -13550,13 +13740,14 @@ run;
The macro is idempotent - if you run it twice, it will only create a folder The macro is idempotent - if you run it twice, it will only create a folder
once. once.
usage: Usage:
%mm_createfolder(path=/some/meta/folder) %mm_createfolder(path=/some/meta/folder)
@param [in] path= Name of the folder to create. @param [in] path= Name of the folder to create.
@param [in] mdebug= set DBG to 1 to disable DEBUG messages @param [in] mdebug= set DBG to 1 to disable DEBUG messages
@version 9.4 @version 9.4
@author Allan Bowe @author Allan Bowe
@@ -14032,7 +14223,7 @@ filename &frefout temp;
This macro is idempotent - if you run it twice, it will only create an STP This macro is idempotent - if you run it twice, it will only create an STP
once. once.
usage (type 1 STP): Usage (type 1 STP):
%mm_createstp(stpname=MyNewSTP %mm_createstp(stpname=MyNewSTP
,filename=mySpecialProgram.sas ,filename=mySpecialProgram.sas
@@ -14051,7 +14242,8 @@ filename &frefout temp;
putlog (_all_)(=); putlog (_all_)(=);
run; run;
usage (type 2 STP): Usage (type 2 STP):
%mm_createstp(stpname=MyNewType2STP %mm_createstp(stpname=MyNewType2STP
,filename=mySpecialProgram.sas ,filename=mySpecialProgram.sas
,directory=SASEnvironment/SASCode/STPs ,directory=SASEnvironment/SASCode/STPs
@@ -14094,8 +14286,9 @@ filename &frefout temp;
@li mf_verifymacvars.sas @li mf_verifymacvars.sas
@li mm_getdirectories.sas @li mm_getdirectories.sas
@li mm_updatestpsourcecode.sas @li mm_updatestpsourcecode.sas
@li mp_dropmembers.sas
@li mm_getservercontexts.sas @li mm_getservercontexts.sas
@li mp_abort.sas
@li mp_dropmembers.sas
<h4> Related Macros </h4> <h4> Related Macros </h4>
@li mm_createwebservice.sas @li mm_createwebservice.sas
@@ -15174,17 +15367,22 @@ run;
%mend mm_deletestp; %mend mm_deletestp;
/** /**
@file mm_getauthinfo.sas @file mm_getauthinfo.sas
@brief extracts authentication info @brief Extracts authentication info for each user in metadata
@details usage: @details
Usage:
%mm_getauthinfo(outds=auths) %mm_getauthinfo(outds=auths)
@param outds= the ONE LEVEL work dataset to create
@param [in] mdebug= (0) Set to 1 to enable DEBUG messages and preserve outputs
@param [out] outds= (mm_getauthinfo) The output dataset to create
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li mm_getobjects.sas
@li mf_getuniquefileref.sas @li mf_getuniquefileref.sas
@li mf_getuniquename.sas
@li mm_getdetails.sas @li mm_getdetails.sas
@li mm_getobjects.sas
@version 9.4 @version 9.4
@author Allan Bowe @author Allan Bowe
@@ -15192,67 +15390,69 @@ run;
**/ **/
%macro mm_getauthinfo(outds=mm_getauthinfo %macro mm_getauthinfo(outds=mm_getauthinfo
,mdebug=0
)/*/STORE SOURCE*/; )/*/STORE SOURCE*/;
%local prefix fileref;
%let prefix=%substr(%mf_getuniquename(),1,25);
%if %length(&outds)>30 %then %do; %mm_getobjects(type=Login,outds=&prefix.0)
%put %str(ERR)OR: Temp tables are created with the &outds prefix, which
therefore needs to be 30 characters or less;
%return;
%end;
%if %index(&outds,'.')>0 %then %do;
%put %str(ERR)OR: Table &outds should be ONE LEVEL (no library);
%return;
%end;
%mm_getobjects(type=Login,outds=&outds.0)
%local fileref; %local fileref;
%let fileref=%mf_getuniquefileref(); %let fileref=%mf_getuniquefileref();
data _null_; data _null_;
file &fileref; file &fileref;
set &outds.0 end=last; set &prefix.0 end=last;
/* run macro */ /* run macro */
str=cats('%mm_getdetails(uri=',id,",outattrs=&outds.d",_n_ str=cats('%mm_getdetails(uri=',id,",outattrs=&prefix.d",_n_
,",outassocs=&outds.a",_n_,")"); ,",outassocs=&prefix.a",_n_,")");
put str; put str;
/* transpose attributes */ /* transpose attributes */
str=cats("proc transpose data=&outds.d",_n_,"(drop=type) out=&outds.da" str=cats("proc transpose data=&prefix.d",_n_,"(drop=type) out=&prefix.da"
,_n_,"(drop=_name_);var value;id name;run;"); ,_n_,"(drop=_name_);var value;id name;run;");
put str; put str;
/* add extra info to attributes */ /* add extra info to attributes */
str=cats("data &outds.da",_n_,";length login_id login_name $256; login_id=" str=cats("data &prefix.da",_n_,";length login_id login_name $256; login_id="
,quote(trim(id)),";set &outds.da",_n_ ,quote(trim(id)),";set &prefix.da",_n_
,";login_name=trim(subpad(name,1,256));drop name;run;"); ,";login_name=trim(subpad(name,1,256));drop name;run;");
put str; put str;
/* add extra info to associations */ /* add extra info to associations */
str=cats("data &outds.a",_n_,";length login_id login_name $256; login_id=" str=cats("data &prefix.a",_n_,";length login_id login_name $256; login_id="
,quote(trim(id)),";login_name=",quote(trim(name)) ,quote(trim(id)),";login_name=",quote(trim(name))
,";set &outds.a",_n_,";run;"); ,";set &prefix.a",_n_,";run;");
put str; put str;
if last then do; if last then do;
/* collate attributes */ /* collate attributes */
str=cats("data &outds._logat; set &outds.da1-&outds.da",_n_,";run;"); str=cats("data &prefix._logat; set &prefix.da1-&prefix.da",_n_,";run;");
put str; put str;
/* collate associations */ /* collate associations */
str=cats("data &outds._logas; set &outds.a1-&outds.a",_n_,";run;"); str=cats("data &prefix._logas; set &prefix.a1-&prefix.a",_n_,";run;");
put str; put str;
/* tidy up */ /* tidy up */
str=cats("proc delete data=&outds.da1-&outds.da",_n_,";run;"); str=cats("proc delete data=&prefix.da1-&prefix.da",_n_,";run;");
put str; put str;
str=cats("proc delete data=&outds.d1-&outds.d",_n_,";run;"); str=cats("proc delete data=&prefix.d1-&prefix.d",_n_,";run;");
put str; put str;
str=cats("proc delete data=&outds.a1-&outds.a",_n_,";run;"); str=cats("proc delete data=&prefix.a1-&prefix.a",_n_,";run;");
put str; put str;
end; end;
run; run;
%if &mdebug=1 %then %do;
data _null_;
infile &fileref;
if _n_=1 then putlog // "Now executing the following code:" //;
input; putlog _infile_;
run;
%end;
%inc &fileref; %inc &fileref;
filename &fileref clear;
/* get libraries */ /* get libraries */
proc sort data=&outds._logas(where=(assoc='Libraries')) out=&outds._temp; proc sort data=&prefix._logas(where=(assoc='Libraries')) out=&prefix._temp;
by login_id; by login_id;
data &outds._temp; data &prefix._temp;
set &outds._temp; set &prefix._temp;
by login_id; by login_id;
length library_list $32767; length library_list $32767;
retain library_list; retain library_list;
@@ -15260,32 +15460,28 @@ data &outds._temp;
else library_list=catx(' !! ',library_list,name); else library_list=catx(' !! ',library_list,name);
proc sql; proc sql;
/* get auth domain */ /* get auth domain */
create table &outds._dom as create table &prefix._dom as
select login_id,name as domain select login_id,name as domain
from &outds._logas from &prefix._logas
where assoc='Domain'; where assoc='Domain';
create unique index login_id on &outds._dom(login_id); create unique index login_id on &prefix._dom(login_id);
/* join it all together */ /* join it all together */
create table &outds._logins as create table &outds as
select a.* select a.*
,c.domain ,c.domain
,b.library_list ,b.library_list
from &outds._logat (drop=ishidden lockedby usageversion publictype) a from &prefix._logat (drop=ishidden lockedby usageversion publictype) a
left join &outds._temp b left join &prefix._temp b
on a.login_id=b.login_id on a.login_id=b.login_id
left join &outds._dom c left join &prefix._dom c
on a.login_id=c.login_id; on a.login_id=c.login_id;
drop table &outds._temp;
drop table &outds._logat;
drop table &outds._logas;
data _null_; %if &mdebug=0 %then %do;
infile &fileref; proc datasets lib=work;
if _n_=1 then putlog // "Now executing the following code:" //; delete &prefix:;
input; putlog _infile_; run;
run; %end;
filename &fileref clear;
%mend mm_getauthinfo;/** %mend mm_getauthinfo;/**
@file @file
@@ -18551,6 +18747,171 @@ run;
%mend mfs_httpheader; %mend mfs_httpheader;
/** /**
@file
@brief Creates a file on SASjs Drive
@details Creates a file on SASjs Drive. To use the file as a Stored Program,
it must have a ".sas" extension.
Example:
filename stpcode temp;
data _null_;
file stpcode;
put '%put hello world;';
run;
%ms_createfile(/some/stored/program.sas, inref=stpcode)
@param [in] driveloc The full path to the file in SASjs Drive
@param [in] inref= (0) The fileref containing the file to create.
@param [in] mdebug= (0) Set to 1 to enable DEBUG messages
<h4> SAS Macros </h4>
@li mf_getuniquefileref.sas
@li mf_getuniquename.sas
@li mp_abort.sas
**/
%macro ms_createfile(driveloc
,inref=0
,mdebug=0
);
%local fname0 fname1 boundary fname statcd msg;
%let fname0=%mf_getuniquefileref();
%let fname1=%mf_getuniquefileref();
%let boundary=%mf_getuniquename();
data _null_;
file &fname0 termstr=crlf;
infile &inref end=eof;
if _n_ = 1 then do;
put "--&boundary.";
put 'Content-Disposition: form-data; name="filePath"';
put ;
put "&driveloc";
put "--&boundary";
put 'Content-Disposition: form-data; name="file"; filename="ignore.sas"';
put "Content-Type: text/plain";
put ;
end;
input;
put _infile_; /* add the actual file to be sent */
if eof then do;
put ;
put "--&boundary--";
end;
run;
%if &mdebug=1 %then %do;
data _null_;
infile &fname0;
input;
put _infile_;
run;
%end;
proc http method='POST' in=&fname0 out=&fname1
url="&_sasjs_apiserverurl/SASjsApi/drive/file";
headers "Content-Type"="multipart/form-data; boundary=&boundary";
%if &mdebug=1 %then %do;
debug level=1;
%end;
run;
%let statcd=0;
data _null_;
infile &fname1;
input;
putlog _infile_;
if _infile_='{"status":"success"}' then call symputx('statcd',1,'l');
else call symputx('msg',_infile_,'l');
run;
%mp_abort(
iftrue=(&statcd=0)
,mac=ms_createfile.sas
,msg=%superq(msg)
)
%mend ms_createfile;
/**
@file
@brief Executes a SASjs Server Stored Program
@details Runs a Stored Program (using POST method) and extracts the webout and
log from the response JSON.
Example:
%ms_runstp(/some/stored/program
,debug=131
,outref=weboot
)
@param [in] pgm The full path to the Stored Program in SASjs Drive (_program
parameter)
@param [in] debug= (131) The value to supply to the _debug URL parameter
@param [in] mdebug= (0) Set to 1 to enable DEBUG messages
@param [out] outref= (outweb) The output fileref to contain the response JSON
(will be created using temp engine)
<h4> SAS Macros </h4>
@li mf_getuniquefileref.sas
@li mp_abort.sas
**/
%macro ms_runstp(pgm
,debug=131
,outref=outweb
,mdebug=0
);
%local dbg fname1;
%if &mdebug=1 %then %do;
%put &sysmacroname entry vars:;
%put _local_;
%end;
%else %let dbg=*;
%let fname1=%mf_getuniquefileref();
%mp_abort(iftrue=("&pgm"="")
,mac=&sysmacroname
,msg=%str(Program not provided)
)
data _null_;
file &fname1;
infile "&_sasjs_tokenfile";
input;
put 'Authorization: Bearer' _infile_;
run;
filename &outref temp;
/* prepare request*/
proc http method='POST' headerin=&fname1 out=&outref
url="&_sasjs_apiserverurl.&_sasjs_apipath?_program=&pgm%str(&)_debug=131";
run;
%if (&SYS_PROCHTTP_STATUS_CODE ne 200 and &SYS_PROCHTTP_STATUS_CODE ne 201)
or &mdebug=1 %then %do;
data _null_;infile &outref;input;putlog _infile_;run;
%end;
%mp_abort(
iftrue=(&SYS_PROCHTTP_STATUS_CODE ne 200 and &SYS_PROCHTTP_STATUS_CODE ne 201)
,mac=&sysmacroname
,msg=%str(&SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE)
)
%if &mdebug=1 %then %do;
%put &sysmacroname exit vars:;
%put _local_;
%end;
%else %do;
/* clear refs */
filename &fname1 clear;
%end;
%mend ms_runstp;/**
@file @file
@brief Send data to/from @sasjs/server @brief Send data to/from @sasjs/server
@details This macro should be added to the start of each web service, @details This macro should be added to the start of each web service,
@@ -23098,10 +23459,10 @@ run;
%mend mv_jobwaitfor;/** %mend mv_jobwaitfor;/**
@file mv_registerclient.sas @file mv_registerclient.sas
@brief Register Client and Secret (admin task) @brief Register Client and Secret (admin task)
@details When building apps on SAS Viya, an client id and secret are sometimes @details When building apps on SAS Viya, a client id and secret are usually
required. In order to generate them, filesystem access to the Consul Token required. In order to generate them, the Consul Token is required. To access
is needed (it is not enough to be in the SASAdministrator group in SAS this token, you need to be a system administrator (it is not enough to be in
Environment Manager). the SASAdministrator group in SAS Environment Manager).
If you are registering a lot of clients / secrets, you may find it more If you are registering a lot of clients / secrets, you may find it more
convenient to use the [Viya Token Generator] convenient to use the [Viya Token Generator]
@@ -23122,62 +23483,69 @@ run;
"https://raw.githubusercontent.com/sasjs/core/main/all.sas"; "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
%inc mc; %inc mc;
%* generate random client using consul token as input parameter;
%mv_registerclient(consul_token=12x34sa43v2345n234lasd)
%* generate random client details with all scopes;
%mv_registerclient(scopes=openid *)
%* specific client with just openid scope; %* specific client with just openid scope;
%mv_registerclient(client_id=YourClient %mv_registerclient(client_id=YourClient
,client_secret=YourSecret ,client_secret=YourSecret
,scopes=openid ,scopes=openid
) )
%* generate random client details with all scopes;
%mv_registerclient(scopes=openid *)
%* generate random client with 90/180 second access/refresh token expiry; %* generate random client with 90/180 second access/refresh token expiry;
%mv_registerclient(scopes=openid * %mv_registerclient(scopes=openid *
,access_token_validity=90 ,access_token_validity=90
,refresh_token_validity=180 ,refresh_token_validity=180
) )
@param client_id= The client name. Auto generated if blank. @param [in,out] client_id= The client name. Auto generated if blank.
@param client_secret= Client secret. Auto generated if client is blank. @param [in,out] client_secret= Client secret. Auto generated if client is
@param scopes=(openid) List of space-seperated unquoted scopes blank.
@param grant_type=(authorization_code|refresh_token) Valid values are @param [in] consul_token= (0) Provide the actual consul token value here if
"password" or "authorization_code" (unquoted) using Viya 4 or above.
@param outds=(mv_registerclient) The dataset to contain the registered client @param [in] scopes= (openid) List of space-seperated unquoted scopes
id and secret @param [in] grant_type= (authorization_code|refresh_token) Valid values are
@param access_token_validity=(DEFAULT) The duration of validity of the access "password" or "authorization_code" (unquoted). Pipe seperated.
token in seconds. A value of DEFAULT will omit the entry (and use system @param [out] outds=(mv_registerclient) The dataset to contain the registered
default) client id and secret
@param refresh_token_validity=(DEFAULT) The duration of validity of the @param [in] access_token_validity= (DEFAULT) The access token duration in
seconds. A value of DEFAULT will omit the entry (and use system default)
@param [in] refresh_token_validity= (DEFAULT) The duration of validity of the
refresh token in seconds. A value of DEFAULT will omit the entry (and use refresh token in seconds. A value of DEFAULT will omit the entry (and use
system default) system default)
@param name= An optional, human readable name for the client @param [in] client_name= (DEFAULT) An optional, human readable name for the
@param required_user_groups= A list of group names. If a user does not belong client.
to all the required groups, the user will not be authenticated and no tokens @param [in] required_user_groups= A list of group names. If a user does not
are issued to this client for that user. If this field is not specified, belong to all the required groups, the user will not be authenticated and no
authentication and token issuance proceeds normally. tokens are issued to this client for that user. If this field is not
@param autoapprove= During the auth step the user can choose which scope to specified, authentication and token issuance proceeds normally.
apply. Setting this to true will autoapprove all the client scopes. @param [in] autoapprove= During the auth step the user can choose which scope
@param use_session= If true, access tokens issued to this client will be to apply. Setting this to true will autoapprove all the client scopes.
@param [in] use_session= If true, access tokens issued to this client will be
associated with an HTTP session and revoked upon logout or time-out. associated with an HTTP session and revoked upon logout or time-out.
@param outjson= (_null_) A dataset containing the lines of JSON submitted. @param [out] outjson= (_null_) A dataset containing the lines of JSON
Useful for debugging. submitted. Useful for debugging.
@version VIYA V.03.04 @version VIYA V.03.04
@author Allan Bowe, source: https://github.com/sasjs/core @author Allan Bowe, source: https://github.com/sasjs/core
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li mp_abort.sas
@li mf_getplatform.sas @li mf_getplatform.sas
@li mf_getuniquefileref.sas @li mf_getuniquefileref.sas
@li mf_getuniquelibref.sas @li mf_getuniquelibref.sas
@li mf_loc.sas @li mf_loc.sas
@li mf_getquotedstr.sas @li mf_getquotedstr.sas
@li mf_getuser.sas @li mf_getuser.sas
@li mp_abort.sas
**/ **/
%macro mv_registerclient(client_id= %macro mv_registerclient(client_id=
,client_secret= ,client_secret=
,consul_token=0
,client_name=DEFAULT ,client_name=DEFAULT
,scopes=openid ,scopes=openid
,grant_type=authorization_code|refresh_token ,grant_type=authorization_code|refresh_token
@@ -23189,33 +23557,41 @@ run;
,refresh_token_validity=DEFAULT ,refresh_token_validity=DEFAULT
,outjson=_null_ ,outjson=_null_
); );
%local consul_token fname1 fname2 fname3 libref access_token url tokloc; %local fname1 fname2 fname3 libref access_token url tokloc msg;
%if client_name=DEFAULT %then %let client_name= %if client_name=DEFAULT %then %let client_name=
Generated by %mf_getuser() on %sysfunc(datetime(),datetime19.) using SASjs; Generated by %mf_getuser() (&sysuserid) on %sysfunc(datetime(),datetime19.
) using SASjs;
options noquotelenmax; options noquotelenmax;
/* first, get consul token needed to get client id / secret */
%let tokloc=/etc/SASSecurityCertificateFramework/tokens/consul/default;
%let tokloc=%mf_loc(VIYACONFIG)&tokloc/client.token;
%if "&consul_token"="0" %then %do;
/* first, get consul token needed to get client id / secret */
%let tokloc=/etc/SASSecurityCertificateFramework/tokens/consul/default;
%let tokloc=%mf_loc(VIYACONFIG)&tokloc/client.token;
%mp_abort(iftrue=(%sysfunc(fileexist(&tokloc))=0) %if %sysfunc(fileexist(&tokloc))=0 %then %do;
,mac=&sysmacroname %let msg=Unable to access the consul token at &tokloc;
,msg=%str(Unable to access the consul token at &tokloc) %put &sysmacroname: &msg;
) %put Try passing the value in the consul= macro parameter;
%put See docs: https://core.sasjs.io/mv__registerclient_8sas.html;
%mp_abort(mac=mv_registerclient,msg=%str(&msg))
%end;
%let consul_token=0; data _null_;
data _null_;
infile "&tokloc"; infile "&tokloc";
input token:$64.; input token:$64.;
call symputx('consul_token',token); call symputx('consul_token',token);
run; run;
%mp_abort(iftrue=("&consul_token"="0") %if "&consul_token"="0" %then %do;
,mac=&sysmacroname %put &sysmacroname: Unable to source the consul token from &tokloc;
,msg=%str(Unable to source the consul token from &tokloc) %put It seems your account (&sysuserid) does not have admin rights;
) %put Please speak with your platform adminstrator;
%put Docs: https://core.sasjs.io/mv__registerclient_8sas.html;
%abort;
%end;
%end;
%local base_uri; /* location of rest apis */ %local base_uri; /* location of rest apis */
%let base_uri=%mf_getplatform(VIYARESTAPI); %let base_uri=%mf_getplatform(VIYARESTAPI);
@@ -23228,6 +23604,9 @@ proc http method='POST' out=&fname1
headers "X-Consul-Token"="&consul_token"; headers "X-Consul-Token"="&consul_token";
run; run;
%put &=SYS_PROCHTTP_STATUS_CODE;
%put &=SYS_PROCHTTP_STATUS_PHRASE;
%let libref=%mf_getuniquelibref(); %let libref=%mf_getuniquelibref();
libname &libref JSON fileref=&fname1; libname &libref JSON fileref=&fname1;

View File

@@ -74,7 +74,8 @@
outds=work.test_results outds=work.test_results
)/*/STORE SOURCE*/; )/*/STORE SOURCE*/;
%local ds test_result test_comments del add mod ilist; %local ds test_result test_comments del add mod ilist;
%let ilist=%upcase(&sasjs_prefix._FUNCTIONS &ignorelist); %let ilist=%upcase(&sasjs_prefix._FUNCTIONS SYS_PROCHTTP_STATUS_CODE
SYS_PROCHTTP_STATUS_CODE SYS_PROCHTTP_STATUS_PHRASE &ignorelist);
/** /**
* this sets up the global vars, it will also enter STRICT mode. If this * this sets up the global vars, it will also enter STRICT mode. If this
@@ -89,7 +90,7 @@
create table &scopeds as create table &scopeds as
select name,offset,value select name,offset,value
from dictionary.macros from dictionary.macros
where scope="&scope" and name not in (%mf_getquotedstr(&ilist)) where scope="&scope" and upcase(name) not in (%mf_getquotedstr(&ilist))
order by name,offset; order by name,offset;
%end; %end;
%else %if &action=COMPARE %then %do; %else %if &action=COMPARE %then %do;

View File

@@ -1,7 +1,8 @@
/** /**
@file @file
@brief Mechanism for locking tables to prevent parallel modifications @brief Mechanism for locking tables to prevent parallel modifications
@details Uses a control table to enable ANY table to be locked for updates. @details Uses a control table to enable ANY table to be locked for updates
(not just SAS datasets).
Only useful if every update uses the macro! Used heavily within Only useful if every update uses the macro! Used heavily within
[Data Controller for SAS](https://datacontroller.io). [Data Controller for SAS](https://datacontroller.io).
@@ -15,7 +16,7 @@
length is 200 characters. length is 200 characters.
@param [out] ctl_ds= (0) The control table which controls the actual locking. @param [out] ctl_ds= (0) The control table which controls the actual locking.
Should already be assigned and available. The definition is available by Should already be assigned and available. The definition is available by
running mp_coretable.sas as follows: `%mp_coretable(LOCKTABLE)`. running the mddl_dc_locktable.sas macro.
@param [in] loops= (25) Number of times to check for a lock. @param [in] loops= (25) Number of times to check for a lock.
@param [in] loop_secs= (1) Seconds to wait between each lock attempt @param [in] loop_secs= (1) Seconds to wait between each lock attempt

149
base/mp_replace.sas Normal file
View File

@@ -0,0 +1,149 @@
/**
@file
@brief Performs a text substitution on a file
@details Performs a find and replace on a file, either in place or to a new
file. Can be used on files where lines are longer than 32767.
Works by reading in the file byte by byte, then marking the beginning and end
of each matched string, before finally doing the replace.
Full credit for this highly efficient and syntactically satisfying SAS logic
goes to [Bartosz Jabłoński](https://www.linkedin.com/in/yabwon), founder of
the [SAS Packages](https://github.com/yabwon/SAS_PACKAGES) framework.
Usage:
%let file="%sysfunc(pathname(work))/file.txt";
%let str=replace/me;
%let rep=with/this;
data _null_;
file &file;
put 'blahblah';
put "blahblah&str.blah";
put 'blahblahblah';
run;
%mp_replace(&file, findvar=str, replacevar=rep)
data _null_;
infile &file;
input;
list;
run;
Note - if you are running a version of SAS that will allow the io package in
LUA, you can also use this macro: mp_gsubfile.sas
@param infile The QUOTED path to the file on which to perform the substitution
@param findvar= Macro variable NAME containing the string to search for
@param replacevar= Macro variable NAME containing the replacement string
@param outfile= (0) Optional QUOTED path to an the adjusted output file (to
avoid overwriting the first file).
<h4> SAS Macros </h4>
@li mf_getuniquefileref.sas
@li mf_getuniquename.sas
<h4> Related Macros </h4>
@li mp_gsubfile.test.sas
@version 9.4
@author Bartosz Jabłoński
@author Allan Bowe
**/
%macro mp_replace(infile,
findvar=,
replacevar=,
outfile=0
)/*/STORE SOURCE*/;
%local inref dttm ds1;
%let inref=%mf_getuniquefileref();
%let outref=%mf_getuniquefileref();
%if &outfile=0 %then %let outfile=&infile;
%let ds1=%mf_getuniquename(prefix=allchars);
%let ds2=%mf_getuniquename(prefix=startmark);
/* START */
%let dttm=%sysfunc(datetime());
filename &inref &infile lrecl=1 recfm=n;
data &ds1;
infile &inref;
input sourcechar $ 1. @@;
format sourcechar hex2.;
run;
data &ds2;
/* set find string to length in bytes to cover trailing spaces */
length string $ %length(%superq(&findvar));
string =symget("&findvar");
drop string;
firstchar=char(string,1);
findlen=lengthm(string); /* <- for trailing bytes */
do _N_=1 to nobs;
set &ds1 nobs=nobs point=_N_;
if sourcechar=firstchar then do;
pos=1;
s=0;
do point=_N_ to min(_N_ + findlen -1,nobs);
set &ds1 point=point;
if sourcechar=char(string, pos) then s + 1;
else goto _leave_;
pos+1;
end;
_leave_:
if s=findlen then do;
START =_N_;
_N_ =_N_+ s - 1;
STOP =_N_;
output;
end;
end;
end;
stop;
keep START STOP;
run;
data &ds1;
declare hash HS(dataset:"&ds2(keep=start)");
HS.defineKey("start");
HS.defineDone();
declare hash HE(dataset:"&ds2(keep=stop)");
HE.defineKey("stop");
HE.defineDone();
do until(eof);
set &ds1 end=eof curobs =n;
start = ^HS.check(key:n);
stop = ^HE.check(key:n);
length strt $ 1;
strt =put(start,best. -L);
retain out 1;
if out then output;
if start then out=0;
if stop then out=1;
end;
stop;
keep sourcechar strt;
run;
filename &outref &outfile recfm=n;
data _null_;
length replace $ %length(%superq(&replacevar));
replace=symget("&replacevar");
file &outref;
do until(eof);
set &ds1 end=eof;
if strt ="1" then put replace char.;
else put sourcechar char1.;
end;
stop;
run;
/* END */
%put &sysmacroname took %sysevalf(%sysfunc(datetime())-&dttm) seconds to run;
%mend mp_replace;

View File

@@ -12,7 +12,7 @@
%mp_streamfile(contenttype=csv,inloc=/some/where.txt,outname=myfile.txt) %mp_streamfile(contenttype=csv,inloc=/some/where.txt,outname=myfile.txt)
@param [in] contenttype= (TEXTS) Either TEXT, ZIP, CSV, EXCEL @param [in] contenttype= (TEXT) Either TEXT, ZIP, CSV, EXCEL
@param [in] inloc= /path/to/file.ext to be sent @param [in] inloc= /path/to/file.ext to be sent
@param [in] inref= fileref of file to be sent (if provided, overrides `inloc`) @param [in] inref= fileref of file to be sent (if provided, overrides `inloc`)
@param [in] iftrue= (1=1) Provide a condition under which to execute. @param [in] iftrue= (1=1) Provide a condition under which to execute.
@@ -22,6 +22,7 @@
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li mf_getplatform.sas @li mf_getplatform.sas
@li mfs_httpheader.sas
@li mp_binarycopy.sas @li mp_binarycopy.sas
@author Allan Bowe @author Allan Bowe
@@ -55,7 +56,7 @@ data _null_;
run; run;
%if &contentype=CSV %then %do; %if &contentype=CSV %then %do;
%if (&platform=SASMETA and &streamweb=1) or &platform=SASJS %then %do; %if (&platform=SASMETA and &streamweb=1) %then %do;
data _null_; data _null_;
rc=stpsrv_header('Content-type','application/csv'); rc=stpsrv_header('Content-type','application/csv');
rc=stpsrv_header('Content-disposition',"attachment; filename=&outname"); rc=stpsrv_header('Content-disposition',"attachment; filename=&outname");
@@ -66,10 +67,14 @@ run;
contenttype='application/csv' contenttype='application/csv'
contentdisp="attachment; filename=&outname"; contentdisp="attachment; filename=&outname";
%end; %end;
%else %if &platform=SASJS %then %do;
%mfs_httpheader(Content-type,application/csv)
%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))
%end;
%end; %end;
%else %if &contentype=EXCEL %then %do; %else %if &contentype=EXCEL %then %do;
/* suitable for XLS format */ /* suitable for XLS format */
%if (&platform=SASMETA and &streamweb=1) or &platform=SASJS %then %do; %if (&platform=SASMETA and &streamweb=1) %then %do;
data _null_; data _null_;
rc=stpsrv_header('Content-type','application/vnd.ms-excel'); rc=stpsrv_header('Content-type','application/vnd.ms-excel');
rc=stpsrv_header('Content-disposition',"attachment; filename=&outname"); rc=stpsrv_header('Content-disposition',"attachment; filename=&outname");
@@ -80,9 +85,13 @@ run;
contenttype='application/vnd.ms-excel' contenttype='application/vnd.ms-excel'
contentdisp="attachment; filename=&outname"; contentdisp="attachment; filename=&outname";
%end; %end;
%else %if &platform=SASJS %then %do;
%mfs_httpheader(Content-type,application/vnd.ms-excel)
%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))
%end;
%end; %end;
%else %if &contentype=GIF or &contentype=JPEG or &contentype=PNG %then %do; %else %if &contentype=GIF or &contentype=JPEG or &contentype=PNG %then %do;
%if (&platform=SASMETA and &streamweb=1) or &platform=SASJS %then %do; %if (&platform=SASMETA and &streamweb=1) %then %do;
data _null_; data _null_;
rc=stpsrv_header('Content-type',"image/%lowcase(&contenttype)"); rc=stpsrv_header('Content-type',"image/%lowcase(&contenttype)");
run; run;
@@ -91,15 +100,21 @@ run;
filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" filename &outref filesrvc parenturi="&SYS_JES_JOB_URI"
contenttype="image/%lowcase(&contenttype)"; contenttype="image/%lowcase(&contenttype)";
%end; %end;
%else %if &platform=SASJS %then %do;
%mfs_httpheader(Content-type,image/%lowcase(&contenttype))
%end;
%end; %end;
%else %if &contentype=HTML %then %do; %else %if &contentype=HTML %then %do;
%if &platform=SASVIYA %then %do; %if &platform=SASVIYA %then %do;
filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name="_webout.json" filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name="_webout.json"
contenttype="text/html"; contenttype="text/html";
%end; %end;
%else %if &platform=SASJS %then %do;
%mfs_httpheader(Content-type,text/html)
%end;
%end; %end;
%else %if &contentype=TEXT %then %do; %else %if &contentype=TEXT %then %do;
%if (&platform=SASMETA and &streamweb=1) or &platform=SASJS %then %do; %if (&platform=SASMETA and &streamweb=1) %then %do;
data _null_; data _null_;
rc=stpsrv_header('Content-type','application/text'); rc=stpsrv_header('Content-type','application/text');
rc=stpsrv_header('Content-disposition',"attachment; filename=&outname"); rc=stpsrv_header('Content-disposition',"attachment; filename=&outname");
@@ -110,9 +125,13 @@ run;
contenttype='application/text' contenttype='application/text'
contentdisp="attachment; filename=&outname"; contentdisp="attachment; filename=&outname";
%end; %end;
%else %if &platform=SASJS %then %do;
%mfs_httpheader(Content-type,application/text)
%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))
%end;
%end; %end;
%else %if &contentype=WOFF or &contentype=WOFF2 or &contentype=TTF %then %do; %else %if &contentype=WOFF or &contentype=WOFF2 or &contentype=TTF %then %do;
%if (&platform=SASMETA and &streamweb=1) or &platform=SASJS %then %do; %if (&platform=SASMETA and &streamweb=1) %then %do;
data _null_; data _null_;
rc=stpsrv_header('Content-type',"font/%lowcase(&contenttype)"); rc=stpsrv_header('Content-type',"font/%lowcase(&contenttype)");
run; run;
@@ -121,9 +140,12 @@ run;
filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" filename &outref filesrvc parenturi="&SYS_JES_JOB_URI"
contenttype="font/%lowcase(&contenttype)"; contenttype="font/%lowcase(&contenttype)";
%end; %end;
%else %if &platform=SASJS %then %do;
%mfs_httpheader(Content-type,font/%lowcase(&contenttype))
%end;
%end; %end;
%else %if &contentype=XLSX %then %do; %else %if &contentype=XLSX %then %do;
%if (&platform=SASMETA and &streamweb=1) or &platform=SASJS %then %do; %if (&platform=SASMETA and &streamweb=1) %then %do;
data _null_; data _null_;
rc=stpsrv_header('Content-type', rc=stpsrv_header('Content-type',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
@@ -136,9 +158,15 @@ run;
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
contentdisp="attachment; filename=&outname"; contentdisp="attachment; filename=&outname";
%end; %end;
%else %if &platform=SASJS %then %do;
%mfs_httpheader(Content-type
,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
)
%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))
%end;
%end; %end;
%else %if &contentype=ZIP %then %do; %else %if &contentype=ZIP %then %do;
%if (&platform=SASMETA and &streamweb=1) or &platform=SASJS %then %do; %if (&platform=SASMETA and &streamweb=1) %then %do;
data _null_; data _null_;
rc=stpsrv_header('Content-type','application/zip'); rc=stpsrv_header('Content-type','application/zip');
rc=stpsrv_header('Content-disposition',"attachment; filename=&outname"); rc=stpsrv_header('Content-disposition',"attachment; filename=&outname");
@@ -149,6 +177,10 @@ run;
contenttype='application/zip' contenttype='application/zip'
contentdisp="attachment; filename=&outname"; contentdisp="attachment; filename=&outname";
%end; %end;
%else %if &platform=SASJS %then %do;
%mfs_httpheader(Content-type,application/zip)
%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))
%end;
%end; %end;
%else %do; %else %do;
%put %str(ERR)OR: Content Type &contenttype NOT SUPPORTED by &sysmacroname!; %put %str(ERR)OR: Content Type &contenttype NOT SUPPORTED by &sysmacroname!;

View File

@@ -40,6 +40,12 @@
/* now try and assign it */ /* now try and assign it */
if libname("&libref",,'meta',cats('liburi="',liburi,'";')) ne 0 then do; if libname("&libref",,'meta',cats('liburi="',liburi,'";')) ne 0 then do;
putlog "&libref could not be assigned"; putlog "&libref could not be assigned";
putlog liburi=;
/**
* Fetch the system message for display in the abort modal. This is
* not always helpful though. One example, previously received:
* NOTE: Libref XX refers to the same library metadata as libref XX.
*/
call symputx('msg',sysmsg(),'l'); call symputx('msg',sysmsg(),'l');
if "&mabort"='HARD' then call symputx('mp_abort',1,'l'); if "&mabort"='HARD' then call symputx('mp_abort',1,'l');
end; end;
@@ -61,7 +67,7 @@
%if &mp_abort=1 %then %do; %if &mp_abort=1 %then %do;
%mp_abort(iftrue= (&mp_abort=1) %mp_abort(iftrue= (&mp_abort=1)
,mac=&sysmacroname ,mac=mm_assignlib.sas
,msg=&msg ,msg=&msg
) )
%return; %return;

View File

@@ -12,13 +12,14 @@
The macro is idempotent - if you run it twice, it will only create a folder The macro is idempotent - if you run it twice, it will only create a folder
once. once.
usage: Usage:
%mm_createfolder(path=/some/meta/folder) %mm_createfolder(path=/some/meta/folder)
@param [in] path= Name of the folder to create. @param [in] path= Name of the folder to create.
@param [in] mdebug= set DBG to 1 to disable DEBUG messages @param [in] mdebug= set DBG to 1 to disable DEBUG messages
@version 9.4 @version 9.4
@author Allan Bowe @author Allan Bowe

View File

@@ -12,7 +12,7 @@
This macro is idempotent - if you run it twice, it will only create an STP This macro is idempotent - if you run it twice, it will only create an STP
once. once.
usage (type 1 STP): Usage (type 1 STP):
%mm_createstp(stpname=MyNewSTP %mm_createstp(stpname=MyNewSTP
,filename=mySpecialProgram.sas ,filename=mySpecialProgram.sas
@@ -31,7 +31,8 @@
putlog (_all_)(=); putlog (_all_)(=);
run; run;
usage (type 2 STP): Usage (type 2 STP):
%mm_createstp(stpname=MyNewType2STP %mm_createstp(stpname=MyNewType2STP
,filename=mySpecialProgram.sas ,filename=mySpecialProgram.sas
,directory=SASEnvironment/SASCode/STPs ,directory=SASEnvironment/SASCode/STPs
@@ -74,8 +75,9 @@
@li mf_verifymacvars.sas @li mf_verifymacvars.sas
@li mm_getdirectories.sas @li mm_getdirectories.sas
@li mm_updatestpsourcecode.sas @li mm_updatestpsourcecode.sas
@li mp_dropmembers.sas
@li mm_getservercontexts.sas @li mm_getservercontexts.sas
@li mp_abort.sas
@li mp_dropmembers.sas
<h4> Related Macros </h4> <h4> Related Macros </h4>
@li mm_createwebservice.sas @li mm_createwebservice.sas

View File

@@ -1,16 +1,21 @@
/** /**
@file mm_getauthinfo.sas @file mm_getauthinfo.sas
@brief extracts authentication info @brief Extracts authentication info for each user in metadata
@details usage: @details
Usage:
%mm_getauthinfo(outds=auths) %mm_getauthinfo(outds=auths)
@param outds= the ONE LEVEL work dataset to create
@param [in] mdebug= (0) Set to 1 to enable DEBUG messages and preserve outputs
@param [out] outds= (mm_getauthinfo) The output dataset to create
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li mm_getobjects.sas
@li mf_getuniquefileref.sas @li mf_getuniquefileref.sas
@li mf_getuniquename.sas
@li mm_getdetails.sas @li mm_getdetails.sas
@li mm_getobjects.sas
@version 9.4 @version 9.4
@author Allan Bowe @author Allan Bowe
@@ -18,67 +23,69 @@
**/ **/
%macro mm_getauthinfo(outds=mm_getauthinfo %macro mm_getauthinfo(outds=mm_getauthinfo
,mdebug=0
)/*/STORE SOURCE*/; )/*/STORE SOURCE*/;
%local prefix fileref;
%let prefix=%substr(%mf_getuniquename(),1,25);
%if %length(&outds)>30 %then %do; %mm_getobjects(type=Login,outds=&prefix.0)
%put %str(ERR)OR: Temp tables are created with the &outds prefix, which
therefore needs to be 30 characters or less;
%return;
%end;
%if %index(&outds,'.')>0 %then %do;
%put %str(ERR)OR: Table &outds should be ONE LEVEL (no library);
%return;
%end;
%mm_getobjects(type=Login,outds=&outds.0)
%local fileref; %local fileref;
%let fileref=%mf_getuniquefileref(); %let fileref=%mf_getuniquefileref();
data _null_; data _null_;
file &fileref; file &fileref;
set &outds.0 end=last; set &prefix.0 end=last;
/* run macro */ /* run macro */
str=cats('%mm_getdetails(uri=',id,",outattrs=&outds.d",_n_ str=cats('%mm_getdetails(uri=',id,",outattrs=&prefix.d",_n_
,",outassocs=&outds.a",_n_,")"); ,",outassocs=&prefix.a",_n_,")");
put str; put str;
/* transpose attributes */ /* transpose attributes */
str=cats("proc transpose data=&outds.d",_n_,"(drop=type) out=&outds.da" str=cats("proc transpose data=&prefix.d",_n_,"(drop=type) out=&prefix.da"
,_n_,"(drop=_name_);var value;id name;run;"); ,_n_,"(drop=_name_);var value;id name;run;");
put str; put str;
/* add extra info to attributes */ /* add extra info to attributes */
str=cats("data &outds.da",_n_,";length login_id login_name $256; login_id=" str=cats("data &prefix.da",_n_,";length login_id login_name $256; login_id="
,quote(trim(id)),";set &outds.da",_n_ ,quote(trim(id)),";set &prefix.da",_n_
,";login_name=trim(subpad(name,1,256));drop name;run;"); ,";login_name=trim(subpad(name,1,256));drop name;run;");
put str; put str;
/* add extra info to associations */ /* add extra info to associations */
str=cats("data &outds.a",_n_,";length login_id login_name $256; login_id=" str=cats("data &prefix.a",_n_,";length login_id login_name $256; login_id="
,quote(trim(id)),";login_name=",quote(trim(name)) ,quote(trim(id)),";login_name=",quote(trim(name))
,";set &outds.a",_n_,";run;"); ,";set &prefix.a",_n_,";run;");
put str; put str;
if last then do; if last then do;
/* collate attributes */ /* collate attributes */
str=cats("data &outds._logat; set &outds.da1-&outds.da",_n_,";run;"); str=cats("data &prefix._logat; set &prefix.da1-&prefix.da",_n_,";run;");
put str; put str;
/* collate associations */ /* collate associations */
str=cats("data &outds._logas; set &outds.a1-&outds.a",_n_,";run;"); str=cats("data &prefix._logas; set &prefix.a1-&prefix.a",_n_,";run;");
put str; put str;
/* tidy up */ /* tidy up */
str=cats("proc delete data=&outds.da1-&outds.da",_n_,";run;"); str=cats("proc delete data=&prefix.da1-&prefix.da",_n_,";run;");
put str; put str;
str=cats("proc delete data=&outds.d1-&outds.d",_n_,";run;"); str=cats("proc delete data=&prefix.d1-&prefix.d",_n_,";run;");
put str; put str;
str=cats("proc delete data=&outds.a1-&outds.a",_n_,";run;"); str=cats("proc delete data=&prefix.a1-&prefix.a",_n_,";run;");
put str; put str;
end; end;
run; run;
%if &mdebug=1 %then %do;
data _null_;
infile &fileref;
if _n_=1 then putlog // "Now executing the following code:" //;
input; putlog _infile_;
run;
%end;
%inc &fileref; %inc &fileref;
filename &fileref clear;
/* get libraries */ /* get libraries */
proc sort data=&outds._logas(where=(assoc='Libraries')) out=&outds._temp; proc sort data=&prefix._logas(where=(assoc='Libraries')) out=&prefix._temp;
by login_id; by login_id;
data &outds._temp; data &prefix._temp;
set &outds._temp; set &prefix._temp;
by login_id; by login_id;
length library_list $32767; length library_list $32767;
retain library_list; retain library_list;
@@ -86,31 +93,27 @@ data &outds._temp;
else library_list=catx(' !! ',library_list,name); else library_list=catx(' !! ',library_list,name);
proc sql; proc sql;
/* get auth domain */ /* get auth domain */
create table &outds._dom as create table &prefix._dom as
select login_id,name as domain select login_id,name as domain
from &outds._logas from &prefix._logas
where assoc='Domain'; where assoc='Domain';
create unique index login_id on &outds._dom(login_id); create unique index login_id on &prefix._dom(login_id);
/* join it all together */ /* join it all together */
create table &outds._logins as create table &outds as
select a.* select a.*
,c.domain ,c.domain
,b.library_list ,b.library_list
from &outds._logat (drop=ishidden lockedby usageversion publictype) a from &prefix._logat (drop=ishidden lockedby usageversion publictype) a
left join &outds._temp b left join &prefix._temp b
on a.login_id=b.login_id on a.login_id=b.login_id
left join &outds._dom c left join &prefix._dom c
on a.login_id=c.login_id; on a.login_id=c.login_id;
drop table &outds._temp;
drop table &outds._logat;
drop table &outds._logas;
data _null_; %if &mdebug=0 %then %do;
infile &fileref; proc datasets lib=work;
if _n_=1 then putlog // "Now executing the following code:" //; delete &prefix:;
input; putlog _infile_; run;
run; %end;
filename &fileref clear;
%mend mm_getauthinfo; %mend mm_getauthinfo;

View File

@@ -73,17 +73,24 @@
"name": "server", "name": "server",
"serverUrl": "https://sas.analytium.co.uk:5000", "serverUrl": "https://sas.analytium.co.uk:5000",
"serverType": "SASJS", "serverType": "SASJS",
"appLoc": "/Shared Data/temp/macrocore", "httpsAgentOptions": {
"allowInsecureRequests": false
},
"appLoc": "/sasjs/core",
"macroFolders": [ "macroFolders": [
"tests/serveronly" "tests/serveronly"
], ],
"programFolders": [],
"binaryFolders": [],
"deployConfig": { "deployConfig": {
"deployServicePack": true "deployServicePack": true,
"deployScripts": []
} }
}, },
{ {
"name": "docsonly", "name": "docsonly",
"serverType": "SAS9", "serverType": "SAS9",
"appLoc": "dummy",
"macroFolders": [ "macroFolders": [
"tests/sas9only", "tests/sas9only",
"tests/viyaonly" "tests/viyaonly"

89
server/ms_createfile.sas Normal file
View File

@@ -0,0 +1,89 @@
/**
@file
@brief Creates a file on SASjs Drive
@details Creates a file on SASjs Drive. To use the file as a Stored Program,
it must have a ".sas" extension.
Example:
filename stpcode temp;
data _null_;
file stpcode;
put '%put hello world;';
run;
%ms_createfile(/some/stored/program.sas, inref=stpcode)
@param [in] driveloc The full path to the file in SASjs Drive
@param [in] inref= (0) The fileref containing the file to create.
@param [in] mdebug= (0) Set to 1 to enable DEBUG messages
<h4> SAS Macros </h4>
@li mf_getuniquefileref.sas
@li mf_getuniquename.sas
@li mp_abort.sas
**/
%macro ms_createfile(driveloc
,inref=0
,mdebug=0
);
%local fname0 fname1 boundary fname statcd msg;
%let fname0=%mf_getuniquefileref();
%let fname1=%mf_getuniquefileref();
%let boundary=%mf_getuniquename();
data _null_;
file &fname0 termstr=crlf;
infile &inref end=eof;
if _n_ = 1 then do;
put "--&boundary.";
put 'Content-Disposition: form-data; name="filePath"';
put ;
put "&driveloc";
put "--&boundary";
put 'Content-Disposition: form-data; name="file"; filename="ignore.sas"';
put "Content-Type: text/plain";
put ;
end;
input;
put _infile_; /* add the actual file to be sent */
if eof then do;
put ;
put "--&boundary--";
end;
run;
%if &mdebug=1 %then %do;
data _null_;
infile &fname0;
input;
put _infile_;
run;
%end;
proc http method='POST' in=&fname0 out=&fname1
url="&_sasjs_apiserverurl/SASjsApi/drive/file";
headers "Content-Type"="multipart/form-data; boundary=&boundary";
%if &mdebug=1 %then %do;
debug level=1;
%end;
run;
%let statcd=0;
data _null_;
infile &fname1;
input;
putlog _infile_;
if _infile_='{"status":"success"}' then call symputx('statcd',1,'l');
else call symputx('msg',_infile_,'l');
run;
%mp_abort(
iftrue=(&statcd=0)
,mac=ms_createfile.sas
,msg=%superq(msg)
)
%mend ms_createfile;

77
server/ms_runstp.sas Normal file
View File

@@ -0,0 +1,77 @@
/**
@file
@brief Executes a SASjs Server Stored Program
@details Runs a Stored Program (using POST method) and extracts the webout and
log from the response JSON.
Example:
%ms_runstp(/some/stored/program
,debug=131
,outref=weboot
)
@param [in] pgm The full path to the Stored Program in SASjs Drive (_program
parameter)
@param [in] debug= (131) The value to supply to the _debug URL parameter
@param [in] mdebug= (0) Set to 1 to enable DEBUG messages
@param [out] outref= (outweb) The output fileref to contain the response JSON
(will be created using temp engine)
<h4> SAS Macros </h4>
@li mf_getuniquefileref.sas
@li mp_abort.sas
**/
%macro ms_runstp(pgm
,debug=131
,outref=outweb
,mdebug=0
);
%local dbg fname1;
%if &mdebug=1 %then %do;
%put &sysmacroname entry vars:;
%put _local_;
%end;
%else %let dbg=*;
%let fname1=%mf_getuniquefileref();
%mp_abort(iftrue=("&pgm"="")
,mac=&sysmacroname
,msg=%str(Program not provided)
)
data _null_;
file &fname1;
infile "&_sasjs_tokenfile";
input;
put 'Authorization: Bearer' _infile_;
run;
filename &outref temp;
/* prepare request*/
proc http method='POST' headerin=&fname1 out=&outref
url="&_sasjs_apiserverurl.&_sasjs_apipath?_program=&pgm%str(&)_debug=131";
run;
%if (&SYS_PROCHTTP_STATUS_CODE ne 200 and &SYS_PROCHTTP_STATUS_CODE ne 201)
or &mdebug=1 %then %do;
data _null_;infile &outref;input;putlog _infile_;run;
%end;
%mp_abort(
iftrue=(&SYS_PROCHTTP_STATUS_CODE ne 200 and &SYS_PROCHTTP_STATUS_CODE ne 201)
,mac=&sysmacroname
,msg=%str(&SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE)
)
%if &mdebug=1 %then %do;
%put &sysmacroname exit vars:;
%put _local_;
%end;
%else %do;
/* clear refs */
filename &fname1 clear;
%end;
%mend ms_runstp;

View File

@@ -3,6 +3,7 @@
@brief Testing mp_cntlout.sas macro @brief Testing mp_cntlout.sas macro
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li mf_nobs.sas
@li mp_cntlout.sas @li mp_cntlout.sas
@li mp_assert.sas @li mp_assert.sas
@li mp_assertscope.sas @li mp_assertscope.sas

View File

@@ -0,0 +1,65 @@
/**
@file
@brief Testing mp_replace.sas macro
<h4> SAS Macros </h4>
@li mp_replace.sas
@li mp_assert.sas
@li mp_assertscope.sas
**/
%let test1="&sasjswork/file.txt";
%let str=replace/me;
%let rep=with/this;
data _null_;
file &test1;
put 'blahblah';
put "blahblah&str.blah";
put 'blahblahblah';
run;
%mp_assertscope(SNAPSHOT)
%mp_replace(&test1, findvar=str, replacevar=rep)
%mp_assertscope(COMPARE)
data _null_;
infile &test1;
input;
if _n_=2 then call symputx('test1result',_infile_);
run;
%mp_assert(
iftrue=("&test1result" = "blahblah&rep.blah"),
desc=Checking first replace,
outds=work.test_results
)
%let test2="&sasjswork/file2.txt";
%let str=%str(replacewith trailing spaces );
%let rep=%str( with more spaces );
data _null_;
file &test2;
put 'blahblah';
put "blahblah&str.blah&str. replace &str.X";
put "blahbreplacewith&str.spacesahblah";
run;
%mp_replace(&test2, findvar=str, replacevar=rep)
data _null_;
infile &test2;
input;
if _n_=2 then call symputx('test2resulta',_infile_);
if _n_=3 then call symputx('test2resultb',_infile_);
run;
%mp_assert(
iftrue=("&test2resulta" = "blahblah&rep.blah&rep. replace &rep.X"),
desc=Checking second replace 2nd row,
outds=work.test_results
)
%mp_assert(
iftrue=("&test2resultb" = "blahbreplacewith&rep.spacesahblah"),
desc=Checking second replace 3rd row,
outds=work.test_results
)

View File

@@ -0,0 +1,21 @@
/**
@file
@brief Testing mm_getauthinfo macro
<h4> SAS Macros </h4>
@li mf_existds.sas
@li mm_getauthinfo.sas
@li mp_assertscope.sas
**/
%mp_assertscope(SNAPSHOT)
%mm_getauthinfo(outds=auths)
%mp_assertscope(COMPARE)
%mp_assert(
iftrue=(%mf_existds(work.auths)=1),
desc=Check if the auths dataset was created
)

View File

@@ -5,12 +5,16 @@
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li mfs_httpheader.sas @li mfs_httpheader.sas
@li mp_assert.sas @li mp_assert.sas
@li mp_assertscope.sas
**/ **/
%let sasjs_stpsrv_header_loc=%sysfunc(pathname(work))/header.txt; %let sasjs_stpsrv_header_loc=%sysfunc(pathname(work))/header.txt;
%mp_assertscope(SNAPSHOT)
%mfs_httpheader(Content-type,application/csv) %mfs_httpheader(Content-type,application/csv)
%mp_assertscope(COMPARE)
data _null_; data _null_;
infile "&sasjs_stpsrv_header_loc"; infile "&sasjs_stpsrv_header_loc";
input; input;

View File

@@ -0,0 +1,30 @@
/**
@file
@brief Testing ms_createfile.sas macro
<h4> SAS Macros </h4>
@li ms_createfile.sas
@li mp_assert.sas
@li mp_assertscope.sas
**/
filename stpcode temp;
data _null_;
file stpcode;
put '%put hello world;';
run;
options mprint;
%let fname=%mf_getuniquename();
%mp_assertscope(SNAPSHOT)
%ms_createfile(/sasjs/tests/&fname..sas
,inref=stpcode
,mdebug=1
)
%mp_assertscope(COMPARE)

View File

@@ -0,0 +1,44 @@
/**
@file
@brief Testing ms_runstp.sas macro
<h4> SAS Macros </h4>
@li ms_runstp.sas
@li mp_assert.sas
@li mp_assertscope.sas
**/
%mp_assertscope(SNAPSHOT)
%ms_runstp(/Public/app/frs/allan/services/common/appinit
,debug=131
,outref=weboot
)
%mp_assertscope(COMPARE)
libname webeen json (weboot);
data _null_;
infile weboot;
input;
putlog _infile_;
run;
data work.httpheaders;
set webeen.httpheaders;
call symputx('test1',content_type);
run;
data work.log;
set webeen.log;
put (_all_)(=);
if _n_>10 then stop;
run;
%mp_assert(
iftrue=("&test1"="application/json"),
desc=Checking line was created,
outds=work.test_results
)

View File

@@ -1,10 +1,10 @@
/** /**
@file mv_registerclient.sas @file mv_registerclient.sas
@brief Register Client and Secret (admin task) @brief Register Client and Secret (admin task)
@details When building apps on SAS Viya, an client id and secret are sometimes @details When building apps on SAS Viya, a client id and secret are usually
required. In order to generate them, filesystem access to the Consul Token required. In order to generate them, the Consul Token is required. To access
is needed (it is not enough to be in the SASAdministrator group in SAS this token, you need to be a system administrator (it is not enough to be in
Environment Manager). the SASAdministrator group in SAS Environment Manager).
If you are registering a lot of clients / secrets, you may find it more If you are registering a lot of clients / secrets, you may find it more
convenient to use the [Viya Token Generator] convenient to use the [Viya Token Generator]
@@ -25,62 +25,69 @@
"https://raw.githubusercontent.com/sasjs/core/main/all.sas"; "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
%inc mc; %inc mc;
%* generate random client using consul token as input parameter;
%mv_registerclient(consul_token=12x34sa43v2345n234lasd)
%* generate random client details with all scopes;
%mv_registerclient(scopes=openid *)
%* specific client with just openid scope; %* specific client with just openid scope;
%mv_registerclient(client_id=YourClient %mv_registerclient(client_id=YourClient
,client_secret=YourSecret ,client_secret=YourSecret
,scopes=openid ,scopes=openid
) )
%* generate random client details with all scopes;
%mv_registerclient(scopes=openid *)
%* generate random client with 90/180 second access/refresh token expiry; %* generate random client with 90/180 second access/refresh token expiry;
%mv_registerclient(scopes=openid * %mv_registerclient(scopes=openid *
,access_token_validity=90 ,access_token_validity=90
,refresh_token_validity=180 ,refresh_token_validity=180
) )
@param client_id= The client name. Auto generated if blank. @param [in,out] client_id= The client name. Auto generated if blank.
@param client_secret= Client secret. Auto generated if client is blank. @param [in,out] client_secret= Client secret. Auto generated if client is
@param scopes=(openid) List of space-seperated unquoted scopes blank.
@param grant_type=(authorization_code|refresh_token) Valid values are @param [in] consul_token= (0) Provide the actual consul token value here if
"password" or "authorization_code" (unquoted) using Viya 4 or above.
@param outds=(mv_registerclient) The dataset to contain the registered client @param [in] scopes= (openid) List of space-seperated unquoted scopes
id and secret @param [in] grant_type= (authorization_code|refresh_token) Valid values are
@param access_token_validity=(DEFAULT) The duration of validity of the access "password" or "authorization_code" (unquoted). Pipe seperated.
token in seconds. A value of DEFAULT will omit the entry (and use system @param [out] outds=(mv_registerclient) The dataset to contain the registered
default) client id and secret
@param refresh_token_validity=(DEFAULT) The duration of validity of the @param [in] access_token_validity= (DEFAULT) The access token duration in
seconds. A value of DEFAULT will omit the entry (and use system default)
@param [in] refresh_token_validity= (DEFAULT) The duration of validity of the
refresh token in seconds. A value of DEFAULT will omit the entry (and use refresh token in seconds. A value of DEFAULT will omit the entry (and use
system default) system default)
@param name= An optional, human readable name for the client @param [in] client_name= (DEFAULT) An optional, human readable name for the
@param required_user_groups= A list of group names. If a user does not belong client.
to all the required groups, the user will not be authenticated and no tokens @param [in] required_user_groups= A list of group names. If a user does not
are issued to this client for that user. If this field is not specified, belong to all the required groups, the user will not be authenticated and no
authentication and token issuance proceeds normally. tokens are issued to this client for that user. If this field is not
@param autoapprove= During the auth step the user can choose which scope to specified, authentication and token issuance proceeds normally.
apply. Setting this to true will autoapprove all the client scopes. @param [in] autoapprove= During the auth step the user can choose which scope
@param use_session= If true, access tokens issued to this client will be to apply. Setting this to true will autoapprove all the client scopes.
@param [in] use_session= If true, access tokens issued to this client will be
associated with an HTTP session and revoked upon logout or time-out. associated with an HTTP session and revoked upon logout or time-out.
@param outjson= (_null_) A dataset containing the lines of JSON submitted. @param [out] outjson= (_null_) A dataset containing the lines of JSON
Useful for debugging. submitted. Useful for debugging.
@version VIYA V.03.04 @version VIYA V.03.04
@author Allan Bowe, source: https://github.com/sasjs/core @author Allan Bowe, source: https://github.com/sasjs/core
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li mp_abort.sas
@li mf_getplatform.sas @li mf_getplatform.sas
@li mf_getuniquefileref.sas @li mf_getuniquefileref.sas
@li mf_getuniquelibref.sas @li mf_getuniquelibref.sas
@li mf_loc.sas @li mf_loc.sas
@li mf_getquotedstr.sas @li mf_getquotedstr.sas
@li mf_getuser.sas @li mf_getuser.sas
@li mp_abort.sas
**/ **/
%macro mv_registerclient(client_id= %macro mv_registerclient(client_id=
,client_secret= ,client_secret=
,consul_token=0
,client_name=DEFAULT ,client_name=DEFAULT
,scopes=openid ,scopes=openid
,grant_type=authorization_code|refresh_token ,grant_type=authorization_code|refresh_token
@@ -92,33 +99,41 @@
,refresh_token_validity=DEFAULT ,refresh_token_validity=DEFAULT
,outjson=_null_ ,outjson=_null_
); );
%local consul_token fname1 fname2 fname3 libref access_token url tokloc; %local fname1 fname2 fname3 libref access_token url tokloc msg;
%if client_name=DEFAULT %then %let client_name= %if client_name=DEFAULT %then %let client_name=
Generated by %mf_getuser() on %sysfunc(datetime(),datetime19.) using SASjs; Generated by %mf_getuser() (&sysuserid) on %sysfunc(datetime(),datetime19.
) using SASjs;
options noquotelenmax; options noquotelenmax;
/* first, get consul token needed to get client id / secret */
%let tokloc=/etc/SASSecurityCertificateFramework/tokens/consul/default;
%let tokloc=%mf_loc(VIYACONFIG)&tokloc/client.token;
%if "&consul_token"="0" %then %do;
/* first, get consul token needed to get client id / secret */
%let tokloc=/etc/SASSecurityCertificateFramework/tokens/consul/default;
%let tokloc=%mf_loc(VIYACONFIG)&tokloc/client.token;
%mp_abort(iftrue=(%sysfunc(fileexist(&tokloc))=0) %if %sysfunc(fileexist(&tokloc))=0 %then %do;
,mac=&sysmacroname %let msg=Unable to access the consul token at &tokloc;
,msg=%str(Unable to access the consul token at &tokloc) %put &sysmacroname: &msg;
) %put Try passing the value in the consul= macro parameter;
%put See docs: https://core.sasjs.io/mv__registerclient_8sas.html;
%mp_abort(mac=mv_registerclient,msg=%str(&msg))
%end;
%let consul_token=0; data _null_;
data _null_;
infile "&tokloc"; infile "&tokloc";
input token:$64.; input token:$64.;
call symputx('consul_token',token); call symputx('consul_token',token);
run; run;
%mp_abort(iftrue=("&consul_token"="0") %if "&consul_token"="0" %then %do;
,mac=&sysmacroname %put &sysmacroname: Unable to source the consul token from &tokloc;
,msg=%str(Unable to source the consul token from &tokloc) %put It seems your account (&sysuserid) does not have admin rights;
) %put Please speak with your platform adminstrator;
%put Docs: https://core.sasjs.io/mv__registerclient_8sas.html;
%abort;
%end;
%end;
%local base_uri; /* location of rest apis */ %local base_uri; /* location of rest apis */
%let base_uri=%mf_getplatform(VIYARESTAPI); %let base_uri=%mf_getplatform(VIYARESTAPI);
@@ -131,6 +146,9 @@ proc http method='POST' out=&fname1
headers "X-Consul-Token"="&consul_token"; headers "X-Consul-Token"="&consul_token";
run; run;
%put &=SYS_PROCHTTP_STATUS_CODE;
%put &=SYS_PROCHTTP_STATUS_PHRASE;
%let libref=%mf_getuniquelibref(); %let libref=%mf_getuniquelibref();
libname &libref JSON fileref=&fname1; libname &libref JSON fileref=&fname1;