The deploy screens offer a checkbox, off by default, that asks makedata to build a sample data library beside the control library. It sits at the bottom of each screen and explains itself only once it is ticked: the option is not one most administrators will want, so it is not put in the path of the screen's action. Each makedata target resolves the flag itself - the URL parameter first, then the adapter's SAMPLEDATA column when the input table carries one - so no extra macro is deployed for it. mpe_makesampledata creates <dcloc>/dcdemo, assigns it as DC_DEMO, writes three tables and registers them in MPE_TABLES, and takes iftrue=0 by default so a call that omits the flag builds nothing. mpe_makesampledata keeps what it already had for the 4GL demo estate (row level security, excel config and security rows, gated on SYSSITE) and gains the sample tables, so one macro owns the whole job. The mock makedata's mpeMakesampledata is the same function with the same arguments, tables and rows. The rows are generated from a fixed rule rather than copied from SASHELP, so a SAS estate and the JS estate hold the same values; makedata-sampledata.test.js compares both estates' registrations against the macro source. The pre-commit prettier gate could not run here (no npm on PATH); the same prettier check was run directly against the staged client files and passes.
329 lines
10 KiB
SAS
329 lines
10 KiB
SAS
/**
|
|
@file
|
|
@brief service for creating the configuration tables
|
|
@details
|
|
STP for creating the configuration tables.
|
|
It also adds the STAGING directory as subdirectory to the BASE
|
|
library location.
|
|
|
|
ACTION is required, and is one of:
|
|
|
|
MAKEDATA create the configuration tables (the original behaviour)
|
|
DIRLIST list the folders below PARENT, for a deploy screen picker
|
|
WRITECHECK is PATH a folder we can write to
|
|
|
|
DIRLIST and WRITECHECK answer the deploy screen's questions about the
|
|
filesystem, which have to be answered before the control library exists -
|
|
so they cannot live in an admin gated service. They are answered here
|
|
because this service already owns that window, and it removes itself after
|
|
a successful run.
|
|
|
|
@warning This STP self destructs! It will delete itself (and the
|
|
configurator) after a successful run, to avoid being executed twice and
|
|
overwriting actual data. Pass a KEEPME column in the input data to keep
|
|
them - test harnesses reseed before every run and cannot redeploy first.
|
|
|
|
@sasjslint {"noBannedStrings": false, "maxLineLength": 101}
|
|
|
|
<h4> SAS Macros </h4>
|
|
@li mf_getapploc.sas
|
|
@li mf_mkdir.sas
|
|
@li mp_abort.sas
|
|
@li mpe_action.sas
|
|
@li mpe_dirlist.sas
|
|
@li mpe_writecheck.sas
|
|
@li mpe_makesampledata.sas
|
|
@li mpe_makedata.sas
|
|
@li mpe_makedatamodel.sas
|
|
@li ms_createfile.sas
|
|
@li ms_deletefile.sas
|
|
|
|
@version 9.2
|
|
@author 4GL Apps Ltd
|
|
@copyright 4GL Apps Ltd. This code may only be used within Data Controller
|
|
and may not be re-distributed or re-sold without the express permission of
|
|
4GL Apps Ltd.
|
|
|
|
**/
|
|
|
|
%global path ADMIN OUTFMT keepme action sampledata;
|
|
|
|
/* response format - HTML (default) or JSON */
|
|
%let OUTFMT=HTML;
|
|
|
|
%webout(FETCH)
|
|
|
|
/* enable vars to be passed as url params */
|
|
%let exist=%sysfunc(exist(work.fromjs));
|
|
%let inds=%sysfunc(ifc(&exist=1,fromjs,_null_));
|
|
|
|
/*
|
|
ACTION is required, and arrives either as a column of the input table or as a
|
|
macro variable (a URL parameter on the platforms that deliver one). The
|
|
resolution lives in mpe_action, which reads any incoming value before it
|
|
assigns one - a default written first is what discarded the parameter.
|
|
*/
|
|
%mpe_action()
|
|
|
|
/* both refusals carry their own condition, so neither needs a branch */
|
|
%mp_abort(iftrue= (&action=MISSING)
|
|
,mac=&_program
|
|
,msg=%str(ACTION is required: MAKEDATA, DIRLIST or WRITECHECK)
|
|
)
|
|
%mp_abort(iftrue= (&action=BAD)
|
|
,mac=&_program
|
|
,msg=%str(ACTION must be MAKEDATA, DIRLIST or WRITECHECK)
|
|
)
|
|
|
|
|
|
%macro emit_response();
|
|
%if &OUTFMT=JSON %then %do;
|
|
data work.makedata_response;
|
|
length STATUS $10 MESSAGE $256 DCPATH $256 LIBREF $8 ADMIN $64;
|
|
STATUS='SUCCESS';
|
|
MESSAGE="Data Controller Config created by &sysuserid";
|
|
DCPATH="&dcpath";
|
|
LIBREF="&DC_LIBREF";
|
|
ADMIN="&admin";
|
|
run;
|
|
%webout(OPEN)
|
|
%webout(OBJ, makedata_response)
|
|
%webout(CLOSE)
|
|
%end;
|
|
%else %do;
|
|
data _null_;
|
|
file _webout;
|
|
put '<h3>Data Controller Config</h3>';
|
|
put '<p>The following items have been successfully configured:</p>';
|
|
put "<ul><li>Library Location (&dcpath)</li>";
|
|
put "<li>Table Creation (&DC_LIBREF library) using the <b>&sysuserid</b> identity</li>";
|
|
put "<li>Logs Location (%trim(&dcpath)/logs)</li>";
|
|
put "<li>Data Controller Admin Group (&admin)</li>";
|
|
put "</ul>";
|
|
put 'Future configuration changes can be made <a href="';
|
|
put "/#/SASjsDrive?filePath=&root/services/public/settings";
|
|
put '">here</a></p>';
|
|
put "<p> Next Steps: </p>";
|
|
put "<ol><li>Populate<a target='_blank' href='";
|
|
put "/SASjsApi/stp/execute?_program=&root/services/admin/refreshcatalog'>";
|
|
put "Data Catalog</a></li>";
|
|
put "<li><a target='_blank' href='/AppStream/DataController'>Launch!</a>";
|
|
put "</li></ol>";
|
|
run;
|
|
%end;
|
|
%mend emit_response;
|
|
|
|
%macro self_destruct();
|
|
%if &keepme ne 1 %then %do;
|
|
%ms_deletefile(&root/services/admin/configurator.sas);
|
|
%ms_deletefile(&_program..sas);
|
|
%end;
|
|
%else %do;
|
|
%put NOTE: &_program retained, as KEEPME was requested;
|
|
%end;
|
|
%mend self_destruct;
|
|
|
|
/*
|
|
keepme is global: it is read by self_destruct, which the body calls, and a
|
|
local in the body would shadow it - the request sets the global with
|
|
symputx(...,'G') and this is the value that has to be seen.
|
|
*/
|
|
%let keepme=0;
|
|
|
|
%macro makedata_body(iftrue=1);
|
|
%if not (&iftrue) %then %return;
|
|
|
|
data _null_;
|
|
set &inds;
|
|
call symputx('path',dcpath);
|
|
call symputx('ADMIN',ADMIN);
|
|
run;
|
|
|
|
/* check if a JSON response was requested (OUTFMT column in fromjs) */
|
|
data _null_;
|
|
set &inds;
|
|
dsid=open('work.fromjs');
|
|
if varnum(dsid,'OUTFMT')>0 then call symputx('OUTFMT',upcase(outfmt),'G');
|
|
rc=close(dsid);
|
|
run;
|
|
|
|
/* optional KEEPME column - keep this program and the configurator on the drive
|
|
* after a successful run. A normal install self-destructs so the seeding
|
|
* cannot be run twice over live data; a test harness reseeds before every run
|
|
* and wants them to stay put. */
|
|
data _null_;
|
|
dsid=open('work.fromjs');
|
|
/* every row, not just the first: KEEPME may be set on any of them */
|
|
if varnum(dsid,'KEEPME')>0 then do until(eof);
|
|
set &inds end=eof;
|
|
if upcase(cats(keepme)) in ('1','Y','YES','TRUE')
|
|
then call symputx('keepme',1,'G');
|
|
end;
|
|
rc=close(dsid);
|
|
stop;
|
|
run;
|
|
|
|
/* optional SAMPLEDATA flag - build the DC_DEMO library as well. Off unless
|
|
the deployment asks for it. The parameter is read first and macro logic
|
|
settles it; the adapter's column is read only when the input table carries
|
|
one, so a route that does not send it compiles no step for it. Anything
|
|
outside the list below is off. */
|
|
%let sample_in=N;
|
|
%if %symexist(sampledata) %then %let sample_in=%upcase(&sampledata);
|
|
%let sampledata=0;
|
|
%if &sample_in=1 or &sample_in=Y or &sample_in=YES or &sample_in=TRUE
|
|
%then %let sampledata=1;
|
|
%if &exist=1 %then %do;
|
|
%let _dsid=%sysfunc(open(work.fromjs));
|
|
%if %sysfunc(varnum(&_dsid,SAMPLEDATA)) ne 0 %then %do;
|
|
data _null_;
|
|
set work.fromjs(obs=1);
|
|
if upcase(strip(cats(sampledata))) in ('1','Y','YES','TRUE')
|
|
then call symputx('sampledata',1);
|
|
run;
|
|
%end;
|
|
%let _rc=%sysfunc(close(&_dsid));
|
|
%end;
|
|
|
|
%mp_abort(iftrue= (&syscc ne 0)
|
|
,mac=&_program
|
|
,msg=%str(Issue on makedata entry)
|
|
)
|
|
|
|
%let root=%mf_getapploc();
|
|
%let dc_libref=%upcase(DC%substr(%sysevalf(%sysfunc(datetime())/60),3,6));
|
|
%let dcpath=%trim(&path)/&dc_libref;
|
|
%put _all_;
|
|
|
|
%mf_mkdir(&dcpath)
|
|
|
|
libname &dc_libref "&dcpath";
|
|
%mp_abort(iftrue= (&syscc ne 0)
|
|
,mac=&_program..sas
|
|
,msg=%str(Libref (&dc_libref) could not be assigned to (&dcpath) )
|
|
)
|
|
|
|
/* check we have physical permissions to the DCLIB folder */
|
|
data _null_;
|
|
putlog "testing write to: &dcpath/permTest.txt";
|
|
putlog "sysuserid=&sysuserid";
|
|
data _null_;
|
|
file "&dcpath/permTest.txt";
|
|
run;
|
|
%mp_abort(iftrue= (&syscc ne 0)
|
|
,mac=&_program
|
|
,msg=%str(User &sysuserid does not have WRITE permissions on physical
|
|
directory: &dcpath )
|
|
)
|
|
|
|
filename delfile "&dcpath/permTest.txt";
|
|
data _null_;
|
|
rc=fdelete('delfile');
|
|
run;
|
|
%mp_abort(iftrue= (&syscc ne 0)
|
|
,mac=&_program..sas
|
|
,msg=%str(User &sysuserid could create (but not delete) &dcpath/permTest.txt )
|
|
)
|
|
|
|
|
|
/* SASAdministrators */
|
|
%let admin=%sysfunc(coalescec(&admin,SASAdministrators));
|
|
%mpe_makedatamodel(lib=&dc_libref)
|
|
%mpe_makedata(lib=&DC_LIBREF,mpeadmins=&admin,path=%str(&dcpath))
|
|
|
|
|
|
%mp_abort(iftrue= (&syscc ne 0)
|
|
,mac=&_program..sas
|
|
,msg=%str(Problem creating tables in &DC_LIBREF library)
|
|
)
|
|
|
|
/* sample data library - only when the deployment asked for it */
|
|
%mpe_makesampledata(lib=&dc_libref,path=%str(&dcpath),iftrue=(&sampledata=1))
|
|
%mp_abort(iftrue= (&syscc ne 0)
|
|
,mac=&_program..sas
|
|
,msg=%str(Problem creating the DC_DEMO library)
|
|
)
|
|
|
|
/* finally, update the app component. The user will need WM perms for this. */
|
|
filename stngs temp;
|
|
data _null_;
|
|
file stngs ;
|
|
put ' ';
|
|
put '%global dc_repo_users dc_macros dc_libref dc_staging_area dc_admin_group mpelib dc_dttmtfmt;';
|
|
put ' ';
|
|
put '%let dc_libref=' "&dc_libref;";
|
|
put ' ';
|
|
put '/* This metadata group has unrestricted access to Data Controller */';
|
|
put '%let dc_admin_group=' "&admin;";
|
|
put ' ';
|
|
put "libname &dc_libref '&dcpath' ;";
|
|
put ' ';
|
|
%if &sampledata=1 %then %do;
|
|
put '/* sample data library, created by this deployment */';
|
|
put "libname DC_DEMO '&dcpath/dcdemo' ;";
|
|
put ' ';
|
|
%end;
|
|
put '/* This physical location is used for staging data and audit history */';
|
|
put '%let dc_staging_area=' "&dcpath/dc_staging;";
|
|
put ' ';
|
|
put 'data _null_;';
|
|
put ' set &DC_LIBREF..mpe_config(where=(';
|
|
put ' var_scope="DC" ';
|
|
put ' and &dc_dttmtfmt. lt tx_to';
|
|
put ' and var_active=1';
|
|
put " ));";
|
|
put " call symputx(var_name,var_value,'G');";
|
|
put ' putlog var_name "=" var_value;';
|
|
put "run;";
|
|
put ' ';
|
|
put '/* to override any DC macros with client versions, place them below */';
|
|
put 'options insert=(sasautos=("&dc_macros"));';
|
|
put '%let mpelib=&DC_LIBREF;';
|
|
run;
|
|
|
|
|
|
|
|
%mp_abort(iftrue= (&syscc ne 0)
|
|
,mac=&_program
|
|
,msg=%str(syscc=syscc=&syscc when preparing data_controller_settings)
|
|
)
|
|
|
|
%ms_deletefile(&root/services/public/settings.sas)
|
|
%ms_createfile(&root/services/public/settings.sas, inref=stngs)
|
|
|
|
|
|
%mp_abort(iftrue= (&syscc ne 0)
|
|
,mac=&_program..sas
|
|
,msg=%str(Problem updating &root/services/public/settings)
|
|
)
|
|
|
|
|
|
%emit_response()
|
|
|
|
/* We ran successfully, now remove configurator and makedata STPs, unless the
|
|
* caller asked to keep them (KEEPME in the input data). Keeping them is for
|
|
* test harnesses, which reseed before every run and cannot redeploy first. */
|
|
%self_destruct()
|
|
|
|
%mend makedata_body;
|
|
|
|
/**
|
|
* The browse actions need no gate of their own.
|
|
*
|
|
* DIRLIST and WRITECHECK exist for the deploy screen, and the deploy window
|
|
* closes by itself: a successful run deletes this program, so once DC is
|
|
* configured there is nothing here left to call. A gate keyed off
|
|
* settings.sas closed the window on the runs that still needed it - a failed
|
|
* install leaves that file behind, and so does any earlier configuration of
|
|
* the same appLoc. That gate closed the very window it was meant to protect.
|
|
*/
|
|
|
|
/*
|
|
Three invocations rather than one branch: each macro takes `iftrue=` and
|
|
returns immediately when its action is not the one requested, so this file
|
|
has no open-code %if and no macro nested inside another.
|
|
*/
|
|
%mpe_dirlist(iftrue= (&action=DIRLIST))
|
|
%mpe_writecheck(iftrue= (&action=WRITECHECK))
|
|
%makedata_body(iftrue= (&action=MAKEDATA))
|