Skip to content

Commit 98e5e3d

Browse files
authored
Merge pull request #38 from crazycga/dev_trunk
New release - enumerate-environment and new documentation
2 parents 12f54ff + 980f6ee commit 98e5e3d

7 files changed

Lines changed: 377 additions & 45 deletions

File tree

_tasks/_build.ps1

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ $paths = @(
1616
"./Publish-BCModuleToTenant",
1717
"./Build-ALPackage",
1818
"./Get-BCDependencies",
19-
"./Get-VSIXCompiler"
19+
"./Get-VSIXCompiler",
20+
"./Enumerate-Environment"
2021
)
2122

2223
foreach($path in $paths) {

_tasks/environments.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"version": {
33
"major": 0,
44
"minor": 1,
5-
"patch": 9,
5+
"patch": 10,
66
"build": 0
77
},
88
"dev": {
@@ -38,6 +38,10 @@
3838
"EGDeployBCModule": {
3939
"location": "bc-tools-extension/Publish-BCModuleToTenant/task.json",
4040
"taskGuid": "def7c0a0-0d00-4f62-ae3f-7f084561e721"
41+
},
42+
"EGEnumerateEnvironment": {
43+
"location": "bc-tools-extension/Enumerate-Environment/task.json",
44+
"taskGuid": "16821c4a-f2e7-4148-a686-2ec76cab618c"
4145
}
4246
}
4347
},
@@ -74,6 +78,10 @@
7478
"EGDeployBCModule": {
7579
"location": "bc-tools-extension/Publish-BCModuleToTenant/task.json",
7680
"taskGuid": "7315a985-6da9-4b4a-bae9-56b04fc492fd"
81+
},
82+
"EGEnumerateEnvironment": {
83+
"location": "bc-tools-extension/Enumerate-Environment/task.json",
84+
"taskGuid": "206d9815-c3dd-459f-b3c1-41e8b18dddab"
7785
}
7886
}
7987
}
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
const { execSync } = require('child_process');
2+
const path = require('path');
3+
const os = require('os');
4+
const fs = require('fs');
5+
const { PassThrough } = require('stream');
6+
const { logger, parseBool, getToken, normalizePath } = require(path.join(__dirname, '_common', 'CommonTools.js'));
7+
8+
const inputFilenameAndPath = process.env.INPUT_FILEPATHANDNAME;
9+
10+
// this routine is intended to provide information about the agent on which it is running
11+
//
12+
// 1. platform
13+
// 2. whoami
14+
// 3. current working directory
15+
// 4. Powershell version(s)
16+
// 5. BCContainerHelper existence / version
17+
// 6. Docker existence / version
18+
// 7. Docker image list
19+
20+
(async () => {
21+
let outputFilenameAndPath;
22+
23+
if (inputFilenameAndPath && inputFilenameAndPath.trim() !== '') {
24+
outputFilenameAndPath = normalizePath(inputFilenameAndPath);
25+
const pathInfo = path.parse(outputFilenameAndPath);
26+
27+
if (!pathInfo.base || !pathInfo.dir) {
28+
logger.warn(`Invalid file path supplied: '${outputFilenameAndPath}'. Skipping file production.`);
29+
outputFilenameAndPath = undefined;
30+
}
31+
}
32+
33+
logger.info('Invoking EGEnumerateEnvironment with the following parameters:');
34+
logger.info('FilePathAndName:'.padStart(2).padEnd(30) + `${outputFilenameAndPath}`);
35+
logger.info('');
36+
37+
// 0. setup
38+
const logColWidth = 30;
39+
40+
// 1. platform
41+
logger.info('[platform]:'.padEnd(logColWidth) + `${os.platform()}`);
42+
43+
// 2. whoami
44+
let textOut;
45+
try {
46+
let whoami = execSync('whoami', { encoding: 'utf8' });
47+
textOut = whoami.toString().trim();
48+
if (textOut.length > 0) {
49+
logger.info('[whoami]: '.padEnd(logColWidth) + `${textOut}`);
50+
} else {
51+
logger.info('[whoami]:'.padEnd(logColWidth) + 'Apparently a ghost; nothing returned');
52+
}
53+
} catch (err) {
54+
logger.error(`[whoami]: Encountered an error while executing a 'whoami'`);
55+
logger.error(`[whoami]: Error: ${err}`);
56+
}
57+
58+
// 3. current working directory
59+
logger.info('[current working directory]:'.padEnd(logColWidth) + `${process.cwd()}`);
60+
61+
// 4. Powershell version(s)
62+
let psVersion;
63+
let pwshVersion;
64+
if (os.platform() === "win32") {
65+
try {
66+
psVersion = execSync(
67+
`powershell -NoProfile -Command "$v = $PSVersionTable.PSVersion; Write-Output ('' + $v.Major + '.' + $v.Minor + '.' + $v.Build + '.' + $v.Revision)"`,
68+
{ encoding: 'utf8' }
69+
).trim();
70+
logger.info('[powershell version]:'.padEnd(logColWidth) + `${psVersion}`);
71+
} catch (err) {
72+
logger.error(`[powershell version]: Encountered an error while executing a 'powerhsell version'`);
73+
logger.error(`[powershell version]: Error: ${err}`);
74+
}
75+
} else {
76+
psVersion = "[not installed; Linux environment]";
77+
logger.info('[powershell version]:'.padEnd(logColWidth) + `${psVersion}`);
78+
}
79+
80+
try {
81+
const isLinux = process.platform === 'linux';
82+
83+
const psCommandRaw = '$PSVersionTable.PSVersion.ToString()';
84+
const psCommand = isLinux
85+
? psCommandRaw.replace(/(["\\$`])/g, '\\$1') // escape for bash
86+
: psCommandRaw; // don't escape on Windows
87+
const fullCommand = `pwsh -NoProfile -Command "${psCommand}"`;
88+
//const quotedCommand = `"${psCommand.replace(/"/g, '\\"')}"`;
89+
pwshVersion = execSync(fullCommand, { encoding: 'utf8' }).trim();
90+
logger.info('[pwsh version]:'.padEnd(logColWidth) + `${pwshVersion}`);
91+
} catch (err) {
92+
logger.error(`[pwsh version]: Encountered an error while executing a 'pwsh version'`);
93+
logger.error(`[pwsh version]: Error: ${err}`);
94+
}
95+
96+
// 5. BCContainerHelper existence / version
97+
let result;
98+
let BCContainerHelperPresent = false;
99+
100+
if (os.platform() === "win32") {
101+
try {
102+
const psCommand = `$modulePath = Get-Module -ListAvailable BCContainerHelper | Select-Object -First 1 -ExpandProperty Path; if ($modulePath) { $psd1 = $modulePath -replace '\\[^\\\\]+$', '.psd1'; if (Test-Path $psd1) { $lines = Get-Content $psd1 -Raw; if ($lines -match 'ModuleVersion\\s*=\\s*[\\"\\'']?([0-9\\.]+)[\\"\\'']?') { Write-Output $matches[1]; } else { Write-Output '[version not found]'; } } else { Write-Output '[not installed]'; } } else { Write-Output '[not installed]'; }`;
103+
104+
result = execSync(`powershell.exe -NoProfile -Command "${psCommand.replace(/\n/g, ' ').replace(/"/g, '\\"')}"`, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
105+
if (result === "") { result = '[not installed]' }
106+
if (result && result != "") {
107+
logger.info('[BCContainerHelper version]:'.padEnd(logColWidth) + `${result}`);
108+
BCContainerHelperPresent = true;
109+
}
110+
BCContainerHelperPresent = true;
111+
} catch (err) {
112+
logger.error(`[BCContainerHelper]: Failed to query module: ${err.message}`);
113+
logger.info(err);
114+
}
115+
} else {
116+
result = "[not installed; Linux environment]";
117+
logger.info('[BCContainerHelper version]:'.padEnd(logColWidth) + `${result}`);
118+
}
119+
120+
// 6. Docker existence / version
121+
let DockerPresent = false;
122+
let DockerVersionResult;
123+
try {
124+
DockerResult = execSync('docker version --format "{{.Client.Version}}"', { stdio: ['pipe', 'pipe', 'pipe'] });
125+
if (DockerResult === "") { DockerVersionResult = '[not installed]' }
126+
else { DockerVersionResult = DockerResult.toString().trim(); }
127+
if (DockerVersionResult && DockerVersionResult != "") {
128+
logger.info('[dockerversion]:'.padEnd(logColWidth) + `${DockerVersionResult}`);
129+
DockerPresent = true;
130+
}
131+
} catch (err) {
132+
const msg = err.message || '';
133+
const stderr = err.stderr?.toString() || '';
134+
135+
const combined = `${msg}\n${stderr}`;
136+
const normalized = combined.toLowerCase();
137+
if (
138+
normalized.includes("'docker' is not recognized") || // Windows case
139+
normalized.includes("command not found") || // Linux case
140+
normalized.includes("no such file or directory") // fallback
141+
) {
142+
DockerVersionResult = '[not installed]';
143+
if (DockerVersionResult && DockerVersionResult != "") {
144+
logger.info('[dockerversion]:'.padEnd(logColWidth) + `${DockerVersionResult}`);
145+
}
146+
} else {
147+
logger.error(`[dockerversion]: Unexpected error: ${combined}`);
148+
}
149+
}
150+
151+
// 7. Docker image list
152+
let DockerPsObject;
153+
if (DockerPresent) {
154+
try {
155+
const psResult = execSync('docker ps -a --no-trunc --format "{{json .}}"', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] });
156+
const lines = psResult.trim().split('\n');
157+
DockerPsObject = lines.filter(line => line && line.trim().startsWith('{') && line.trim().endsWith('}')).map(line => JSON.parse(line));
158+
159+
if (DockerPsObject.length > 0) {
160+
logger.info('[dockerimage]:'.padEnd(logColWidth) + '**Name**'.padEnd(logColWidth) + '**Status**');
161+
DockerPsObject.forEach((image, idx) => {
162+
if (image && image.name != "") {
163+
logger.info('[dockerimage]:'.padEnd(logColWidth) + `${image.Names}`.padEnd(logColWidth) + `${image.Status}`);
164+
}
165+
});
166+
} else {
167+
logger.info('[dockerimage]:'.padEnd(logColWidth) + '[no images]');
168+
}
169+
} catch (err) {
170+
const msg = err.message || '';
171+
const stderr = err.stderr?.toString() || '';
172+
173+
const combined = `${msg}\n${stderr}`;
174+
logger.error(`[dockerimage]: Unexpected error: ${combined}`);
175+
}
176+
} else {
177+
logger.info('[dockerimage]:'.padEnd(logColWidth) + '[not installed]');
178+
}
179+
180+
// Deal with the file if requested (note it has already been parsed at the top of this routine)
181+
if (outputFilenameAndPath) {
182+
183+
let dockerList = [];
184+
try {
185+
dockerList = DockerPsObject.filter(img => img && img.Names).map(img => ({ name: img.Names, status: img.Status }));
186+
} catch {
187+
dockerList = [];
188+
}
189+
190+
let candidateFile = {
191+
platform: os.platform(),
192+
whoami: textOut,
193+
workingDirectory: process.cwd(),
194+
powershellVersion: psVersion,
195+
pscoreVersion: pwshVersion,
196+
bcContainerVersion: result,
197+
dockerVersion: DockerVersionResult,
198+
dockerImages: dockerList
199+
}
200+
201+
let candidateFileString = JSON.stringify(candidateFile);
202+
fs.writeFileSync(outputFilenameAndPath, candidateFileString);
203+
204+
logger.info('');
205+
logger.info(`Produced file at: ${outputFilenameAndPath}`);
206+
}
207+
})();
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"id": "0d4e6693-bdcb-47c0-a373-67a34549da07",
3+
"name": "EGEnumerateEnvironment",
4+
"friendlyName": "Enumerate compiling environment",
5+
"description": "Provides information about the pipeline environment in the context of the agent.",
6+
"helpMarkDown": "Please open a GitHub issue at https://github.com/crazycga/bcdevopsextension/issues for queries or support.",
7+
"category": "Build",
8+
"author": "Evergrowth Consulting",
9+
"version": {
10+
"Major": 0,
11+
"Minor": 1,
12+
"Patch": 5
13+
},
14+
"instanceNameFormat": "Enumerate compiling environment",
15+
"inputs": [
16+
{
17+
"name": "ProduceFile",
18+
"type": "boolean",
19+
"label": "Produce file",
20+
"defaultValue": false,
21+
"required": false,
22+
"helpMarkDown": "Specifies whether or not to produce an output file as one of the artifacts."
23+
},
24+
{
25+
"name": "FilePathAndName",
26+
"type": "string",
27+
"label": "Output file path and name",
28+
"defaultValue": "$(Build.ArtifactStagingDirectory)/environment.$(System.StageName).$(Agent.JobName).$(Build.BuildId).json",
29+
"required": false,
30+
"helpMarkDown": "The output path and name of the output file if specified; default '$(Build.ArtifactStagingDirectory)/environment.$(System.StageName).$(Agent.JobName).$(Build.BuildId).json'"
31+
}
32+
],
33+
"execution": {
34+
"Node16": {
35+
"target": "function_Enumerate-Environment.js"
36+
},
37+
"Node20_1": {
38+
"target": "function_Enumerate-Environment.js"
39+
}
40+
}
41+
}

0 commit comments

Comments
 (0)