Skip to content

Commit

Permalink
Merge pull request #495 from DinoChiesa/download-feature
Browse files Browse the repository at this point in the history
implement the download feature for the cli
  • Loading branch information
ssvaidyanathan authored Dec 19, 2024
2 parents 5ce7ae9 + 98b05de commit ebde757
Show file tree
Hide file tree
Showing 6 changed files with 326 additions and 616 deletions.
41 changes: 40 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ Usage: apigeelint [options]
Options:
-V, --version output the version number
-s, --path <path> Path of the proxy to analyze
-d, --download [value] Download the API proxy or sharedflow to analyze. Exclusive of -s / --path. Example: org:ORG,API:proxyname or org:ORG,sf:SHAREDFLOWNAME
-f, --formatter [value] Specify formatters (default: json.js)
-w, --write [value] file path to write results
-e, --excluded [value] The comma separated list of tests to exclude (default: none)
Expand Down Expand Up @@ -106,10 +107,48 @@ archive. This tool also can read and analyze these zipped bundles:
apigeelint -f table.js -s path/to/your/apiproxy.zip
```

The tool will unzip the bundle to a temporary directory, perform the analysis,
The tool will unzip the bundle into a temporary directory, perform the analysis,
and then remove the temporary directory.


### Basic usage: downloading a proxy bundle to analyze

You can ask apigeelint to export an API Proxy or Sharedflow bundle from Apigee,
and analyze the resulting zip archive. This connects to apigee.googleapis.com to
perform the export, which means it will work only with Apigee X or hybrid.

```
# to download and then analyze a proxy bundle
apigeelint -f table.js -d org:your-org-name,api:name-of-your-api-proxy
# to download and then analyze a sharedflow bundle
apigeelint -f table.js -d org:your-org-name,sf:name-of-your-shared-flow
```

With this invocation, the tool will:
- obtain a token using the `gcloud auth print-access-token` command
- use the token to inquire the latest revision of the proxy or sharedflow
- use the token to download the bundle for the latest revision
- unzip the bundle into a temporary directory
- perform the lint analysis
- render the result
- and then remove the temporary directory

If you do not have the [`gcloud` command line
tool](https://cloud.google.com/sdk/gcloud) installed, and available on your
path, this will fail.


You can also specify a token you have obtained previously:

```
apigeelint -f table.js -d org:your-org-name,api:name-of-your-api-proxy,token:ACCESS_TOKEN_HERE
```

In this case, apigeelint does not try to use `gcloud` to obtain an access token.



### Using External Plugins:
```
apigeelint -x ./externalPlugins -s path/to/your/apiproxy -f table.js
Expand Down
253 changes: 136 additions & 117 deletions cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const program = require("commander"),
bl = require("./lib/package/bundleLinter.js"),
rc = require("./lib/package/apigeelintrc.js"),
pkj = require("./package.json"),
downloader = require("./lib/package/downloader.js"),
bundleType = require("./lib/package/BundleTypes.js"),
debug = require("debug");

Expand Down Expand Up @@ -77,136 +78,154 @@ const findBundle = (p) => {
);
};

program
.version(pkj.version)
.option(
"-s, --path <path>",
"Path of the exploded apiproxy or sharedflowbundle directory",
)
.option("-f, --formatter [value]", "Specify formatters (default: json.js)")
.option("-w, --write [value]", "file path to write results")
.option(
"-e, --excluded [value]",
"The comma separated list of tests to exclude (default: none)",
)
// .option("-M, --mgmtserver [value]", "Apigee management server")
// .option("-u, --user [value]", "Apigee user account")
// .option("-p, --password [value]", "Apigee password")
// .option("-o, --organization [value]", "Apigee organization")
.option(
"-x, --externalPluginsDirectory [value]",
"Relative or full path to an external plugins directory",
)
.option(
"-q, --quiet",
"do not emit the report to stdout. (can use --write option to write to file)",
)
.option(
"--list",
"do not execute, instead list the available plugins and formatters",
)
.option(
"--maxWarnings [value]",
"Number of warnings to trigger nonzero exit code (default: -1)",
)
.option("--profile [value]", "Either apigee or apigeex (default: apigee)")
.option(
"--norc",
"do not search for and use the .apigeelintrc file for settings",
)
.option(
"--ignoreDirectives",
"ignore any directives within XML files that disable warnings",
);
(async function main() {
program
.version(pkj.version)
.option(
"-s, --path <path>",
"Path of the exploded apiproxy or sharedflowbundle directory",
)
.option(
"-d, --download [value]",
"Download the API proxy or sharedflow to analyze. Exclusive of -s / --path. Example: org:ORG,API:proxyname or org:ORG,sf:SHAREDFLOWNAME",
)
.option("-f, --formatter [value]", "Specify formatters (default: json.js)")
.option("-w, --write [value]", "file path to write results")
.option(
"-e, --excluded [value]",
"The comma separated list of tests to exclude (default: none)",
)
.option(
"-x, --externalPluginsDirectory [value]",
"Relative or full path to an external plugins directory",
)
.option(
"-q, --quiet",
"do not emit the report to stdout. (can use --write option to write to file)",
)
.option(
"--list",
"do not scan, instead list the available plugins and formatters",
)
.option(
"--maxWarnings [value]",
"Number of warnings to trigger nonzero exit code (default: -1)",
)
.option("--profile [value]", "Either apigee or apigeex (default: apigee)")
.option(
"--norc",
"do not search for and use the .apigeelintrc file for settings",
)
.option(
"--ignoreDirectives",
"ignore any directives within XML files that disable warnings",
);

program.on("--help", function () {
console.log("\nExample: apigeelint -f table.js -s sampleProxy/apiproxy");
console.log("");
});
program.on("--help", function () {
console.log("\nExamples:");
console.log(" apigeelint -f table.js -s sampleProxy/apiproxy\n");
console.log(" apigeelint -f table.js -s path/to/your/apiproxy.zip\n");
console.log(
" apigeelint -f table.js --download org:my-org,api:my-proxy\n",
);
console.log("");
});

program.parse(process.argv);

if (program.list) {
console.log("available plugins: " + bl.listRuleIds().join(", ") + "\n");
console.log("available formatters: " + bl.listFormatters().join(", "));
if (fs.existsSync(program.externalPluginsDirectory)) {
console.log(
"\n" +
"available external plugins: " +
bl.listExternalRuleIds(program.externalPluginsDirectory).join(", ") +
"\n",
);
}
process.exit(0);
}

program.parse(process.argv);
if (program.download) {
if (program.path) {
console.log(
"you must specify either the -s /--path option, or the -d /--download option. Not both.",
);
process.exit(1);
}
// This will work only with Apigee X/hybrid
program.path = await downloader.downloadBundle(program.download);
}

if (program.list) {
console.log("available plugins: " + bl.listRuleIds().join(", ") + "\n");
console.log("available formatters: " + bl.listFormatters().join(", "));
if (fs.existsSync(program.externalPluginsDirectory)) {
if (!program.path) {
console.log(
"\n" +
"available external plugins: " +
bl.listExternalRuleIds(program.externalPluginsDirectory).join(", ") +
"\n",
"you must specify the -s option, or the long form of that: --path ",
);
process.exit(1);
}
process.exit(0);
}

if (!program.path) {
console.log(
"you must specify the -s option, or the long form of that: --path ",
);
process.exit(1);
}

let [sourcePath, resolvedPath, sourceType] = findBundle(program.path);

// apply RC file
if (!program.norc) {
const rcSettings = rc.readRc([".apigeelintrc"], sourcePath);
if (rcSettings) {
Object.keys(rcSettings)
.filter((key) => key != "path" && key != "list" && !program[key])
.forEach((key) => {
debug("apigeelint:rc")(`applying [${key}] = ${rcSettings[key]}`);
program[key] = rcSettings[key];
});
let [sourcePath, resolvedPath, sourceType] = findBundle(program.path);

// apply RC file
if (!program.norc) {
const rcSettings = rc.readRc([".apigeelintrc"], sourcePath);
if (rcSettings) {
Object.keys(rcSettings)
.filter((key) => key != "path" && key != "list" && !program[key])
.forEach((key) => {
debug("apigeelint:rc")(`applying [${key}] = ${rcSettings[key]}`);
program[key] = rcSettings[key];
});
}
}
}

const configuration = {
debug: true,
source: {
type: sourceType,
path: resolvedPath,
sourcePath: sourcePath,
bundleType: resolvedPath.endsWith(bundleType.BundleType.SHAREDFLOW)
? bundleType.BundleType.SHAREDFLOW
: bundleType.BundleType.APIPROXY,
},
externalPluginsDirectory: program.externalPluginsDirectory,
excluded: {},
maxWarnings: -1,
profile: "apigee",
};

if (!isNaN(program.maxWarnings)) {
configuration.maxWarnings = Number.parseInt(program.maxWarnings);
}
const configuration = {
debug: true,
source: {
type: sourceType,
path: resolvedPath,
sourcePath: sourcePath,
bundleType: resolvedPath.endsWith(bundleType.BundleType.SHAREDFLOW)
? bundleType.BundleType.SHAREDFLOW
: bundleType.BundleType.APIPROXY,
},
externalPluginsDirectory: program.externalPluginsDirectory,
excluded: {},
maxWarnings: -1,
profile: "apigee",
};

if (!isNaN(program.maxWarnings)) {
configuration.maxWarnings = Number.parseInt(program.maxWarnings);
}

if (program.formatter) {
configuration.formatter = program.formatter || "json.js";
}
if (program.formatter) {
configuration.formatter = program.formatter || "json.js";
}

if (program.quiet) {
configuration.output = "none";
}
if (program.quiet) {
configuration.output = "none";
}

if (program.ignoreDirectives) {
configuration.ignoreDirectives = true;
}
if (program.ignoreDirectives) {
configuration.ignoreDirectives = true;
}

if (program.excluded && typeof program.excluded === "string") {
configuration.excluded = program.excluded
.split(",")
.map((s) => s.trim())
.reduce((acc, item) => ((acc[item] = true), acc), {});
}
if (program.excluded && typeof program.excluded === "string") {
configuration.excluded = program.excluded
.split(",")
.map((s) => s.trim())
.reduce((acc, item) => ((acc[item] = true), acc), {});
}

if (program.write) {
configuration.writePath = program.write;
}
if (program.write) {
configuration.writePath = program.write;
}

if (program.profile) {
configuration.profile = program.profile;
}
if (program.profile) {
configuration.profile = program.profile;
}

bl.lint(configuration);
bl.lint(configuration);
})();
Loading

0 comments on commit ebde757

Please sign in to comment.