Compare commits

...

18 Commits

Author SHA1 Message Date
Henry Mercer c719ec0b33 Merge pull request #1389 from github/update-supported-enterprise-server-versions
Update supported GitHub Enterprise Server versions.
2022-11-23 10:31:21 +00:00
Henry Mercer 09bfd560f6 Merge pull request #1385 from github/henrymercer/update-v1-deprecation-date
Update v1 deprecation date
2022-11-23 10:24:17 +00:00
Henry Mercer 33b2045510 Merge branch 'main' into henrymercer/update-v1-deprecation-date 2022-11-23 09:53:16 +00:00
GitHub 93c6b70dc3 Update supported GitHub Enterprise Server versions. 2022-11-23 00:13:03 +00:00
Henry Mercer def4f60c6c Merge pull request #1387 from github/henrymercer/fix-swift-version
Use Swift version 5.7 in PR checks
2022-11-22 22:39:17 +00:00
Henry Mercer 34d91a9ce7 Use runner.os in ML-powered queries tests too 2022-11-22 20:45:26 +00:00
Henry Mercer c49c05b5df Perform the check using runner.os 2022-11-22 20:41:32 +00:00
Henry Mercer 2ccaddd459 Define swift-version as a string 2022-11-22 20:33:22 +00:00
Henry Mercer 5dcca8a6e4 Pin the version of swift-actions/setup-swift@v1 2022-11-22 20:13:49 +00:00
Henry Mercer b6e17a6616 Drop unneeded CODEQL_ENABLE_EXPERIMENTAL_FEATURES env var 2022-11-22 11:31:22 +00:00
Henry Mercer e9e73b0cb9 Use Swift version 5.7 in PR checks
The version installed in the latest runner image, 5.7.1, is not yet
supported.
2022-11-22 11:30:27 +00:00
Angela P Wen bab5a146ac Add Ruby and Swift language autodetect tests (#1369)
Co-authored-by: Henry Mercer <henry.mercer@me.com>
2022-11-22 11:03:25 +00:00
Henry Mercer 6013661451 Update v1 deprecation date
The expected deprecation date of GHES 3.3 has been postponed, so v1 will
now be deprecated in January.
2022-11-22 09:43:07 +00:00
Andrew Eisenberg 26df9a9a4f Merge pull request #1384 from github/aeisenberg/feature-flags-disk
Cache feature flags on disk
2022-11-21 15:25:34 -08:00
Andrew Eisenberg 4ee97e5e55 Add extra test ensuring env var overrides cached feature flag 2022-11-21 14:30:36 -08:00
Andrew Eisenberg cfce1c4e19 Rename 2022-11-21 13:42:32 -08:00
Andrew Eisenberg da75623c67 Update changelog 2022-11-21 11:19:34 -08:00
Andrew Eisenberg c29fca48a1 Cache feature flags on disk
This will allow feature flags to be shared across steps in the same job,
avoiding an error we saw earlier where the init action had the flag
enabled, but the analyze step had it disabled.

This uses the runner's temp folder to cache the flags file, which will
stick around until the job completes.
2022-11-21 11:14:38 -08:00
31 changed files with 392 additions and 152 deletions
@@ -42,6 +42,10 @@ jobs:
uses: ./.github/prepare-test uses: ./.github/prepare-test
with: with:
version: ${{ matrix.version }} version: ${{ matrix.version }}
- uses: swift-actions/setup-swift@5cdaa9161ad1f55ae39a5ea1784ef96de72f95d9
if: runner.os != 'Windows'
with:
swift-version: '5.7'
- uses: ./../action/init - uses: ./../action/init
with: with:
languages: javascript languages: javascript
+2 -3
View File
@@ -87,8 +87,7 @@ jobs:
- name: Check sarif - name: Check sarif
uses: ./../action/.github/check-sarif uses: ./../action/.github/check-sarif
# Running on Windows requires CodeQL CLI 2.9.0+. # Running on Windows requires CodeQL CLI 2.9.0+.
if: "!(matrix.version == 'stable-20220120' && (matrix.os == 'windows-latest'\ if: "!(matrix.version == 'stable-20220120' && runner.os == 'Windows')"
\ || matrix.os == 'windows-2019'))"
with: with:
sarif-file: ${{ runner.temp }}/results/javascript.sarif sarif-file: ${{ runner.temp }}/results/javascript.sarif
queries-run: js/ml-powered/nosql-injection,js/ml-powered/path-injection,js/ml-powered/sql-injection,js/ml-powered/xss queries-run: js/ml-powered/nosql-injection,js/ml-powered/path-injection,js/ml-powered/sql-injection,js/ml-powered/xss
@@ -98,7 +97,7 @@ jobs:
env: env:
# Running on Windows requires CodeQL CLI 2.9.0+. # Running on Windows requires CodeQL CLI 2.9.0+.
SHOULD_RUN_ML_POWERED_QUERIES: ${{ !(matrix.version == 'stable-20220120' && SHOULD_RUN_ML_POWERED_QUERIES: ${{ !(matrix.version == 'stable-20220120' &&
(matrix.os == 'windows-latest' || matrix.os == 'windows-2019')) }} runner.os == 'Windows') }}
shell: bash shell: bash
run: | run: |
echo "Expecting ML-powered queries to be run: ${SHOULD_RUN_ML_POWERED_QUERIES}" echo "Expecting ML-powered queries to be run: ${SHOULD_RUN_ML_POWERED_QUERIES}"
+33 -1
View File
@@ -65,16 +65,25 @@ jobs:
uses: actions/setup-go@v3 uses: actions/setup-go@v3
with: with:
go-version: ^1.13.1 go-version: ^1.13.1
- uses: swift-actions/setup-swift@5cdaa9161ad1f55ae39a5ea1784ef96de72f95d9
if: runner.os != 'Windows'
with:
swift-version: '5.7'
- uses: ./../action/init - uses: ./../action/init
with: with:
db-location: ${{ runner.temp }}/customDbLocation db-location: ${{ runner.temp }}/customDbLocation
tools: ${{ steps.prepare-test.outputs.tools-url }} tools: ${{ steps.prepare-test.outputs.tools-url }}
- name: Build code - name: Build code
shell: bash shell: bash
run: ./build.sh run: ./build.sh
- uses: ./../action/analyze - uses: ./../action/analyze
id: analysis id: analysis
- shell: bash
- name: Check language autodetect for all languages excluding Ruby, Swift
shell: bash
run: | run: |
CPP_DB=${{ fromJson(steps.analysis.outputs.db-locations).cpp }} CPP_DB=${{ fromJson(steps.analysis.outputs.db-locations).cpp }}
if [[ ! -d $CPP_DB ]] || [[ ! $CPP_DB == ${{ runner.temp }}/customDbLocation/* ]]; then if [[ ! -d $CPP_DB ]] || [[ ! $CPP_DB == ${{ runner.temp }}/customDbLocation/* ]]; then
@@ -106,5 +115,28 @@ jobs:
echo "Did not create a database for Python, or created it in the wrong location." echo "Did not create a database for Python, or created it in the wrong location."
exit 1 exit 1
fi fi
- name: Check language autodetect for Ruby
if: (matrix.version == 'cached' || matrix.version == 'latest' || matrix.version
== 'nightly-latest')
shell: bash
run: |
RUBY_DB=${{ fromJson(steps.analysis.outputs.db-locations).ruby }}
if [[ ! -d $RUBY_DB ]] || [[ ! $RUBY_DB == ${{ runner.temp }}/customDbLocation/* ]]; then
echo "Did not create a database for Ruby, or created it in the wrong location."
exit 1
fi
- name: Check language autodetect for Swift
if: "!startsWith(matrix.os, 'windows') && (matrix.version == 'cached' || matrix.version\
\ == 'latest' || matrix.version == 'nightly-latest')"
shell: bash
run: |
SWIFT_DB=${{ fromJson(steps.analysis.outputs.db-locations).swift }}
if [[ ! -d $SWIFT_DB ]] || [[ ! $SWIFT_DB == ${{ runner.temp }}/customDbLocation/* ]]; then
echo "Did not create a database for Swift, or created it in the wrong location."
exit 1
fi
env: env:
CODEQL_ENABLE_EXPERIMENTAL_FEATURES_SWIFT: 'true' # Remove when Swift is GA.
CODEQL_ACTION_TEST_MODE: true CODEQL_ACTION_TEST_MODE: true
-66
View File
@@ -1,66 +0,0 @@
# Warning: This file is generated automatically, and should not be modified.
# Instead, please modify the template in the pr-checks directory and run:
# pip install ruamel.yaml && python3 sync.py
# to regenerate this file.
name: PR Check - Ruby analysis using autodetect
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GO111MODULE: auto
on:
push:
branches:
- main
- releases/v1
- releases/v2
pull_request:
types:
- opened
- synchronize
- reopened
- ready_for_review
workflow_dispatch: {}
jobs:
ruby-autodetect:
strategy:
matrix:
include:
- os: ubuntu-latest
version: latest
- os: macos-latest
version: latest
- os: ubuntu-latest
version: cached
- os: macos-latest
version: cached
- os: ubuntu-latest
version: nightly-latest
- os: macos-latest
version: nightly-latest
name: Ruby analysis using autodetect
timeout-minutes: 45
runs-on: ${{ matrix.os }}
steps:
- name: Check out repository
uses: actions/checkout@v3
- name: Prepare test
id: prepare-test
uses: ./.github/prepare-test
with:
version: ${{ matrix.version }}
- uses: ./../action/init
with:
tools: ${{ steps.prepare-test.outputs.tools-url }}
- uses: ./../action/analyze
id: analysis
- name: Check database
shell: bash
run: |
RUBY_DB="${{ fromJson(steps.analysis.outputs.db-locations).ruby }}"
if [[ ! -d "$RUBY_DB" ]]; then
echo "Did not create a database for Ruby."
exit 1
fi
env:
CODEQL_ENABLE_EXPERIMENTAL_FEATURES: 'true'
CODEQL_ACTION_TEST_MODE: true
-1
View File
@@ -63,5 +63,4 @@ jobs:
exit 1 exit 1
fi fi
env: env:
CODEQL_ENABLE_EXPERIMENTAL_FEATURES: 'true'
CODEQL_ACTION_TEST_MODE: true CODEQL_ACTION_TEST_MODE: true
+3
View File
@@ -42,6 +42,9 @@ jobs:
uses: ./.github/prepare-test uses: ./.github/prepare-test
with: with:
version: ${{ matrix.version }} version: ${{ matrix.version }}
- uses: swift-actions/setup-swift@5cdaa9161ad1f55ae39a5ea1784ef96de72f95d9
with:
swift-version: '5.7'
- uses: ./../action/init - uses: ./../action/init
with: with:
languages: swift languages: swift
+3
View File
@@ -48,6 +48,9 @@ jobs:
uses: ./.github/prepare-test uses: ./.github/prepare-test
with: with:
version: ${{ matrix.version }} version: ${{ matrix.version }}
- uses: swift-actions/setup-swift@5cdaa9161ad1f55ae39a5ea1784ef96de72f95d9
with:
swift-version: '5.7'
- uses: ./../action/init - uses: ./../action/init
with: with:
languages: swift languages: swift
+1 -1
View File
@@ -2,7 +2,7 @@
## [UNRELEASED] ## [UNRELEASED]
No user facing changes. - Fixed a bug where some the `init` action and the `analyze` action would have different sets of experimental feature flags enabled. [#1384](https://github.com/github/codeql-action/pull/1384)
## 2.1.33 - 16 Nov 2022 ## 2.1.33 - 16 Nov 2022
+1 -1
View File
@@ -155,7 +155,7 @@ async function run() {
const memory = util.getMemoryFlag(actionsUtil.getOptionalInput("ram") || process.env["CODEQL_RAM"]); const memory = util.getMemoryFlag(actionsUtil.getOptionalInput("ram") || process.env["CODEQL_RAM"]);
const repositoryNwo = (0, repository_1.parseRepositoryNwo)(util.getRequiredEnvParam("GITHUB_REPOSITORY")); const repositoryNwo = (0, repository_1.parseRepositoryNwo)(util.getRequiredEnvParam("GITHUB_REPOSITORY"));
const gitHubVersion = await (0, api_client_1.getGitHubVersion)(); const gitHubVersion = await (0, api_client_1.getGitHubVersion)();
const features = new feature_flags_1.Features(gitHubVersion, repositoryNwo, logger); const features = new feature_flags_1.Features(gitHubVersion, repositoryNwo, actionsUtil.getTemporaryDirectory(), logger);
await runAutobuildIfLegacyGoWorkflow(config, logger); await runAutobuildIfLegacyGoWorkflow(config, logger);
dbCreationTimings = await (0, analyze_1.runFinalize)(outputDir, threads, memory, config, logger); dbCreationTimings = await (0, analyze_1.runFinalize)(outputDir, threads, memory, config, logger);
if (actionsUtil.getRequiredInput("skip-queries") !== "true") { if (actionsUtil.getRequiredInput("skip-queries") !== "true") {
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1 +1 @@
{ "maximumVersion": "3.7", "minimumVersion": "3.3" } { "maximumVersion": "3.8", "minimumVersion": "3.3" }
+51 -9
View File
@@ -19,7 +19,9 @@ var __importStar = (this && this.__importStar) || function (mod) {
return result; return result;
}; };
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
exports.Features = exports.featureConfig = exports.Feature = void 0; exports.Features = exports.FEATURE_FLAGS_FILE_NAME = exports.featureConfig = exports.Feature = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const api_client_1 = require("./api-client"); const api_client_1 = require("./api-client");
const util = __importStar(require("./util")); const util = __importStar(require("./util"));
var Feature; var Feature;
@@ -57,14 +59,15 @@ exports.featureConfig = {
minimumVersion: undefined, minimumVersion: undefined,
}, },
}; };
exports.FEATURE_FLAGS_FILE_NAME = "cached-feature-flags.json";
/** /**
* Determines the enablement status of a number of features. * Determines the enablement status of a number of features.
* If feature enablement is not able to be determined locally, a request to the * If feature enablement is not able to be determined locally, a request to the
* GitHub API is made to determine the enablement status. * GitHub API is made to determine the enablement status.
*/ */
class Features { class Features {
constructor(gitHubVersion, repositoryNwo, logger) { constructor(gitHubVersion, repositoryNwo, tempDir, logger) {
this.gitHubFeatureFlags = new GitHubFeatureFlags(gitHubVersion, repositoryNwo, logger); this.gitHubFeatureFlags = new GitHubFeatureFlags(gitHubVersion, repositoryNwo, path.join(tempDir, exports.FEATURE_FLAGS_FILE_NAME), logger);
} }
/** /**
* *
@@ -108,14 +111,15 @@ class Features {
} }
exports.Features = Features; exports.Features = Features;
class GitHubFeatureFlags { class GitHubFeatureFlags {
constructor(gitHubVersion, repositoryNwo, logger) { constructor(gitHubVersion, repositoryNwo, featureFlagsFile, logger) {
this.gitHubVersion = gitHubVersion; this.gitHubVersion = gitHubVersion;
this.repositoryNwo = repositoryNwo; this.repositoryNwo = repositoryNwo;
this.featureFlagsFile = featureFlagsFile;
this.logger = logger; this.logger = logger;
/**/ /**/
} }
async getValue(feature) { async getValue(feature) {
const response = await this.getApiResponse(); const response = await this.getAllFeatures();
if (response === undefined) { if (response === undefined) {
this.logger.debug(`No feature flags API response for ${feature}, considering it disabled.`); this.logger.debug(`No feature flags API response for ${feature}, considering it disabled.`);
return false; return false;
@@ -127,10 +131,48 @@ class GitHubFeatureFlags {
} }
return !!featureEnablement; return !!featureEnablement;
} }
async getApiResponse() { async getAllFeatures() {
const apiResponse = this.cachedApiResponse || (await this.loadApiResponse()); // if we have an in memory cache, use that
this.cachedApiResponse = apiResponse; if (this.cachedApiResponse !== undefined) {
return apiResponse; return this.cachedApiResponse;
}
// if a previous step has written a feature flags file to disk, use that
const fileFlags = await this.readLocalFlags();
if (fileFlags !== undefined) {
this.cachedApiResponse = fileFlags;
return fileFlags;
}
// if not, request flags from the server
let remoteFlags = await this.loadApiResponse();
if (remoteFlags === undefined) {
remoteFlags = {};
}
// cache the response in memory
this.cachedApiResponse = remoteFlags;
// and cache them to disk so future workflow steps can use them
await this.writeLocalFlags(remoteFlags);
return remoteFlags;
}
async readLocalFlags() {
try {
if (fs.existsSync(this.featureFlagsFile)) {
this.logger.debug(`Loading feature flags from ${this.featureFlagsFile}`);
return JSON.parse(fs.readFileSync(this.featureFlagsFile, "utf8"));
}
}
catch (e) {
this.logger.warning(`Error reading cached feature flags file ${this.featureFlagsFile}: ${e}. Requesting from GitHub instead.`);
}
return undefined;
}
async writeLocalFlags(flags) {
try {
this.logger.debug(`Writing feature flags to ${this.featureFlagsFile}`);
fs.writeFileSync(this.featureFlagsFile, JSON.stringify(flags));
}
catch (e) {
this.logger.warning(`Error writing cached feature flags file ${this.featureFlagsFile}: ${e}.`);
}
} }
async loadApiResponse() { async loadApiResponse() {
// Do nothing when not running against github.com // Do nothing when not running against github.com
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"feature-flags.js","sourceRoot":"","sources":["../src/feature-flags.ts"],"names":[],"mappings":"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"} {"version":3,"file":"feature-flags.js","sourceRoot":"","sources":["../src/feature-flags.ts"],"names":[],"mappings":"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"}
+63 -11
View File
@@ -1,8 +1,29 @@
"use strict"; "use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) { var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod }; return (mod && mod.__esModule) ? mod : { "default": mod };
}; };
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const ava_1 = __importDefault(require("ava")); const ava_1 = __importDefault(require("ava"));
const feature_flags_1 = require("./feature-flags"); const feature_flags_1 = require("./feature-flags");
const logging_1 = require("./logging"); const logging_1 = require("./logging");
@@ -25,7 +46,7 @@ for (const variant of ALL_FEATURES_DISABLED_VARIANTS) {
(0, ava_1.default)(`All features are disabled if running against ${variant.description}`, async (t) => { (0, ava_1.default)(`All features are disabled if running against ${variant.description}`, async (t) => {
await (0, util_1.withTmpDir)(async (tmpDir) => { await (0, util_1.withTmpDir)(async (tmpDir) => {
const loggedMessages = []; const loggedMessages = [];
const featureEnablement = setUpTests(tmpDir, (0, testing_utils_1.getRecordingLogger)(loggedMessages), variant.gitHubVersion); const featureEnablement = setUpFeatureFlagTests(tmpDir, (0, testing_utils_1.getRecordingLogger)(loggedMessages), variant.gitHubVersion);
for (const feature of Object.values(feature_flags_1.Feature)) { for (const feature of Object.values(feature_flags_1.Feature)) {
t.false(await featureEnablement.getValue(feature, includeCodeQlIfRequired(feature))); t.false(await featureEnablement.getValue(feature, includeCodeQlIfRequired(feature)));
} }
@@ -38,7 +59,7 @@ for (const variant of ALL_FEATURES_DISABLED_VARIANTS) {
(0, ava_1.default)("API response missing", async (t) => { (0, ava_1.default)("API response missing", async (t) => {
await (0, util_1.withTmpDir)(async (tmpDir) => { await (0, util_1.withTmpDir)(async (tmpDir) => {
const loggedMessages = []; const loggedMessages = [];
const featureEnablement = setUpTests(tmpDir, (0, testing_utils_1.getRecordingLogger)(loggedMessages)); const featureEnablement = setUpFeatureFlagTests(tmpDir, (0, testing_utils_1.getRecordingLogger)(loggedMessages));
(0, testing_utils_1.mockFeatureFlagApiEndpoint)(403, {}); (0, testing_utils_1.mockFeatureFlagApiEndpoint)(403, {});
for (const feature of Object.values(feature_flags_1.Feature)) { for (const feature of Object.values(feature_flags_1.Feature)) {
t.assert((await featureEnablement.getValue(feature, includeCodeQlIfRequired(feature))) === false); t.assert((await featureEnablement.getValue(feature, includeCodeQlIfRequired(feature))) === false);
@@ -49,7 +70,7 @@ for (const variant of ALL_FEATURES_DISABLED_VARIANTS) {
(0, ava_1.default)("Features are disabled if they're not returned in API response", async (t) => { (0, ava_1.default)("Features are disabled if they're not returned in API response", async (t) => {
await (0, util_1.withTmpDir)(async (tmpDir) => { await (0, util_1.withTmpDir)(async (tmpDir) => {
const loggedMessages = []; const loggedMessages = [];
const featureEnablement = setUpTests(tmpDir, (0, testing_utils_1.getRecordingLogger)(loggedMessages)); const featureEnablement = setUpFeatureFlagTests(tmpDir, (0, testing_utils_1.getRecordingLogger)(loggedMessages));
(0, testing_utils_1.mockFeatureFlagApiEndpoint)(200, {}); (0, testing_utils_1.mockFeatureFlagApiEndpoint)(200, {});
for (const feature of Object.values(feature_flags_1.Feature)) { for (const feature of Object.values(feature_flags_1.Feature)) {
t.assert((await featureEnablement.getValue(feature, includeCodeQlIfRequired(feature))) === false); t.assert((await featureEnablement.getValue(feature, includeCodeQlIfRequired(feature))) === false);
@@ -59,7 +80,7 @@ for (const variant of ALL_FEATURES_DISABLED_VARIANTS) {
}); });
(0, ava_1.default)("Feature flags exception is propagated if the API request errors", async (t) => { (0, ava_1.default)("Feature flags exception is propagated if the API request errors", async (t) => {
await (0, util_1.withTmpDir)(async (tmpDir) => { await (0, util_1.withTmpDir)(async (tmpDir) => {
const featureEnablement = setUpTests(tmpDir); const featureEnablement = setUpFeatureFlagTests(tmpDir);
(0, testing_utils_1.mockFeatureFlagApiEndpoint)(500, {}); (0, testing_utils_1.mockFeatureFlagApiEndpoint)(500, {});
await t.throwsAsync(async () => featureEnablement.getValue(feature_flags_1.Feature.MlPoweredQueriesEnabled, includeCodeQlIfRequired(feature_flags_1.Feature.MlPoweredQueriesEnabled)), { await t.throwsAsync(async () => featureEnablement.getValue(feature_flags_1.Feature.MlPoweredQueriesEnabled, includeCodeQlIfRequired(feature_flags_1.Feature.MlPoweredQueriesEnabled)), {
message: "Encountered an error while trying to determine feature enablement: Error: some error message", message: "Encountered an error while trying to determine feature enablement: Error: some error message",
@@ -69,7 +90,7 @@ for (const variant of ALL_FEATURES_DISABLED_VARIANTS) {
for (const feature of Object.keys(feature_flags_1.featureConfig)) { for (const feature of Object.keys(feature_flags_1.featureConfig)) {
(0, ava_1.default)(`Only feature '${feature}' is enabled if enabled in the API response. Other features disabled`, async (t) => { (0, ava_1.default)(`Only feature '${feature}' is enabled if enabled in the API response. Other features disabled`, async (t) => {
await (0, util_1.withTmpDir)(async (tmpDir) => { await (0, util_1.withTmpDir)(async (tmpDir) => {
const featureEnablement = setUpTests(tmpDir); const featureEnablement = setUpFeatureFlagTests(tmpDir);
// set all features to false except the one we're testing // set all features to false except the one we're testing
const expectedFeatureEnablement = {}; const expectedFeatureEnablement = {};
for (const f of Object.keys(feature_flags_1.featureConfig)) { for (const f of Object.keys(feature_flags_1.featureConfig)) {
@@ -87,7 +108,7 @@ for (const feature of Object.keys(feature_flags_1.featureConfig)) {
}); });
(0, ava_1.default)(`Only feature '${feature}' is enabled if the associated environment variable is true. Others disabled.`, async (t) => { (0, ava_1.default)(`Only feature '${feature}' is enabled if the associated environment variable is true. Others disabled.`, async (t) => {
await (0, util_1.withTmpDir)(async (tmpDir) => { await (0, util_1.withTmpDir)(async (tmpDir) => {
const featureEnablement = setUpTests(tmpDir); const featureEnablement = setUpFeatureFlagTests(tmpDir);
const expectedFeatureEnablement = initializeFeatures(false); const expectedFeatureEnablement = initializeFeatures(false);
(0, testing_utils_1.mockFeatureFlagApiEndpoint)(200, expectedFeatureEnablement); (0, testing_utils_1.mockFeatureFlagApiEndpoint)(200, expectedFeatureEnablement);
// feature should be disabled initially // feature should be disabled initially
@@ -99,7 +120,7 @@ for (const feature of Object.keys(feature_flags_1.featureConfig)) {
}); });
(0, ava_1.default)(`Feature '${feature}' is disabled if the associated environment variable is false, even if enabled in API`, async (t) => { (0, ava_1.default)(`Feature '${feature}' is disabled if the associated environment variable is false, even if enabled in API`, async (t) => {
await (0, util_1.withTmpDir)(async (tmpDir) => { await (0, util_1.withTmpDir)(async (tmpDir) => {
const featureEnablement = setUpTests(tmpDir); const featureEnablement = setUpFeatureFlagTests(tmpDir);
const expectedFeatureEnablement = initializeFeatures(true); const expectedFeatureEnablement = initializeFeatures(true);
(0, testing_utils_1.mockFeatureFlagApiEndpoint)(200, expectedFeatureEnablement); (0, testing_utils_1.mockFeatureFlagApiEndpoint)(200, expectedFeatureEnablement);
// feature should be enabled initially // feature should be enabled initially
@@ -112,7 +133,7 @@ for (const feature of Object.keys(feature_flags_1.featureConfig)) {
if (feature_flags_1.featureConfig[feature].minimumVersion !== undefined) { if (feature_flags_1.featureConfig[feature].minimumVersion !== undefined) {
(0, ava_1.default)(`Getting feature '${feature} should throw if no codeql is provided`, async (t) => { (0, ava_1.default)(`Getting feature '${feature} should throw if no codeql is provided`, async (t) => {
await (0, util_1.withTmpDir)(async (tmpDir) => { await (0, util_1.withTmpDir)(async (tmpDir) => {
const featureEnablement = setUpTests(tmpDir); const featureEnablement = setUpFeatureFlagTests(tmpDir);
const expectedFeatureEnablement = initializeFeatures(true); const expectedFeatureEnablement = initializeFeatures(true);
(0, testing_utils_1.mockFeatureFlagApiEndpoint)(200, expectedFeatureEnablement); (0, testing_utils_1.mockFeatureFlagApiEndpoint)(200, expectedFeatureEnablement);
await t.throwsAsync(async () => featureEnablement.getValue(feature), { await t.throwsAsync(async () => featureEnablement.getValue(feature), {
@@ -124,7 +145,7 @@ for (const feature of Object.keys(feature_flags_1.featureConfig)) {
if (feature_flags_1.featureConfig[feature].minimumVersion !== undefined) { if (feature_flags_1.featureConfig[feature].minimumVersion !== undefined) {
(0, ava_1.default)(`Feature '${feature}' is disabled if the minimum CLI version is below ${feature_flags_1.featureConfig[feature].minimumVersion}`, async (t) => { (0, ava_1.default)(`Feature '${feature}' is disabled if the minimum CLI version is below ${feature_flags_1.featureConfig[feature].minimumVersion}`, async (t) => {
await (0, util_1.withTmpDir)(async (tmpDir) => { await (0, util_1.withTmpDir)(async (tmpDir) => {
const featureEnablement = setUpTests(tmpDir); const featureEnablement = setUpFeatureFlagTests(tmpDir);
const expectedFeatureEnablement = initializeFeatures(true); const expectedFeatureEnablement = initializeFeatures(true);
(0, testing_utils_1.mockFeatureFlagApiEndpoint)(200, expectedFeatureEnablement); (0, testing_utils_1.mockFeatureFlagApiEndpoint)(200, expectedFeatureEnablement);
// feature should be disabled when an old CLI version is set // feature should be disabled when an old CLI version is set
@@ -156,6 +177,37 @@ for (const feature of Object.keys(feature_flags_1.featureConfig)) {
// An even less likely scenario is that we no longer have any features. // An even less likely scenario is that we no longer have any features.
t.assert(Object.values(feature_flags_1.featureConfig).length > 0, "There should be at least one feature"); t.assert(Object.values(feature_flags_1.featureConfig).length > 0, "There should be at least one feature");
}); });
(0, ava_1.default)("Feature flags are saved to disk", async (t) => {
await (0, util_1.withTmpDir)(async (tmpDir) => {
const featureEnablement = setUpFeatureFlagTests(tmpDir);
const expectedFeatureEnablement = initializeFeatures(true);
(0, testing_utils_1.mockFeatureFlagApiEndpoint)(200, expectedFeatureEnablement);
const cachedFeatureFlags = path.join(tmpDir, feature_flags_1.FEATURE_FLAGS_FILE_NAME);
t.false(fs.existsSync(cachedFeatureFlags), "Feature flag cached file should not exist before getting feature flags");
t.true(await featureEnablement.getValue(feature_flags_1.Feature.CliConfigFileEnabled, includeCodeQlIfRequired(feature_flags_1.Feature.CliConfigFileEnabled)), "Feature flag should be enabled initially");
t.true(fs.existsSync(cachedFeatureFlags), "Feature flag cached file should exist after getting feature flags");
const actualFeatureEnablement = JSON.parse(fs.readFileSync(cachedFeatureFlags, "utf8"));
t.deepEqual(actualFeatureEnablement, expectedFeatureEnablement);
// now test that we actually use the feature flag cache instead of the server
actualFeatureEnablement[feature_flags_1.Feature.CliConfigFileEnabled] = false;
fs.writeFileSync(cachedFeatureFlags, JSON.stringify(actualFeatureEnablement));
// delete the in memory cache so that we are forced to use the cached file
featureEnablement.gitHubFeatureFlags.cachedApiResponse = undefined;
t.false(await featureEnablement.getValue(feature_flags_1.Feature.CliConfigFileEnabled, includeCodeQlIfRequired(feature_flags_1.Feature.CliConfigFileEnabled)), "Feature flag should be enabled after reading from cached file");
});
});
(0, ava_1.default)("Environment variable can override feature flag cache", async (t) => {
await (0, util_1.withTmpDir)(async (tmpDir) => {
const featureEnablement = setUpFeatureFlagTests(tmpDir);
const expectedFeatureEnablement = initializeFeatures(true);
(0, testing_utils_1.mockFeatureFlagApiEndpoint)(200, expectedFeatureEnablement);
const cachedFeatureFlags = path.join(tmpDir, feature_flags_1.FEATURE_FLAGS_FILE_NAME);
t.true(await featureEnablement.getValue(feature_flags_1.Feature.CliConfigFileEnabled, includeCodeQlIfRequired(feature_flags_1.Feature.CliConfigFileEnabled)), "Feature flag should be enabled initially");
t.true(fs.existsSync(cachedFeatureFlags), "Feature flag cached file should exist after getting feature flags");
process.env.CODEQL_PASS_CONFIG_TO_CLI = "false";
t.false(await featureEnablement.getValue(feature_flags_1.Feature.CliConfigFileEnabled, includeCodeQlIfRequired(feature_flags_1.Feature.CliConfigFileEnabled)), "Feature flag should be disabled after setting env var");
});
});
function assertAllFeaturesUndefinedInApi(t, loggedMessages) { function assertAllFeaturesUndefinedInApi(t, loggedMessages) {
for (const feature of Object.keys(feature_flags_1.featureConfig)) { for (const feature of Object.keys(feature_flags_1.featureConfig)) {
t.assert(loggedMessages.find((v) => v.type === "debug" && t.assert(loggedMessages.find((v) => v.type === "debug" &&
@@ -169,9 +221,9 @@ function initializeFeatures(initialValue) {
return features; return features;
}, {}); }, {});
} }
function setUpTests(tmpDir, logger = (0, logging_1.getRunnerLogger)(true), gitHubVersion = { type: util_1.GitHubVariant.DOTCOM }) { function setUpFeatureFlagTests(tmpDir, logger = (0, logging_1.getRunnerLogger)(true), gitHubVersion = { type: util_1.GitHubVariant.DOTCOM }) {
(0, testing_utils_1.setupActionsVars)(tmpDir, tmpDir); (0, testing_utils_1.setupActionsVars)(tmpDir, tmpDir);
return new feature_flags_1.Features(gitHubVersion, testRepositoryNwo, logger); return new feature_flags_1.Features(gitHubVersion, testRepositoryNwo, tmpDir, logger);
} }
function includeCodeQlIfRequired(feature) { function includeCodeQlIfRequired(feature) {
return feature_flags_1.featureConfig[feature].minimumVersion !== undefined return feature_flags_1.featureConfig[feature].minimumVersion !== undefined
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -88,7 +88,7 @@ async function run() {
const gitHubVersion = await (0, api_client_1.getGitHubVersion)(); const gitHubVersion = await (0, api_client_1.getGitHubVersion)();
(0, util_1.checkGitHubVersionInRange)(gitHubVersion, logger); (0, util_1.checkGitHubVersionInRange)(gitHubVersion, logger);
const repositoryNwo = (0, repository_1.parseRepositoryNwo)((0, util_1.getRequiredEnvParam)("GITHUB_REPOSITORY")); const repositoryNwo = (0, repository_1.parseRepositoryNwo)((0, util_1.getRequiredEnvParam)("GITHUB_REPOSITORY"));
const features = new feature_flags_1.Features(gitHubVersion, repositoryNwo, logger); const features = new feature_flags_1.Features(gitHubVersion, repositoryNwo, (0, actions_util_1.getTemporaryDirectory)(), logger);
try { try {
const workflowErrors = await (0, actions_util_1.validateWorkflow)(); const workflowErrors = await (0, actions_util_1.validateWorkflow)();
if (!(await (0, actions_util_1.sendStatusReport)(await (0, actions_util_1.createStatusReportBase)("init", "starting", startedAt, workflowErrors)))) { if (!(await (0, actions_util_1.sendStatusReport)(await (0, actions_util_1.createStatusReportBase)("init", "starting", startedAt, workflowErrors)))) {
File diff suppressed because one or more lines are too long
Generated
+1 -1
View File
@@ -545,7 +545,7 @@ async function checkActionVersion(version) {
githubVersion.type === GitHubVariant.GHAE || githubVersion.type === GitHubVariant.GHAE ||
(githubVersion.type === GitHubVariant.GHES && (githubVersion.type === GitHubVariant.GHES &&
semver.satisfies((_a = semver.coerce(githubVersion.version)) !== null && _a !== void 0 ? _a : "0.0.0", ">=3.4"))) { semver.satisfies((_a = semver.coerce(githubVersion.version)) !== null && _a !== void 0 ? _a : "0.0.0", ">=3.4"))) {
core.warning("CodeQL Action v1 will be deprecated on December 7th, 2022. Please upgrade to v2. For " + core.warning("CodeQL Action v1 will be deprecated on January 18th, 2023. Please upgrade to v2. For " +
"more information, see " + "more information, see " +
"https://github.blog/changelog/2022-04-27-code-scanning-deprecation-of-codeql-action-v1/"); "https://github.blog/changelog/2022-04-27-code-scanning-deprecation-of-codeql-action-v1/");
} }
@@ -2,6 +2,10 @@ name: "Export file baseline information"
description: "Tests that file baseline information is exported when the feature is enabled" description: "Tests that file baseline information is exported when the feature is enabled"
versions: ["nightly-latest"] versions: ["nightly-latest"]
steps: steps:
- uses: swift-actions/setup-swift@5cdaa9161ad1f55ae39a5ea1784ef96de72f95d9
if: runner.os != 'Windows'
with:
swift-version: "5.7"
- uses: ./../action/init - uses: ./../action/init
with: with:
languages: javascript languages: javascript
+2 -2
View File
@@ -30,7 +30,7 @@ steps:
- name: Check sarif - name: Check sarif
uses: ./../action/.github/check-sarif uses: ./../action/.github/check-sarif
# Running on Windows requires CodeQL CLI 2.9.0+. # Running on Windows requires CodeQL CLI 2.9.0+.
if: "!(matrix.version == 'stable-20220120' && (matrix.os == 'windows-latest' || matrix.os == 'windows-2019'))" if: "!(matrix.version == 'stable-20220120' && runner.os == 'Windows')"
with: with:
sarif-file: ${{ runner.temp }}/results/javascript.sarif sarif-file: ${{ runner.temp }}/results/javascript.sarif
queries-run: js/ml-powered/nosql-injection,js/ml-powered/path-injection,js/ml-powered/sql-injection,js/ml-powered/xss queries-run: js/ml-powered/nosql-injection,js/ml-powered/path-injection,js/ml-powered/sql-injection,js/ml-powered/xss
@@ -39,7 +39,7 @@ steps:
- name: Check results - name: Check results
env: env:
# Running on Windows requires CodeQL CLI 2.9.0+. # Running on Windows requires CodeQL CLI 2.9.0+.
SHOULD_RUN_ML_POWERED_QUERIES: ${{ !(matrix.version == 'stable-20220120' && (matrix.os == 'windows-latest' || matrix.os == 'windows-2019')) }} SHOULD_RUN_ML_POWERED_QUERIES: ${{ !(matrix.version == 'stable-20220120' && runner.os == 'Windows') }}
shell: bash shell: bash
run: | run: |
echo "Expecting ML-powered queries to be run: ${SHOULD_RUN_ML_POWERED_QUERIES}" echo "Expecting ML-powered queries to be run: ${SHOULD_RUN_ML_POWERED_QUERIES}"
+32 -1
View File
@@ -1,17 +1,28 @@
name: "Multi-language repository" name: "Multi-language repository"
description: "An end-to-end integration test of a multi-language repository using automatic language detection" description: "An end-to-end integration test of a multi-language repository using automatic language detection"
operatingSystems: ["ubuntu", "macos"] operatingSystems: ["ubuntu", "macos"]
env:
CODEQL_ENABLE_EXPERIMENTAL_FEATURES_SWIFT: "true" # Remove when Swift is GA.
steps: steps:
- uses: swift-actions/setup-swift@5cdaa9161ad1f55ae39a5ea1784ef96de72f95d9
if: runner.os != 'Windows'
with:
swift-version: "5.7"
- uses: ./../action/init - uses: ./../action/init
with: with:
db-location: "${{ runner.temp }}/customDbLocation" db-location: "${{ runner.temp }}/customDbLocation"
tools: ${{ steps.prepare-test.outputs.tools-url }} tools: ${{ steps.prepare-test.outputs.tools-url }}
- name: Build code - name: Build code
shell: bash shell: bash
run: ./build.sh run: ./build.sh
- uses: ./../action/analyze - uses: ./../action/analyze
id: analysis id: analysis
- shell: bash
- name: Check language autodetect for all languages excluding Ruby, Swift
shell: bash
run: | run: |
CPP_DB=${{ fromJson(steps.analysis.outputs.db-locations).cpp }} CPP_DB=${{ fromJson(steps.analysis.outputs.db-locations).cpp }}
if [[ ! -d $CPP_DB ]] || [[ ! $CPP_DB == ${{ runner.temp }}/customDbLocation/* ]]; then if [[ ! -d $CPP_DB ]] || [[ ! $CPP_DB == ${{ runner.temp }}/customDbLocation/* ]]; then
@@ -43,3 +54,23 @@ steps:
echo "Did not create a database for Python, or created it in the wrong location." echo "Did not create a database for Python, or created it in the wrong location."
exit 1 exit 1
fi fi
- name: Check language autodetect for Ruby
if: "(matrix.version == 'cached' || matrix.version == 'latest' || matrix.version == 'nightly-latest')"
shell: bash
run: |
RUBY_DB=${{ fromJson(steps.analysis.outputs.db-locations).ruby }}
if [[ ! -d $RUBY_DB ]] || [[ ! $RUBY_DB == ${{ runner.temp }}/customDbLocation/* ]]; then
echo "Did not create a database for Ruby, or created it in the wrong location."
exit 1
fi
- name: Check language autodetect for Swift
if: "!startsWith(matrix.os, 'windows') && (matrix.version == 'cached' || matrix.version == 'latest' || matrix.version == 'nightly-latest')"
shell: bash
run: |
SWIFT_DB=${{ fromJson(steps.analysis.outputs.db-locations).swift }}
if [[ ! -d $SWIFT_DB ]] || [[ ! $SWIFT_DB == ${{ runner.temp }}/customDbLocation/* ]]; then
echo "Did not create a database for Swift, or created it in the wrong location."
exit 1
fi
-22
View File
@@ -1,22 +0,0 @@
# This check should be combined into `multi-language-autodetect.yml` once Ruby is GA'ed
# and the `CODEQL_ENABLE_EXPERIMENTAL_FEATURES` environment variable is not needed.
name: "Ruby analysis using autodetect"
description: "Tests creation of a Ruby database when language isn't specified in init"
versions: ["latest", "cached", "nightly-latest"]
operatingSystems: ["ubuntu", "macos"]
env:
CODEQL_ENABLE_EXPERIMENTAL_FEATURES: "true"
steps:
- uses: ./../action/init
with:
tools: ${{ steps.prepare-test.outputs.tools-url }}
- uses: ./../action/analyze
id: analysis
- name: Check database
shell: bash
run: |
RUBY_DB="${{ fromJson(steps.analysis.outputs.db-locations).ruby }}"
if [[ ! -d "$RUBY_DB" ]]; then
echo "Did not create a database for Ruby."
exit 1
fi
-2
View File
@@ -2,8 +2,6 @@ name: "Ruby analysis"
description: "Tests creation of a Ruby database" description: "Tests creation of a Ruby database"
versions: ["latest", "cached", "nightly-latest"] versions: ["latest", "cached", "nightly-latest"]
operatingSystems: ["ubuntu", "macos"] operatingSystems: ["ubuntu", "macos"]
env:
CODEQL_ENABLE_EXPERIMENTAL_FEATURES: "true"
steps: steps:
- uses: ./../action/init - uses: ./../action/init
with: with:
+3
View File
@@ -6,6 +6,9 @@ operatingSystems: ["macos"]
env: env:
CODEQL_ENABLE_EXPERIMENTAL_FEATURES_SWIFT: "true" CODEQL_ENABLE_EXPERIMENTAL_FEATURES_SWIFT: "true"
steps: steps:
- uses: swift-actions/setup-swift@5cdaa9161ad1f55ae39a5ea1784ef96de72f95d9
with:
swift-version: "5.7"
- uses: ./../action/init - uses: ./../action/init
with: with:
languages: swift languages: swift
+3
View File
@@ -6,6 +6,9 @@ env:
CODEQL_ENABLE_EXPERIMENTAL_FEATURES_SWIFT: "true" CODEQL_ENABLE_EXPERIMENTAL_FEATURES_SWIFT: "true"
DOTNET_GENERATE_ASPNET_CERTIFICATE: "false" DOTNET_GENERATE_ASPNET_CERTIFICATE: "false"
steps: steps:
- uses: swift-actions/setup-swift@5cdaa9161ad1f55ae39a5ea1784ef96de72f95d9
with:
swift-version: "5.7"
- uses: ./../action/init - uses: ./../action/init
with: with:
languages: swift languages: swift
+6 -1
View File
@@ -217,7 +217,12 @@ async function run() {
const gitHubVersion = await getGitHubVersion(); const gitHubVersion = await getGitHubVersion();
const features = new Features(gitHubVersion, repositoryNwo, logger); const features = new Features(
gitHubVersion,
repositoryNwo,
actionsUtil.getTemporaryDirectory(),
logger
);
await runAutobuildIfLegacyGoWorkflow(config, logger); await runAutobuildIfLegacyGoWorkflow(config, logger);
+1 -1
View File
@@ -1 +1 @@
{"maximumVersion": "3.7", "minimumVersion": "3.3"} {"maximumVersion": "3.8", "minimumVersion": "3.3"}
+97 -11
View File
@@ -1,3 +1,6 @@
import * as fs from "fs";
import * as path from "path";
import test from "ava"; import test from "ava";
import { import {
@@ -5,6 +8,7 @@ import {
featureConfig, featureConfig,
FeatureEnablement, FeatureEnablement,
Features, Features,
FEATURE_FLAGS_FILE_NAME,
} from "./feature-flags"; } from "./feature-flags";
import { getRunnerLogger } from "./logging"; import { getRunnerLogger } from "./logging";
import { parseRepositoryNwo } from "./repository"; import { parseRepositoryNwo } from "./repository";
@@ -42,7 +46,7 @@ for (const variant of ALL_FEATURES_DISABLED_VARIANTS) {
test(`All features are disabled if running against ${variant.description}`, async (t) => { test(`All features are disabled if running against ${variant.description}`, async (t) => {
await withTmpDir(async (tmpDir) => { await withTmpDir(async (tmpDir) => {
const loggedMessages = []; const loggedMessages = [];
const featureEnablement = setUpTests( const featureEnablement = setUpFeatureFlagTests(
tmpDir, tmpDir,
getRecordingLogger(loggedMessages), getRecordingLogger(loggedMessages),
variant.gitHubVersion variant.gitHubVersion
@@ -72,7 +76,7 @@ for (const variant of ALL_FEATURES_DISABLED_VARIANTS) {
test("API response missing", async (t) => { test("API response missing", async (t) => {
await withTmpDir(async (tmpDir) => { await withTmpDir(async (tmpDir) => {
const loggedMessages: LoggedMessage[] = []; const loggedMessages: LoggedMessage[] = [];
const featureEnablement = setUpTests( const featureEnablement = setUpFeatureFlagTests(
tmpDir, tmpDir,
getRecordingLogger(loggedMessages) getRecordingLogger(loggedMessages)
); );
@@ -94,7 +98,7 @@ test("API response missing", async (t) => {
test("Features are disabled if they're not returned in API response", async (t) => { test("Features are disabled if they're not returned in API response", async (t) => {
await withTmpDir(async (tmpDir) => { await withTmpDir(async (tmpDir) => {
const loggedMessages: LoggedMessage[] = []; const loggedMessages: LoggedMessage[] = [];
const featureEnablement = setUpTests( const featureEnablement = setUpFeatureFlagTests(
tmpDir, tmpDir,
getRecordingLogger(loggedMessages) getRecordingLogger(loggedMessages)
); );
@@ -116,7 +120,7 @@ test("Features are disabled if they're not returned in API response", async (t)
test("Feature flags exception is propagated if the API request errors", async (t) => { test("Feature flags exception is propagated if the API request errors", async (t) => {
await withTmpDir(async (tmpDir) => { await withTmpDir(async (tmpDir) => {
const featureEnablement = setUpTests(tmpDir); const featureEnablement = setUpFeatureFlagTests(tmpDir);
mockFeatureFlagApiEndpoint(500, {}); mockFeatureFlagApiEndpoint(500, {});
@@ -137,7 +141,7 @@ test("Feature flags exception is propagated if the API request errors", async (t
for (const feature of Object.keys(featureConfig)) { for (const feature of Object.keys(featureConfig)) {
test(`Only feature '${feature}' is enabled if enabled in the API response. Other features disabled`, async (t) => { test(`Only feature '${feature}' is enabled if enabled in the API response. Other features disabled`, async (t) => {
await withTmpDir(async (tmpDir) => { await withTmpDir(async (tmpDir) => {
const featureEnablement = setUpTests(tmpDir); const featureEnablement = setUpFeatureFlagTests(tmpDir);
// set all features to false except the one we're testing // set all features to false except the one we're testing
const expectedFeatureEnablement: { [feature: string]: boolean } = {}; const expectedFeatureEnablement: { [feature: string]: boolean } = {};
@@ -162,7 +166,7 @@ for (const feature of Object.keys(featureConfig)) {
test(`Only feature '${feature}' is enabled if the associated environment variable is true. Others disabled.`, async (t) => { test(`Only feature '${feature}' is enabled if the associated environment variable is true. Others disabled.`, async (t) => {
await withTmpDir(async (tmpDir) => { await withTmpDir(async (tmpDir) => {
const featureEnablement = setUpTests(tmpDir); const featureEnablement = setUpFeatureFlagTests(tmpDir);
const expectedFeatureEnablement = initializeFeatures(false); const expectedFeatureEnablement = initializeFeatures(false);
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement); mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
@@ -188,7 +192,7 @@ for (const feature of Object.keys(featureConfig)) {
test(`Feature '${feature}' is disabled if the associated environment variable is false, even if enabled in API`, async (t) => { test(`Feature '${feature}' is disabled if the associated environment variable is false, even if enabled in API`, async (t) => {
await withTmpDir(async (tmpDir) => { await withTmpDir(async (tmpDir) => {
const featureEnablement = setUpTests(tmpDir); const featureEnablement = setUpFeatureFlagTests(tmpDir);
const expectedFeatureEnablement = initializeFeatures(true); const expectedFeatureEnablement = initializeFeatures(true);
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement); mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
@@ -215,7 +219,7 @@ for (const feature of Object.keys(featureConfig)) {
if (featureConfig[feature].minimumVersion !== undefined) { if (featureConfig[feature].minimumVersion !== undefined) {
test(`Getting feature '${feature} should throw if no codeql is provided`, async (t) => { test(`Getting feature '${feature} should throw if no codeql is provided`, async (t) => {
await withTmpDir(async (tmpDir) => { await withTmpDir(async (tmpDir) => {
const featureEnablement = setUpTests(tmpDir); const featureEnablement = setUpFeatureFlagTests(tmpDir);
const expectedFeatureEnablement = initializeFeatures(true); const expectedFeatureEnablement = initializeFeatures(true);
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement); mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
@@ -233,7 +237,7 @@ for (const feature of Object.keys(featureConfig)) {
if (featureConfig[feature].minimumVersion !== undefined) { if (featureConfig[feature].minimumVersion !== undefined) {
test(`Feature '${feature}' is disabled if the minimum CLI version is below ${featureConfig[feature].minimumVersion}`, async (t) => { test(`Feature '${feature}' is disabled if the minimum CLI version is below ${featureConfig[feature].minimumVersion}`, async (t) => {
await withTmpDir(async (tmpDir) => { await withTmpDir(async (tmpDir) => {
const featureEnablement = setUpTests(tmpDir); const featureEnablement = setUpFeatureFlagTests(tmpDir);
const expectedFeatureEnablement = initializeFeatures(true); const expectedFeatureEnablement = initializeFeatures(true);
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement); mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
@@ -285,6 +289,88 @@ test("At least one feature has a minimum version specified", (t) => {
); );
}); });
test("Feature flags are saved to disk", async (t) => {
await withTmpDir(async (tmpDir) => {
const featureEnablement = setUpFeatureFlagTests(tmpDir);
const expectedFeatureEnablement = initializeFeatures(true);
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
const cachedFeatureFlags = path.join(tmpDir, FEATURE_FLAGS_FILE_NAME);
t.false(
fs.existsSync(cachedFeatureFlags),
"Feature flag cached file should not exist before getting feature flags"
);
t.true(
await featureEnablement.getValue(
Feature.CliConfigFileEnabled,
includeCodeQlIfRequired(Feature.CliConfigFileEnabled)
),
"Feature flag should be enabled initially"
);
t.true(
fs.existsSync(cachedFeatureFlags),
"Feature flag cached file should exist after getting feature flags"
);
const actualFeatureEnablement = JSON.parse(
fs.readFileSync(cachedFeatureFlags, "utf8")
);
t.deepEqual(actualFeatureEnablement, expectedFeatureEnablement);
// now test that we actually use the feature flag cache instead of the server
actualFeatureEnablement[Feature.CliConfigFileEnabled] = false;
fs.writeFileSync(
cachedFeatureFlags,
JSON.stringify(actualFeatureEnablement)
);
// delete the in memory cache so that we are forced to use the cached file
(featureEnablement as any).gitHubFeatureFlags.cachedApiResponse = undefined;
t.false(
await featureEnablement.getValue(
Feature.CliConfigFileEnabled,
includeCodeQlIfRequired(Feature.CliConfigFileEnabled)
),
"Feature flag should be enabled after reading from cached file"
);
});
});
test("Environment variable can override feature flag cache", async (t) => {
await withTmpDir(async (tmpDir) => {
const featureEnablement = setUpFeatureFlagTests(tmpDir);
const expectedFeatureEnablement = initializeFeatures(true);
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
const cachedFeatureFlags = path.join(tmpDir, FEATURE_FLAGS_FILE_NAME);
t.true(
await featureEnablement.getValue(
Feature.CliConfigFileEnabled,
includeCodeQlIfRequired(Feature.CliConfigFileEnabled)
),
"Feature flag should be enabled initially"
);
t.true(
fs.existsSync(cachedFeatureFlags),
"Feature flag cached file should exist after getting feature flags"
);
process.env.CODEQL_PASS_CONFIG_TO_CLI = "false";
t.false(
await featureEnablement.getValue(
Feature.CliConfigFileEnabled,
includeCodeQlIfRequired(Feature.CliConfigFileEnabled)
),
"Feature flag should be disabled after setting env var"
);
});
});
function assertAllFeaturesUndefinedInApi(t, loggedMessages: LoggedMessage[]) { function assertAllFeaturesUndefinedInApi(t, loggedMessages: LoggedMessage[]) {
for (const feature of Object.keys(featureConfig)) { for (const feature of Object.keys(featureConfig)) {
t.assert( t.assert(
@@ -305,14 +391,14 @@ function initializeFeatures(initialValue: boolean) {
}, {}); }, {});
} }
function setUpTests( function setUpFeatureFlagTests(
tmpDir: string, tmpDir: string,
logger = getRunnerLogger(true), logger = getRunnerLogger(true),
gitHubVersion = { type: GitHubVariant.DOTCOM } as util.GitHubVersion gitHubVersion = { type: GitHubVariant.DOTCOM } as util.GitHubVersion
): FeatureEnablement { ): FeatureEnablement {
setupActionsVars(tmpDir, tmpDir); setupActionsVars(tmpDir, tmpDir);
return new Features(gitHubVersion, testRepositoryNwo, logger); return new Features(gitHubVersion, testRepositoryNwo, tmpDir, logger);
} }
function includeCodeQlIfRequired(feature: string) { function includeCodeQlIfRequired(feature: string) {
+69 -10
View File
@@ -1,3 +1,6 @@
import * as fs from "fs";
import * as path from "path";
import { getApiClient } from "./api-client"; import { getApiClient } from "./api-client";
import { CodeQL } from "./codeql"; import { CodeQL } from "./codeql";
import { Logger } from "./logging"; import { Logger } from "./logging";
@@ -55,6 +58,8 @@ export const featureConfig: Record<
*/ */
type GitHubFeatureFlagsApiResponse = Partial<Record<Feature, boolean>>; type GitHubFeatureFlagsApiResponse = Partial<Record<Feature, boolean>>;
export const FEATURE_FLAGS_FILE_NAME = "cached-feature-flags.json";
/** /**
* Determines the enablement status of a number of features. * Determines the enablement status of a number of features.
* If feature enablement is not able to be determined locally, a request to the * If feature enablement is not able to be determined locally, a request to the
@@ -66,11 +71,13 @@ export class Features implements FeatureEnablement {
constructor( constructor(
gitHubVersion: util.GitHubVersion, gitHubVersion: util.GitHubVersion,
repositoryNwo: RepositoryNwo, repositoryNwo: RepositoryNwo,
tempDir: string,
logger: Logger logger: Logger
) { ) {
this.gitHubFeatureFlags = new GitHubFeatureFlags( this.gitHubFeatureFlags = new GitHubFeatureFlags(
gitHubVersion, gitHubVersion,
repositoryNwo, repositoryNwo,
path.join(tempDir, FEATURE_FLAGS_FILE_NAME),
logger logger
); );
} }
@@ -120,7 +127,6 @@ export class Features implements FeatureEnablement {
if (envVar === "true") { if (envVar === "true") {
return true; return true;
} }
// Ask the GitHub API if the feature is enabled. // Ask the GitHub API if the feature is enabled.
return await this.gitHubFeatureFlags.getValue(feature); return await this.gitHubFeatureFlags.getValue(feature);
} }
@@ -130,15 +136,16 @@ class GitHubFeatureFlags implements FeatureEnablement {
private cachedApiResponse: GitHubFeatureFlagsApiResponse | undefined; private cachedApiResponse: GitHubFeatureFlagsApiResponse | undefined;
constructor( constructor(
private gitHubVersion: util.GitHubVersion, private readonly gitHubVersion: util.GitHubVersion,
private repositoryNwo: RepositoryNwo, private readonly repositoryNwo: RepositoryNwo,
private logger: Logger private readonly featureFlagsFile: string,
private readonly logger: Logger
) { ) {
/**/ /**/
} }
async getValue(feature: Feature): Promise<boolean> { async getValue(feature: Feature): Promise<boolean> {
const response = await this.getApiResponse(); const response = await this.getAllFeatures();
if (response === undefined) { if (response === undefined) {
this.logger.debug( this.logger.debug(
`No feature flags API response for ${feature}, considering it disabled.` `No feature flags API response for ${feature}, considering it disabled.`
@@ -155,11 +162,63 @@ class GitHubFeatureFlags implements FeatureEnablement {
return !!featureEnablement; return !!featureEnablement;
} }
private async getApiResponse(): Promise<GitHubFeatureFlagsApiResponse> { private async getAllFeatures(): Promise<GitHubFeatureFlagsApiResponse> {
const apiResponse = // if we have an in memory cache, use that
this.cachedApiResponse || (await this.loadApiResponse()); if (this.cachedApiResponse !== undefined) {
this.cachedApiResponse = apiResponse; return this.cachedApiResponse;
return apiResponse; }
// if a previous step has written a feature flags file to disk, use that
const fileFlags = await this.readLocalFlags();
if (fileFlags !== undefined) {
this.cachedApiResponse = fileFlags;
return fileFlags;
}
// if not, request flags from the server
let remoteFlags = await this.loadApiResponse();
if (remoteFlags === undefined) {
remoteFlags = {};
}
// cache the response in memory
this.cachedApiResponse = remoteFlags;
// and cache them to disk so future workflow steps can use them
await this.writeLocalFlags(remoteFlags);
return remoteFlags;
}
private async readLocalFlags(): Promise<
GitHubFeatureFlagsApiResponse | undefined
> {
try {
if (fs.existsSync(this.featureFlagsFile)) {
this.logger.debug(
`Loading feature flags from ${this.featureFlagsFile}`
);
return JSON.parse(fs.readFileSync(this.featureFlagsFile, "utf8"));
}
} catch (e) {
this.logger.warning(
`Error reading cached feature flags file ${this.featureFlagsFile}: ${e}. Requesting from GitHub instead.`
);
}
return undefined;
}
private async writeLocalFlags(
flags: GitHubFeatureFlagsApiResponse
): Promise<void> {
try {
this.logger.debug(`Writing feature flags to ${this.featureFlagsFile}`);
fs.writeFileSync(this.featureFlagsFile, JSON.stringify(flags));
} catch (e) {
this.logger.warning(
`Error writing cached feature flags file ${this.featureFlagsFile}: ${e}.`
);
}
} }
private async loadApiResponse() { private async loadApiResponse() {
+6 -1
View File
@@ -158,7 +158,12 @@ async function run() {
getRequiredEnvParam("GITHUB_REPOSITORY") getRequiredEnvParam("GITHUB_REPOSITORY")
); );
const features = new Features(gitHubVersion, repositoryNwo, logger); const features = new Features(
gitHubVersion,
repositoryNwo,
getTemporaryDirectory(),
logger
);
try { try {
const workflowErrors = await validateWorkflow(); const workflowErrors = await validateWorkflow();
+1 -1
View File
@@ -651,7 +651,7 @@ export async function checkActionVersion(version: string) {
)) ))
) { ) {
core.warning( core.warning(
"CodeQL Action v1 will be deprecated on December 7th, 2022. Please upgrade to v2. For " + "CodeQL Action v1 will be deprecated on January 18th, 2023. Please upgrade to v2. For " +
"more information, see " + "more information, see " +
"https://github.blog/changelog/2022-04-27-code-scanning-deprecation-of-codeql-action-v1/" "https://github.blog/changelog/2022-04-27-code-scanning-deprecation-of-codeql-action-v1/"
); );