mirror of
https://github.com/sasjs/core.git
synced 2026-01-07 09:30:06 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
26c085b354 | ||
|
|
d13ac52739 | ||
|
|
bbbc28ad6d | ||
|
|
530cd6e95c | ||
|
|
c4e17e43e8 | ||
|
|
fed217eec3 | ||
|
|
1934dc8332 | ||
|
|
9de512cfc7 | ||
|
|
cadafcc86b |
128
CODE_OF_CONDUCT.md
Normal file
128
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
We as members, contributors, and leaders pledge to make participation in our
|
||||||
|
community a harassment-free experience for everyone, regardless of age, body
|
||||||
|
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||||
|
identity and expression, level of experience, education, socio-economic status,
|
||||||
|
nationality, personal appearance, race, religion, or sexual identity
|
||||||
|
and orientation.
|
||||||
|
|
||||||
|
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||||
|
diverse, inclusive, and healthy community.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to a positive environment for our
|
||||||
|
community include:
|
||||||
|
|
||||||
|
* Demonstrating empathy and kindness toward other people
|
||||||
|
* Being respectful of differing opinions, viewpoints, and experiences
|
||||||
|
* Giving and gracefully accepting constructive feedback
|
||||||
|
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||||
|
and learning from the experience
|
||||||
|
* Focusing on what is best not just for us as individuals, but for the
|
||||||
|
overall community
|
||||||
|
|
||||||
|
Examples of unacceptable behavior include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery, and sexual attention or
|
||||||
|
advances of any kind
|
||||||
|
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or email
|
||||||
|
address, without their explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
|
## Enforcement Responsibilities
|
||||||
|
|
||||||
|
Community leaders are responsible for clarifying and enforcing our standards of
|
||||||
|
acceptable behavior and will take appropriate and fair corrective action in
|
||||||
|
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||||
|
or harmful.
|
||||||
|
|
||||||
|
Community leaders have the right and responsibility to remove, edit, or reject
|
||||||
|
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||||
|
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||||
|
decisions when appropriate.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies within all community spaces, and also applies when
|
||||||
|
an individual is officially representing the community in public spaces.
|
||||||
|
Examples of representing our community include using an official e-mail address,
|
||||||
|
posting via an official social media account, or acting as an appointed
|
||||||
|
representative at an online or offline event.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported to the community leaders responsible for enforcement at
|
||||||
|
https://sasapps.io/contact-us.
|
||||||
|
All complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
|
All community leaders are obligated to respect the privacy and security of the
|
||||||
|
reporter of any incident.
|
||||||
|
|
||||||
|
## Enforcement Guidelines
|
||||||
|
|
||||||
|
Community leaders will follow these Community Impact Guidelines in determining
|
||||||
|
the consequences for any action they deem in violation of this Code of Conduct:
|
||||||
|
|
||||||
|
### 1. Correction
|
||||||
|
|
||||||
|
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||||
|
unprofessional or unwelcome in the community.
|
||||||
|
|
||||||
|
**Consequence**: A private, written warning from community leaders, providing
|
||||||
|
clarity around the nature of the violation and an explanation of why the
|
||||||
|
behavior was inappropriate. A public apology may be requested.
|
||||||
|
|
||||||
|
### 2. Warning
|
||||||
|
|
||||||
|
**Community Impact**: A violation through a single incident or series
|
||||||
|
of actions.
|
||||||
|
|
||||||
|
**Consequence**: A warning with consequences for continued behavior. No
|
||||||
|
interaction with the people involved, including unsolicited interaction with
|
||||||
|
those enforcing the Code of Conduct, for a specified period of time. This
|
||||||
|
includes avoiding interactions in community spaces as well as external channels
|
||||||
|
like social media. Violating these terms may lead to a temporary or
|
||||||
|
permanent ban.
|
||||||
|
|
||||||
|
### 3. Temporary Ban
|
||||||
|
|
||||||
|
**Community Impact**: A serious violation of community standards, including
|
||||||
|
sustained inappropriate behavior.
|
||||||
|
|
||||||
|
**Consequence**: A temporary ban from any sort of interaction or public
|
||||||
|
communication with the community for a specified period of time. No public or
|
||||||
|
private interaction with the people involved, including unsolicited interaction
|
||||||
|
with those enforcing the Code of Conduct, is allowed during this period.
|
||||||
|
Violating these terms may lead to a permanent ban.
|
||||||
|
|
||||||
|
### 4. Permanent Ban
|
||||||
|
|
||||||
|
**Community Impact**: Demonstrating a pattern of violation of community
|
||||||
|
standards, including sustained inappropriate behavior, harassment of an
|
||||||
|
individual, or aggression toward or disparagement of classes of individuals.
|
||||||
|
|
||||||
|
**Consequence**: A permanent ban from any sort of public interaction within
|
||||||
|
the community.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||||
|
version 2.0, available at
|
||||||
|
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||||
|
|
||||||
|
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||||
|
enforcement ladder](https://github.com/mozilla/diversity).
|
||||||
|
|
||||||
|
[homepage]: https://www.contributor-covenant.org
|
||||||
|
|
||||||
|
For answers to common questions about this code of conduct, see the FAQ at
|
||||||
|
https://www.contributor-covenant.org/faq. Translations are available at
|
||||||
|
https://www.contributor-covenant.org/translations.
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
[](/LICENSE) [](/package.json)   [](https://gitpod.io/#https://github.com/sasjs/core)
|
||||||
|
|
||||||
# Macro Core
|
# Macro Core
|
||||||
|
|
||||||
Much quality. Many standards. The **Macro Core** library exists to save time and development effort! Herein ye shall find a veritable host of MIT-licenced, production quality SAS macros. These are a mix of tools, utilities, functions and code generators that are useful in the context of [Application Development](https://sasapps.io) on the SAS platform (eg https://datacontroller.io). [Contributions](https://github.com/sasjs/core/blob/main/CONTRIBUTING.md) are welcomed.
|
Much quality. Many standards. The **Macro Core** library exists to save time and development effort! Herein ye shall find a veritable host of MIT-licenced, production quality SAS macros. These are a mix of tools, utilities, functions and code generators that are useful in the context of [Application Development](https://sasapps.io) on the SAS platform (eg https://datacontroller.io). [Contributions](https://github.com/sasjs/core/blob/main/CONTRIBUTING.md) are welcomed.
|
||||||
|
|||||||
104
all.sas
104
all.sas
@@ -2179,7 +2179,11 @@ Usage:
|
|||||||
@param [in] test= (HASOBS) The test to apply. Valid values are:
|
@param [in] test= (HASOBS) The test to apply. Valid values are:
|
||||||
@li HASOBS - Test is a PASS if the input dataset has any observations
|
@li HASOBS - Test is a PASS if the input dataset has any observations
|
||||||
@li EMPTY - Test is a PASS if input dataset is empty
|
@li EMPTY - Test is a PASS if input dataset is empty
|
||||||
@li EQUALS [integer] - Test passes if obs count matches the provided integer
|
@li EQUALS [integer] - Test passes if row count matches the provided integer
|
||||||
|
@LI ATLEAST [integer] - Test passes if row count is more than or equal to
|
||||||
|
the provided integer
|
||||||
|
@LI ATMOST [integer] - Test passes if row count is less than or equal to
|
||||||
|
the provided integer
|
||||||
@param [out] outds= (work.test_results) The output dataset to contain the
|
@param [out] outds= (work.test_results) The output dataset to contain the
|
||||||
results. If it does not exist, it will be created, with the following format:
|
results. If it does not exist, it will be created, with the following format:
|
||||||
|TEST_DESCRIPTION:$256|TEST_RESULT:$4|TEST_COMMENTS:$256|
|
|TEST_DESCRIPTION:$256|TEST_RESULT:$4|TEST_COMMENTS:$256|
|
||||||
@@ -2188,6 +2192,8 @@ Usage:
|
|||||||
|
|
||||||
<h4> Related Macros </h4>
|
<h4> Related Macros </h4>
|
||||||
@li mp_assertcolvals.sas
|
@li mp_assertcolvals.sas
|
||||||
|
@li mp_assert.sas
|
||||||
|
@li mp_assertcols.sas
|
||||||
|
|
||||||
@version 9.2
|
@version 9.2
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
@@ -2212,6 +2218,22 @@ Usage:
|
|||||||
)
|
)
|
||||||
%let test=EQUALS;
|
%let test=EQUALS;
|
||||||
%end;
|
%end;
|
||||||
|
%else %if %substr(&test.xxxxxxx,1,7)=ATLEAST %then %do;
|
||||||
|
%let val=%scan(&test,2,%str( ));
|
||||||
|
%mp_abort(iftrue= (%DATATYP(&val)=CHAR)
|
||||||
|
,mac=&sysmacroname
|
||||||
|
,msg=%str(Invalid test - &test, expected ATLEAST [integer])
|
||||||
|
)
|
||||||
|
%let test=ATLEAST;
|
||||||
|
%end;
|
||||||
|
%else %if %substr(&test.xxxxxxx,1,7)=ATMOST %then %do;
|
||||||
|
%let val=%scan(&test,2,%str( ));
|
||||||
|
%mp_abort(iftrue= (%DATATYP(&val)=CHAR)
|
||||||
|
,mac=&sysmacroname
|
||||||
|
,msg=%str(Invalid test - &test, expected ATMOST [integer])
|
||||||
|
)
|
||||||
|
%let test=ATMOST;
|
||||||
|
%end;
|
||||||
%else %if &test ne HASOBS and &test ne EMPTY %then %do;
|
%else %if &test ne HASOBS and &test ne EMPTY %then %do;
|
||||||
%mp_abort(
|
%mp_abort(
|
||||||
mac=&sysmacroname,
|
mac=&sysmacroname,
|
||||||
@@ -2223,7 +2245,8 @@ Usage:
|
|||||||
length test_description $256 test_result $4 test_comments $256;
|
length test_description $256 test_result $4 test_comments $256;
|
||||||
test_description=symget('desc');
|
test_description=symget('desc');
|
||||||
test_result='FAIL';
|
test_result='FAIL';
|
||||||
test_comments="&sysmacroname: Dataset &inds has &nobs observations";
|
test_comments="&sysmacroname: Dataset &inds has &nobs observations.";
|
||||||
|
test_comments=test_comments!!" Test was "!!symget('test');
|
||||||
%if &test=HASOBS %then %do;
|
%if &test=HASOBS %then %do;
|
||||||
if &nobs>0 then test_result='PASS';
|
if &nobs>0 then test_result='PASS';
|
||||||
%end;
|
%end;
|
||||||
@@ -2233,6 +2256,12 @@ Usage:
|
|||||||
%else %if &test=EQUALS %then %do;
|
%else %if &test=EQUALS %then %do;
|
||||||
if &nobs=&val then test_result='PASS';
|
if &nobs=&val then test_result='PASS';
|
||||||
%end;
|
%end;
|
||||||
|
%else %if &test=ATLEAST %then %do;
|
||||||
|
if &nobs ge &val then test_result='PASS';
|
||||||
|
%end;
|
||||||
|
%else %if &test=ATMOST %then %do;
|
||||||
|
if &nobs le &val then test_result='PASS';
|
||||||
|
%end;
|
||||||
%else %do;
|
%else %do;
|
||||||
test_comments="&sysmacroname: Unsatisfied test condition - &test";
|
test_comments="&sysmacroname: Unsatisfied test condition - &test";
|
||||||
%end;
|
%end;
|
||||||
@@ -2247,7 +2276,7 @@ Usage:
|
|||||||
proc sql;
|
proc sql;
|
||||||
drop table &ds;
|
drop table &ds;
|
||||||
|
|
||||||
%mend;/**
|
%mend mp_assertdsobs;/**
|
||||||
@file
|
@file
|
||||||
@brief Copy any file using binary input / output streams
|
@brief Copy any file using binary input / output streams
|
||||||
@details Reads in a file byte by byte and writes it back out. Is an
|
@details Reads in a file byte by byte and writes it back out. Is an
|
||||||
@@ -16932,14 +16961,22 @@ 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 is required.
|
@details When building apps on SAS Viya, an client id and secret are sometimes
|
||||||
This macro will obtain the Consul Token and use that to call the Web Service.
|
required. In order to generate them, filesystem access to the Consul Token
|
||||||
|
is needed (it is not enough to be in the SASAdministrator group in SAS
|
||||||
|
Environment Manager).
|
||||||
|
|
||||||
more info: https://developer.sas.com/reference/auth/#register
|
If you are registering a lot of clients / secrets, you may find it more
|
||||||
and:
|
convenient to use the [Viya Token Generator]
|
||||||
http://proc-x.com/2019/01/authentication-to-sas-viya-a-couple-of-approaches
|
(https://sasjs.io/apps/#viya-client-token-generator) (a SASjs Web App to
|
||||||
|
automate the generation of clients & secrets with various settings).
|
||||||
|
|
||||||
The default viyaroot location is /opt/sas/viya/config
|
For further information on clients / secrets, see;
|
||||||
|
@li https://developer.sas.com/reference/auth/#register
|
||||||
|
@li https://proc-x.com/2019/01/authentication-to-sas-viya-a-couple-of-approaches
|
||||||
|
@li https://cli.sasjs.io/faq/#how-can-i-obtain-a-viya-client-and-secret
|
||||||
|
|
||||||
|
The default viyaroot location is: `/opt/sas/viya/config`
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
|
|
||||||
@@ -16964,16 +17001,19 @@ run;
|
|||||||
)
|
)
|
||||||
|
|
||||||
@param client_id= The client name. Auto generated if blank.
|
@param client_id= The client name. Auto generated if blank.
|
||||||
@param client_secret= Client secret Auto generated if client is blank.
|
@param client_secret= Client secret. Auto generated if client is blank.
|
||||||
@param scopes= list of space-seperated unquoted scopes (default is openid)
|
@param scopes=(openid) List of space-seperated unquoted scopes
|
||||||
@param grant_type= valid values are "password" or "authorization_code"
|
@param grant_type=(authorization_code|refresh_token) Valid values are
|
||||||
(unquoted)
|
"password" or "authorization_code" (unquoted)
|
||||||
@param outds= the dataset to contain the registered client id and secret
|
@param outds=(mv_registerclient) The dataset to contain the registered client
|
||||||
@param access_token_validity= The duration of validity of the access token
|
id and secret
|
||||||
in seconds. A value of DEFAULT will omit the entry (and use system default)
|
@param access_token_validity=(DEFAULT) The duration of validity of the access
|
||||||
@param refresh_token_validity= The duration of validity of the refresh token
|
token in seconds. A value of DEFAULT will omit the entry (and use system
|
||||||
in seconds. A value of DEFAULT will omit the entry (and use system default)
|
default)
|
||||||
@param name= A human readable name for the client
|
@param refresh_token_validity=(DEFAULT) The duration of validity of the
|
||||||
|
refresh token in seconds. A value of DEFAULT will omit the entry (and use
|
||||||
|
system default)
|
||||||
|
@param name= An optional, human readable name for the client
|
||||||
@param required_user_groups= A list of group names. If a user does not belong
|
@param required_user_groups= A list of group names. If a user does not belong
|
||||||
to all the required groups, the user will not be authenticated and no tokens
|
to all the required groups, the user will not be authenticated and no tokens
|
||||||
are issued to this client for that user. If this field is not specified,
|
are issued to this client for that user. If this field is not specified,
|
||||||
@@ -16982,8 +17022,8 @@ run;
|
|||||||
apply. Setting this to true will autoapprove all the client scopes.
|
apply. Setting this to true will autoapprove all the client scopes.
|
||||||
@param use_session= If true, access tokens issued to this client will be
|
@param 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= A dataset containing the lines of JSON submitted. Useful
|
@param outjson= (_null_) A dataset containing the lines of JSON submitted.
|
||||||
for debugging. Default= _null_.
|
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
|
||||||
@@ -17020,12 +17060,26 @@ run;
|
|||||||
options noquotelenmax;
|
options noquotelenmax;
|
||||||
/* first, get consul token needed to get client id / secret */
|
/* first, get consul token needed to get client id / secret */
|
||||||
%let tokloc=/etc/SASSecurityCertificateFramework/tokens/consul/default;
|
%let tokloc=/etc/SASSecurityCertificateFramework/tokens/consul/default;
|
||||||
|
%let tokloc=%mf_loc(VIYACONFIG)&tokloc/client.token;
|
||||||
|
|
||||||
|
|
||||||
|
%mp_abort(iftrue=(%sysfunc(fileexist(&tokloc))=0)
|
||||||
|
,mac=&sysmacroname
|
||||||
|
,msg=%str(Unable to access the consul token at &tokloc)
|
||||||
|
)
|
||||||
|
|
||||||
|
%let consul_token=0;
|
||||||
data _null_;
|
data _null_;
|
||||||
infile "%mf_loc(VIYACONFIG)&tokloc/client.token";
|
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")
|
||||||
|
,mac=&sysmacroname
|
||||||
|
,msg=%str(Unable to source the consul token from &tokloc)
|
||||||
|
)
|
||||||
|
|
||||||
%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);
|
||||||
|
|
||||||
@@ -17172,7 +17226,7 @@ filename &fname2 clear;
|
|||||||
filename &fname3 clear;
|
filename &fname3 clear;
|
||||||
libname &libref clear;
|
libname &libref clear;
|
||||||
|
|
||||||
%mend;
|
%mend mv_registerclient;
|
||||||
/**
|
/**
|
||||||
@file mv_tokenauth.sas
|
@file mv_tokenauth.sas
|
||||||
@brief Get initial Refresh and Access Tokens
|
@brief Get initial Refresh and Access Tokens
|
||||||
@@ -17693,6 +17747,8 @@ filename &fref1 clear;
|
|||||||
%macro ml_json();
|
%macro ml_json();
|
||||||
data _null_;
|
data _null_;
|
||||||
file "%sysfunc(pathname(work))/ml_json.lua";
|
file "%sysfunc(pathname(work))/ml_json.lua";
|
||||||
|
put '-- NOTE - THE COPYRIGHT BELOW IS IN RELATION TO THE JSON.LUA FILE ONLY ';
|
||||||
|
put '-- THIS FILE STARTS ON THE NEXT LINE AND WILL FINISH WITH "JSON.LUA ENDS HERE" ';
|
||||||
put '-- ';
|
put '-- ';
|
||||||
put '-- json.lua ';
|
put '-- json.lua ';
|
||||||
put '-- ';
|
put '-- ';
|
||||||
@@ -18064,6 +18120,8 @@ data _null_;
|
|||||||
put 'end ';
|
put 'end ';
|
||||||
put ' ';
|
put ' ';
|
||||||
put 'return json ';
|
put 'return json ';
|
||||||
|
put ' ';
|
||||||
|
put '-- JSON.LUA ENDS HERE ';
|
||||||
run;
|
run;
|
||||||
|
|
||||||
%inc "%sysfunc(pathname(work))/ml_json.lua";
|
%inc "%sysfunc(pathname(work))/ml_json.lua";
|
||||||
|
|||||||
@@ -18,7 +18,11 @@
|
|||||||
@param [in] test= (HASOBS) The test to apply. Valid values are:
|
@param [in] test= (HASOBS) The test to apply. Valid values are:
|
||||||
@li HASOBS - Test is a PASS if the input dataset has any observations
|
@li HASOBS - Test is a PASS if the input dataset has any observations
|
||||||
@li EMPTY - Test is a PASS if input dataset is empty
|
@li EMPTY - Test is a PASS if input dataset is empty
|
||||||
@li EQUALS [integer] - Test passes if obs count matches the provided integer
|
@li EQUALS [integer] - Test passes if row count matches the provided integer
|
||||||
|
@LI ATLEAST [integer] - Test passes if row count is more than or equal to
|
||||||
|
the provided integer
|
||||||
|
@LI ATMOST [integer] - Test passes if row count is less than or equal to
|
||||||
|
the provided integer
|
||||||
@param [out] outds= (work.test_results) The output dataset to contain the
|
@param [out] outds= (work.test_results) The output dataset to contain the
|
||||||
results. If it does not exist, it will be created, with the following format:
|
results. If it does not exist, it will be created, with the following format:
|
||||||
|TEST_DESCRIPTION:$256|TEST_RESULT:$4|TEST_COMMENTS:$256|
|
|TEST_DESCRIPTION:$256|TEST_RESULT:$4|TEST_COMMENTS:$256|
|
||||||
@@ -27,6 +31,8 @@
|
|||||||
|
|
||||||
<h4> Related Macros </h4>
|
<h4> Related Macros </h4>
|
||||||
@li mp_assertcolvals.sas
|
@li mp_assertcolvals.sas
|
||||||
|
@li mp_assert.sas
|
||||||
|
@li mp_assertcols.sas
|
||||||
|
|
||||||
@version 9.2
|
@version 9.2
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
@@ -51,6 +57,22 @@
|
|||||||
)
|
)
|
||||||
%let test=EQUALS;
|
%let test=EQUALS;
|
||||||
%end;
|
%end;
|
||||||
|
%else %if %substr(&test.xxxxxxx,1,7)=ATLEAST %then %do;
|
||||||
|
%let val=%scan(&test,2,%str( ));
|
||||||
|
%mp_abort(iftrue= (%DATATYP(&val)=CHAR)
|
||||||
|
,mac=&sysmacroname
|
||||||
|
,msg=%str(Invalid test - &test, expected ATLEAST [integer])
|
||||||
|
)
|
||||||
|
%let test=ATLEAST;
|
||||||
|
%end;
|
||||||
|
%else %if %substr(&test.xxxxxxx,1,7)=ATMOST %then %do;
|
||||||
|
%let val=%scan(&test,2,%str( ));
|
||||||
|
%mp_abort(iftrue= (%DATATYP(&val)=CHAR)
|
||||||
|
,mac=&sysmacroname
|
||||||
|
,msg=%str(Invalid test - &test, expected ATMOST [integer])
|
||||||
|
)
|
||||||
|
%let test=ATMOST;
|
||||||
|
%end;
|
||||||
%else %if &test ne HASOBS and &test ne EMPTY %then %do;
|
%else %if &test ne HASOBS and &test ne EMPTY %then %do;
|
||||||
%mp_abort(
|
%mp_abort(
|
||||||
mac=&sysmacroname,
|
mac=&sysmacroname,
|
||||||
@@ -62,7 +84,8 @@
|
|||||||
length test_description $256 test_result $4 test_comments $256;
|
length test_description $256 test_result $4 test_comments $256;
|
||||||
test_description=symget('desc');
|
test_description=symget('desc');
|
||||||
test_result='FAIL';
|
test_result='FAIL';
|
||||||
test_comments="&sysmacroname: Dataset &inds has &nobs observations";
|
test_comments="&sysmacroname: Dataset &inds has &nobs observations.";
|
||||||
|
test_comments=test_comments!!" Test was "!!symget('test');
|
||||||
%if &test=HASOBS %then %do;
|
%if &test=HASOBS %then %do;
|
||||||
if &nobs>0 then test_result='PASS';
|
if &nobs>0 then test_result='PASS';
|
||||||
%end;
|
%end;
|
||||||
@@ -72,6 +95,12 @@
|
|||||||
%else %if &test=EQUALS %then %do;
|
%else %if &test=EQUALS %then %do;
|
||||||
if &nobs=&val then test_result='PASS';
|
if &nobs=&val then test_result='PASS';
|
||||||
%end;
|
%end;
|
||||||
|
%else %if &test=ATLEAST %then %do;
|
||||||
|
if &nobs ge &val then test_result='PASS';
|
||||||
|
%end;
|
||||||
|
%else %if &test=ATMOST %then %do;
|
||||||
|
if &nobs le &val then test_result='PASS';
|
||||||
|
%end;
|
||||||
%else %do;
|
%else %do;
|
||||||
test_comments="&sysmacroname: Unsatisfied test condition - &test";
|
test_comments="&sysmacroname: Unsatisfied test condition - &test";
|
||||||
%end;
|
%end;
|
||||||
@@ -86,4 +115,4 @@
|
|||||||
proc sql;
|
proc sql;
|
||||||
drop table &ds;
|
drop table &ds;
|
||||||
|
|
||||||
%mend;
|
%mend mp_assertdsobs;
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
-- NOTE - THE COPYRIGHT BELOW IS IN RELATION TO THE JSON.LUA FILE ONLY
|
||||||
|
-- THIS FILE STARTS ON THE NEXT LINE AND WILL FINISH WITH "JSON.LUA ENDS HERE"
|
||||||
--
|
--
|
||||||
-- json.lua
|
-- json.lua
|
||||||
--
|
--
|
||||||
@@ -369,3 +371,5 @@ function json.decode(str)
|
|||||||
end
|
end
|
||||||
|
|
||||||
return json
|
return json
|
||||||
|
|
||||||
|
-- JSON.LUA ENDS HERE
|
||||||
@@ -12,6 +12,8 @@
|
|||||||
%macro ml_json();
|
%macro ml_json();
|
||||||
data _null_;
|
data _null_;
|
||||||
file "%sysfunc(pathname(work))/ml_json.lua";
|
file "%sysfunc(pathname(work))/ml_json.lua";
|
||||||
|
put '-- NOTE - THE COPYRIGHT BELOW IS IN RELATION TO THE JSON.LUA FILE ONLY ';
|
||||||
|
put '-- THIS FILE STARTS ON THE NEXT LINE AND WILL FINISH WITH "JSON.LUA ENDS HERE" ';
|
||||||
put '-- ';
|
put '-- ';
|
||||||
put '-- json.lua ';
|
put '-- json.lua ';
|
||||||
put '-- ';
|
put '-- ';
|
||||||
@@ -383,6 +385,8 @@ data _null_;
|
|||||||
put 'end ';
|
put 'end ';
|
||||||
put ' ';
|
put ' ';
|
||||||
put 'return json ';
|
put 'return json ';
|
||||||
|
put ' ';
|
||||||
|
put '-- JSON.LUA ENDS HERE ';
|
||||||
run;
|
run;
|
||||||
|
|
||||||
%inc "%sysfunc(pathname(work))/ml_json.lua";
|
%inc "%sysfunc(pathname(work))/ml_json.lua";
|
||||||
|
|||||||
@@ -24,10 +24,9 @@
|
|||||||
"url": "https://github.com/sasjs/core/issues"
|
"url": "https://github.com/sasjs/core/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/sasjs/core#readme",
|
"homepage": "https://github.com/sasjs/core#readme",
|
||||||
"version": "1.0.0",
|
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "sasjs test -t viya",
|
||||||
"docs": "sasjs doc && ./sasjs/utils/build.sh"
|
"docs": "sasjs doc && ./sasjs/utils/build.sh"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
60
tests/base/mp_assertdsobs.test.sas
Normal file
60
tests/base/mp_assertdsobs.test.sas
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@brief Testing mp_assertdsobs.sas macro
|
||||||
|
|
||||||
|
<h4> SAS Macros </h4>
|
||||||
|
@li mp_assertdsobs.sas
|
||||||
|
@li mp_assertcolvals.sas
|
||||||
|
|
||||||
|
**/
|
||||||
|
|
||||||
|
|
||||||
|
data work.somedata;
|
||||||
|
do x=1 to 15;
|
||||||
|
output;
|
||||||
|
end;
|
||||||
|
run;
|
||||||
|
|
||||||
|
%mp_assertdsobs(work.somedata,
|
||||||
|
test=ATLEAST 15,
|
||||||
|
outds=work.test_the_test
|
||||||
|
)
|
||||||
|
|
||||||
|
%mp_assertdsobs(work.somedata,
|
||||||
|
test=ATMOST 15,
|
||||||
|
outds=work.test_the_test
|
||||||
|
)
|
||||||
|
|
||||||
|
data work.check;
|
||||||
|
val='PASS';
|
||||||
|
run;
|
||||||
|
%mp_assertcolvals(work.test_the_test.test_result,
|
||||||
|
checkvals=work.check.val,
|
||||||
|
desc=Testing ATLEAST / ATMOST for passes,
|
||||||
|
test=ALLVALS
|
||||||
|
)
|
||||||
|
|
||||||
|
%mp_assertdsobs(work.somedata,
|
||||||
|
test=ATLEAST 16,
|
||||||
|
outds=work.test_the_test2
|
||||||
|
)
|
||||||
|
%mp_assertdsobs(work.somedata,
|
||||||
|
test=ATMOST 14,
|
||||||
|
outds=work.test_the_test2
|
||||||
|
)
|
||||||
|
|
||||||
|
data _null_;
|
||||||
|
set work.test_the_test2;
|
||||||
|
putlog (_all_)(=);
|
||||||
|
run;
|
||||||
|
|
||||||
|
data work.check2;
|
||||||
|
val='FAIL';
|
||||||
|
run;
|
||||||
|
%mp_assertcolvals(work.test_the_test2.test_result,
|
||||||
|
checkvals=work.check2.val,
|
||||||
|
desc=Testing ATLEAST / ATMOST for failures,
|
||||||
|
test=ALLVALS
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
36
tests/viya/mv_registerclient.test.1.sas
Normal file
36
tests/viya/mv_registerclient.test.1.sas
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
|
||||||
|
@file
|
||||||
|
@brief Testing mv_registerclient.sas macro
|
||||||
|
@details Tests for successful registration. For this to work, the test
|
||||||
|
account must be an admin.
|
||||||
|
|
||||||
|
<h4> SAS Macros </h4>
|
||||||
|
@li mf_getuniquename.sas
|
||||||
|
@li mp_assertcolvals.sas
|
||||||
|
@li mv_registerclient.sas
|
||||||
|
|
||||||
|
**/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test Case 1
|
||||||
|
*/
|
||||||
|
|
||||||
|
%let id=%mf_getuniquename();
|
||||||
|
%let sec=%mf_getuniquename();
|
||||||
|
%mv_registerclient(client_id=&id,client_secret=&sec, outds=testds)
|
||||||
|
|
||||||
|
data work.checkds;
|
||||||
|
id="&id";
|
||||||
|
sec="&sec";
|
||||||
|
run;
|
||||||
|
%mp_assertcolvals(work.testds.client_id,
|
||||||
|
checkvals=work.checkds.id,
|
||||||
|
desc=Checking client id was created
|
||||||
|
test=ALLVALS
|
||||||
|
)
|
||||||
|
%mp_assertcolvals(work.testds.client_secret,
|
||||||
|
checkvals=work.checkds.sec,
|
||||||
|
desc=Checking client secret was created
|
||||||
|
test=ALLVALS
|
||||||
|
)
|
||||||
43
tests/viya/mv_registerclient.test.2.sas
Normal file
43
tests/viya/mv_registerclient.test.2.sas
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
|
||||||
|
@file
|
||||||
|
@brief Testing mv_registerclient.sas macro
|
||||||
|
@details Tests for unsuccessful registration. To do this, overrides are
|
||||||
|
applied for the mf_loc.sas and mp_abort.sas macros.
|
||||||
|
|
||||||
|
<h4> SAS Macros </h4>
|
||||||
|
@li mp_assert.sas
|
||||||
|
@li mv_registerclient.sas
|
||||||
|
|
||||||
|
**/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test Case
|
||||||
|
*/
|
||||||
|
|
||||||
|
%macro mf_loc(param);
|
||||||
|
/does/not/exist
|
||||||
|
%mend mf_loc;
|
||||||
|
|
||||||
|
%macro mp_abort(iftrue=,mac=mp_abort.sas, type=, msg=);
|
||||||
|
%if not(%eval(%unquote(&iftrue))) %then %return;
|
||||||
|
%put %substr(&msg,1,16);
|
||||||
|
%mp_assert(
|
||||||
|
iftrue=("%substr(&msg,1,16)"="Unable to access"),
|
||||||
|
desc=Check that abort happens when consul token is unavailable
|
||||||
|
)
|
||||||
|
%webout(OPEN)
|
||||||
|
%webout(OBJ, TEST_RESULTS)
|
||||||
|
%webout(CLOSE)
|
||||||
|
%let syscc=0;
|
||||||
|
data _null_;
|
||||||
|
abort cancel nolist;
|
||||||
|
run;
|
||||||
|
%mend mp_abort;
|
||||||
|
|
||||||
|
%mv_registerclient( outds=testds)
|
||||||
|
|
||||||
|
%mp_assert(
|
||||||
|
iftrue=(0=1),
|
||||||
|
desc=Check that abort happens when consul token is unavailable
|
||||||
|
)
|
||||||
@@ -1,14 +1,22 @@
|
|||||||
/**
|
/**
|
||||||
@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 is required.
|
@details When building apps on SAS Viya, an client id and secret are sometimes
|
||||||
This macro will obtain the Consul Token and use that to call the Web Service.
|
required. In order to generate them, filesystem access to the Consul Token
|
||||||
|
is needed (it is not enough to be in the SASAdministrator group in SAS
|
||||||
|
Environment Manager).
|
||||||
|
|
||||||
more info: https://developer.sas.com/reference/auth/#register
|
If you are registering a lot of clients / secrets, you may find it more
|
||||||
and:
|
convenient to use the [Viya Token Generator]
|
||||||
http://proc-x.com/2019/01/authentication-to-sas-viya-a-couple-of-approaches
|
(https://sasjs.io/apps/#viya-client-token-generator) (a SASjs Web App to
|
||||||
|
automate the generation of clients & secrets with various settings).
|
||||||
|
|
||||||
The default viyaroot location is /opt/sas/viya/config
|
For further information on clients / secrets, see;
|
||||||
|
@li https://developer.sas.com/reference/auth/#register
|
||||||
|
@li https://proc-x.com/2019/01/authentication-to-sas-viya-a-couple-of-approaches
|
||||||
|
@li https://cli.sasjs.io/faq/#how-can-i-obtain-a-viya-client-and-secret
|
||||||
|
|
||||||
|
The default viyaroot location is: `/opt/sas/viya/config`
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
|
|
||||||
@@ -33,16 +41,19 @@
|
|||||||
)
|
)
|
||||||
|
|
||||||
@param client_id= The client name. Auto generated if blank.
|
@param client_id= The client name. Auto generated if blank.
|
||||||
@param client_secret= Client secret Auto generated if client is blank.
|
@param client_secret= Client secret. Auto generated if client is blank.
|
||||||
@param scopes= list of space-seperated unquoted scopes (default is openid)
|
@param scopes=(openid) List of space-seperated unquoted scopes
|
||||||
@param grant_type= valid values are "password" or "authorization_code"
|
@param grant_type=(authorization_code|refresh_token) Valid values are
|
||||||
(unquoted)
|
"password" or "authorization_code" (unquoted)
|
||||||
@param outds= the dataset to contain the registered client id and secret
|
@param outds=(mv_registerclient) The dataset to contain the registered client
|
||||||
@param access_token_validity= The duration of validity of the access token
|
id and secret
|
||||||
in seconds. A value of DEFAULT will omit the entry (and use system default)
|
@param access_token_validity=(DEFAULT) The duration of validity of the access
|
||||||
@param refresh_token_validity= The duration of validity of the refresh token
|
token in seconds. A value of DEFAULT will omit the entry (and use system
|
||||||
in seconds. A value of DEFAULT will omit the entry (and use system default)
|
default)
|
||||||
@param name= A human readable name for the client
|
@param refresh_token_validity=(DEFAULT) The duration of validity of the
|
||||||
|
refresh token in seconds. A value of DEFAULT will omit the entry (and use
|
||||||
|
system default)
|
||||||
|
@param name= An optional, human readable name for the client
|
||||||
@param required_user_groups= A list of group names. If a user does not belong
|
@param required_user_groups= A list of group names. If a user does not belong
|
||||||
to all the required groups, the user will not be authenticated and no tokens
|
to all the required groups, the user will not be authenticated and no tokens
|
||||||
are issued to this client for that user. If this field is not specified,
|
are issued to this client for that user. If this field is not specified,
|
||||||
@@ -51,8 +62,8 @@
|
|||||||
apply. Setting this to true will autoapprove all the client scopes.
|
apply. Setting this to true will autoapprove all the client scopes.
|
||||||
@param use_session= If true, access tokens issued to this client will be
|
@param 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= A dataset containing the lines of JSON submitted. Useful
|
@param outjson= (_null_) A dataset containing the lines of JSON submitted.
|
||||||
for debugging. Default= _null_.
|
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
|
||||||
@@ -89,12 +100,26 @@
|
|||||||
options noquotelenmax;
|
options noquotelenmax;
|
||||||
/* first, get consul token needed to get client id / secret */
|
/* first, get consul token needed to get client id / secret */
|
||||||
%let tokloc=/etc/SASSecurityCertificateFramework/tokens/consul/default;
|
%let tokloc=/etc/SASSecurityCertificateFramework/tokens/consul/default;
|
||||||
|
%let tokloc=%mf_loc(VIYACONFIG)&tokloc/client.token;
|
||||||
|
|
||||||
|
|
||||||
|
%mp_abort(iftrue=(%sysfunc(fileexist(&tokloc))=0)
|
||||||
|
,mac=&sysmacroname
|
||||||
|
,msg=%str(Unable to access the consul token at &tokloc)
|
||||||
|
)
|
||||||
|
|
||||||
|
%let consul_token=0;
|
||||||
data _null_;
|
data _null_;
|
||||||
infile "%mf_loc(VIYACONFIG)&tokloc/client.token";
|
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")
|
||||||
|
,mac=&sysmacroname
|
||||||
|
,msg=%str(Unable to source the consul token from &tokloc)
|
||||||
|
)
|
||||||
|
|
||||||
%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);
|
||||||
|
|
||||||
@@ -241,4 +266,4 @@ filename &fname2 clear;
|
|||||||
filename &fname3 clear;
|
filename &fname3 clear;
|
||||||
libname &libref clear;
|
libname &libref clear;
|
||||||
|
|
||||||
%mend;
|
%mend mv_registerclient;
|
||||||
|
|||||||
Reference in New Issue
Block a user