Skip to content

Commit 53e3a55

Browse files
authored
Merge pull request #310 from snyk/feat/include_openshift_support
feat: include integration test support for OpenShift env
2 parents cc16ee2 + 2705282 commit 53e3a55

File tree

9 files changed

+156
-12
lines changed

9 files changed

+156
-12
lines changed

.circleci/config.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,32 @@ jobs:
222222
fi
223223
when: on_fail
224224

225+
openshift4_integration_tests:
226+
<<: *default_machine_config
227+
steps:
228+
- checkout
229+
- run:
230+
name: INTEGRATION TESTS OpenShift 4
231+
command: |
232+
export NVM_DIR="/opt/circleci/.nvm" &&
233+
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" &&
234+
nvm install v10 &&
235+
npm install &&
236+
docker login --username ${DOCKERHUB_USER} --password ${DOCKERHUB_PASSWORD} &&
237+
export IMAGE_TAG=$([[ "$CIRCLE_BRANCH" == "staging" ]] && echo "staging-candidate" || echo "discardable") &&
238+
export KUBERNETES_MONITOR_IMAGE_NAME_AND_TAG=snyk/kubernetes-monitor:${IMAGE_TAG}-${CIRCLE_SHA1} &&
239+
docker pull ${KUBERNETES_MONITOR_IMAGE_NAME_AND_TAG} &&
240+
.circleci/do-exclusively --branch staging npm run test:integration:openshift4
241+
- run:
242+
name: Notify Slack on failure
243+
command: |
244+
if [[ "$CIRCLE_BRANCH" == "staging" ]]; then
245+
./scripts/slack-notify-failure.sh "staging-openshift4-integration-tests-${CIRCLE_SHA1}"
246+
else
247+
echo "Current branch is $CIRCLE_BRANCH so skipping notifying Slack"
248+
fi
249+
when: on_fail
250+
225251
######################## MERGE TO STAGING ########################
226252
tag_and_push:
227253
<<: *default_container_config
@@ -350,6 +376,10 @@ workflows:
350376
requires:
351377
- build_image
352378
<<: *staging_branch_only_filter
379+
- openshift4_integration_tests:
380+
requires:
381+
- build_image
382+
<<: *staging_branch_only_filter
353383
- package_manager_test_apk:
354384
requires:
355385
- build_image

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"test:integration": "TEST_PLATFORM=kind CREATE_CLUSTER=true tap test/integration/kubernetes.test.ts --timeout=1200",
1111
"test:integration:kind": "TEST_PLATFORM=kind CREATE_CLUSTER=true tap test/integration/kubernetes.test.ts --timeout=1200",
1212
"test:integration:eks": "TEST_PLATFORM=eks CREATE_CLUSTER=false tap test/integration/kubernetes.test.ts --timeout=1200",
13+
"test:integration:openshift4": "TEST_PLATFORM=openshift4 CREATE_CLUSTER=false tap test/integration/kubernetes.test.ts --timeout=1200",
1314
"test:coverage": "npm run test:unit -- --coverage",
1415
"test:watch": "tsc-watch --onSuccess 'npm run test:unit'",
1516
"test:apk": "TEST_PLATFORM=kind CREATE_CLUSTER=true PACKAGE_MANAGER=apk tap test/integration/package-manager.test.ts --timeout=7200",

snyk-monitor-deployment.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ spec:
5656
optional: true
5757
- name: SNYK_MONITOR_VERSION
5858
value: IMAGE_TAG_OVERRIDE_WHEN_PUBLISHING
59+
- name: HOME
60+
value: /srv/app
5961
resources:
6062
requests:
6163
cpu: '250m'

src/common/process.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export function exec(bin: string, ...processArgs: IProcessArgument[]):
1616
// For example, that process doesn't need to know secrets like our integrationId!
1717
const env = {
1818
PATH: process.env.PATH,
19+
HOME: process.env.HOME,
1920
};
2021

2122
const allArguments = processArgs.map((arg) => arg.body);

test/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,17 @@ This test runs whenever we commit to our `staging` branch, and at the moment may
6969

7070
Run with `npm run test:integration:eks`.
7171

72+
### OpenShift 4 ###
73+
74+
OpenShift 4 is Red Hat platform and helps us ensure we support not only the generic Kubernetes API, but also specifically OpenShift 4.
75+
76+
This test uses an existing Google Cloud Platform (GCP) account with an existing OpenShift 4 cluster, and as such has a few more prerequisites:
77+
- OpenShift 4 environment variables: `OPEN_SHIFT_4_USER_PASSWORD` and `OPEN_SHIFT_4_CLUSTER_URL` are used to authenticate against the OpenShift 4 cluster in GCP account.
78+
79+
This test runs whenever we commit to our `staging` branch, and at the moment may only run once concurrently since it uses the same cluster.
80+
81+
Run with `npm run test:integration:openshift4`.
82+
7283
### Package Managers ###
7384

7485
These tests attempt to provide some more thorough coverage for our scans of specific package manager: APK, APT and RPM.

test/helpers/deployment.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import * as tap from 'tap';
22
import { V1Deployment } from '@kubernetes/client-node';
33

4+
const OPENSHIFT4 = "openshift4";
5+
const testPlatform = process.env['TEST_PLATFORM'] || 'kind';
6+
47
export function validateSecureConfiguration(test: tap, deployment: V1Deployment) {
58
if (
69
!deployment.spec ||
@@ -42,16 +45,19 @@ export function validateSecureConfiguration(test: tap, deployment: V1Deployment)
4245
tap.ok(securityContext.allowPrivilegeEscalation === false, 'must explicitly set allowPrivilegeEscalation to false');
4346
tap.ok(securityContext.privileged === false, 'must explicitly set privileged to false');
4447
tap.ok(securityContext.runAsNonRoot === true, 'must explicitly set runAsNonRoot to true');
45-
tap.ok(
46-
securityContext.runAsUser !== undefined &&
47-
securityContext.runAsUser >= 10001,
48-
'must explicitly set runAsUser to be 10001 or greater',
49-
);
50-
tap.ok(
51-
securityContext.runAsGroup !== undefined &&
52-
securityContext.runAsGroup >= 10001,
53-
'must explicitly set runAsGroup to be 10001 or greater',
54-
);
48+
49+
if (testPlatform !== OPENSHIFT4) {
50+
tap.ok(
51+
securityContext.runAsUser !== undefined &&
52+
securityContext.runAsUser >= 10001,
53+
'must explicitly set runAsUser to be 10001 or greater',
54+
);
55+
tap.ok(
56+
securityContext.runAsGroup !== undefined &&
57+
securityContext.runAsGroup >= 10001,
58+
'must explicitly set runAsGroup to be 10001 or greater',
59+
);
60+
}
5561
}
5662

5763
export function validateVolumeMounts(test: tap, deployment: V1Deployment) {

test/setup/index.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import platforms from './platforms';
66
import * as kubectl from '../helpers/kubectl';
77
import * as waiters from './waiters';
88

9+
const OPENSHIFT4 = "openshift4";
910
const testPlatform = process.env['TEST_PLATFORM'] || 'kind';
1011
const createCluster = process.env['CREATE_CLUSTER'] === 'true';
1112

@@ -27,6 +28,7 @@ function createTestYamlDeployment(
2728
integrationId: string,
2829
imageNameAndTag: string,
2930
imagePullPolicy: string,
31+
platform: string,
3032
): void {
3133
console.log('Creating test deployment...');
3234
const originalDeploymentYaml = readFileSync('./snyk-monitor-deployment.yaml', 'utf8');
@@ -53,6 +55,11 @@ function createTestYamlDeployment(
5355
value: 'https://kubernetes-upstream.dev.snyk.io',
5456
};
5557

58+
if (platform === OPENSHIFT4) {
59+
delete deployment.spec.template.spec.containers[0].securityContext.runAsUser;
60+
delete deployment.spec.template.spec.containers[0].securityContext.runAsGroup;
61+
}
62+
5663
writeFileSync(newYamlPath, stringify(deployment));
5764
console.log('Created test deployment');
5865
}
@@ -109,6 +116,7 @@ async function createSecretForGcrIoAccess(): Promise<void> {
109116
async function installKubernetesMonitor(
110117
imageNameAndTag: string,
111118
imagePullPolicy: string,
119+
platform: string,
112120
): Promise<string> {
113121
const namespace = 'snyk-monitor';
114122
await kubectl.createNamespace(namespace);
@@ -122,7 +130,7 @@ async function installKubernetesMonitor(
122130
});
123131

124132
const testYaml = 'snyk-monitor-test-deployment.yaml';
125-
createTestYamlDeployment(testYaml, integrationId, imageNameAndTag, imagePullPolicy);
133+
createTestYamlDeployment(testYaml, integrationId, imageNameAndTag, imagePullPolicy, platform);
126134

127135
await kubectl.applyK8sYaml('./snyk-monitor-cluster-permissions.yaml');
128136
await kubectl.applyK8sYaml('./snyk-monitor-test-deployment.yaml');
@@ -154,7 +162,7 @@ export async function deployMonitor(): Promise<string> {
154162
// TODO: hack, rewrite this
155163
const imagePullPolicy = testPlatform === 'kind' ? 'Never' : 'Always';
156164

157-
const integrationId = await installKubernetesMonitor(remoteImageName, imagePullPolicy);
165+
const integrationId = await installKubernetesMonitor(remoteImageName, imagePullPolicy, testPlatform);
158166
await waiters.waitForMonitorToBeReady();
159167
console.log(`Deployed the snyk-monitor with integration ID ${integrationId}`);
160168
return integrationId;

test/setup/platforms/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as kind from './kind';
22
import * as eks from './eks';
3+
import * as openshift4 from './openshift4';
34

45
interface IPlatformSetup {
56
// create a Kubernetes cluster
@@ -30,7 +31,16 @@ const eksSetup: IPlatformSetup = {
3031
clean: eks.clean,
3132
};
3233

34+
const openshift4Setup: IPlatformSetup = {
35+
create: openshift4.createCluster,
36+
loadImage: openshift4.loadImageInCluster,
37+
delete: openshift4.deleteCluster,
38+
config: openshift4.exportKubeConfig,
39+
clean: openshift4.clean,
40+
};
41+
3342
export default {
3443
kind: kindSetup,
3544
eks: eksSetup,
45+
openshift4: openshift4Setup,
3646
};

test/setup/platforms/openshift4.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { exec } from 'child-process-promise';
2+
import * as kubectl from '../../helpers/kubectl';
3+
import { accessSync, chmodSync, constants, writeFileSync } from 'fs';
4+
import { platform, tmpdir } from 'os';
5+
import { resolve } from 'path';
6+
import * as needle from 'needle';
7+
8+
export async function createCluster(): Promise<void> {
9+
throw new Error('Not implemented');
10+
}
11+
12+
export async function deleteCluster(): Promise<void> {
13+
throw new Error('Not implemented');
14+
}
15+
16+
export async function exportKubeConfig(): Promise<void> {
17+
await installOpenShiftCli(normalizePlatform(platform()), version());
18+
const userPassword = process.env['OPEN_SHIFT_4_USER_PASSWORD'];
19+
const clusterURL = process.env['OPEN_SHIFT_4_CLUSTER_URL'];
20+
const cmd = `./oc login -u kubeadmin -p ${userPassword} ${clusterURL} --insecure-skip-tls-verify=true --kubeconfig ./kubeconfig`;
21+
22+
await exec(cmd);
23+
process.env.KUBECONFIG = './kubeconfig';
24+
}
25+
26+
export async function loadImageInCluster(imageNameAndTag: string): Promise<string> {
27+
return imageNameAndTag;
28+
}
29+
30+
export async function clean(): Promise<void> {
31+
await exportKubeConfig();
32+
await Promise.all([
33+
kubectl.deleteNamespace('services'),
34+
kubectl.deleteNamespace('snyk-monitor'),
35+
]);
36+
}
37+
38+
async function installOpenShiftCli(osDistro: string, version: string): Promise<void> {
39+
try {
40+
accessSync(resolve(process.cwd(), 'oc'), constants.R_OK);
41+
} catch (error) {
42+
console.log('Downloading OpenShift OC...');
43+
44+
const ocpURL = constructCliDownloadUrl(osDistro, version);
45+
const bodyData = null;
46+
// eslint-disable-next-line @typescript-eslint/camelcase
47+
const requestOptions = { follow_max: 2 };
48+
await needle('get',
49+
ocpURL,
50+
bodyData,
51+
requestOptions,
52+
).then( async (response) => {
53+
writeFileSync(`${tmpdir()}/openshift-client`, response.body);
54+
await exec(`tar -xzvf ${tmpdir()}/openshift-client -C ${tmpdir()}`);
55+
await exec(`cp ${tmpdir()}/oc .`);
56+
chmodSync('oc', 0o755); // rwxr-xr-x
57+
});
58+
59+
console.log('OpenShift OC downloaded and installed!');
60+
}
61+
}
62+
63+
function constructCliDownloadUrl(platform: string, version: string): string {
64+
return `https://mirror.openshift.com/pub/openshift-v4/clients/ocp/latest/openshift-client-${platform}-${version}.tar.gz`;
65+
}
66+
67+
function version(): string {
68+
return "4.3.0";
69+
}
70+
71+
function normalizePlatform(platform: string): string {
72+
if (platform === "darwin") platform = "mac";
73+
74+
return platform;
75+
}

0 commit comments

Comments
 (0)