Skip to content

sasjs fs

The sasjs fs command allows users to manage a remote SAS (physical) filesystem from a local machine. No SSH or FTP accounts are necessary - the tool makes use of the APIs (Viya, SASjs Server) or a SAS EBI STP runner.

Not to be confused with:

  • the logical SAS Folders (eg SAS Drive or Metadata BIP Tree), managed using the sasjs folder command.
  • the regular sasjs compile command, which creates self-contained Jobs / Services / Tests that do not require a filesystem.

It is also possible to use this feature natively within the SASjs VSCode Extension - https://github.com/sasjs/vscode-extension#directory-synchronisation.

Prerequisites

Before using this command, you will need to:

  • install the SASjs CLI
  • add a deployment target (for all operations except sasjs fs compile)

Syntax

sasjs fs <action> [additional arguments]

Additional arguments may include:

  • --target (alias -t) - the target SAS Environment which contains the filesystem. Required attributes are serverUrl and serverType. If not specified, default target will be used, mentioned in sasjsconfig.json. The target can exist either in the local project configuration or in the global .sasjsrc file.

sasjs fs compile

Used to generate a single SAS program that contains all the files (and subdirectories) of a given LOCAL folder. This program can be executed in any flavour of SAS to generate the files on the SAS server - simply set %let fsTarget=/your/target/folder; and run the program.

To preserve encoding and special characters, as well as to enable the compilation of binary content, all files are base64 encoded.

Syntax

sasjs fs compile <localFolder> -o <programPath>

Usage

sasjs fs compile C:\my\project -o C:\temp\deployme.sas

An extract from such a compiled program is shown below:

%mf_mkdir(&fsTarget)

filename _in64 temp lrecl=99999999;
data _null_;
file _in64;
 put 'ewogICIkc2NoZW1hIjogImh0dHBzOi8vY2xpLnNhc2pzLmlvL3Nhc2pzY29uZmlnLXNjaGVtYS5qc29uIiwKICAiZG9jQ29uZmlnIjogewogICAgImVuYWJsZUxpbmVhZ2UiOiB0cnVlLAogICAgImRveHlDb250ZW50IjogewogICAgICAicmVhZE1lIjogIi4uLy4uL1JFQURNRS5tZCIKICAg'@;
 put 'IH0sCiAgICAib3V0RGlyZWN0b3J5IjogInB1YmxpYy9kb2NzL3N0YXRpYy9zYXMiCiAgfSwKICAibWFjcm9Gb2xkZXJzIjogWyJzYXNqcy9tYWNyb3MiXSwKICAicHJvZ3JhbUZvbGRlcnMiOiBbXSwKICAic2VydmljZUNvbmZpZyI6IHsKICAgICJzZXJ2aWNlRm9sZGVycyI6IFsic2FzanMv'@;
 put 'c2VydmljZXMvY29tbW9uIiwgInNhc2pzL3NlcnZpY2VzL2ZpbGVzIl0sCiAgICAiaW5pdFByb2dyYW0iOiAic2FzanMvc2VydmljZXMvc2VydmljZWluaXQuc2FzIgogIH0sCiAgInN0cmVhbUNvbmZpZyI6IHsKICAgICJzdHJlYW1XZWIiOiB0cnVlLAogICAgInN0cmVhbVdlYkZvbGRlciI6'@;
 put 'ICJ3ZWJ2IiwKICAgICJ3ZWJTb3VyY2VQYXRoIjogImJ1aWxkIgogIH0sCiAgImRlZmF1bHRUYXJnZXQiOiAidml5YSIsCiAgInRhcmdldHMiOiBbCiAgICB7CiAgICAgICJuYW1lIjogInZpeWEiLAogICAgICAic2VydmVyVXJsIjogImh0dHBzOi8vYXp1cmV1c2UwMTEwNTkubXktdHJpYWxz'@;
 put 'LnNhcy5jb20vIiwKICAgICAgInNlcnZlclR5cGUiOiAiU0FTVklZQSIsCiAgICAgICJhcHBMb2MiOiAiL1B1YmxpYy9hcHAvcmVhY3Qtc2VlZC1hcHAiLAogICAgICAiY29udGV4dE5hbWUiOiAiU0FTIEpvYiBFeGVjdXRpb24gY29tcHV0ZSBjb250ZXh0IgogICAgfSwKICAgIHsKICAgICAg'@;
 put 'Im5hbWUiOiAic2VydmVyIiwKICAgICAgInNlcnZlclVybCI6ICJodHRwOi8vbG9jYWxob3N0OjUwMDAiLAogICAgICAic2VydmVyVHlwZSI6ICJTQVNKUyIsCiAgICAgICJodHRwc0FnZW50T3B0aW9ucyI6IHsKICAgICAgICAiYWxsb3dJbnNlY3VyZVJlcXVlc3RzIjogZmFsc2UKICAgICAg'@;
 put 'fSwKICAgICAgImFwcExvYyI6ICIvUHVibGljL2FwcC9yZWFjdC1zZWVkLWFwcCIsCiAgICAgICJkZXBsb3lDb25maWciOiB7CiAgICAgICAgImRlcGxveVNlcnZpY2VQYWNrIjogdHJ1ZSwKICAgICAgICAiZGVwbG95U2NyaXB0cyI6IFtdCiAgICAgIH0sCiAgICAgICJzdHJlYW1Db25maWci'@;
 put 'OiB7CiAgICAgICAgInN0cmVhbUxvZ28iOiAibG9nbzUxMi5wbmciLAogICAgICAgICJzdHJlYW1TZXJ2aWNlTmFtZSI6ICJSZWFjdCIsCiAgICAgICAgInN0cmVhbVdlYiI6IHRydWUsCiAgICAgICAgInN0cmVhbVdlYkZvbGRlciI6ICJ3ZWIiLAogICAgICAgICJ3ZWJTb3VyY2VQYXRoIjog'@;
 put 'ImJ1aWxkIiwKICAgICAgICAiYXNzZXRQYXRocyI6IFtdCiAgICAgIH0KICAgIH0sCiAgICB7CiAgICAgICJuYW1lIjogInNhczkiLAogICAgICAic2VydmVyVHlwZSI6ICJTQVM5IiwKICAgICAgImFwcExvYyI6ICIvU2hhcmVkIERhdGEvc2FzanMvcmVhY3Qtc2VlZC1hcHAiLAogICAgICAi'@;
 put 'ZGVwbG95Q29uZmlnIjogewogICAgICAgICJkZXBsb3lTZXJ2aWNlUGFjayI6IHRydWUKICAgICAgfSwKICAgICAgInNlcnZlck5hbWUiOiAiU0FTQXBwIiwKICAgICAgInJlcG9zaXRvcnlOYW1lIjogIkZvdW5kYXRpb24iCiAgICB9CiAgXQp9Cg==';
run;

filename _out64 "&fsTarget/sasjsconfig.json";

/* convert from base64 */
data _null_;
  length filein 8 fileout 8;
  filein = fopen("_in64",'I',4,'B');
  fileout = fopen("_out64",'O',3,'B');
  char= '20'x;
  do while(fread(filein)=0);
    length raw $4 ;
    do i=1 to 4;
      rc=fget(filein,char,1);
      substr(raw,i,1)=char;
    end;
    rc = fput(fileout, input(raw,$base64X4.));
    rc =fwrite(fileout);
  end;
  rc = fclose(filein);
  rc = fclose(fileout);
run;

filename _in64 clear;
filename _out64 clear;

sasjs fs sync

Will hash up a remote SAS filesystem, compare with local hashes, and deploy only the differences. Will also create the folder(s) on the remote server if necessary (if the SAS user account has the requisite permissions).

Here's a demo video:

Syntax

sasjs fs sync <localFolder> <remoteFolder> -t targetName

Can also be used without the arguments, taking values from the sasjsconfig.json file, eg:

sasjs fs sync

or

sasjs fs sync -t myTarget

Known Limitations

The following issues exist with the current implementation - they do not affect the functionality (syncing local directory with remote) but we do plan to address them in a future release:

  • If remoteFolder path is relative, or contains a tilde (eg ./somedir or ~/somedir), the terminal response will incorrectly state that there were sync issues. This is due to path expansion affecting the comparison. In fact the files were deployed successfully.
  • If a file is deleted locally, it will not be deleted remotely. This part has not been built yet.
  • If a file in a subdirectory is renamed it will not be re-synced. This is due to hash compares at folder level being based only on file content. We will change the algorithm to include filenames when hashing folder content into a folder hash.