diff --git a/lib/ci-config-stack-benchmark.ts b/lib/ci-config-stack-benchmark.ts
new file mode 100644
index 00000000..ce4fe00d
--- /dev/null
+++ b/lib/ci-config-stack-benchmark.ts
@@ -0,0 +1,93 @@
+/**
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * The OpenSearch Contributors require contributions made to
+ * this file be licensed under the Apache-2.0 license or a
+ * compatible open source license.
+ */
+
+import { CfnOutput, Stack, StackProps } from 'aws-cdk-lib';
+import { Secret } from 'aws-cdk-lib/aws-secretsmanager';
+import { Construct } from 'constructs';
+
+export class CIConfigStackBenchmark extends Stack {
+ static readonly CERTIFICATE_ARN_SECRET_EXPORT_VALUE: string = 'certificateArnSecretBenchmark';
+
+ static readonly CERTIFICATE_CONTENTS_SECRET_EXPORT_VALUE: string = 'certContentsSecretBenchmark';
+
+ static readonly CERTIFICATE_CHAIN_SECRET_EXPORT_VALUE: string = 'certChainSecretBenchmark';
+
+ static readonly PRIVATE_KEY_SECRET_EXPORT_VALUE: string = 'privateKeySecretBenchmark';
+
+ static readonly REDIRECT_URL_SECRET_EXPORT_VALUE: string = 'redirectUrlSecretBenchmark';
+
+ static readonly OIDC_CONFIGURATION_VALUE_SECRET_EXPORT_VALUE: string = 'OIDCConfigValueSecretBenchmark';
+
+ static readonly CASC_RELOAD_TOKEN_SECRET_EXPORT_VALUE: string = 'cascBenchmark';
+
+ constructor(scope: Construct, id: string, props?: StackProps) {
+ // @ts-ignore
+ super(scope, id, props);
+
+ const arnSecret = new Secret(this, 'certificateArnBenchmark', {
+ description: 'Certificate ARN retrieved after uploading certificate to IAM server',
+ });
+ const certContentsSecret = new Secret(this, 'certContentsBenchmark', {
+ description: 'Contents of public key of the SSL certificate',
+ });
+ const certChainSecret = new Secret(this, 'certChainBenchmark', {
+ description: 'Contents of the SSL certificate chain',
+ });
+ const privateKeySecret = new Secret(this, 'privateKeyBenchmark', {
+ description: 'Contents of private key of the SSL certificate',
+ });
+ const redirectUrlSecret = new Secret(this, 'redirectUrlBenchmark', {
+ description: 'Redirect url for Jenkins',
+ });
+ const OIDCConfigValuesSecret = new Secret(this, 'OIDCConfigValuesBenchmark', {
+ description: 'OIDC params in JSON format',
+ });
+ const CascReloadTokenValuesSecret = new Secret(this, 'CascReloadTokenValueBenchmark', {
+ description: 'Reload token (password) required for configuration as code plugin',
+ generateSecretString: {
+ excludeCharacters: '#$_!"%&\'()*+,./:;<=>?@[\\]^`{|}~',
+ passwordLength: 5,
+ },
+ });
+
+ new CfnOutput(this, 'certificateArnSecretBenchmark', {
+ value: arnSecret.secretArn,
+ exportName: CIConfigStackBenchmark.CERTIFICATE_ARN_SECRET_EXPORT_VALUE,
+ });
+
+ new CfnOutput(this, 'certContentsSecretBenchmark', {
+ value: certContentsSecret.secretArn,
+ exportName: CIConfigStackBenchmark.CERTIFICATE_CONTENTS_SECRET_EXPORT_VALUE,
+ });
+
+ new CfnOutput(this, 'certChainSecretBenchmark', {
+ value: certChainSecret.secretArn,
+ exportName: CIConfigStackBenchmark.CERTIFICATE_CHAIN_SECRET_EXPORT_VALUE,
+ });
+
+ new CfnOutput(this, 'privateKeySecretBenchmark', {
+ value: privateKeySecret.secretArn,
+ exportName: CIConfigStackBenchmark.PRIVATE_KEY_SECRET_EXPORT_VALUE,
+ });
+
+ new CfnOutput(this, 'redirectUrlSecretBenchmark', {
+ value: redirectUrlSecret.secretArn,
+ exportName: CIConfigStackBenchmark.REDIRECT_URL_SECRET_EXPORT_VALUE,
+ });
+
+ new CfnOutput(this, 'OIDCConfigValuesSecretBenchmark', {
+ value: OIDCConfigValuesSecret.secretArn,
+ exportName: CIConfigStackBenchmark.OIDC_CONFIGURATION_VALUE_SECRET_EXPORT_VALUE,
+ });
+
+ new CfnOutput(this, 'cascSecretValueBenchmark', {
+ value: CascReloadTokenValuesSecret.secretArn,
+ exportName: CIConfigStackBenchmark.CASC_RELOAD_TOKEN_SECRET_EXPORT_VALUE,
+ });
+ }
+}
diff --git a/lib/ci-stack-benchmark.ts b/lib/ci-stack-benchmark.ts
new file mode 100644
index 00000000..dc73fa19
--- /dev/null
+++ b/lib/ci-stack-benchmark.ts
@@ -0,0 +1,245 @@
+/**
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * The OpenSearch Contributors require contributions made to
+ * this file be licensed under the Apache-2.0 license or a
+ * compatible open source license.
+ */
+
+import { CfnOutput, CfnParameter, Fn, Stack, StackProps } from 'aws-cdk-lib';
+import {
+ IPeer, Peer, Vpc,
+} from 'aws-cdk-lib/aws-ec2';
+import {
+ ApplicationTargetGroup,
+ ListenerCertificate, Protocol, ListenerCondition, ApplicationListener,
+} from 'aws-cdk-lib/aws-elasticloadbalancingv2';
+import { Bucket } from 'aws-cdk-lib/aws-s3';
+import { Secret } from 'aws-cdk-lib/aws-secretsmanager';
+import { Construct } from 'constructs';
+import { CIConfigStackBenchmark } from './ci-config-stack-benchmark';
+import { AgentNodeProps } from './compute/agent-node-config';
+import { AgentNodesBenchmark } from './compute/agent-nodes-benchmark';
+import { JenkinsMainNodeBenchmark } from './compute/jenkins-main-node-benchmark';
+import { RunAdditionalCommands } from './compute/run-additional-commands';
+import { JenkinsMonitoringBenchmark } from './monitoring/ci-alarms-benchmark';
+import { JenkinsSecurityGroups } from './security/ci-security-groups';
+
+export interface CIStackPropsBenchmark extends StackProps {
+ /** Should the Jenkins use https */
+ readonly useSsl?: boolean;
+ /** Should an OIDC provider be installed on Jenkins. */
+ readonly runWithOidc?: boolean;
+ /** Restrict jenkins access to */
+ readonly restrictServerAccessTo?: IPeer;
+ /** Additional verification during deployment and resource startup. */
+ readonly ignoreResourcesFailures?: boolean;
+ /** Users with admin access during initial deployment */
+ readonly adminUsers?: string[];
+ /** Additional logic that needs to be run on Master Node. The value has to be path to a file */
+ readonly additionalCommands?: string;
+ /** Do you want to retain jenkins jobs and build history */
+ readonly dataRetention?: boolean;
+ /** IAM role ARN to be assumed by jenkins agent nodes eg: cross-account */
+ readonly agentAssumeRole?: string[];
+ /** File path containing global environment variables to be added to jenkins enviornment */
+ readonly envVarsFilePath?: string;
+ /** Add Mac agent to jenkins */
+ readonly macAgent?: boolean;
+ /** Enable views on jenkins UI */
+ readonly enableViews?: boolean;
+ /** Use Production Agents */
+ readonly useProdAgents?: boolean;
+}
+
+function getServerAccess(serverAccessType: string, restrictServerAccessTo: string) : IPeer {
+ if (typeof restrictServerAccessTo === 'undefined') {
+ throw new Error('restrictServerAccessTo should be specified');
+ }
+ switch (serverAccessType) {
+ case 'ipv4':
+ return restrictServerAccessTo === 'all' ? Peer.anyIpv4() : Peer.ipv4(restrictServerAccessTo);
+ case 'ipv6':
+ return restrictServerAccessTo === 'all' ? Peer.anyIpv6() : Peer.ipv6(restrictServerAccessTo);
+ case 'prefixList':
+ return Peer.prefixList(restrictServerAccessTo);
+ case 'securityGroupId':
+ return Peer.securityGroupId(restrictServerAccessTo);
+ default:
+ throw new Error('serverAccessType should be one of the below values: ipv4, ipv6, prefixList or securityGroupId');
+ }
+}
+
+export class CIStackBenchmark extends Stack {
+ public readonly monitoring: JenkinsMonitoringBenchmark;
+
+ public readonly agentNodesBenchmark: AgentNodeProps[];
+
+ public readonly securityGroups: JenkinsSecurityGroups;
+
+ constructor(scope: Construct, id: string, props: CIStackPropsBenchmark) {
+ // @ts-ignore
+ super(scope, id, props);
+ const accessPort = props.useSsl ? 443 : 80;
+ // @ts-ignore
+ const vpcId = Fn.importValue('CIstackVPCId');
+ const vpc = Vpc.fromVpcAttributes(this, 'VPC', {
+ availabilityZones: ['us-east-1a', 'us-east-1b', 'us-east-1c'],
+ vpcCidrBlock: '10.0.0.0/16',
+ privateSubnetIds: [
+ Fn.importValue('CIstackVPCPrivateSubnets-0'),
+ Fn.importValue('CIstackVPCPrivateSubnets-1'),
+ Fn.importValue('CIstackVPCPrivateSubnets-2'),
+ ],
+ publicSubnetIds: [
+ Fn.importValue('CIstackVPCPublicSubnets-0'),
+ Fn.importValue('CIstackVPCPublicSubnets-1'),
+ Fn.importValue('CIstackVPCPublicSubnets-2'),
+ ],
+ vpcId,
+ });
+
+ const listenerArn = Fn.importValue('ALBListenerArn');
+
+ const macAgentParameter = `${props?.macAgent ?? this.node.tryGetContext('macAgent')}`;
+
+ const useSslParameter = `${props?.useSsl ?? this.node.tryGetContext('useSsl')}`;
+ if (useSslParameter !== 'true' && useSslParameter !== 'false') {
+ throw new Error('useSsl parameter is required to be set as - true or false');
+ }
+
+ const useSsl = useSslParameter === 'true';
+
+ const runWithOidcParameter = `${props?.runWithOidc ?? this.node.tryGetContext('runWithOidc')}`;
+ if (runWithOidcParameter !== 'true' && runWithOidcParameter !== 'false') {
+ throw new Error('runWithOidc parameter is required to be set as - true or false');
+ }
+
+ let useProdAgents = `${props?.useProdAgents ?? this.node.tryGetContext('useProdAgents')}`;
+ if (useProdAgents.toString() === 'undefined') {
+ useProdAgents = 'false';
+ }
+
+ const runWithOidc = runWithOidcParameter === 'true';
+
+ const serverAccessType = this.node.tryGetContext('serverAccessType');
+ const restrictServerAccessTo = this.node.tryGetContext('restrictServerAccessTo');
+ const serverAcess = props?.restrictServerAccessTo ?? getServerAccess(serverAccessType, restrictServerAccessTo);
+ if (!serverAcess) {
+ throw new Error('serverAccessType and restrictServerAccessTo parameters are required - eg: serverAccessType=ipv4 restrictServerAccessTo=10.10.10.10/32');
+ }
+
+ const additionalCommandsContext = `${props?.additionalCommands ?? this.node.tryGetContext('additionalCommands')}`;
+
+ // Setting CfnParameters to record the value in cloudFormation
+ new CfnParameter(this, 'runWithOidc', {
+ description: 'If the jenkins instance should use OIDC + federate',
+ default: runWithOidc,
+ });
+
+ // Setting CfnParameters to record the value in cloudFormation
+ new CfnParameter(this, 'useSsl', {
+ description: 'If the jenkins instance should be access via SSL',
+ default: useSsl,
+ });
+
+ this.securityGroups = new JenkinsSecurityGroups(this, vpc, useSsl, serverAcess, 'benchmarkCI');
+ const importedContentsSecretBucketValue = Fn.importValue(`${CIConfigStackBenchmark.CERTIFICATE_CONTENTS_SECRET_EXPORT_VALUE}`);
+ const importedContentsChainBucketValue = Fn.importValue(`${CIConfigStackBenchmark.CERTIFICATE_CHAIN_SECRET_EXPORT_VALUE}`);
+ const importedCertSecretBucketValue = Fn.importValue(`${CIConfigStackBenchmark.PRIVATE_KEY_SECRET_EXPORT_VALUE}`);
+ const importedArnSecretBucketValue = Fn.importValue(`${CIConfigStackBenchmark.CERTIFICATE_ARN_SECRET_EXPORT_VALUE}`);
+ const importedRedirectUrlSecretBucketValue = Fn.importValue(`${CIConfigStackBenchmark.REDIRECT_URL_SECRET_EXPORT_VALUE}`);
+ const importedOidcConfigValuesSecretBucketValue = Fn.importValue(`${CIConfigStackBenchmark.OIDC_CONFIGURATION_VALUE_SECRET_EXPORT_VALUE}`);
+ const certificateArn = Secret.fromSecretCompleteArn(this, 'certificateArn', importedArnSecretBucketValue.toString());
+ const importedReloadPasswordSecretsArn = Fn.importValue(`${CIConfigStackBenchmark.CASC_RELOAD_TOKEN_SECRET_EXPORT_VALUE}`);
+ const listenerCertificate = ListenerCertificate.fromArn(certificateArn.secretValue.toString());
+
+ // @ts-ignore
+ const agentNodeBenchmark = new AgentNodesBenchmark(this);
+ if (useProdAgents.toString() === 'true') {
+ // eslint-disable-next-line no-console
+ console.warn('Please note that if you have decided to use the provided production jenkins agents then '
+ + 'please make sure that you are deploying the stack in US-EAST-1 region as the AMIs used are only publicly '
+ + 'available in US-EAST-1 region. '
+ + 'If you want to deploy the stack in another region then please make sure you copy the public AMIs used '
+ + 'from us-east-1 region to your region of choice and update the ami-id in agent-nodes.ts file accordingly. '
+ + 'If you do not copy the AMI in required region and update the code then the jenkins agents will not spin up.');
+
+ this.agentNodesBenchmark = [
+ agentNodeBenchmark.AL2023_X64,
+ agentNodeBenchmark.AL2_X64_DOCKER_HOST,
+ agentNodeBenchmark.AL2023_X64_DOCKER_HOST,
+ agentNodeBenchmark.AL2023_ARM64,
+ agentNodeBenchmark.AL2_ARM64_DOCKER_HOST,
+ agentNodeBenchmark.AL2023_ARM64_DOCKER_HOST,
+ agentNodeBenchmark.AL2023_X64_BENCHMARK_TEST,
+ agentNodeBenchmark.UBUNTU2004_X64_GRADLE_CHECK,
+ agentNodeBenchmark.UBUNTU2004_X64_DOCKER_BUILDER,
+ agentNodeBenchmark.MACOS12_X64_MULTI_HOST,
+ agentNodeBenchmark.WINDOWS2019_X64_DOCKER_HOST,
+ agentNodeBenchmark.WINDOWS2019_X64_DOCKER_BUILDER,
+ agentNodeBenchmark.WINDOWS2019_X64_GRADLE_CHECK,
+ ];
+ } else {
+ this.agentNodesBenchmark = [agentNodeBenchmark.AL2_X64_DEFAULT_AGENT, agentNodeBenchmark.AL2_ARM64_DEFAULT_AGENT];
+ }
+
+ // @ts-ignore
+ const mainJenkinsNodeBenchmark = new JenkinsMainNodeBenchmark(this, {
+ vpc,
+ sg: this.securityGroups.mainNodeSG,
+ efsSG: this.securityGroups.efsSG,
+ dataRetention: props.dataRetention ?? false,
+ envVarsFilePath: props.envVarsFilePath ?? '',
+ enableViews: props.enableViews ?? false,
+ reloadPasswordSecretsArn: importedReloadPasswordSecretsArn.toString(),
+ sslCertContentsArn: importedContentsSecretBucketValue.toString(),
+ sslCertChainArn: importedContentsChainBucketValue.toString(),
+ sslCertPrivateKeyContentsArn: importedCertSecretBucketValue.toString(),
+ redirectUrlArn: importedRedirectUrlSecretBucketValue.toString(),
+ oidcCredArn: importedOidcConfigValuesSecretBucketValue.toString(),
+ useSsl,
+ runWithOidc,
+ failOnCloudInitError: props?.ignoreResourcesFailures,
+ adminUsers: props?.adminUsers,
+ agentNodeSecurityGroup: this.securityGroups.agentNodeSG.securityGroupId,
+ subnetId: vpc.privateSubnets[0].subnetId,
+ }, this.agentNodesBenchmark, macAgentParameter.toString(), props?.agentAssumeRole);
+
+ const targetGroupBenchmark = new ApplicationTargetGroup(this, 'MainJenkinsNodeTargetBenchmark', {
+ port: accessPort,
+ vpc,
+ targets: [mainJenkinsNodeBenchmark.mainNodeBenchAsg],
+ healthCheck: {
+ protocol: props.useSsl ? Protocol.HTTPS : Protocol.HTTP,
+ path: '/benchmark/',
+ },
+ });
+
+ const existingListener = ApplicationListener.fromApplicationListenerAttributes(this, 'ALB listener', {
+ listenerArn,
+ securityGroup: this.securityGroups.mainNodeSG,
+ });
+
+ existingListener.addTargetGroups('targetGroupBenchmark', {
+ priority: 1,
+ conditions: [ListenerCondition.pathPatterns(['/benchmark*'])],
+ targetGroups: [targetGroupBenchmark],
+ });
+
+ const artifactBucket = new Bucket(this, 'BuildBucketBenchmark');
+
+ // @ts-ignore
+ this.monitoring = new JenkinsMonitoringBenchmark(this, mainJenkinsNodeBenchmark);
+
+ if (additionalCommandsContext.toString() !== 'undefined') {
+ // @ts-ignore
+ new RunAdditionalCommands(this, additionalCommandsContext.toString());
+ }
+
+ new CfnOutput(this, 'Artifact Bucket Arn Benchmark', {
+ value: artifactBucket.bucketArn.toString(),
+ exportName: 'buildBucketArnBenchmark',
+ });
+ }
+}
diff --git a/lib/ci-stack.ts b/lib/ci-stack.ts
index be53b17b..2a9fdd52 100644
--- a/lib/ci-stack.ts
+++ b/lib/ci-stack.ts
@@ -132,7 +132,7 @@ export class CIStack extends Stack {
default: useSsl,
});
- this.securityGroups = new JenkinsSecurityGroups(this, vpc, useSsl, serverAcess);
+ this.securityGroups = new JenkinsSecurityGroups(this, vpc, useSsl, serverAcess, 'mainCI');
const importedContentsSecretBucketValue = Fn.importValue(`${CIConfigStack.CERTIFICATE_CONTENTS_SECRET_EXPORT_VALUE}`);
const importedContentsChainBucketValue = Fn.importValue(`${CIConfigStack.CERTIFICATE_CHAIN_SECRET_EXPORT_VALUE}`);
const importedCertSecretBucketValue = Fn.importValue(`${CIConfigStack.PRIVATE_KEY_SECRET_EXPORT_VALUE}`);
@@ -213,5 +213,32 @@ export class CIStack extends Stack {
value: artifactBucket.bucketArn.toString(),
exportName: 'buildBucketArn',
});
+
+ new CfnOutput(this, 'ALB SG Id', {
+ value: this.securityGroups.externalAccessSG.securityGroupId,
+ exportName: 'ALBSGId',
+ });
+
+ vpc.publicSubnets.map((subnet) => subnet.subnetId).map((idstring, index) => new CfnOutput(this, `PUBLIC_SUBNET_PREFIX-${index}`, {
+ description: `subnet id for public subnet ${index}`,
+ value: idstring,
+ exportName: `CIstackVPCPublicSubnets-${index}`,
+ }));
+
+ vpc.privateSubnets.map((subnet) => subnet.subnetId).map((idstring, index) => new CfnOutput(this, `PRIVATE_SUBNET_PREFIX-${index}`, {
+ description: `subnet id for private subnet ${index}`,
+ value: idstring,
+ exportName: `CIstackVPCPrivateSubnets-${index}`,
+ }));
+
+ new CfnOutput(this, 'VPC Id', {
+ value: vpc.vpcId,
+ exportName: 'CIstackVPCId',
+ });
+
+ new CfnOutput(this, 'mainCI mainNode SG Id', {
+ value: this.securityGroups.mainNodeSG.securityGroupId,
+ exportName: 'CIStackMainNodeSGId',
+ });
}
}
diff --git a/lib/compute/agent-node-config-benchmark.ts b/lib/compute/agent-node-config-benchmark.ts
new file mode 100644
index 00000000..e4e0099b
--- /dev/null
+++ b/lib/compute/agent-node-config-benchmark.ts
@@ -0,0 +1,371 @@
+/**
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * The OpenSearch Contributors require contributions made to
+ * this file be licensed under the Apache-2.0 license or a
+ * compatible open source license.
+ */
+
+import {
+ CfnOutput, Fn, Stack, Tags,
+ } from 'aws-cdk-lib';
+ import {
+ CfnInstanceProfile, Effect, ManagedPolicy, PolicyStatement, Role, ServicePrincipal,
+ } from 'aws-cdk-lib/aws-iam';
+ import { KeyPair } from 'cdk-ec2-key-pair';
+ import { readFileSync } from 'fs';
+ import { load } from 'js-yaml';
+ import { JenkinsMainNodeBenchmark } from './jenkins-main-node-benchmark';
+
+ export interface AgentNodePropsBenchmark {
+ agentType: string;
+ amiId: string;
+ instanceType: string;
+ customDeviceMapping: string;
+ workerLabelString: string;
+ remoteUser: string;
+ maxTotalUses: number;
+ minimumNumberOfSpareInstances: number;
+ numExecutors: number;
+ initScript: string;
+ remoteFs: string;
+ }
+ export interface AgentNodeNetworkPropsBenchmark {
+ readonly agentNodeSecurityGroup: string;
+ readonly subnetId: string;
+ }
+
+ export class AgentNodeConfigBenchmark {
+ public readonly AgentNodeInstanceProfileArn: string;
+
+ public readonly STACKREGION: string;
+
+ public readonly ACCOUNT: string;
+
+ public readonly SSHEC2KeySecretId: string;
+
+ constructor(stack: Stack, assumeRole?: string[]) {
+ this.STACKREGION = stack.region;
+ this.ACCOUNT = stack.account;
+
+ const key = new KeyPair(stack, 'AgentNode-KeyPair-Benchmark', {
+ name: 'AgentNodeKeyPairBenchmark',
+ description: 'KeyPair used by Jenkins Main Node to SSH into Agent Nodes',
+ });
+ Tags.of(key)
+ .add('jenkins:credentials:type', 'sshUserPrivateKey');
+ const AgentNodeRole = new Role(stack, 'OpenSearch-CI-AgentNodeRole-Benchmark', {
+ assumedBy: new ServicePrincipal('ec2.amazonaws.com'),
+ // assumedBy: new AccountPrincipal(this.ACCOUNT),
+ description: 'Jenkins agents Node Role',
+ roleName: 'OpenSearch-CI-AgentNodeRole-Benchmark',
+ });
+
+ const ecrManagedPolicy = new ManagedPolicy(stack, 'OpenSearch-CI-AgentNodePolicy-Benchmark', {
+ description: 'Jenkins agents Node Policy',
+ statements: [
+ new PolicyStatement({
+ effect: Effect.ALLOW,
+ actions: [
+ 'ecr-public:BatchCheckLayerAvailability',
+ 'ecr-public:GetRepositoryPolicy',
+ 'ecr-public:DescribeRepositories',
+ 'ecr-public:DescribeRegistries',
+ 'ecr-public:DescribeImages',
+ 'ecr-public:DescribeImageTags',
+ 'ecr-public:GetRepositoryCatalogData',
+ 'ecr-public:GetRegistryCatalogData',
+ 'ecr-public:InitiateLayerUpload',
+ 'ecr-public:UploadLayerPart',
+ 'ecr-public:CompleteLayerUpload',
+ 'ecr-public:PutImage',
+ ],
+ resources: [`arn:aws:ecr-public::${this.ACCOUNT}:repository/*`],
+ conditions: {
+ StringEquals: {
+ 'aws:RequestedRegion': this.STACKREGION,
+ 'aws:PrincipalAccount': [this.ACCOUNT],
+ },
+ },
+ }),
+ ],
+ roles: [AgentNodeRole],
+ });
+ ecrManagedPolicy.addStatements(
+ new PolicyStatement({
+ actions: [
+ 'ecr-public:GetAuthorizationToken',
+ 'sts:GetServiceBearerToken',
+ ],
+ resources: ['*'],
+ conditions: {
+ StringEquals: {
+ 'aws:RequestedRegion': this.STACKREGION,
+ 'aws:PrincipalAccount': [this.ACCOUNT],
+ },
+ },
+ }),
+ );
+
+ /* eslint-disable eqeqeq */
+ if (assumeRole) {
+ // policy to allow assume role AssumeRole
+ AgentNodeRole.addToPolicy(
+ new PolicyStatement({
+ resources: assumeRole,
+ actions: ['sts:AssumeRole'],
+ }),
+ );
+ }
+ AgentNodeRole.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'));
+ const AgentNodeInstanceProfile = new CfnInstanceProfile(stack, 'JenkinsAgentNodeInstanceProfileBenchmark', { roles: [AgentNodeRole.roleName] });
+ this.AgentNodeInstanceProfileArn = AgentNodeInstanceProfile.attrArn.toString();
+ this.SSHEC2KeySecretId = Fn.join('/', ['ec2-ssh-key', key.keyPairName.toString(), 'private']);
+
+ new CfnOutput(stack, 'Jenkins Agent Node Role Arn Benchmark', {
+ value: `${AgentNodeRole.roleArn}`,
+ exportName: 'agentNodeRoleArnBenchmark',
+ });
+ }
+
+ public addAgentConfigToJenkinsYaml(stack: Stack, templates: AgentNodePropsBenchmark[], props: AgentNodeNetworkPropsBenchmark, macAgent: string): any {
+ const jenkinsYaml: any = load(readFileSync(JenkinsMainNodeBenchmark.BASE_JENKINS_YAML_PATH, 'utf-8'));
+ const configTemplates: any = [];
+
+ templates.forEach((element) => {
+ if (element.agentType == 'unix') {
+ configTemplates.push(this.getUnixTemplate(stack, element, props));
+ } else if (element.agentType == 'mac' && macAgent == 'true') {
+ configTemplates.push(this.getMacTemplate(stack, element, props));
+ } else if (element.agentType == 'windows') {
+ configTemplates.push(this.getWindowsTemplate(stack, element, props));
+ }
+ });
+
+ const agentNodeYamlConfig = [{
+ amazonEC2: {
+ cloudName: 'Amazon_ec2_cloud',
+ region: this.STACKREGION,
+ sshKeysCredentialsId: this.SSHEC2KeySecretId,
+ templates: configTemplates,
+ useInstanceProfileForCredentials: true,
+ },
+ }];
+ jenkinsYaml.jenkins.clouds = agentNodeYamlConfig;
+ return jenkinsYaml;
+ }
+
+ private getUnixTemplate(stack: Stack, config: AgentNodePropsBenchmark, props: AgentNodeNetworkPropsBenchmark): { [x: string]: any; } {
+ return {
+ ami: config.amiId,
+ amiType:
+ { unixData: { sshPort: '22' } },
+ associatePublicIp: false,
+ connectBySSHProcess: false,
+ connectionStrategy: 'PRIVATE_IP',
+ customDeviceMapping: config.customDeviceMapping,
+ deleteRootOnTermination: true,
+ description: `jenkinsAgentNode-${config.workerLabelString}`,
+ ebsEncryptRootVolume: 'ENCRYPTED',
+ ebsOptimized: false,
+ metadataTokensRequired: true,
+ metadataHopsLimit: '2',
+ hostKeyVerificationStrategy: 'OFF',
+ iamInstanceProfile: this.AgentNodeInstanceProfileArn,
+ idleTerminationMinutes: '60',
+ initScript: config.initScript,
+ labelString: config.workerLabelString,
+ launchTimeoutStr: '300',
+ maxTotalUses: config.maxTotalUses,
+ minimumNumberOfInstances: 0,
+ minimumNumberOfSpareInstances: config.minimumNumberOfSpareInstances,
+ mode: 'EXCLUSIVE',
+ monitoring: true,
+ numExecutors: config.numExecutors,
+ remoteAdmin: config.remoteUser,
+ remoteFS: config.remoteFs,
+ securityGroups: props.agentNodeSecurityGroup,
+ stopOnTerminate: false,
+ subnetId: props.subnetId,
+ t2Unlimited: false,
+ tags: [{
+ name: 'Name',
+ value: `${stack.stackName}/AgentNode/${config.workerLabelString}`,
+ },
+ {
+ name: 'type',
+ value: `jenkinsAgentNode-${config.workerLabelString}`,
+ },
+ ],
+ tenancy: 'Default',
+ type: config.instanceType,
+ nodeProperties: [
+ {
+ envVars: {
+ env: [
+ {
+ key: 'JENKINS_HOME_PATH',
+ value: config.remoteFs,
+ },
+ {
+ key: 'JAVA8_HOME',
+ value: '/usr/lib/jvm/temurin-8-jdk-amd64',
+ },
+ {
+ key: 'JAVA11_HOME',
+ value: '/usr/lib/jvm/temurin-11-jdk-amd64',
+ },
+ {
+ key: 'JAVA14_HOME',
+ value: '/usr/lib/jvm/adoptopenjdk-14-amd64',
+ },
+ {
+ key: 'JAVA17_HOME',
+ value: '/usr/lib/jvm/temurin-17-jdk-amd64',
+ },
+ {
+ key: 'JAVA19_HOME',
+ value: '/usr/lib/jvm/temurin-19-jdk-amd64',
+ },
+ {
+ key: 'JAVA20_HOME',
+ value: '/usr/lib/jvm/temurin-20-jdk-amd64',
+ },
+ {
+ key: 'JAVA21_HOME',
+ value: '/usr/lib/jvm/temurin-21-jdk-amd64',
+ },
+ ],
+ },
+ },
+ ],
+ useEphemeralDevices: false,
+ };
+ }
+
+ private getMacTemplate(stack: Stack, config: AgentNodePropsBenchmark, props: AgentNodeNetworkPropsBenchmark): { [x: string]: any; } {
+ return {
+ ami: config.amiId,
+ amiType:
+ { macData: { sshPort: '22' } },
+ associatePublicIp: false,
+ connectBySSHProcess: false,
+ connectionStrategy: 'PRIVATE_IP',
+ customDeviceMapping: config.customDeviceMapping,
+ deleteRootOnTermination: true,
+ description: `jenkinsAgentNode-${config.workerLabelString}`,
+ ebsEncryptRootVolume: 'ENCRYPTED',
+ ebsOptimized: true,
+ metadataTokensRequired: true,
+ metadataHopsLimit: '2',
+ hostKeyVerificationStrategy: 'OFF',
+ iamInstanceProfile: this.AgentNodeInstanceProfileArn,
+ idleTerminationMinutes: '720',
+ labelString: config.workerLabelString,
+ launchTimeoutStr: '1000',
+ initScript: config.initScript,
+ maxTotalUses: config.maxTotalUses,
+ minimumNumberOfInstances: 1,
+ minimumNumberOfSpareInstances: config.minimumNumberOfSpareInstances,
+ mode: 'EXCLUSIVE',
+ monitoring: true,
+ numExecutors: config.numExecutors,
+ remoteAdmin: config.remoteUser,
+ remoteFS: config.remoteFs,
+ securityGroups: props.agentNodeSecurityGroup,
+ stopOnTerminate: false,
+ subnetId: props.subnetId,
+ t2Unlimited: false,
+ tags: [
+ {
+ name: 'Name',
+ value: `${stack.stackName}/AgentNode/${config.workerLabelString}`,
+ },
+ {
+ name: 'type',
+ value: `jenkinsAgentNode-${config.workerLabelString}`,
+ },
+ ],
+ tenancy: 'Host',
+ type: config.instanceType,
+ nodeProperties: [
+ {
+ envVars: {
+ env: [
+ {
+ key: 'JENKINS_HOME_PATH',
+ value: config.remoteFs,
+ },
+ ],
+ },
+ },
+ ],
+ useEphemeralDevices: false,
+ };
+ }
+
+ private getWindowsTemplate(stack: Stack, config: AgentNodePropsBenchmark, props: AgentNodeNetworkPropsBenchmark): { [x: string]: any; } {
+ return {
+ ami: config.amiId,
+ amiType:
+ {
+ windowsData: {
+ allowSelfSignedCertificate: false, bootDelay: '7.5', specifyPassword: false, useHTTPS: false,
+ },
+ },
+ associatePublicIp: false,
+ connectBySSHProcess: false,
+ connectionStrategy: 'PRIVATE_IP',
+ customDeviceMapping: config.customDeviceMapping,
+ deleteRootOnTermination: true,
+ description: `jenkinsAgentNode-${config.workerLabelString}`,
+ ebsEncryptRootVolume: 'ENCRYPTED',
+ ebsOptimized: true,
+ metadataTokensRequired: true,
+ metadataHopsLimit: '2',
+ hostKeyVerificationStrategy: 'OFF',
+ iamInstanceProfile: this.AgentNodeInstanceProfileArn,
+ idleTerminationMinutes: '120',
+ initScript: config.initScript,
+ labelString: config.workerLabelString,
+ launchTimeoutStr: '1200',
+ maxTotalUses: config.maxTotalUses,
+ minimumNumberOfInstances: 0,
+ minimumNumberOfSpareInstances: config.minimumNumberOfSpareInstances,
+ mode: 'EXCLUSIVE',
+ monitoring: true,
+ numExecutors: config.numExecutors,
+ remoteAdmin: config.remoteUser,
+ remoteFS: config.remoteFs,
+ securityGroups: props.agentNodeSecurityGroup,
+ stopOnTerminate: false,
+ subnetId: props.subnetId,
+ t2Unlimited: false,
+ tags: [{
+ name: 'Name',
+ value: `${stack.stackName}/AgentNode/${config.workerLabelString}`,
+ },
+ {
+ name: 'type',
+ value: `jenkinsAgentNode-${config.workerLabelString}`,
+ },
+ ],
+ tenancy: 'Default',
+ type: config.instanceType,
+ nodeProperties: [
+ {
+ envVars: {
+ env: [
+ {
+ key: 'JENKINS_HOME_PATH',
+ value: config.remoteFs,
+ },
+ ],
+ },
+ },
+ ],
+ useEphemeralDevices: false,
+ };
+ }
+ }
+
\ No newline at end of file
diff --git a/lib/compute/agent-nodes-benchmark.ts b/lib/compute/agent-nodes-benchmark.ts
new file mode 100644
index 00000000..30b4353b
--- /dev/null
+++ b/lib/compute/agent-nodes-benchmark.ts
@@ -0,0 +1,269 @@
+/**
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * The OpenSearch Contributors require contributions made to
+ * this file be licensed under the Apache-2.0 license or a
+ * compatible open source license.
+ */
+
+import { Stack } from 'aws-cdk-lib';
+import { AmazonLinuxCpuType, AmazonLinuxGeneration, MachineImage } from 'aws-cdk-lib/aws-ec2';
+import { AgentNodePropsBenchmark } from './agent-node-config-benchmark';
+
+export class AgentNodesBenchmark {
+ // Refer: https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/ec2/model/InstanceType.html for instance types
+ readonly AL2023_X64: AgentNodePropsBenchmark;
+
+ readonly AL2_X64_DOCKER_HOST: AgentNodePropsBenchmark;
+
+ readonly AL2023_X64_DOCKER_HOST: AgentNodePropsBenchmark;
+
+ readonly AL2023_ARM64: AgentNodePropsBenchmark;
+
+ readonly AL2_ARM64_DOCKER_HOST: AgentNodePropsBenchmark;
+
+ readonly AL2023_ARM64_DOCKER_HOST: AgentNodePropsBenchmark;
+
+ readonly AL2023_X64_BENCHMARK_TEST: AgentNodePropsBenchmark;
+
+ readonly UBUNTU2004_X64_GRADLE_CHECK: AgentNodePropsBenchmark;
+
+ readonly UBUNTU2004_X64_DOCKER_BUILDER: AgentNodePropsBenchmark;
+
+ readonly MACOS12_X64_MULTI_HOST: AgentNodePropsBenchmark;
+
+ readonly WINDOWS2019_X64_DOCKER_HOST: AgentNodePropsBenchmark;
+
+ readonly WINDOWS2019_X64_DOCKER_BUILDER: AgentNodePropsBenchmark;
+
+ readonly WINDOWS2019_X64_GRADLE_CHECK: AgentNodePropsBenchmark;
+
+ readonly AL2_X64_DEFAULT_AGENT: AgentNodePropsBenchmark;
+
+ readonly AL2_ARM64_DEFAULT_AGENT: AgentNodePropsBenchmark;
+
+ constructor(stack: Stack) {
+ this.AL2023_X64 = {
+ agentType: 'unix',
+ customDeviceMapping: '/dev/xvda=:300:true:::encrypted',
+ workerLabelString: 'Jenkins-Agent-AL2023-X64-C54xlarge-Single-Host',
+ instanceType: 'C54xlarge',
+ remoteUser: 'ec2-user',
+ maxTotalUses: -1,
+ minimumNumberOfSpareInstances: 1,
+ numExecutors: 1,
+ amiId: 'ami-0d09563cd5663bdc7',
+ initScript: 'sudo dnf clean all && sudo rm -rf /var/cache/dnf && sudo dnf repolist &&'
+ + ' sudo dnf update --releasever=latest --skip-broken --exclude=openssh* --exclude=docker* --exclude=gh* --exclude=python* -y && docker ps',
+ remoteFs: '/var/jenkins',
+ };
+ this.AL2_X64_DOCKER_HOST = {
+ agentType: 'unix',
+ customDeviceMapping: '/dev/xvda=:300:true:::encrypted',
+ workerLabelString: 'Jenkins-Agent-AL2-X64-C54xlarge-Docker-Host',
+ instanceType: 'C54xlarge',
+ remoteUser: 'ec2-user',
+ maxTotalUses: -1,
+ minimumNumberOfSpareInstances: 1,
+ numExecutors: 4,
+ amiId: 'ami-047328312ef36d12b',
+ initScript: 'sudo yum clean all && sudo rm -rf /var/cache/yum /var/lib/yum/history && sudo yum repolist &&'
+ + ' sudo yum update --skip-broken --exclude=openssh* --exclude=docker* --exclude=gh* -y && docker ps',
+ remoteFs: '/var/jenkins',
+ };
+ this.AL2023_X64_DOCKER_HOST = {
+ agentType: 'unix',
+ customDeviceMapping: '/dev/xvda=:300:true:::encrypted',
+ workerLabelString: 'Jenkins-Agent-AL2023-X64-C54xlarge-Docker-Host',
+ instanceType: 'C54xlarge',
+ remoteUser: 'ec2-user',
+ maxTotalUses: -1,
+ minimumNumberOfSpareInstances: 4,
+ numExecutors: 4,
+ amiId: 'ami-0d09563cd5663bdc7',
+ initScript: 'sudo dnf clean all && sudo rm -rf /var/cache/dnf && sudo dnf repolist &&'
+ + ' sudo dnf update --releasever=latest --skip-broken --exclude=openssh* --exclude=docker* --exclude=gh* --exclude=python* -y && docker ps',
+ remoteFs: '/var/jenkins',
+ };
+ this.AL2023_ARM64 = {
+ agentType: 'unix',
+ customDeviceMapping: '/dev/xvda=:300:true:::encrypted',
+ workerLabelString: 'Jenkins-Agent-AL2023-Arm64-C6g4xlarge-Single-Host',
+ instanceType: 'C6g4xlarge',
+ remoteUser: 'ec2-user',
+ maxTotalUses: -1,
+ minimumNumberOfSpareInstances: 1,
+ numExecutors: 1,
+ amiId: 'ami-0444fd195657f193f',
+ initScript: 'sudo dnf clean all && sudo rm -rf /var/cache/dnf && sudo dnf repolist &&'
+ + ' sudo dnf update --releasever=latest --skip-broken --exclude=openssh* --exclude=docker* --exclude=gh* --exclude=python* -y && docker ps',
+ remoteFs: '/var/jenkins',
+ };
+ this.AL2_ARM64_DOCKER_HOST = {
+ agentType: 'unix',
+ customDeviceMapping: '/dev/xvda=:300:true:::encrypted',
+ workerLabelString: 'Jenkins-Agent-AL2-Arm64-C6g4xlarge-Docker-Host',
+ instanceType: 'C6g4xlarge',
+ remoteUser: 'ec2-user',
+ maxTotalUses: -1,
+ minimumNumberOfSpareInstances: 1,
+ numExecutors: 4,
+ amiId: 'ami-06ba4c81e8dd7ab49',
+ initScript: 'sudo yum clean all && sudo rm -rf /var/cache/yum /var/lib/yum/history && sudo yum repolist &&'
+ + ' sudo yum update --skip-broken --exclude=openssh* --exclude=docker* --exclude=gh* -y && docker ps',
+ remoteFs: '/var/jenkins',
+ };
+ this.AL2023_ARM64_DOCKER_HOST = {
+ agentType: 'unix',
+ customDeviceMapping: '/dev/xvda=:300:true:::encrypted',
+ workerLabelString: 'Jenkins-Agent-AL2023-Arm64-C6g4xlarge-Docker-Host',
+ instanceType: 'C6g4xlarge',
+ remoteUser: 'ec2-user',
+ maxTotalUses: -1,
+ minimumNumberOfSpareInstances: 4,
+ numExecutors: 4,
+ amiId: 'ami-0444fd195657f193f',
+ initScript: 'sudo dnf clean all && sudo rm -rf /var/cache/dnf && sudo dnf repolist &&'
+ + ' sudo dnf update --releasever=latest --skip-broken --exclude=openssh* --exclude=docker* --exclude=gh* --exclude=python* -y && docker ps',
+ remoteFs: '/var/jenkins',
+ };
+ this.AL2023_X64_BENCHMARK_TEST = {
+ agentType: 'unix',
+ customDeviceMapping: '/dev/xvda=:300:true:::encrypted',
+ workerLabelString: 'Jenkins-Agent-AL2023-X64-M52xlarge-Benchmark-Test',
+ instanceType: 'M52xlarge',
+ remoteUser: 'ec2-user',
+ maxTotalUses: -1,
+ minimumNumberOfSpareInstances: 1,
+ numExecutors: 2,
+ amiId: 'ami-0d09563cd5663bdc7',
+ initScript: 'sudo dnf clean all && sudo rm -rf /var/cache/dnf && sudo dnf repolist &&'
+ + ' sudo dnf update --releasever=latest --skip-broken --exclude=openssh* --exclude=docker* --exclude=gh* --exclude=python* -y && docker ps',
+ remoteFs: '/var/jenkins',
+ };
+ this.UBUNTU2004_X64_GRADLE_CHECK = {
+ agentType: 'unix',
+ customDeviceMapping: '/dev/sda1=:300:true:::encrypted',
+ workerLabelString: 'Jenkins-Agent-Ubuntu2004-X64-M58xlarge-Single-Host',
+ instanceType: 'M58xlarge',
+ remoteUser: 'ubuntu',
+ maxTotalUses: 1,
+ minimumNumberOfSpareInstances: 1,
+ numExecutors: 1,
+ amiId: 'ami-0bbfc7f398eefe0e8',
+ initScript: 'sudo apt-mark hold docker docker.io openssh-server gh grub-efi* shim-signed && docker ps &&'
+ + ' sudo apt-get update -y && (sudo killall -9 apt-get apt 2>&1 || echo) && sudo env "DEBIAN_FRONTEND=noninteractive" apt-get upgrade -y',
+ remoteFs: '/var/jenkins',
+ };
+ this.UBUNTU2004_X64_DOCKER_BUILDER = {
+ agentType: 'unix',
+ customDeviceMapping: '/dev/sda1=:300:true:::encrypted',
+ workerLabelString: 'Jenkins-Agent-Ubuntu2004-X64-M52xlarge-Docker-Builder',
+ instanceType: 'M52xlarge',
+ remoteUser: 'ubuntu',
+ maxTotalUses: -1,
+ minimumNumberOfSpareInstances: 2,
+ numExecutors: 1,
+ amiId: 'ami-0bbfc7f398eefe0e8',
+ initScript: 'sudo apt-mark hold docker docker.io openssh-server gh grub-efi* shim-signed && docker ps &&'
+ + ' sudo apt-get update -y && (sudo killall -9 apt-get apt 2>&1 || echo) && sudo env "DEBIAN_FRONTEND=noninteractive" apt-get upgrade -y',
+ remoteFs: '/var/jenkins',
+ };
+ this.MACOS12_X64_MULTI_HOST = {
+ agentType: 'mac',
+ customDeviceMapping: '/dev/sda1=:300:true:gp3::encrypted',
+ workerLabelString: 'Jenkins-Agent-MacOS12-X64-Mac1Metal-Multi-Host',
+ instanceType: 'Mac1Metal',
+ remoteUser: 'ec2-user',
+ maxTotalUses: -1,
+ minimumNumberOfSpareInstances: 1,
+ numExecutors: 6,
+ amiId: 'ami-0a5f5363c1db8ff67',
+ initScript: 'echo',
+ remoteFs: '/var/jenkins',
+ };
+ this.WINDOWS2019_X64_DOCKER_HOST = {
+ agentType: 'windows',
+ customDeviceMapping: '/dev/sda1=:600:true:::encrypted',
+ workerLabelString: 'Jenkins-Agent-Windows2019-X64-C54xlarge-Docker-Host',
+ instanceType: 'C54xlarge',
+ remoteUser: 'Administrator',
+ maxTotalUses: -1,
+ minimumNumberOfSpareInstances: 4,
+ numExecutors: 4,
+ amiId: 'ami-0a9759da263ce9304',
+ initScript: 'echo %USERNAME% && dockerd --register-service && net start docker && echo started docker deamon && docker ps && '
+ + 'echo initializing docker images now waiting for 5min && git clone https://github.com/opensearch-project/opensearch-build.git && '
+ + 'bash.exe -c "docker run --rm -it --name docker-windows-test -d `opensearch-build/docker/ci/get-ci-images.sh '
+ + '-p windows2019-servercore -u opensearch -t build | head -1` bash.exe && sleep 5" && docker exec docker-windows-test whoami && '
+ + 'docker ps && docker stop docker-windows-test && docker ps && rm -rf opensearch-build',
+ remoteFs: 'C:/Users/Administrator/jenkins',
+ };
+ this.WINDOWS2019_X64_DOCKER_BUILDER = {
+ agentType: 'windows',
+ customDeviceMapping: '/dev/sda1=:300:true:::encrypted',
+ workerLabelString: 'Jenkins-Agent-Windows2019-X64-C54xlarge-Docker-Builder',
+ instanceType: 'C54xlarge',
+ remoteUser: 'Administrator',
+ maxTotalUses: -1,
+ minimumNumberOfSpareInstances: 1,
+ numExecutors: 1,
+ amiId: 'ami-0a9759da263ce9304',
+ initScript: 'echo %USERNAME% && dockerd --register-service && net start docker && echo started docker deamon && docker ps && '
+ + 'echo initializing docker images now waiting for 5min && git clone https://github.com/opensearch-project/opensearch-build.git && '
+ + 'bash.exe -c "docker run --rm -it --name docker-windows-test -d `opensearch-build/docker/ci/get-ci-images.sh '
+ + '-p windows2019-servercore -u opensearch -t build | head -1` bash.exe && sleep 5" && docker exec docker-windows-test whoami && '
+ + 'docker ps && docker stop docker-windows-test && docker ps && rm -rf opensearch-build',
+ remoteFs: 'C:/Users/Administrator/jenkins',
+ };
+ this.WINDOWS2019_X64_GRADLE_CHECK = {
+ agentType: 'windows',
+ customDeviceMapping: '/dev/sda1=:300:true:::encrypted',
+ workerLabelString: 'Jenkins-Agent-Windows2019-X64-C524xlarge-Single-Host',
+ instanceType: 'C524xlarge',
+ remoteUser: 'Administrator',
+ maxTotalUses: 1,
+ minimumNumberOfSpareInstances: 1,
+ numExecutors: 1,
+ amiId: 'ami-0ca4f0ba85855e148',
+ initScript: 'echo',
+ remoteFs: 'C:/Users/Administrator/jenkins',
+ };
+ this.AL2_X64_DEFAULT_AGENT = {
+ agentType: 'unix',
+ customDeviceMapping: '/dev/xvda=:300:true:::encrypted',
+ workerLabelString: 'Jenkins-Default-Agent-X64-C5xlarge-Single-Host',
+ instanceType: 'C54xlarge',
+ remoteUser: 'ec2-user',
+ maxTotalUses: -1,
+ minimumNumberOfSpareInstances: 1,
+ numExecutors: 1,
+ amiId: MachineImage.latestAmazonLinux({
+ generation: AmazonLinuxGeneration.AMAZON_LINUX_2,
+ cpuType: AmazonLinuxCpuType.X86_64,
+ }).getImage(stack).imageId.toString(),
+ initScript: 'sudo amazon-linux-extras install java-openjdk11 -y && sudo yum install -y cmake python3 python3-pip && '
+ + 'sudo yum groupinstall -y \'Development Tools\' && sudo ln -sfn `which pip3` /usr/bin/pip && '
+ + 'pip3 install pipenv && sudo ln -s ~/.local/bin/pipenv /usr/local/bin',
+ remoteFs: '/home/ec2-user',
+ };
+ this.AL2_ARM64_DEFAULT_AGENT = {
+ agentType: 'unix',
+ customDeviceMapping: '/dev/xvda=:300:true:::encrypted',
+ workerLabelString: 'Jenkins-Default-Agent-ARM64-C5xlarge-Single-Host',
+ instanceType: 'C6g4xlarge',
+ remoteUser: 'ec2-user',
+ maxTotalUses: -1,
+ minimumNumberOfSpareInstances: 1,
+ numExecutors: 1,
+ amiId: MachineImage.latestAmazonLinux({
+ generation: AmazonLinuxGeneration.AMAZON_LINUX_2,
+ cpuType: AmazonLinuxCpuType.ARM_64,
+ }).getImage(stack).imageId.toString(),
+ initScript: 'sudo amazon-linux-extras install java-openjdk11 -y && sudo yum install -y cmake python3 python3-pip && '
+ + 'sudo yum groupinstall -y \'Development Tools\' && sudo ln -sfn `which pip3` /usr/bin/pip && '
+ + 'pip3 install pipenv && sudo ln -s ~/.local/bin/pipenv /usr/local/bin',
+ remoteFs: '/home/ec2-user',
+ };
+ }
+}
diff --git a/lib/compute/jenkins-main-node-benchmark.ts b/lib/compute/jenkins-main-node-benchmark.ts
new file mode 100644
index 00000000..15bc0004
--- /dev/null
+++ b/lib/compute/jenkins-main-node-benchmark.ts
@@ -0,0 +1,457 @@
+/**
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * The OpenSearch Contributors require contributions made to
+ * this file be licensed under the Apache-2.0 license or a
+ * compatible open source license.
+ */
+
+import {
+ CfnOutput, Duration, Fn, Stack,
+ } from 'aws-cdk-lib';
+ import {
+ AutoScalingGroup, BlockDeviceVolume, Monitoring, Signals,
+ } from 'aws-cdk-lib/aws-autoscaling';
+ import { Metric, Unit } from 'aws-cdk-lib/aws-cloudwatch';
+ import {
+ AmazonLinuxGeneration, CloudFormationInit, InitCommand, InitElement, InitFile, InitPackage,
+ InstanceClass,
+ InstanceSize,
+ InstanceType,
+ MachineImage, SecurityGroup, ISecurityGroup,
+ SubnetType, IVpc,
+ } from 'aws-cdk-lib/aws-ec2';
+ import { FileSystem, PerformanceMode, ThroughputMode } from 'aws-cdk-lib/aws-efs';
+ import {
+ IManagedPolicy, ManagedPolicy, PolicyStatement, Role, ServicePrincipal,
+ } from 'aws-cdk-lib/aws-iam';
+ import { writeFileSync } from 'fs';
+ import { dump } from 'js-yaml';
+ import { join } from 'path';
+ import { CloudwatchAgent } from '../constructs/cloudwatch-agent';
+ import { AgentNodeConfigBenchmark, AgentNodeNetworkPropsBenchmark, AgentNodePropsBenchmark } from './agent-node-config-benchmark';
+ import { EnvConfig } from './env-config';
+ import { OidcConfig } from './oidc-config';
+ import { ViewsConfig } from './views-benchmark';
+
+ interface HttpConfigPropsBenchmark {
+ readonly redirectUrlArn: string;
+ readonly sslCertContentsArn: string;
+ readonly sslCertChainArn: string;
+ readonly sslCertPrivateKeyContentsArn: string;
+ readonly useSsl: boolean;
+ }
+
+ interface OidcFederatePropsBenchmark {
+ readonly oidcCredArn: string;
+ readonly runWithOidc: boolean;
+ readonly adminUsers?: string[];
+ }
+
+ interface DataRetentionPropsBenchmark {
+ readonly dataRetention?: boolean;
+ readonly efsSG?: SecurityGroup;
+ }
+
+ export interface JenkinsMainNodePropsBenchmark extends HttpConfigPropsBenchmark, OidcFederatePropsBenchmark,
+ AgentNodeNetworkPropsBenchmark, DataRetentionPropsBenchmark {
+ readonly vpc: IVpc;
+ readonly sg: ISecurityGroup;
+ readonly envVarsFilePath: string;
+ readonly reloadPasswordSecretsArn: string;
+ readonly enableViews: boolean;
+ readonly failOnCloudInitError?: boolean;
+ }
+
+ export class JenkinsMainNodeBenchmark {
+ static readonly BASE_JENKINS_YAML_PATH: string = join(__dirname, '../../resources/baseJenkins-benchmark.yaml');
+
+ static readonly NEW_JENKINS_YAML_PATH: string = join(__dirname, '../../resources/jenkins-benchmark.yaml');
+
+ static readonly CERTIFICATE_FILE_PATH: String = '/etc/ssl/certs/test-jenkins.opensearch.org.crt';
+
+ static readonly CERTIFICATE_CHAIN_FILE_PATH: String = '/etc/ssl/certs/test-jenkins.opensearch.org.pem';
+
+ static readonly PRIVATE_KEY_PATH: String = '/etc/ssl/private/test-jenkins.opensearch.org.key';
+
+ static readonly JENKINS_DEFAULT_ID_PASS_PATH: String = '/var/lib/jenkins/secrets/myIdPassDefault';
+
+ private readonly EFS_ID: string;
+
+ private static ACCOUNT: string;
+
+ private static STACKREGION: string
+
+ public readonly mainNodeBenchAsg: AutoScalingGroup;
+
+ public readonly ec2InstanceMetrics: {
+ memUsed: Metric,
+ foundJenkinsProcessCount: Metric
+ }
+
+ constructor(stack: Stack, props: JenkinsMainNodePropsBenchmark, agentNode: AgentNodePropsBenchmark[], macAgent: string, assumeRole?: string[]) {
+ this.ec2InstanceMetrics = {
+ memUsed: new Metric({
+ metricName: 'mem_used_percent_benchmark',
+ namespace: `${stack.stackName}/JenkinsMainNodeBenchmark`,
+ }),
+ foundJenkinsProcessCount: new Metric({
+ metricName: 'procstat_lookup_pid_count_benchmark',
+ namespace: `${stack.stackName}/JenkinsMainNodeBenchmark`,
+ }),
+ };
+
+ const importedSGId = Fn.importValue('CIStackMainNodeSGId');
+ const mainCiMainNodeSGId = SecurityGroup.fromSecurityGroupId(stack, 'CIStackMainNodeSGId', importedSGId);
+
+ const agentNodeConfig = new AgentNodeConfigBenchmark(stack, assumeRole);
+ const jenkinsyaml = JenkinsMainNodeBenchmark.addConfigtoJenkinsYaml(stack, props, props, agentNodeConfig, props, agentNode, macAgent);
+ if (props.dataRetention) {
+ const efs = new FileSystem(stack, 'EFSfilesystemBenchmark', {
+ vpc: props.vpc,
+ encrypted: true,
+ enableAutomaticBackups: true,
+ performanceMode: PerformanceMode.GENERAL_PURPOSE,
+ throughputMode: ThroughputMode.BURSTING,
+ securityGroup: props.efsSG,
+ });
+ this.EFS_ID = efs.fileSystemId;
+ }
+ this.mainNodeBenchAsg = new AutoScalingGroup(stack, 'MainNodeAsgBenchmark', {
+ instanceType: InstanceType.of(InstanceClass.C5, InstanceSize.XLARGE9),
+ machineImage: MachineImage.latestAmazonLinux({
+ generation: AmazonLinuxGeneration.AMAZON_LINUX_2,
+ }),
+ role: new Role(stack, 'OpenSearch-CI-MainNodeRole-Benchmark', {
+ roleName: 'OpenSearch-CI-MainNodeRole-Benchmark',
+ assumedBy: new ServicePrincipal('ec2.amazonaws.com'),
+ }),
+ initOptions: {
+ ignoreFailures: props.failOnCloudInitError ?? true,
+ },
+ vpc: props.vpc,
+ vpcSubnets: {
+ subnetType: SubnetType.PUBLIC,
+ },
+ minCapacity: 1,
+ maxCapacity: 1,
+ desiredCapacity: 1,
+ securityGroup: props.sg,
+ init: CloudFormationInit.fromElements(...JenkinsMainNodeBenchmark.configElements(
+ stack.stackName,
+ stack.region,
+ props,
+ props,
+ props,
+ jenkinsyaml,
+ props.reloadPasswordSecretsArn,
+ this.EFS_ID,
+ )),
+ blockDevices: [{
+ deviceName: '/dev/xvda',
+ volume: BlockDeviceVolume.ebs(100, { encrypted: true, deleteOnTermination: true }),
+ }],
+ signals: Signals.waitForAll({
+ timeout: Duration.minutes(20),
+ }),
+ requireImdsv2: true,
+ instanceMonitoring: Monitoring.DETAILED,
+ });
+
+ this.mainNodeBenchAsg.addSecurityGroup(mainCiMainNodeSGId);
+
+ JenkinsMainNodeBenchmark.createPoliciesForMainNode(stack).map(
+ (policy) => this.mainNodeBenchAsg.role.addManagedPolicy(policy),
+ );
+
+ new CfnOutput(stack, 'Jenkins Main Node Role Arn Benchmark', {
+ value: this.mainNodeBenchAsg.role.roleArn,
+ exportName: 'mainNodeRoleArnBenchmark',
+ });
+ }
+
+ public static createPoliciesForMainNode(stack: Stack): (IManagedPolicy | ManagedPolicy)[] {
+ this.STACKREGION = stack.region;
+ this.ACCOUNT = stack.account;
+
+ // Policy for SSM management of the host - Removes the need of SSH keys
+ const ec2SsmManagementPolicy = ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore');
+
+ // Policy for EC2 instance to publish logs and metrics to cloudwatch
+ const cloudwatchEventPublishingPolicy = ManagedPolicy.fromAwsManagedPolicyName('CloudWatchAgentServerPolicy');
+
+ const accessPolicy = ManagedPolicy.fromAwsManagedPolicyName('SecretsManagerReadWrite');
+
+ // Main jenkins node will start/stop agent ec2 instances to run build jobs
+ const mainJenkinsNodePolicy = new ManagedPolicy(stack, 'MainJenkinsNodePolicyBenchmark',
+ {
+ description: 'Policy for a main jenkins node',
+ statements: [new PolicyStatement({
+ actions: [
+ 'ec2:DescribeSpotInstanceRequests',
+ 'ec2:ModifyInstanceMetadataOptions',
+ 'ec2:CancelSpotInstanceRequests',
+ 'ec2:GetConsoleOutput',
+ 'ec2:RequestSpotInstances',
+ 'ec2:RunInstances',
+ 'ec2:StartInstances',
+ 'ec2:StopInstances',
+ 'ec2:TerminateInstances',
+ 'ec2:CreateTags',
+ 'ec2:DeleteTags',
+ 'ec2:DescribeInstances',
+ 'ec2:DescribeKeyPairs',
+ 'ec2:DescribeRegions',
+ 'ec2:DescribeImages',
+ 'ec2:DescribeAvailabilityZones',
+ 'ec2:DescribeSecurityGroups',
+ 'ec2:DescribeSubnets',
+ 'iam:ListInstanceProfilesForRole',
+ 'iam:PassRole',
+ 'logs:CreateLogDelivery',
+ 'logs:DeleteLogDelivery',
+ 'secretsmanager:GetSecretValue',
+ 'secretsmanager:ListSecrets',
+ 'sts:AssumeRole',
+ 'elasticfilesystem:DescribeFileSystems',
+ 'elasticfilesystem:DescribeMountTargets',
+ 'ec2:DescribeAvailabilityZones',
+ 'ec2:GetPasswordData',
+ ],
+ resources: ['*'],
+ conditions: {
+ 'ForAllValues:StringEquals': {
+ 'aws:RequestedRegion': this.STACKREGION,
+ 'aws:PrincipalAccount': this.ACCOUNT,
+ },
+ },
+ })],
+ });
+
+ return [ec2SsmManagementPolicy, cloudwatchEventPublishingPolicy, accessPolicy, mainJenkinsNodePolicy];
+ }
+
+ public static configElements(stackName: string, stackRegion: string, httpConfigProps: HttpConfigPropsBenchmark,
+ oidcFederateProps: OidcFederatePropsBenchmark, dataRetentionProps: DataRetentionPropsBenchmark, jenkinsyaml: string,
+ reloadPasswordSecretsArn: string, efsId?: string): InitElement[] {
+ return [
+ InitPackage.yum('wget'),
+ InitPackage.yum('openssl'),
+ InitPackage.yum('mod_ssl'),
+ InitPackage.yum('amazon-efs-utils'),
+ InitCommand.shellCommand('amazon-linux-extras install java-openjdk11 -y'),
+ InitPackage.yum('docker'),
+ InitPackage.yum('python3'),
+ InitPackage.yum('python3-pip.noarch'),
+ InitCommand.shellCommand('pip3 install botocore'),
+ // eslint-disable-next-line max-len
+ InitCommand.shellCommand('sudo wget -nv https://github.com/mikefarah/yq/releases/download/v4.22.1/yq_linux_amd64 -O /usr/bin/yq && sudo chmod +x /usr/bin/yq'),
+ // eslint-disable-next-line max-len
+ InitCommand.shellCommand('sudo curl -L https://github.com/docker/compose/releases/download/v2.9.0/docker-compose-$(uname -s)-$(uname -m) -o /usr/bin/docker-compose && sudo chmod +x /usr/bin/docker-compose'),
+ InitCommand.shellCommand('python3 -m pip install --upgrade pip && python3 -m pip install cryptography boto3 requests-aws4auth'),
+
+ InitCommand.shellCommand(httpConfigProps.useSsl
+ // eslint-disable-next-line max-len
+ ? `aws --region ${stackRegion} secretsmanager get-secret-value --secret-id ${httpConfigProps.sslCertContentsArn} --query SecretString --output text > ${JenkinsMainNodeBenchmark.CERTIFICATE_FILE_PATH}`
+ : 'echo useSsl is false, not creating cert file'),
+
+ InitCommand.shellCommand(httpConfigProps.useSsl
+ // eslint-disable-next-line max-len
+ ? `aws --region ${stackRegion} secretsmanager get-secret-value --secret-id ${httpConfigProps.sslCertChainArn} --query SecretString --output text > ${JenkinsMainNodeBenchmark.CERTIFICATE_CHAIN_FILE_PATH}`
+ : 'echo useSsl is false, not creating cert-chain file'),
+
+ InitCommand.shellCommand(httpConfigProps.useSsl
+ // eslint-disable-next-line max-len
+ ? `mkdir /etc/ssl/private/ && aws --region ${stackRegion} secretsmanager get-secret-value --secret-id ${httpConfigProps.sslCertPrivateKeyContentsArn} --query SecretString --output text > ${JenkinsMainNodeBenchmark.PRIVATE_KEY_PATH}`
+ : 'echo useSsl is false, not creating key file'),
+
+ // Local reverse proxy is used
+ InitPackage.yum('httpd'),
+
+ // Change hop limit for IMDSv2 from 1 to 2
+ InitCommand.shellCommand('TOKEN=`curl -f -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"` &&'
+ + ' instance_id=`curl -f -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/instance-id` && echo $ami_id &&'
+ + ` aws ec2 --region ${stackRegion} modify-instance-metadata-options --instance-id $instance_id --http-put-response-hop-limit 2`),
+
+ // Configuration to proxy jenkins on :8080 -> :80
+ InitFile.fromString('/etc/httpd/conf.d/jenkins.conf',
+ httpConfigProps.useSsl
+ ? `
+ ServerAdmin webmaster@localhost
+ Redirect permanent / https://replace_url.com/
+
+
+ SSLEngine on
+ SSLCertificateFile ${JenkinsMainNodeBenchmark.CERTIFICATE_FILE_PATH}
+ SSLCertificateKeyFile ${JenkinsMainNodeBenchmark.PRIVATE_KEY_PATH}
+ SSLCertificateChainFile ${JenkinsMainNodeBenchmark.CERTIFICATE_CHAIN_FILE_PATH}
+ ServerAdmin webmaster@localhost
+ ProxyRequests Off
+ ProxyPreserveHost On
+ AllowEncodedSlashes NoDecode
+
+ Order deny,allow
+ Allow from all
+
+ ProxyPass /benchmark/ http://localhost:8080/benchmark/ nocanon
+ ProxyPassReverse /benchmark/ http://localhost:8080/benchmark/
+
+ ProxyPass / http://localhost:8080/ nocanon
+ ProxyPassReverse / http://localhost:8080/
+ ProxyPassReverse / http://replace_url.com/
+ RequestHeader set X-Forwarded-Proto "https"
+ RequestHeader set X-Forwarded-Port "443"
+
+
+ Header unset Server
+ `
+ : `
+ ServerAdmin webmaster@127.0.0.1
+ ProxyRequests Off
+ ProxyPreserveHost On
+ AllowEncodedSlashes NoDecode
+
+
+ Order deny,allow
+ Allow from all
+
+
+ ProxyPass /benchmark/ http://localhost:8080/benchmark/ nocanon
+ ProxyPassReverse /benchmark/ http://localhost:8080/benchmark/
+
+ ProxyPass / http://127.0.0.1:8080/ nocanon
+ ProxyPassReverse / http://127.0.0.1:8080/
+ `),
+
+ // replacing the jenkins redirect url if the using ssl
+ InitCommand.shellCommand(httpConfigProps.useSsl
+ // eslint-disable-next-line max-len
+ ? `var=\`aws --region ${stackRegion} secretsmanager get-secret-value --secret-id ${httpConfigProps.redirectUrlArn} --query SecretString --output text\``
+ + ' && sed -i "s,https://replace_url.com/,$var," /etc/httpd/conf.d/jenkins.conf'
+ : 'echo Not altering the jenkins url'),
+
+ // Auto redirect http to https if ssl is enabled
+ InitCommand.shellCommand(httpConfigProps.useSsl
+ // eslint-disable-next-line max-len
+ ? `var=\`aws --region ${stackRegion} secretsmanager get-secret-value --secret-id ${httpConfigProps.redirectUrlArn} --query SecretString --output text\``
+ + '&& newVar=`echo $var | sed \'s/https/http/g\'` && sed -i "s,http://replace_url.com/,$newVar," /etc/httpd/conf.d/jenkins.conf'
+ : 'echo Not altering the ProxyPassReverse url'),
+
+ InitCommand.shellCommand('systemctl start httpd'),
+
+ InitPackage.yum('amazon-cloudwatch-agent'),
+
+ CloudwatchAgent.asInitFile('/opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json', {
+ agent: {
+ metrics_collection_interval: 60, // seconds between collections
+ logfile: '/opt/aws/amazon-cloudwatch-agent/logs/amazon-cloudwatch-agent.log',
+ omit_hostname: true,
+ debug: false,
+ },
+ metrics: {
+ namespace: `${stackName}/JenkinsMainNodeBenchmark`,
+ append_dimensions: {
+ // eslint-disable-next-line no-template-curly-in-string
+ InstanceId: '${aws:InstanceId}',
+ },
+ aggregation_dimensions: [[]], // Create rollups without instance id
+ metrics_collected: {
+ procstat: [
+ {
+ pattern: 'jenkins.war',
+ measurement: [
+ 'cpu_usage',
+ 'cpu_time_system',
+ 'cpu_time_user',
+ 'read_bytes',
+ 'write_bytes',
+ 'pid_count',
+ ],
+ metrics_collection_interval: 10,
+ },
+ ],
+ mem: {
+ measurement: [
+ { name: 'available_percent', unit: Unit.PERCENT },
+ { name: 'used_percent', unit: Unit.PERCENT },
+ { name: 'mem_total', unit: Unit.BYTES },
+ ],
+ metrics_collection_interval: 1, // capture every second
+ },
+ },
+ },
+ logs: {
+ logs_collected: {
+ files: {
+ collect_list: [
+ {
+ file_path: '/var/lib/jenkins/logs/custom/workflowRun.log',
+ log_group_name: 'JenkinsMainNode/workflow.log',
+ auto_removal: true,
+ log_stream_name: 'workflow-logs',
+ },
+ ],
+ },
+ },
+ force_flush_interval: 5,
+ },
+ }),
+ InitCommand.shellCommand('/opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a stop'),
+ // eslint-disable-next-line max-len
+ InitCommand.shellCommand('/opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -c file:/opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json -s'),
+
+ InitCommand.shellCommand(dataRetentionProps.dataRetention
+ ? `mkdir /var/lib/jenkins && mount -t efs ${efsId} /var/lib/jenkins`
+ : 'echo Data rentention is disabled, not mounting efs'),
+
+ InitFile.fromFileInline('/docker-compose.yml', join(__dirname, '../../resources/docker-compose-benchmark.yml')),
+
+ InitCommand.shellCommand('systemctl start docker &&'
+ + ` var=\`aws --region ${stackRegion} secretsmanager get-secret-value --secret-id ${reloadPasswordSecretsArn} --query SecretString --output text\` &&`
+ + ' yq -i \'.services.jenkins.environment[1] = "CASC_RELOAD_TOKEN=\'$var\'"\' docker-compose.yml &&'
+ + ' docker-compose up -d'),
+
+ // Commands are fired one after the other but it does not wait for the command to complete.
+ // Therefore, sleep 60 seconds to wait for jenkins to start
+ InitCommand.shellCommand('sleep 60'),
+
+ InitFile.fromFileInline('/initial_jenkins.yaml', jenkinsyaml),
+
+ // Make any changes to initial jenkins.yaml
+ InitCommand.shellCommand(oidcFederateProps.runWithOidc
+ // eslint-disable-next-line max-len
+ ? `var=\`aws --region ${stackRegion} secretsmanager get-secret-value --secret-id ${oidcFederateProps.oidcCredArn} --query SecretString --output text\` && `
+ + ' varkeys=`echo $var | yq \'keys\' | cut -d "-" -f2 | cut -d " " -f2` &&'
+ // eslint-disable-next-line max-len
+ + ' for i in $varkeys; do newvalue=`echo $var | yq .$i` && myenv=$newvalue i=$i yq -i \'.jenkins.securityRealm.oic.[env(i)]=env(myenv)\' /initial_jenkins.yaml ; done'
+ : 'echo No changes made to initial_jenkins.yaml with respect to OIDC'),
+
+ // eslint-disable-next-line max-len
+ InitCommand.shellCommand('while [[ "$(curl -s -o /dev/null -w \'\'%{http_code}\'\' localhost:8080/benchmark/api/json?pretty)" != "200" ]]; do sleep 5; done'),
+
+ // Reload configuration via Jenkins.yaml
+ InitCommand.shellCommand('cp /initial_jenkins.yaml /var/lib/jenkins/jenkins.yaml &&'
+ + ` var=\`aws --region ${stackRegion} secretsmanager get-secret-value --secret-id ${reloadPasswordSecretsArn} --query SecretString --output text\` &&`
+ + ' curl -f -X POST "http://localhost:8080/benchmark/reload-configuration-as-code/?casc-reload-token=$var"'),
+ ];
+ }
+
+ public static addConfigtoJenkinsYaml(stack: Stack, jenkinsMainNodeProps: JenkinsMainNodePropsBenchmark, oidcProps: OidcFederatePropsBenchmark,
+ agentNodeObject: AgentNodeConfigBenchmark, props: AgentNodeNetworkPropsBenchmark, agentNode: AgentNodePropsBenchmark[],
+ macAgent: string): string {
+ let updatedConfig = agentNodeObject.addAgentConfigToJenkinsYaml(stack, agentNode, props, macAgent);
+ if (oidcProps.runWithOidc) {
+ updatedConfig = OidcConfig.addOidcConfigToJenkinsYaml(updatedConfig, oidcProps.adminUsers);
+ }
+ if (jenkinsMainNodeProps.envVarsFilePath !== '' && jenkinsMainNodeProps.envVarsFilePath != null) {
+ updatedConfig = EnvConfig.addEnvConfigToJenkinsYaml(updatedConfig, jenkinsMainNodeProps.envVarsFilePath);
+ }
+ if (jenkinsMainNodeProps.enableViews) {
+ updatedConfig = ViewsConfig.addViewsConfigToJenkinsYaml(updatedConfig);
+ }
+ const newConfig = dump(updatedConfig);
+ writeFileSync(JenkinsMainNodeBenchmark.NEW_JENKINS_YAML_PATH, newConfig, 'utf-8');
+ return JenkinsMainNodeBenchmark.NEW_JENKINS_YAML_PATH;
+ }
+ }
+
\ No newline at end of file
diff --git a/lib/compute/jenkins-main-node.ts b/lib/compute/jenkins-main-node.ts
index 69485673..e14496f0 100644
--- a/lib/compute/jenkins-main-node.ts
+++ b/lib/compute/jenkins-main-node.ts
@@ -160,6 +160,11 @@ export class JenkinsMainNode {
value: this.mainNodeAsg.role.roleArn,
exportName: 'mainNodeRoleArn',
});
+
+ new CfnOutput(stack, 'Jenkins Main Node ASG Name', {
+ value: this.mainNodeAsg.role.roleArn,
+ exportName: 'mainNodeASGName',
+ });
}
public static createPoliciesForMainNode(stack: Stack): (IManagedPolicy | ManagedPolicy)[] {
diff --git a/lib/compute/views-benchmark.ts b/lib/compute/views-benchmark.ts
new file mode 100644
index 00000000..ea06f2fa
--- /dev/null
+++ b/lib/compute/views-benchmark.ts
@@ -0,0 +1,47 @@
+/**
+ * SPDX,License,Identifier: Apache,2.0
+ *
+ * The OpenSearch Contributors require contributions made to
+ * this file be licensed under the Apache,2.0 license or a
+ * compatible open source license.
+ */
+
+export class ViewsConfig {
+ private static getViewsConfig(name: string, regex: string): any {
+ const config = {
+ list: {
+ columns: [
+ 'status',
+ 'weather',
+ 'jobName',
+ 'lastSuccess',
+ 'lastFailure',
+ 'lastDuration',
+ 'buildButton',
+ 'favoriteColumn',
+ ],
+ includeRegex: regex,
+ name,
+ },
+ };
+ return config;
+ }
+
+ public static addViewsConfigToJenkinsYaml(yamlObject: any): any {
+ const jenkinsYaml: any = yamlObject;
+ const viewsConfig: any = {
+ Build: '.*build.*',
+ Test: '.*test.*',
+ Release: '.*release.*',
+ Misc: '(?!.*(test|build|release).*).*',
+ };
+ const viewConfigsArray: any[] = [];
+ Object.keys(viewsConfig).forEach((item) => {
+ viewConfigsArray.push(this.getViewsConfig(item, viewsConfig[item]));
+ });
+
+ viewConfigsArray.forEach((item) => jenkinsYaml.jenkins.views.push(item));
+ return jenkinsYaml;
+ }
+ }
+
\ No newline at end of file
diff --git a/lib/monitoring/ci-alarms-benchmark.ts b/lib/monitoring/ci-alarms-benchmark.ts
new file mode 100644
index 00000000..3da52c48
--- /dev/null
+++ b/lib/monitoring/ci-alarms-benchmark.ts
@@ -0,0 +1,62 @@
+/**
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * The OpenSearch Contributors require contributions made to
+ * this file be licensed under the Apache-2.0 license or a
+ * compatible open source license.
+ */
+
+import { Stack } from 'aws-cdk-lib';
+import {
+ Alarm, AlarmWidget, ComparisonOperator, Dashboard, Metric, TreatMissingData,
+} from 'aws-cdk-lib/aws-cloudwatch';
+import { JenkinsMainNodeBenchmark } from '../compute/jenkins-main-node-benchmark';
+
+export class JenkinsMonitoringBenchmark {
+ public readonly alarms: Alarm[] = [];
+
+ constructor(stack: Stack, mainNode: JenkinsMainNodeBenchmark) {
+ const dashboard = new Dashboard(stack, 'AlarmDashboard-Benchmark');
+
+ const cpuMetric = new Metric({
+ namespace: 'AWS/EC2',
+ metricName: 'CPUUtilization',
+ dimensionsMap: {
+ AutoScalingGroupName: mainNode.mainNodeBenchAsg.autoScalingGroupName,
+ },
+ });
+
+ this.alarms.push(new Alarm(stack, 'AverageMainNodeCpuUtilization', {
+ alarmDescription: 'Overall EC2 avg CPU Utilization',
+ evaluationPeriods: 3,
+ metric: cpuMetric,
+ threshold: 50,
+ comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
+ }));
+
+ /**
+ *If the Jenkins over the last 15 (evaluationPeriods:3 * Period:5) minutes period is less than 1 (jenkins down) for at least 2 times. */
+ this.alarms.push(new Alarm(stack, 'MainNodeJenkinsProcessNotFound', {
+ alarmDescription: 'Jenkins process is not running',
+ metric: mainNode.ec2InstanceMetrics.foundJenkinsProcessCount.with({ statistic: 'avg' }),
+ evaluationPeriods: 3,
+ threshold: 1,
+ datapointsToAlarm: 3,
+ comparisonOperator: ComparisonOperator.LESS_THAN_THRESHOLD,
+ treatMissingData: TreatMissingData.IGNORE,
+ }));
+
+ this.alarms.push(new Alarm(stack, 'MainNodeHighMemoryUtilization', {
+ alarmDescription: 'The jenkins process is using more memory than expected, it should be investigated for a large number of jobs or heavy weight jobs',
+ metric: mainNode.ec2InstanceMetrics.memUsed.with({ statistic: 'avg' }),
+ evaluationPeriods: 5,
+ threshold: 65,
+ comparisonOperator: ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
+ treatMissingData: TreatMissingData.IGNORE,
+ }));
+
+ this.alarms
+ .map((alarm) => new AlarmWidget({ alarm }))
+ .forEach((widget) => dashboard.addWidgets(widget));
+ }
+}
diff --git a/lib/network/ci-external-load-balancer.ts b/lib/network/ci-external-load-balancer.ts
index cfbafa29..160cd315 100644
--- a/lib/network/ci-external-load-balancer.ts
+++ b/lib/network/ci-external-load-balancer.ts
@@ -7,16 +7,16 @@
*/
import { CfnOutput, Stack } from 'aws-cdk-lib';
-import { Instance, SecurityGroup, Vpc } from 'aws-cdk-lib/aws-ec2';
+import { ISecurityGroup, IVpc } from 'aws-cdk-lib/aws-ec2';
import {
- ApplicationListener, ApplicationLoadBalancer, ApplicationProtocol, ApplicationTargetGroup, ListenerCertificate, Protocol, SslPolicy,
+ ApplicationListener, ApplicationLoadBalancer, ApplicationProtocol, ApplicationTargetGroup, ListenerCertificate,
+ Protocol, SslPolicy, ListenerAction,
} from 'aws-cdk-lib/aws-elasticloadbalancingv2';
-import { InstanceTarget } from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets';
import { AutoScalingGroup } from 'aws-cdk-lib/aws-autoscaling';
export interface JenkinsExternalLoadBalancerProps {
- readonly vpc: Vpc;
- readonly sg: SecurityGroup;
+ readonly vpc: IVpc;
+ readonly sg: ISecurityGroup;
readonly targetInstance: AutoScalingGroup;
readonly listenerCertificate: ListenerCertificate;
readonly useSsl: boolean;
@@ -39,11 +39,22 @@ export class JenkinsExternalLoadBalancer {
internetFacing: true,
});
+ this.targetGroup = new ApplicationTargetGroup(this.loadBalancer, 'MainJenkinsNodeTarget', {
+ port: accessPort,
+ vpc: props.vpc,
+ targets: [props.targetInstance],
+ healthCheck: {
+ protocol: props.useSsl ? Protocol.HTTPS : Protocol.HTTP,
+ path: '/login',
+ },
+ });
+
this.listener = this.loadBalancer.addListener('JenkinsListener', {
sslPolicy: props.useSsl ? SslPolicy.RECOMMENDED : undefined,
port: accessPort,
open: false,
certificates: props.useSsl ? [props.listenerCertificate] : undefined,
+ defaultAction: ListenerAction.forward([this.targetGroup]),
});
if (props.useSsl) {
@@ -55,17 +66,19 @@ export class JenkinsExternalLoadBalancer {
});
}
- this.targetGroup = this.listener.addTargets('MainJenkinsNodeTarget', {
- port: accessPort,
- targets: [props.targetInstance],
- healthCheck: {
- protocol: props.useSsl ? Protocol.HTTPS : Protocol.HTTP,
- path: '/login',
- },
- });
-
new CfnOutput(stack, 'Jenkins External Load Balancer Dns', {
value: this.loadBalancer.loadBalancerDnsName,
+ exportName: 'ALBDns',
+ });
+
+ new CfnOutput(stack, 'Jenkins External Load Balancer Arn', {
+ value: this.loadBalancer.loadBalancerArn,
+ exportName: 'ALBArn',
+ });
+
+ new CfnOutput(stack, 'Jenkins External Load Balancer Listerner Arn', {
+ value: this.listener.listenerArn,
+ exportName: 'ALBListenerArn',
});
}
}
diff --git a/lib/security/ci-security-groups.ts b/lib/security/ci-security-groups.ts
index 8a4cbbb9..b6264d6d 100644
--- a/lib/security/ci-security-groups.ts
+++ b/lib/security/ci-security-groups.ts
@@ -8,7 +8,7 @@
import { Stack } from 'aws-cdk-lib';
import {
- IPeer, Port, SecurityGroup, Vpc,
+ IPeer, Port, SecurityGroup, IVpc,
} from 'aws-cdk-lib/aws-ec2';
export class JenkinsSecurityGroups {
@@ -20,36 +20,36 @@ export class JenkinsSecurityGroups {
public readonly efsSG: SecurityGroup;
- constructor(stack: Stack, vpc: Vpc, useSsl: boolean, restrictServerAccessTo: IPeer) {
+ constructor(stack: Stack, vpc: IVpc, useSsl: boolean, restrictServerAccessTo: IPeer, idSG: string) {
let accessPort = 80;
if (useSsl) {
accessPort = 443;
}
- this.externalAccessSG = new SecurityGroup(stack, 'ExternalAccessSG', {
+ this.externalAccessSG = new SecurityGroup(stack, `${idSG}-ExternalAccessSG`, {
vpc,
- description: 'External access to Jenkins',
+ description: `External access to Jenkins ${idSG}`,
});
this.externalAccessSG.addIngressRule(restrictServerAccessTo, Port.tcp(accessPort), 'Restrict jenkins endpoint access to this source');
- this.mainNodeSG = new SecurityGroup(stack, 'MainNodeSG', {
+ this.mainNodeSG = new SecurityGroup(stack, `${idSG}-MainNodeSG`, {
vpc,
- description: 'Main node of Jenkins',
+ description: `Main node of Jenkins ${idSG}`,
});
this.mainNodeSG.addIngressRule(this.externalAccessSG, Port.tcp(accessPort));
- this.agentNodeSG = new SecurityGroup(stack, 'AgentNodeSG', {
+ this.agentNodeSG = new SecurityGroup(stack, `${idSG}-AgentNodeSG`, {
vpc,
- description: 'Agent Node of Jenkins',
+ description: `Agent Node of Jenkins ${idSG}`,
});
this.agentNodeSG.addIngressRule(this.mainNodeSG, Port.tcp(22), 'Main node SSH Access into agent nodes');
this.agentNodeSG.addIngressRule(this.mainNodeSG, Port.tcp(445), 'Main node SMB Access into agent nodes for Windows');
this.agentNodeSG.addIngressRule(this.mainNodeSG, Port.tcp(5985), 'Main node WinRM HTTP Access into agent nodes for Windows');
this.agentNodeSG.addIngressRule(this.agentNodeSG, Port.allTraffic(), 'Agent node open all ports to other agent nodes within the same SG');
- this.efsSG = new SecurityGroup(stack, 'efsSG', {
+ this.efsSG = new SecurityGroup(stack, `${idSG}-efsSG`, {
vpc,
- description: 'Jenkins EFS',
+ description: `Jenkins EFS ${idSG}`,
});
this.efsSG.addIngressRule(this.mainNodeSG, Port.allTraffic(), 'Main node Access to EFS');
}
diff --git a/package.json b/package.json
index fd3e4bbe..672591af 100644
--- a/package.json
+++ b/package.json
@@ -8,7 +8,8 @@
"build": "tsc",
"watch": "tsc -w",
"postbuild": "eslint --fix \"bin/**/*.ts\" \"lib/**/*.ts\" \"test/**/*.ts\" --ignore-pattern \"**/*.d.ts\" && jest",
- "cdk": "cdk"
+ "cdk": "cdk",
+ "test": "jest"
},
"devDependencies": {
"@types/jest": "^26.0.10",
diff --git a/resources/baseJenkins-benchmark.yaml b/resources/baseJenkins-benchmark.yaml
new file mode 100644
index 00000000..03689d01
--- /dev/null
+++ b/resources/baseJenkins-benchmark.yaml
@@ -0,0 +1,259 @@
+jenkins:
+ log:
+ recorders:
+ - loggers:
+ - level: "FINE"
+ name: "org.jenkinsci.plugins.workflow.job.WorkflowRun"
+ name: "workflowRun"
+ agentProtocols:
+ - "JNLP4-connect"
+ - "Ping"
+ authorizationStrategy: "loggedInUsersCanDoAnything"
+ crumbIssuer:
+ standard:
+ excludeClientIPFromCrumb: false
+ disableRememberMe: false
+ labelAtoms:
+ - name: "built-in"
+ - name: "main-node"
+ labelString: "main-node"
+ markupFormatter:
+ rawHtml:
+ disableSyntaxHighlighting: true
+ mode: EXCLUSIVE
+ myViewsTabBar: "standard"
+ numExecutors: 4
+ primaryView:
+ all:
+ name: "all"
+ projectNamingStrategy: "standard"
+ quietPeriod: 5
+ scmCheckoutRetryCount: 0
+ securityRealm:
+ local:
+ allowsSignup: false
+ enableCaptcha: false
+ users:
+ - id: "admin"
+ name: "admin"
+ properties:
+ - "apiToken"
+ - preferredProvider:
+ providerId: "default"
+ - "loginDetailsProperty"
+ slaveAgentPort: 50000
+ updateCenter:
+ sites:
+ - id: "default"
+ url: "https://updates.jenkins.io/update-center.json"
+ views:
+ - all:
+ name: "all"
+ viewsTabBar: "standard"
+globalCredentialsConfiguration:
+ configuration:
+ providerFilter: "none"
+ typeFilter: "none"
+security:
+ apiToken:
+ creationOfLegacyTokenEnabled: false
+ tokenGenerationOnCreationEnabled: false
+ usageStatisticsEnabled: true
+ copyartifact:
+ mode: PRODUCTION
+ globalJobDslSecurityConfiguration:
+ useScriptSecurity: true
+ sSHD:
+ port: -1
+unclassified:
+ audit-trail:
+ logBuildCause: true
+ pattern: ".*/(?:configSubmit|doDelete|postBuildResult|enable|disable|cancelQueue|stop|toggleLogKeep|doWipeOutWorkspace|createItem|createView|toggleOffline|cancelQuietDown|quietDown|restart|exit|safeExit)/?.*"
+ awsCredentialsProvider:
+ cache: false
+ client:
+ credentialsProvider: "default"
+ buildDiscarders:
+ configuredBuildDiscarders:
+ - "jobBuildDiscarder"
+ buildStepOperation:
+ enabled: false
+ buildTimestamp:
+ enableBuildTimestamp: true
+ pattern: "yyyy-MM-dd HH:mm:ss z"
+ timezone: "Etc/UTC"
+ descriptionSetterWrapper:
+ disableTokens: false
+ email-ext:
+ adminRequiredForTemplateTesting: false
+ allowUnregisteredEnabled: false
+ charset: "UTF-8"
+ debugMode: false
+ defaultBody: |-
+ $PROJECT_NAME - Build # $BUILD_NUMBER - $BUILD_STATUS:
+
+ Check console output at $BUILD_URL to view the results.
+ defaultSubject: "$PROJECT_NAME - Build # $BUILD_NUMBER - $BUILD_STATUS!"
+ defaultTriggerIds:
+ - "hudson.plugins.emailext.plugins.trigger.FailureTrigger"
+ maxAttachmentSize: -1
+ maxAttachmentSizeMb: -1
+ precedenceBulk: false
+ watchingEnabled: false
+ fingerprints:
+ fingerprintCleanupDisabled: false
+ storage: "file"
+ ghprbTrigger:
+ autoCloseFailedPullRequests: false
+ cron: "H/5 * * * *"
+ extensions:
+ - ghprbSimpleStatus:
+ addTestResults: false
+ showMatrixStatus: false
+ githubAuth:
+ - description: "Anonymous connection"
+ id: "d4456c70-9c5e-4b40-bee4-e1ebb693153b"
+ serverAPIUrl: "https://api.github.com"
+ manageWebhooks: true
+ okToTestPhrase: ".*ok\\W+to\\W+test.*"
+ retestPhrase: ".*test\\W+this\\W+please.*"
+ skipBuildPhrase: ".*\\[skip\\W+ci\\].*"
+ useComments: false
+ useDetailedComments: false
+ whitelistPhrase: ".*add\\W+to\\W+whitelist.*"
+ gitHubConfiguration:
+ apiRateLimitChecker: ThrottleForNormalize
+ gitHubPluginConfig:
+ hookUrl: "http://localhost:8080/github-webhook/"
+ gitSCM:
+ addGitTagAction: false
+ allowSecondFetch: false
+ createAccountBasedOnEmail: false
+ disableGitToolChooser: false
+ hideCredentials: false
+ showEntireCommitSummaryInChanges: false
+ useExistingAccountWithSameEmail: false
+ ivyBuildTrigger:
+ extendedVersionMatching: false
+ junitTestResultStorage:
+ storage: "file"
+ location:
+ adminAddress: "address not configured yet "
+ login-theme-plugin:
+ useDefaultTheme: true
+ mailer:
+ charset: "UTF-8"
+ useSsl: false
+ useTls: false
+ pluginImpl:
+ enableCredentialsFromNode: false
+ pollSCM:
+ pollingThreadCount: 10
+ timestamper:
+ allPipelines: false
+ elapsedTimeFormat: "''HH:mm:ss.S' '"
+ systemTimeFormat: "''HH:mm:ss' '"
+ upstream:
+ globalUpstreamFilterStrategy: UseOldest
+ whitelist:
+ enabled: false
+tool:
+ git:
+ installations:
+ - home: "git"
+ name: "Default"
+ jdk:
+ installations:
+ - name: "openjdk-8"
+ properties:
+ - installSource:
+ installers:
+ - adoptOpenJdkInstaller:
+ id: "jdk8u332-b09"
+ - name: "openjdk-11"
+ properties:
+ - installSource:
+ installers:
+ - adoptOpenJdkInstaller:
+ id: "jdk-11.0.15+10"
+ - name: "openjdk-17"
+ properties:
+ - installSource:
+ installers:
+ - adoptOpenJdkInstaller:
+ id: "jdk-17.0.3+7"
+ - name: "openjdk-19"
+ properties:
+ - installSource:
+ installers:
+ - adoptOpenJdkInstaller:
+ id: "jdk-19.0.1+10"
+ - name: "openjdk-20"
+ properties:
+ - installSource:
+ installers:
+ - adoptOpenJdkInstaller:
+ id: "jdk-20.0.1+9"
+ - name: "openjdk-21"
+ properties:
+ - installSource:
+ installers:
+ - adoptOpenJdkInstaller:
+ id: "jdk-21.0.1+12"
+ mavenGlobalConfig:
+ globalSettingsProvider: "standard"
+ settingsProvider: "standard"
+ powerShellInstallation:
+ installations:
+ - home: "powershell.exe"
+ name: "DefaultWindows"
+ - home: "pwsh"
+ name: "DefaultLinux"
+support:
+ automatedBundleConfiguration:
+ componentIds:
+ - "AgentsConfigFile"
+ - "ConfigFileComponent"
+ - "OtherConfigFilesComponent"
+ - "AboutBrowser"
+ - "AboutJenkins"
+ - "AboutUser"
+ - "AdministrativeMonitors"
+ - "AgentProtocols"
+ - "BuildQueue"
+ - "CustomLogs"
+ - "DumpExportTable"
+ - "EnvironmentVariables"
+ - "FileDescriptorLimit"
+ - "GCLogs"
+ - "HeapUsageHistogram"
+ - "ItemsContent"
+ - "AgentsJVMProcessSystemMetricsContents"
+ - "MasterJVMProcessSystemMetricsContents"
+ - "JenkinsLogs"
+ - "LoadStats"
+ - "LoggerManager"
+ - "Metrics"
+ - "NetworkInterfaces"
+ - "NodeMonitors"
+ - "OtherLogs"
+ - "ReverseProxy"
+ - "RootCAs"
+ - "RunningBuilds"
+ - "SlaveCommandStatistics"
+ - "SlaveLaunchLogs"
+ - "SlaveLogs"
+ - "AgentsSystemConfiguration"
+ - "MasterSystemConfiguration"
+ - "SystemProperties"
+ - "TaskLogs"
+ - "ThreadDumps"
+ - "UpdateCenter"
+ - "UserCount"
+ - "SlowRequestComponent"
+ - "HighLoadComponent"
+ - "DeadlockRequestComponent"
+ - "PipelineTimings"
+ - "PipelineThreadDump"
+ enabled: true
+ period: 1
diff --git a/resources/docker-compose-benchmark.yml b/resources/docker-compose-benchmark.yml
new file mode 100644
index 00000000..bb3d1012
--- /dev/null
+++ b/resources/docker-compose-benchmark.yml
@@ -0,0 +1,31 @@
+version: '3.8'
+services:
+ jenkins:
+ image: opensearchstaging/jenkins:2.387.1-lts-jdk11
+ restart: on-failure
+ privileged: true
+ tty: true
+ user: root
+ ports:
+ - 8080:8080
+ - 50000:50000
+ container_name: jenkins
+ environment:
+ - JENKINS_JAVA_OPTS=-Xms4g -Xmx16g -Dhudson.model.ParametersAction.keepUndefinedParameters=true -XX:+UseG1GC -XX:+ExplicitGCInvokesConcurrent -XX:+ParallelRefProcEnabled -XX:+UseStringDeduplication -XX:+UnlockExperimentalVMOptions -XX:G1NewSizePercent=20 -XX:+UnlockDiagnosticVMOptions
+ - CASC_RELOAD_TOKEN=reloadPasswordHere
+ - JENKINS_OPTS=--prefix="/benchmark"
+ volumes:
+ - /var/lib/jenkins:/var/jenkins_home
+ deploy:
+ resources:
+ limits:
+ cpus: '16'
+ memory: '32g'
+ reservations:
+ cpus: '16'
+ memory: '32g'
+ logging:
+ driver: awslogs
+ options:
+ awslogs-group: JenkinsMainNodeBenchmark/jenkins.log
+ awslogs-create-group: 'true'
diff --git a/resources/jenkins-benchmark.yaml b/resources/jenkins-benchmark.yaml
new file mode 100644
index 00000000..860a33c8
--- /dev/null
+++ b/resources/jenkins-benchmark.yaml
@@ -0,0 +1,451 @@
+jenkins:
+ log:
+ recorders:
+ - loggers:
+ - level: FINE
+ name: org.jenkinsci.plugins.workflow.job.WorkflowRun
+ name: workflowRun
+ agentProtocols:
+ - JNLP4-connect
+ - Ping
+ authorizationStrategy:
+ roleBased:
+ roles:
+ global:
+ - assignments:
+ - admin
+ name: admin
+ pattern: .*
+ permissions:
+ - Overall/Administer
+ - Overall/Read
+ - Job/Move
+ - Job/Build
+ - Job/Read
+ - Job/Delete
+ - Job/Create
+ - Job/Discover
+ - Job/Cancel
+ - Job/Configure
+ - Job Config History/DeleteEntry
+ - Job/Workspace
+ - Credentials/Delete
+ - Credentials/ManageDomains
+ - Credentials/Update
+ - Credentials/View
+ - Credentials/Create
+ - Manage ownership/Nodes
+ - Manage ownership/Jobs
+ - Agent/Configure
+ - Agent/Create
+ - Agent/Build
+ - Agent/Provision
+ - Agent/Connect
+ - Agent/Delete
+ - Agent/Disconnect
+ - Run/Replay
+ - Run/Delete
+ - Run/Update
+ - View/Delete
+ - View/Read
+ - View/Create
+ - View/Configure
+ - SCM/Tag
+ - assignments:
+ - anonymous
+ name: read
+ pattern: .*
+ permissions:
+ - Overall/Read
+ - Job/Read
+ crumbIssuer:
+ standard:
+ excludeClientIPFromCrumb: false
+ disableRememberMe: false
+ labelAtoms:
+ - name: built-in
+ - name: main-node
+ labelString: main-node
+ markupFormatter:
+ rawHtml:
+ disableSyntaxHighlighting: true
+ mode: EXCLUSIVE
+ myViewsTabBar: standard
+ numExecutors: 4
+ primaryView:
+ all:
+ name: all
+ projectNamingStrategy: standard
+ quietPeriod: 5
+ scmCheckoutRetryCount: 0
+ securityRealm:
+ oic:
+ clientId: clientId
+ clientSecret: clientSecret
+ authorizationServerUrl: http://localhost
+ wellKnownOpenIDConfigurationUrl: wellKnownOpenIDConfigurationUrl
+ tokenServerUrl: tokenServerUrl
+ userInfoServerUrl: userInfoServerUrl
+ disableSslVerification: false
+ userNameField: sub
+ escapeHatchEnabled: false
+ logoutFromOpenidProvider: true
+ postLogoutRedirectUrl: ''
+ scopes: openid
+ escapeHatchSecret: random
+ slaveAgentPort: 50000
+ updateCenter:
+ sites:
+ - id: default
+ url: https://updates.jenkins.io/update-center.json
+ views:
+ - all:
+ name: all
+ viewsTabBar: standard
+ clouds:
+ - amazonEC2:
+ cloudName: Amazon_ec2_cloud
+ region: ${Token[AWS.Region.13]}
+ sshKeysCredentialsId: ${Token[Fn::Join.636]}
+ templates:
+ - ami: ${Token[TOKEN.556]}
+ amiType:
+ unixData:
+ sshPort: '22'
+ associatePublicIp: false
+ connectBySSHProcess: false
+ connectionStrategy: PRIVATE_IP
+ customDeviceMapping: /dev/xvda=:300:true:::encrypted
+ deleteRootOnTermination: true
+ description: jenkinsAgentNode-Jenkins-Default-Agent-X64-C5xlarge-Single-Host
+ ebsEncryptRootVolume: ENCRYPTED
+ ebsOptimized: false
+ metadataTokensRequired: true
+ metadataHopsLimit: '2'
+ hostKeyVerificationStrategy: 'OFF'
+ iamInstanceProfile: ${Token[TOKEN.635]}
+ idleTerminationMinutes: '60'
+ initScript: >-
+ sudo amazon-linux-extras install java-openjdk11 -y && sudo yum
+ install -y cmake python3 python3-pip && sudo yum groupinstall -y
+ 'Development Tools' && sudo ln -sfn `which pip3` /usr/bin/pip &&
+ pip3 install pipenv && sudo ln -s ~/.local/bin/pipenv
+ /usr/local/bin
+ labelString: Jenkins-Default-Agent-X64-C5xlarge-Single-Host
+ launchTimeoutStr: '300'
+ maxTotalUses: -1
+ minimumNumberOfInstances: 0
+ minimumNumberOfSpareInstances: 1
+ mode: EXCLUSIVE
+ monitoring: true
+ numExecutors: 1
+ remoteAdmin: ec2-user
+ remoteFS: /home/ec2-user
+ securityGroups: ${Token[TOKEN.520]}
+ stopOnTerminate: false
+ subnetId: ${Token[TOKEN.491]}
+ t2Unlimited: false
+ tags:
+ - name: Name
+ value: >-
+ TestStack/AgentNode/Jenkins-Default-Agent-X64-C5xlarge-Single-Host
+ - name: type
+ value: >-
+ jenkinsAgentNode-Jenkins-Default-Agent-X64-C5xlarge-Single-Host
+ tenancy: Default
+ type: C54xlarge
+ nodeProperties:
+ - envVars:
+ env:
+ - key: JENKINS_HOME_PATH
+ value: /home/ec2-user
+ - key: JAVA8_HOME
+ value: /usr/lib/jvm/temurin-8-jdk-amd64
+ - key: JAVA11_HOME
+ value: /usr/lib/jvm/temurin-11-jdk-amd64
+ - key: JAVA14_HOME
+ value: /usr/lib/jvm/adoptopenjdk-14-amd64
+ - key: JAVA17_HOME
+ value: /usr/lib/jvm/temurin-17-jdk-amd64
+ - key: JAVA19_HOME
+ value: /usr/lib/jvm/temurin-19-jdk-amd64
+ - key: JAVA20_HOME
+ value: /usr/lib/jvm/temurin-20-jdk-amd64
+ - key: JAVA21_HOME
+ value: /usr/lib/jvm/temurin-21-jdk-amd64
+ useEphemeralDevices: false
+ - ami: ${Token[TOKEN.559]}
+ amiType:
+ unixData:
+ sshPort: '22'
+ associatePublicIp: false
+ connectBySSHProcess: false
+ connectionStrategy: PRIVATE_IP
+ customDeviceMapping: /dev/xvda=:300:true:::encrypted
+ deleteRootOnTermination: true
+ description: jenkinsAgentNode-Jenkins-Default-Agent-ARM64-C5xlarge-Single-Host
+ ebsEncryptRootVolume: ENCRYPTED
+ ebsOptimized: false
+ metadataTokensRequired: true
+ metadataHopsLimit: '2'
+ hostKeyVerificationStrategy: 'OFF'
+ iamInstanceProfile: ${Token[TOKEN.635]}
+ idleTerminationMinutes: '60'
+ initScript: >-
+ sudo amazon-linux-extras install java-openjdk11 -y && sudo yum
+ install -y cmake python3 python3-pip && sudo yum groupinstall -y
+ 'Development Tools' && sudo ln -sfn `which pip3` /usr/bin/pip &&
+ pip3 install pipenv && sudo ln -s ~/.local/bin/pipenv
+ /usr/local/bin
+ labelString: Jenkins-Default-Agent-ARM64-C5xlarge-Single-Host
+ launchTimeoutStr: '300'
+ maxTotalUses: -1
+ minimumNumberOfInstances: 0
+ minimumNumberOfSpareInstances: 1
+ mode: EXCLUSIVE
+ monitoring: true
+ numExecutors: 1
+ remoteAdmin: ec2-user
+ remoteFS: /home/ec2-user
+ securityGroups: ${Token[TOKEN.520]}
+ stopOnTerminate: false
+ subnetId: ${Token[TOKEN.491]}
+ t2Unlimited: false
+ tags:
+ - name: Name
+ value: >-
+ TestStack/AgentNode/Jenkins-Default-Agent-ARM64-C5xlarge-Single-Host
+ - name: type
+ value: >-
+ jenkinsAgentNode-Jenkins-Default-Agent-ARM64-C5xlarge-Single-Host
+ tenancy: Default
+ type: C6g4xlarge
+ nodeProperties:
+ - envVars:
+ env:
+ - key: JENKINS_HOME_PATH
+ value: /home/ec2-user
+ - key: JAVA8_HOME
+ value: /usr/lib/jvm/temurin-8-jdk-amd64
+ - key: JAVA11_HOME
+ value: /usr/lib/jvm/temurin-11-jdk-amd64
+ - key: JAVA14_HOME
+ value: /usr/lib/jvm/adoptopenjdk-14-amd64
+ - key: JAVA17_HOME
+ value: /usr/lib/jvm/temurin-17-jdk-amd64
+ - key: JAVA19_HOME
+ value: /usr/lib/jvm/temurin-19-jdk-amd64
+ - key: JAVA20_HOME
+ value: /usr/lib/jvm/temurin-20-jdk-amd64
+ - key: JAVA21_HOME
+ value: /usr/lib/jvm/temurin-21-jdk-amd64
+ useEphemeralDevices: false
+ useInstanceProfileForCredentials: true
+globalCredentialsConfiguration:
+ configuration:
+ providerFilter: none
+ typeFilter: none
+security:
+ apiToken:
+ creationOfLegacyTokenEnabled: false
+ tokenGenerationOnCreationEnabled: false
+ usageStatisticsEnabled: true
+ copyartifact:
+ mode: PRODUCTION
+ globalJobDslSecurityConfiguration:
+ useScriptSecurity: true
+ sSHD:
+ port: -1
+unclassified:
+ audit-trail:
+ logBuildCause: true
+ pattern: >-
+ .*/(?:configSubmit|doDelete|postBuildResult|enable|disable|cancelQueue|stop|toggleLogKeep|doWipeOutWorkspace|createItem|createView|toggleOffline|cancelQuietDown|quietDown|restart|exit|safeExit)/?.*
+ awsCredentialsProvider:
+ cache: false
+ client:
+ credentialsProvider: default
+ buildDiscarders:
+ configuredBuildDiscarders:
+ - jobBuildDiscarder
+ buildStepOperation:
+ enabled: false
+ buildTimestamp:
+ enableBuildTimestamp: true
+ pattern: yyyy-MM-dd HH:mm:ss z
+ timezone: Etc/UTC
+ descriptionSetterWrapper:
+ disableTokens: false
+ email-ext:
+ adminRequiredForTemplateTesting: false
+ allowUnregisteredEnabled: false
+ charset: UTF-8
+ debugMode: false
+ defaultBody: |-
+ $PROJECT_NAME - Build # $BUILD_NUMBER - $BUILD_STATUS:
+
+ Check console output at $BUILD_URL to view the results.
+ defaultSubject: '$PROJECT_NAME - Build # $BUILD_NUMBER - $BUILD_STATUS!'
+ defaultTriggerIds:
+ - hudson.plugins.emailext.plugins.trigger.FailureTrigger
+ maxAttachmentSize: -1
+ maxAttachmentSizeMb: -1
+ precedenceBulk: false
+ watchingEnabled: false
+ fingerprints:
+ fingerprintCleanupDisabled: false
+ storage: file
+ ghprbTrigger:
+ autoCloseFailedPullRequests: false
+ cron: H/5 * * * *
+ extensions:
+ - ghprbSimpleStatus:
+ addTestResults: false
+ showMatrixStatus: false
+ githubAuth:
+ - description: Anonymous connection
+ id: d4456c70-9c5e-4b40-bee4-e1ebb693153b
+ serverAPIUrl: https://api.github.com
+ manageWebhooks: true
+ okToTestPhrase: .*ok\W+to\W+test.*
+ retestPhrase: .*test\W+this\W+please.*
+ skipBuildPhrase: .*\[skip\W+ci\].*
+ useComments: false
+ useDetailedComments: false
+ whitelistPhrase: .*add\W+to\W+whitelist.*
+ gitHubConfiguration:
+ apiRateLimitChecker: ThrottleForNormalize
+ gitHubPluginConfig:
+ hookUrl: http://localhost:8080/github-webhook/
+ gitSCM:
+ addGitTagAction: false
+ allowSecondFetch: false
+ createAccountBasedOnEmail: false
+ disableGitToolChooser: false
+ hideCredentials: false
+ showEntireCommitSummaryInChanges: false
+ useExistingAccountWithSameEmail: false
+ ivyBuildTrigger:
+ extendedVersionMatching: false
+ junitTestResultStorage:
+ storage: file
+ location:
+ adminAddress: address not configured yet
+ login-theme-plugin:
+ useDefaultTheme: true
+ mailer:
+ charset: UTF-8
+ useSsl: false
+ useTls: false
+ pluginImpl:
+ enableCredentialsFromNode: false
+ pollSCM:
+ pollingThreadCount: 10
+ timestamper:
+ allPipelines: false
+ elapsedTimeFormat: '''''HH:mm:ss.S'' '''
+ systemTimeFormat: '''''HH:mm:ss'' '''
+ upstream:
+ globalUpstreamFilterStrategy: UseOldest
+ whitelist:
+ enabled: false
+tool:
+ git:
+ installations:
+ - home: git
+ name: Default
+ jdk:
+ installations:
+ - name: openjdk-8
+ properties:
+ - installSource:
+ installers:
+ - adoptOpenJdkInstaller:
+ id: jdk8u332-b09
+ - name: openjdk-11
+ properties:
+ - installSource:
+ installers:
+ - adoptOpenJdkInstaller:
+ id: jdk-11.0.15+10
+ - name: openjdk-17
+ properties:
+ - installSource:
+ installers:
+ - adoptOpenJdkInstaller:
+ id: jdk-17.0.3+7
+ - name: openjdk-19
+ properties:
+ - installSource:
+ installers:
+ - adoptOpenJdkInstaller:
+ id: jdk-19.0.1+10
+ - name: openjdk-20
+ properties:
+ - installSource:
+ installers:
+ - adoptOpenJdkInstaller:
+ id: jdk-20.0.1+9
+ - name: openjdk-21
+ properties:
+ - installSource:
+ installers:
+ - adoptOpenJdkInstaller:
+ id: jdk-21.0.1+12
+ mavenGlobalConfig:
+ globalSettingsProvider: standard
+ settingsProvider: standard
+ powerShellInstallation:
+ installations:
+ - home: powershell.exe
+ name: DefaultWindows
+ - home: pwsh
+ name: DefaultLinux
+support:
+ automatedBundleConfiguration:
+ componentIds:
+ - AgentsConfigFile
+ - ConfigFileComponent
+ - OtherConfigFilesComponent
+ - AboutBrowser
+ - AboutJenkins
+ - AboutUser
+ - AdministrativeMonitors
+ - AgentProtocols
+ - BuildQueue
+ - CustomLogs
+ - DumpExportTable
+ - EnvironmentVariables
+ - FileDescriptorLimit
+ - GCLogs
+ - HeapUsageHistogram
+ - ItemsContent
+ - AgentsJVMProcessSystemMetricsContents
+ - MasterJVMProcessSystemMetricsContents
+ - JenkinsLogs
+ - LoadStats
+ - LoggerManager
+ - Metrics
+ - NetworkInterfaces
+ - NodeMonitors
+ - OtherLogs
+ - ReverseProxy
+ - RootCAs
+ - RunningBuilds
+ - SlaveCommandStatistics
+ - SlaveLaunchLogs
+ - SlaveLogs
+ - AgentsSystemConfiguration
+ - MasterSystemConfiguration
+ - SystemProperties
+ - TaskLogs
+ - ThreadDumps
+ - UpdateCenter
+ - UserCount
+ - SlowRequestComponent
+ - HighLoadComponent
+ - DeadlockRequestComponent
+ - PipelineTimings
+ - PipelineThreadDump
+ enabled: true
+ period: 1
diff --git a/test/ci-stack-benchmark.test.ts b/test/ci-stack-benchmark.test.ts
new file mode 100644
index 00000000..af450c77
--- /dev/null
+++ b/test/ci-stack-benchmark.test.ts
@@ -0,0 +1,185 @@
+/**
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * The OpenSearch Contributors require contributions made to
+ * this file be licensed under the Apache-2.0 license or a
+ * compatible open source license.
+ */
+
+import { App } from 'aws-cdk-lib';
+import { Template } from 'aws-cdk-lib/assertions';
+import { Peer } from 'aws-cdk-lib/aws-ec2';
+import { CIStackBenchmark } from '../lib/ci-stack-benchmark';
+
+test('CI Stack Basic Resources', () => {
+ const app = new App({
+ context: {
+ useSsl: 'true', runWithOidc: 'true', serverAccessType: 'ipv4', restrictServerAccessTo: '10.10.10.10/32', additionalCommands: './test/data/hello-world.py',
+ },
+ });
+
+ // WHEN
+ const stack = new CIStackBenchmark(app, 'TestStack', {
+ dataRetention: true,
+ });
+ const template = Template.fromStack(stack);
+
+ // THEN
+ template.resourceCountIs('AWS::AutoScaling::AutoScalingGroup', 1);
+ template.resourceCountIs('AWS::AutoScaling::LaunchConfiguration', 1);
+ template.resourceCountIs('AWS::EC2::SecurityGroup', 4);
+ template.resourceCountIs('AWS::IAM::Policy', 1);
+ template.resourceCountIs('AWS::IAM::Role', 3);
+ template.resourceCountIs('AWS::S3::Bucket', 1);
+ template.resourceCountIs('Custom::EC2-Key-Pair', 1);
+ template.resourceCountIs('AWS::IAM::InstanceProfile', 2);
+ template.resourceCountIs('AWS::SSM::Document', 1);
+ template.resourceCountIs('AWS::SSM::Association', 1);
+ template.resourceCountIs('AWS::EFS::FileSystem', 1);
+ template.resourceCountIs('AWS::CloudWatch::Alarm', 3);
+});
+
+test('External security group is open', () => {
+ const app = new App({
+ context: {
+ useSsl: 'true', runWithOidc: 'true', serverAccessType: 'ipv4', restrictServerAccessTo: 'all',
+ },
+ });
+
+ // WHEN
+ const stack = new CIStackBenchmark(app, 'MyTestStack', {});
+ const template = Template.fromStack(stack);
+
+ // THEN
+ template.hasResourceProperties('AWS::EC2::SecurityGroup', {
+ GroupDescription: 'External access to Jenkins benchmarkCI',
+ SecurityGroupEgress: [
+ {
+ CidrIp: '0.0.0.0/0',
+ Description: 'Allow all outbound traffic by default',
+ IpProtocol: '-1',
+ },
+ ],
+ });
+
+ // Make sure that `open` is false on all the load balancers
+ template.hasResourceProperties('AWS::EC2::SecurityGroup', {
+ SecurityGroupIngress: [
+ {
+ CidrIp: '0.0.0.0/0',
+ Description: 'Restrict jenkins endpoint access to this source',
+ FromPort: 443,
+ IpProtocol: 'tcp',
+ ToPort: 443,
+ },
+ ],
+ "VpcId": {
+ "Fn::ImportValue": "CIstackVPCId"
+ }
+ });
+});
+
+test('External security group is restricted', () => {
+ const app = new App({
+ context: {
+ useSsl: 'true', runWithOidc: 'true', serverAccessType: 'ipv4', restrictServerAccessTo: '10.0.0.0/24',
+ },
+ });
+
+ // WHEN
+ const stack = new CIStackBenchmark(app, 'MyTestStack', { useSsl: true, restrictServerAccessTo: Peer.ipv4('10.0.0.0/24') });
+ const template = Template.fromStack(stack);
+
+ // THEN
+ template.hasResourceProperties('AWS::EC2::SecurityGroup', {
+ GroupDescription: 'External access to Jenkins benchmarkCI',
+ SecurityGroupEgress: [
+ {
+ CidrIp: '0.0.0.0/0',
+ },
+ ],
+ });
+
+ // Make sure that load balancer access is restricted to given Ipeer
+ template.hasResourceProperties('AWS::EC2::SecurityGroup', {
+ SecurityGroupIngress: [
+ {
+ CidrIp: '10.0.0.0/24',
+ Description: 'Restrict jenkins endpoint access to this source',
+ FromPort: 443,
+ IpProtocol: 'tcp',
+ ToPort: 443,
+ },
+ ],
+ "VpcId": {
+ "Fn::ImportValue": "CIstackVPCId"
+ }
+ });
+});
+
+test('MainNode', () => {
+ const app = new App({
+ context: {
+ useSsl: 'true', runWithOidc: 'true', serverAccessType: 'ipv4', restrictServerAccessTo: '10.10.10.10/32',
+ },
+ });
+
+ // WHEN
+ const stack = new CIStackBenchmark(app, 'MyTestStack', {});
+
+ // THEN
+ Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::LaunchConfiguration', {
+ InstanceType: 'c5.9xlarge',
+ SecurityGroups: [
+ {
+ 'Fn::GetAtt': [
+ 'benchmarkCIMainNodeSGA298DB9C',
+ 'GroupId',
+ ],
+ },
+ {
+ "Fn::ImportValue": "CIStackMainNodeSGId"
+ }
+ ],
+ });
+
+ Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::AutoScalingGroup', {
+ MaxSize: '1',
+ MinSize: '1',
+ DesiredCapacity: '1',
+ });
+});
+
+test('CloudwatchCpuAlarm', () => {
+ const app = new App({
+ context: {
+ useSsl: 'false', runWithOidc: 'false', serverAccessType: 'ipv4', restrictServerAccessTo: '10.10.10.10/32',
+ },
+ });
+
+ // WHEN
+ const stack = new CIStackBenchmark(app, 'MyTestStack', {});
+
+ // THEN
+ Template.fromStack(stack).hasResourceProperties('AWS::CloudWatch::Alarm', {
+ MetricName: 'CPUUtilization',
+ Statistic: 'Average',
+ });
+});
+
+test('CloudwatchMemoryAlarm', () => {
+ const app = new App({
+ context: {
+ useSsl: 'false', runWithOidc: 'false', serverAccessType: 'ipv4', restrictServerAccessTo: '10.10.10.10/32',
+ },
+ });
+
+ // WHEN
+ const stack = new CIStackBenchmark(app, 'MyTestStack', {});
+
+ // THEN
+ Template.fromStack(stack).hasResourceProperties('AWS::CloudWatch::Alarm', {
+ MetricName: 'CPUUtilization',
+ Statistic: 'Average',
+ });
+});
diff --git a/test/ci-stack.test.ts b/test/ci-stack.test.ts
index eef438c6..6ae09f0a 100644
--- a/test/ci-stack.test.ts
+++ b/test/ci-stack.test.ts
@@ -53,7 +53,7 @@ test('External security group is open', () => {
// THEN
template.hasResourceProperties('AWS::EC2::SecurityGroup', {
- GroupDescription: 'External access to Jenkins',
+ GroupDescription: 'External access to Jenkins mainCI',
SecurityGroupEgress: [
{
CidrIp: '0.0.0.0/0',
@@ -97,7 +97,7 @@ test('External security group is restricted', () => {
// THEN
template.hasResourceProperties('AWS::EC2::SecurityGroup', {
- GroupDescription: 'External access to Jenkins',
+ GroupDescription: 'External access to Jenkins mainCI',
SecurityGroupEgress: [
{
CidrIp: '0.0.0.0/0',
@@ -142,7 +142,7 @@ test('MainNode', () => {
SecurityGroups: [
{
'Fn::GetAtt': [
- 'MainNodeSG5CEF04F0',
+ 'mainCIMainNodeSG9A510FD4',
'GroupId',
],
},
@@ -171,7 +171,7 @@ test('LoadBalancer', () => {
SecurityGroups: [
{
'Fn::GetAtt': [
- 'ExternalAccessSGFD03F4DC',
+ 'mainCIExternalAccessSGF769D576',
'GroupId',
],
},
diff --git a/test/compute/agent-node-config-benchmark.test.ts b/test/compute/agent-node-config-benchmark.test.ts
new file mode 100644
index 00000000..5aed299c
--- /dev/null
+++ b/test/compute/agent-node-config-benchmark.test.ts
@@ -0,0 +1,174 @@
+/**
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * The OpenSearch Contributors require contributions made to
+ * this file be licensed under the Apache-2.0 license or a
+ * compatible open source license.
+ */
+
+import { App } from 'aws-cdk-lib';
+import { Template } from 'aws-cdk-lib/assertions';
+import { readFileSync } from 'fs';
+import { load } from 'js-yaml';
+import { CIStackBenchmark } from '../../lib/ci-stack-benchmark';
+
+test('Agents Resource is present', () => {
+ const app = new App({
+ context: {
+ useSsl: 'true', runWithOidc: 'true', serverAccessType: 'ipv4', restrictServerAccessTo: '10.10.10.10/32',
+ },
+ });
+ const stack = new CIStackBenchmark(app, 'TestStack', {});
+ const template = Template.fromStack(stack);
+
+ template.hasResourceProperties('AWS::IAM::Role', {
+ RoleName: 'OpenSearch-CI-AgentNodeRole-Benchmark',
+ AssumeRolePolicyDocument: {
+ Statement: [
+ {
+ Action: 'sts:AssumeRole',
+ Effect: 'Allow',
+ Principal: {
+ Service: {
+ 'Fn::Join': [
+ '',
+ [
+ 'ec2.',
+ {
+ Ref: 'AWS::URLSuffix',
+ },
+ ],
+ ],
+ },
+ },
+ },
+ ],
+ Version: '2012-10-17',
+ },
+ });
+ template.hasResourceProperties('AWS::IAM::ManagedPolicy', {
+ Description: 'Jenkins agents Node Policy',
+ Path: '/',
+ Roles: [
+ {
+ Ref: 'OpenSearchCIAgentNodeRoleBenchmark15380959',
+ },
+ ],
+ PolicyDocument: {
+ Statement: [
+ {
+ Action: [
+ 'ecr-public:BatchCheckLayerAvailability',
+ 'ecr-public:GetRepositoryPolicy',
+ 'ecr-public:DescribeRepositories',
+ 'ecr-public:DescribeRegistries',
+ 'ecr-public:DescribeImages',
+ 'ecr-public:DescribeImageTags',
+ 'ecr-public:GetRepositoryCatalogData',
+ 'ecr-public:GetRegistryCatalogData',
+ 'ecr-public:InitiateLayerUpload',
+ 'ecr-public:UploadLayerPart',
+ 'ecr-public:CompleteLayerUpload',
+ 'ecr-public:PutImage',
+ ],
+ Condition: {
+ StringEquals: {
+ 'aws:RequestedRegion': {
+ Ref: 'AWS::Region',
+ },
+ 'aws:PrincipalAccount': [
+ {
+ Ref: 'AWS::AccountId',
+ },
+ ],
+ },
+ },
+ Effect: 'Allow',
+ Resource: {
+ 'Fn::Join': [
+ '',
+ [
+ 'arn:aws:ecr-public::',
+ {
+ Ref: 'AWS::AccountId',
+ },
+ ':repository/*',
+ ],
+ ],
+ },
+ },
+ {
+ Action: [
+ 'ecr-public:GetAuthorizationToken',
+ 'sts:GetServiceBearerToken',
+ ],
+ Condition: {
+ StringEquals: {
+ 'aws:RequestedRegion': {
+ Ref: 'AWS::Region',
+ },
+ 'aws:PrincipalAccount': [
+ {
+ Ref: 'AWS::AccountId',
+ },
+ ],
+ },
+ },
+ Effect: 'Allow',
+ Resource: '*',
+ },
+ ],
+ Version: '2012-10-17',
+ },
+ });
+});
+
+test('Agents Node policy with assume role Resource is present', () => {
+ const app = new App({
+ context: {
+ useSsl: 'true', runWithOidc: 'true', serverAccessType: 'ipv4', restrictServerAccessTo: '10.10.10.10/32',
+ },
+ });
+ const stack = new CIStackBenchmark(app, 'TestStack', {
+ agentAssumeRole: ['arn:aws:iam::12345:role/test-role', 'arn:aws:iam::901523:role/test-role2'],
+ });
+ Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', {
+ PolicyDocument: {
+ Statement: [
+ {
+ Action: 'sts:AssumeRole',
+ Effect: 'Allow',
+ Resource: [
+ 'arn:aws:iam::12345:role/test-role',
+ 'arn:aws:iam::901523:role/test-role2',
+ ],
+ },
+ ],
+ Version: '2012-10-17',
+ },
+
+ });
+});
+
+describe('JenkinsMainNode Config with macAgent template', () => {
+ // WHEN
+ const testYaml = 'test/data/jenkins.yaml';
+ const yml: any = load(readFileSync(testYaml, 'utf-8'));
+ // THEN
+ test('Verify Mac template tenancy ', async () => {
+ const macConfig = yml.jenkins.clouds[0].amazonEC2.templates[0].tenancy;
+ expect(macConfig).toEqual('Host');
+ });
+ test('Verify Mac template type', async () => {
+ const macConfig = yml.jenkins.clouds[0].amazonEC2.templates[0].type;
+ expect(macConfig).toEqual('Mac1Metal');
+ });
+ test('Verify Mac template amiType.macData.sshPort', async () => {
+ const macConfig = yml.jenkins.clouds[0].amazonEC2.templates[0].amiType.macData.sshPort;
+ expect(macConfig).toEqual('22');
+ });
+ test('Verify Mac template customDeviceMapping', async () => {
+ const macConfig = yml.jenkins.clouds[0].amazonEC2.templates[0].customDeviceMapping;
+ expect(macConfig).toEqual('/dev/sda1=:300:true:gp3::encrypted');
+ });
+});
diff --git a/test/compute/jenkins-main-node-benchmark.test.ts b/test/compute/jenkins-main-node-benchmark.test.ts
new file mode 100644
index 00000000..99fbcdda
--- /dev/null
+++ b/test/compute/jenkins-main-node-benchmark.test.ts
@@ -0,0 +1,37 @@
+/**
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * The OpenSearch Contributors require contributions made to
+ * this file be licensed under the Apache-2.0 license or a
+ * compatible open source license.
+ */
+
+import { JenkinsMainNodeBenchmark } from '../../lib/compute/jenkins-main-node-benchmark';
+
+describe('JenkinsMainNode Config Elements', () => {
+ // WHEN
+ const configElements = JenkinsMainNodeBenchmark.configElements('MyTestStack', 'us-west-2', {
+ redirectUrlArn: 'ARN:ABC',
+ sslCertContentsArn: 'ARN:BCD',
+ sslCertPrivateKeyContentsArn: 'ARN:CDE',
+ sslCertChainArn: 'ARN:DEF',
+ useSsl: true,
+ }, {
+ oidcCredArn: 'ABC:EFG',
+ runWithOidc: true,
+ }, {
+ dataRetention: true,
+ }, 'test/data/jenkins.yaml',
+ 'ARN:ABC');
+
+ // THEN
+ test('Config elements expected counts', async () => {
+ expect(configElements.filter((e) => e.elementType === 'COMMAND')).toHaveLength(20);
+ expect(configElements.filter((e) => e.elementType === 'PACKAGE')).toHaveLength(9);
+ expect(configElements.filter((e) => e.elementType === 'FILE')).toHaveLength(4);
+ });
+
+ test('Does not use service in config elements', async () => {
+ expect(configElements.filter((e) => e.elementType === 'SERVICE')).toHaveLength(0);
+ });
+});
diff --git a/test/data/test_env.yaml b/test/data/test_env.yaml
index 2dadc1d3..4dd3810e 100644
--- a/test/data/test_env.yaml
+++ b/test/data/test_env.yaml
@@ -311,3 +311,4 @@ support:
- PipelineThreadDump
enabled: true
period: 1
+
\ No newline at end of file