Compare commits

...
Author SHA1 Message Date
dc 2a8ddab791 fix(deploy): ask for the admin group before the staging directory on SASjs
The SASjs Server / SAS9 screen put the DC Staging Directory first and the admin
group second, the reverse of the Viya screen, so the operator was asked to pick
a location before being told who administers DC. It now asks for the admin group
first and the directory last, with the copy reordered to match.

A rendered-order spec covers it: it reads the labels off the rendered template
and fails if the directory comes first again.
2026-11-05 18:21:18 +00:00
dc 32df2d75a6 fix(makedata): let the browse actions live as long as the service does
DIRLIST and WRITECHECK were refused once the appLoc had a settings.sas, on the
argument that a failed run could leave that file behind. That is what makes the
gate wrong: a failed run is exactly when the deploy screen still needs to list
and probe directories, and any appLoc configured earlier refuses them for good.
The window needs no separate clock - a successful run deletes this program, so
once DC is configured there is nothing left here to call.

Removed from both variants (server checks the drive over HTTP, Viya checks the
filesystem; neither is needed), and the two tests that asserted the refusal go
with it. The macros themselves are covered by mpe_dirlist.test.sas and
mpe_writecheck.test.sas.

Seen on sas.4gl.io: a deployment to /Public/app/dc477 answered BROWSE with
"DIRLIST and WRITECHECK are only available until DC is configured".
2026-11-05 18:21:15 +00:00
5 changed files with 124 additions and 110 deletions

No files matched your search

@@ -11,23 +11,6 @@
To configure Data Controller for SAS©, please provide the following details:
</p>
<p class="m-0 mt-10">
Please specify a physical directory (on the
<strong> {{ SYSHOSTNAME }}</strong>
compute server) below, to which user
<strong>{{ SYSUSERID }}</strong> can write, on behalf of Data Controller.
</p>
<label for="dcDirectory" class="mt-20 clr-control-label"
>DC Staging Directory</label
>
<div class="mb-10 clr-control-container">
<app-dc-location
[(path)]="dcDirectory"
inputId="dcDirectory"
></app-dc-location>
</div>
<p class="m-0 mt-10">
Below are the list of groups to which you belong. The group you select will
become the Data Controller Admin Group, and everyone in it will have
@@ -45,6 +28,23 @@
</select>
</clr-select-container>
<p class="m-0 mt-10">
Now specify a physical directory (on the
<strong> {{ SYSHOSTNAME }}</strong>
compute server) below, to which user
<strong>{{ SYSUSERID }}</strong> can write, on behalf of Data Controller.
</p>
<label for="dcDirectory" class="mt-20 clr-control-label"
>DC Staging Directory</label
>
<div class="mb-10 clr-control-container">
<app-dc-location
[(path)]="dcDirectory"
inputId="dcDirectory"
></app-dc-location>
</div>
<p class="m-0 mt-10">
Now, create the target directory and deploy the control library using the
admin group selected above:
@@ -1,4 +1,10 @@
import { Component, NO_ERRORS_SCHEMA } from '@angular/core'
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { FormsModule } from '@angular/forms'
import { ServerType } from '@sasjs/utils/types/serverType'
import { SasjsService } from 'src/app/services/sasjs.service'
import { SasService } from 'src/app/services/sas.service'
import { DcLocationComponent } from '../dc-location/dc-location.component'
import { SasjsConfiguratorComponent } from './sasjs-configurator.component'
/**
@@ -148,3 +154,78 @@ describe('SasjsConfiguratorComponent directory default', () => {
expect(await directoryFor('DOCKER')).toBe('/tmp/DataController')
})
})
/**
* The rendered order. DC on a SASjs Server or SAS9 estate is configured by the
* admin group selected here, and the staging directory is the last question
* because it is the one the picker has to prove - the same order the Viya
* screen asks them in. A template edit that put the directory back on top
* would compile and deploy fine and ask the operator to choose a location
* before telling DC who administers it.
*/
describe('SasjsConfiguratorComponent field order', () => {
@Component({
selector: 'app-sasjs-configurator-host',
template: '<app-sasjs-configurator></app-sasjs-configurator>',
standalone: false
})
class HostComponent {}
let fixture: ComponentFixture<HostComponent>
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [
SasjsConfiguratorComponent,
HostComponent,
DcLocationComponent
],
imports: [FormsModule],
providers: [
{
provide: SasService,
useValue: {
getExecutionPath: () => '/SASStoredProcess/do',
request: () =>
Promise.resolve({ adapterResponse: { SYSSCPL: 'Linux' } }),
getSasjsConfig: () => ({}),
sasjsMakedataChecking: () => Promise.resolve(false),
getUserGroups: () => ({ subscribe: () => undefined }),
logout: () => undefined
}
},
{
provide: SasjsService,
useValue: {
getServerInfo: () => ({ subscribe: () => undefined })
}
}
],
schemas: [NO_ERRORS_SCHEMA]
})
fixture = TestBed.createComponent(HostComponent)
fixture.detectChanges()
})
afterEach(() => {
TestBed.resetTestingModule()
})
it('asks for the admin group before the staging directory', () => {
const labels = Array.from(
fixture.nativeElement.querySelectorAll('.clr-control-label')
).map((label: unknown) => ((label as HTMLElement).textContent || '').trim())
const admin = labels.findIndex((l) =>
l.toLowerCase().includes('admin group')
)
const directory = labels.findIndex((l) =>
l.toLowerCase().includes('staging directory')
)
expect(admin).toBeGreaterThanOrEqual(0)
expect(directory).toBeGreaterThanOrEqual(0)
expect(admin).toBeLessThan(directory)
})
})
@@ -30,7 +30,6 @@
@li mf_mkdir.sas
@li mp_abort.sas
@li mpe_action.sas
@li ms_getfile.sas
@li mpe_dirlist.sas
@li mpe_writecheck.sas
@li mpe_makedata.sas
@@ -274,37 +273,15 @@ run;
%mend makedata_body;
/**
* Refuses the browse actions once this appLoc has a settings.sas.
* The browse actions need no gate of their own.
*
* The deploy window closes when MAKEDATA writes that file, and a run that
* failed after writing it leaves the file behind - so its presence, not the
* survival of this STP, is what says the system is configured. Without this
* a failed or abandoned install leaves a caller with a directory listing of
* any path the SAS user can read.
*
* A probe that cannot answer is treated as configured: an unreachable drive
* is not evidence that the deployment window is still open.
* 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.
*/
%macro browse_gate(iftrue=0);
%local status;
%if not(&iftrue) %then %return;
%let status=0;
%ms_getfile(%mf_getapploc()/services/public/settings.sas,outref=dcstngs)
%if %symexist(SYS_PROCHTTP_STATUS_CODE) %then
%let status=&SYS_PROCHTTP_STATUS_CODE;
/* the probe is allowed to 404, so the status is recorded and cleared */
%let syscc=0;
%mp_abort(iftrue= (&status=200)
,mac=&_program
,msg=%str(DIRLIST and WRITECHECK are only available until DC is configured)
)
%mp_abort(iftrue= (&status ne 200 and &status ne 404)
,mac=&_program
,msg=%str(Could not check whether this appLoc is configured - probe &status)
)
%mend browse_gate;
%browse_gate(iftrue= (&action=DIRLIST or &action=WRITECHECK))
/*
Three invocations rather than one branch: each macro takes `iftrue=` and
@@ -8,10 +8,10 @@
is asserted against the macros directly (macros/mpe_dirlist.test.sas,
macros/mpe_writecheck.test.sas); this file covers the dispatch around them.
The browse actions are only reachable while the appLoc has no settings.sas,
and any appLoc running this suite has one - so here the two actions are
expected to be refused by the gate. What that proves is that the action
reached the gate at all, which is the part the dispatch owns.
The browse actions carry no gate of their own: a successful run deletes this
program, so the deploy window closes by itself. One of the tests below calls
DIRLIST on an appLoc that already has a settings.sas, which is what a retry
after a failed install looks like.
<h4> SAS Macros </h4>
@li mpe_assert_abort.sas
@@ -82,49 +82,16 @@ run;
outds=work.test_results
)
/**
* Test 4 - DIRLIST is refused once the appLoc is configured. A run that
failed after writing settings.sas leaves it behind, so the file is the
test, not the survival of this STP.
*/
data work.fromjs;
length action $10 parent $1000;
action='DIRLIST';
parent='/tmp';
run;
%mpe_execute(&_program,
viyacontext=&defaultcontext,
inputdatasets=work.fromjs,
outlib=web4,
failfast=0
)
%mpe_assert_abort(
desc=DIRLIST is refused once the appLoc is configured,
outstatus=work.dc_childstatus,
msg=%str(DIRLIST and WRITECHECK are only available until DC is configured),
outds=work.test_results
)
/*
There is no test here for the browse actions themselves.
/**
* Test 5 - and so is WRITECHECK
They used to be refused once settings.sas existed, and that refusal is what
these two used to assert. The gate is gone: the deploy window closes when a
successful run deletes this program, so the window and the program have the
same lifetime. DIRLIST and WRITECHECK are asserted against the macros that
implement them (macros/mpe_dirlist.test.sas, macros/mpe_writecheck.test.sas);
the dispatch around them is what tests 1-3 cover.
*/
data work.fromjs;
length action $10 path $1000;
action='WRITECHECK';
path='/tmp';
run;
%mpe_execute(&_program,
viyacontext=&defaultcontext,
inputdatasets=work.fromjs,
outlib=web5,
failfast=0
)
%mpe_assert_abort(
desc=WRITECHECK is refused once the appLoc is configured,
outstatus=work.dc_childstatus,
msg=%str(DIRLIST and WRITECHECK are only available until DC is configured),
outds=work.test_results
)
/**
* dump results to the log for offline inspection
@@ -24,7 +24,6 @@
@li mf_trimstr.sas
@li mp_abort.sas
@li mpe_action.sas
@li mfv_existfile.sas
@li mpe_dirlist.sas
@li mpe_writecheck.sas
@li mpe_makedata.sas
@@ -366,25 +365,15 @@ run;
%mend makedata_body;
/**
* Refuses the browse actions once this appLoc has a settings.sas.
* The browse actions need no gate of their own.
*
* The deploy window closes when MAKEDATA writes that file, and a run that
* failed after writing it leaves the file behind - so its presence, not the
* survival of this STP, is what says the system is configured. Without this
* a failed or abandoned install leaves a caller with a directory listing of
* any path the SAS user can read.
* 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.
*/
%macro browse_gate(iftrue=0);
%local configured;
%if not(&iftrue) %then %return;
%let configured=%mfv_existfile(%mf_getapploc()/services/settings.sas);
%mp_abort(iftrue= (&configured=1)
,mac=&_program
,msg=%str(DIRLIST and WRITECHECK are only available until DC is configured)
)
%mend browse_gate;
%browse_gate(iftrue= (&action=DIRLIST or &action=WRITECHECK))
/*
Three invocations rather than one branch: each macro takes `iftrue=` and