Skip to content
This repository has been archived by the owner on Oct 9, 2024. It is now read-only.

feat: add execution environment auto completion #42

Merged
merged 13 commits into from
Oct 5, 2021
433 changes: 240 additions & 193 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"globby": "^11.0.4",
"ini": "^1.3.8",
"lodash": "^4.17.21",
"uuid": "^8.3.2",
"vscode-languageserver": "^7.0.0",
"vscode-languageserver-textdocument": "^1.0.1",
"vscode-uri": "^3.0.2",
Expand All @@ -56,13 +57,15 @@
"@types/lodash": "^4.14.171",
"@types/mocha": "^8.2.3",
"@types/node": "^14.17.5",
"@types/uuid": "^8.3.1",
"@types/vscode": "^1.56.0",
"@typescript-eslint/eslint-plugin": "^4.28.3",
"@typescript-eslint/parser": "^4.28.3",
"chai": "^4.3.4",
"eslint": "^7.30.0",
"eslint-config-prettier": "^8.3.0",
"mocha": "^8.4.0",
"prettier": "^2.4.1",
"rimraf": "^3.0.2",
"ts-node": "^9.1.1",
"typescript": "^4.3.5"
Expand Down
12 changes: 12 additions & 0 deletions src/interfaces/extensionSettings.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
export type IContainerEngine = 'auto' | 'podman' | 'docker';

export type IPullPolicy = 'always' | 'missing' | 'never' | 'tag';

export interface ExtensionSettings {
ansible: { path: string; useFullyQualifiedCollectionNames: boolean };
ansibleLint: { enabled: boolean; path: string; arguments: string };
executionEnvironment: ExecutionEnvironmentSettings;
python: { interpreterPath: string; activationScript: string };
}

interface ExecutionEnvironmentSettings {
containerEngine: IContainerEngine;
enabled: boolean;
image: string;
pullPolicy: IPullPolicy;
}
64 changes: 28 additions & 36 deletions src/services/ansibleConfig.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import * as child_process from 'child_process';
import * as ini from 'ini';
import * as _ from 'lodash';
import * as path from 'path';
import { URI } from 'vscode-uri';
import { Connection } from 'vscode-languageserver';
import { withInterpreter } from '../utils/misc';
import { WorkspaceFolderContext } from './workspaceManager';
import { CommandRunner } from '../utils/commandRunner';

export class AnsibleConfig {
private connection: Connection;
Expand All @@ -25,41 +23,32 @@ export class AnsibleConfig {
this.context.workspaceFolder.uri
);

// get Ansible configuration
const [ansibleConfigCommand, ansibleConfigEnv] = withInterpreter(
`${settings.ansible.path}-config`,
'dump',
settings.python.interpreterPath,
settings.python.activationScript
const commandRunner = new CommandRunner(
this.connection,
this.context,
settings
);

const ansibleConfigResult = child_process.execSync(ansibleConfigCommand, {
encoding: 'utf-8',
cwd: URI.parse(this.context.workspaceFolder.uri).path,
env: ansibleConfigEnv,
});
// get Ansible configuration
const ansibleConfigResult = await commandRunner.runCommand(
'ansible-config',
'dump'
);

let config = ini.parse(ansibleConfigResult);
let config = ini.parse(ansibleConfigResult.stdout);
config = _.mapKeys(
config,
(_, key) => key.substring(0, key.indexOf('(')) // remove config source in parenthesis
);
this._collection_paths = parsePythonStringArray(config.COLLECTIONS_PATHS);

// get Ansible basic information
const [ansibleCommand, ansibleEnv] = withInterpreter(
`${settings.ansible.path}`,
'--version',
settings.python.interpreterPath,
settings.python.activationScript
const ansibleVersionResult = await commandRunner.runCommand(
'ansible',
'--version'
);

const ansibleVersionResult = child_process.execSync(ansibleCommand, {
encoding: 'utf-8',
env: ansibleEnv,
});

const versionInfo = ini.parse(ansibleVersionResult);
const versionInfo = ini.parse(ansibleVersionResult.stdout);
this._module_locations = parsePythonStringArray(
versionInfo['configured module search path']
);
Expand All @@ -71,18 +60,13 @@ export class AnsibleConfig {

// get Python sys.path
// this is needed to get the pre-installed collections to work
const [pythonPathCommand, pythonPathEnv] = withInterpreter(
const pythonPathResult = await commandRunner.runCommand(
'python3',
' -c "import sys; print(sys.path, end=\\"\\")"',
settings.python.interpreterPath,
settings.python.activationScript
' -c "import sys; print(sys.path, end=\\"\\")"'
);
this._collection_paths.push(
...parsePythonStringArray(pythonPathResult.stdout)
);

const pythonPathResult = child_process.execSync(pythonPathCommand, {
encoding: 'utf-8',
env: pythonPathEnv,
});
this._collection_paths.push(...parsePythonStringArray(pythonPathResult));
} catch (error) {
if (error instanceof Error) {
this.connection.window.showErrorMessage(error.message);
Expand All @@ -94,10 +78,18 @@ export class AnsibleConfig {
}
}

set collections_paths(updatedCollectionPath: string[]) {
this._collection_paths = updatedCollectionPath;
}

get collections_paths(): string[] {
return this._collection_paths;
}

set module_locations(updatedModulesPath: string[]) {
this._module_locations = updatedModulesPath;
}

get module_locations(): string[] {
return this._module_locations;
}
Expand Down
90 changes: 55 additions & 35 deletions src/services/docsLibrary.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Connection } from 'vscode-languageserver';
import { Node } from 'yaml/types';
import { getDeclaredCollections } from '../utils/yaml';
import { findDocumentation, findPluginRouting } from '../utils/docsFinder';
Expand All @@ -13,6 +14,7 @@ import {
} from '../utils/docsParser';
import { IModuleMetadata } from '../interfaces/module';
export class DocsLibrary {
private connection: Connection;
private modules = new Map<string, IModuleMetadata>();
private _moduleFqcns = new Set<string>();
private docFragments = new Map<string, IModuleMetadata>();
Expand All @@ -22,55 +24,73 @@ export class DocsLibrary {
IPluginRoutesByType
>();

constructor(context: WorkspaceFolderContext) {
constructor(connection: Connection, context: WorkspaceFolderContext) {
this.connection = connection;
this.context = context;
}

public async initialize(): Promise<void> {
const ansibleConfig = await this.context.ansibleConfig;
for (const modulesPath of ansibleConfig.module_locations) {
(await findDocumentation(modulesPath, 'builtin')).forEach((doc) => {
this.modules.set(doc.fqcn, doc);
this.moduleFqcns.add(doc.fqcn);
});

(await findDocumentation(modulesPath, 'builtin_doc_fragment')).forEach(
(doc) => {
this.docFragments.set(doc.fqcn, doc);
}
try {
const settings = await this.context.documentSettings.get(
this.context.workspaceFolder.uri
);
}

(
await findPluginRouting(ansibleConfig.ansible_location, 'builtin')
).forEach((r, collection) => this.pluginRouting.set(collection, r));

for (const collectionsPath of ansibleConfig.collections_paths) {
(await findDocumentation(collectionsPath, 'collection')).forEach(
(doc) => {
const ansibleConfig = await this.context.ansibleConfig;
if (settings.executionEnvironment.enabled) {
// ensure plugin/module cache is established
await this.context.executionEnvironment;
}
for (const modulesPath of ansibleConfig.module_locations) {
(await findDocumentation(modulesPath, 'builtin')).forEach((doc) => {
this.modules.set(doc.fqcn, doc);
this.moduleFqcns.add(doc.fqcn);
}
);
});

(await findDocumentation(modulesPath, 'builtin_doc_fragment')).forEach(
(doc) => {
this.docFragments.set(doc.fqcn, doc);
}
);
}

(
await findDocumentation(collectionsPath, 'collection_doc_fragment')
).forEach((doc) => {
this.docFragments.set(doc.fqcn, doc);
});
await findPluginRouting(ansibleConfig.ansible_location, 'builtin')
).forEach((r, collection) => this.pluginRouting.set(collection, r));

for (const collectionsPath of ansibleConfig.collections_paths) {
(await findDocumentation(collectionsPath, 'collection')).forEach(
(doc) => {
this.modules.set(doc.fqcn, doc);
this.moduleFqcns.add(doc.fqcn);
}
);

(await findPluginRouting(collectionsPath, 'collection')).forEach(
(r, collection) => this.pluginRouting.set(collection, r)
);
(
await findDocumentation(collectionsPath, 'collection_doc_fragment')
).forEach((doc) => {
this.docFragments.set(doc.fqcn, doc);
});

(await findPluginRouting(collectionsPath, 'collection')).forEach(
(r, collection) => this.pluginRouting.set(collection, r)
);

// add all valid redirect routes as possible FQCNs
for (const [collection, routesByType] of this.pluginRouting) {
for (const [name, route] of routesByType.get('modules') || []) {
if (route.redirect && !route.tombstone) {
this.moduleFqcns.add(`${collection}.${name}`);
// add all valid redirect routes as possible FQCNs
for (const [collection, routesByType] of this.pluginRouting) {
for (const [name, route] of routesByType.get('modules') || []) {
if (route.redirect && !route.tombstone) {
this.moduleFqcns.add(`${collection}.${name}`);
}
}
}
}
} catch (error) {
if (error instanceof Error) {
this.connection.window.showErrorMessage(error.message);
} else {
this.connection.console.error(
`Exception in DocsLibrary service: ${JSON.stringify(error)}`
);
}
}
}

Expand Down
Loading