From 82bf9bc4d20d01360aba41cc7da089555c7ea4a0 Mon Sep 17 00:00:00 2001 From: Sayali Gaikawad <61760125+gaiksaya@users.noreply.github.com> Date: Tue, 17 Dec 2024 22:44:46 -0800 Subject: [PATCH] Add support for fine-grained access control (#514) Signed-off-by: Sayali Gaikawad --- README.md | 32 +- lib/ci-stack.ts | 4 + lib/compute/auth-config.ts | 36 +- lib/compute/jenkins-main-node.ts | 6 +- test/ci-stack.test.ts | 24 +- test/compute/auth-config.test.ts | 66 ++-- test/compute/config.test.ts | 12 +- test/data/github_auth.yaml | 572 ------------------------------- test/data/test_env.yaml | 318 ----------------- 9 files changed, 112 insertions(+), 958 deletions(-) delete mode 100644 test/data/github_auth.yaml delete mode 100644 test/data/test_env.yaml diff --git a/README.md b/README.md index 8a4c7492..cf65da26 100644 --- a/README.md +++ b/README.md @@ -81,20 +81,21 @@ $aws secretsmanager put-secret-value \ ### Executing Optional Tasks #### Construct Props -| Name | Type | Description | -|-----------------------------------------------------------|:---------|:-----------------------------------------------------------------------------------------| -| [useSsl](#ssl-configuration) | boolean | Should the Jenkins use https | -| [restrictServerAccessTo](#restricting-server-access) | Ipeer | Restrict jenkins server access | -| [authType](#setup-authentication-using-openid-connect-oidc-or-github-authentication) | string | Authentication type for Jenkins login. Acceptable values: github, oidc, default | -| [ignoreResourcesFailures](#ignore-resources-failure) | boolean | Additional verification during deployment and resource startup | -| [adminUsers](#setup-authentication-using-openid-connect-oidc-or-github-authentication) | string[] | List of users with admin access during initial deployment | -| [additionalCommands](#runnning-additional-commands) | string | Additional logic that needs to be run on Master Node. The value has to be path to a file | -| [dataRetention](#data-retention) | boolean | Do you want to retain jenkins jobs and build history | -| [agentAssumeRole](#assume-role) | string | IAM role ARN to be assumed by jenkins agent nodes | -| [envVarsFilePath](#add-environment-variables) | string | Path to file containing env variables in the form of key value pairs | -| [macAgent](#mac-agents) | boolean | Add mac agents to jenkins | -| [useProdAgents](#use-production-agents) | boolean | Should jenkins server use production agents | -| [enableViews](#enable-views) | boolean | Adds Build, Test, Release and Misc views to Jenkins Dashboard . Defaults to false | +| Name | Type | Description | +|----------------------------------------------------------------------------------------|:---------|:---------------------------------------------------------------------------------------------------------------------------| +| [useSsl](#ssl-configuration) | boolean | Should the Jenkins use https | +| [restrictServerAccessTo](#restricting-server-access) | Ipeer | Restrict jenkins server access | +| [authType](#setup-authentication-using-openid-connect-oidc-or-github-authentication) | string | Authentication type for Jenkins login. Acceptable values: github, oidc, default | +| [ignoreResourcesFailures](#ignore-resources-failure) | boolean | Additional verification during deployment and resource startup | +| [adminUsers](#setup-authentication-using-openid-connect-oidc-or-github-authentication) | string[] | List of users with admin access during initial deployment | +| [additionalCommands](#runnning-additional-commands) | string | Additional logic that needs to be run on Master Node. The value has to be path to a file | +| [dataRetention](#data-retention) | boolean | Do you want to retain jenkins jobs and build history | +| [agentAssumeRole](#assume-role) | string | IAM role ARN to be assumed by jenkins agent nodes | +| [envVarsFilePath](#add-environment-variables) | string | Path to file containing env variables in the form of key value pairs | +| [macAgent](#mac-agents) | boolean | Add mac agents to jenkins | +| [useProdAgents](#use-production-agents) | boolean | Should jenkins server use production agents | +| [enableViews](#enable-views) | boolean | Adds Build, Test, Release and Misc views to Jenkins Dashboard . Defaults to false | +| [FineGrainedAccessSpecs](#fine-grained-access) | FineGrainedAccessSpecs | Add specifications for fine grained access contol. See [FineGrainedAccessSpecs](lib/compute/auth-config.ts) for more details | #### SSL Configuration 1. Locate the secret manager arns in the ci-config-stack outputs 1. Update the secret value ([see docs](https://docs.aws.amazon.com/cli/latest/reference/secretsmanager/put-secret-value.html)) for the `certContentsSecret` with the certificate contents @@ -264,6 +265,9 @@ Views on Jenkins dashboard allows us to classify jobs into different sections. B The `All` is the default view and contains all the jobs. +#### Fine Grained Access +Apart from global admin and read-only access, users can be given fine-grained access for specific workflows/folder. The access is set up using role based strategy plugin. The construct props should be of type [FineGrainedAccessSpecs](./lib/compute/auth-config.ts). Check the details for specifying [patterns](https://plugins.jenkins.io/role-strategy/#plugin-content-configuring-roles). Currently, this code base only adds `builder-template` that allows to build jobs. For adding more templates, please contribute. + ### Troubleshooting #### Main Node Useful links diff --git a/lib/ci-stack.ts b/lib/ci-stack.ts index 17724147..ca3dd4d0 100644 --- a/lib/ci-stack.ts +++ b/lib/ci-stack.ts @@ -26,6 +26,7 @@ import { JenkinsMonitoring } from './monitoring/ci-alarms'; import { JenkinsExternalLoadBalancer } from './network/ci-external-load-balancer'; import { JenkinsSecurityGroups } from './security/ci-security-groups'; import { JenkinsWAF } from './security/waf'; +import { FineGrainedAccessSpecs } from './compute/auth-config'; export interface CIStackProps extends StackProps { /** Should the Jenkins use https */ @@ -52,6 +53,8 @@ export interface CIStackProps extends StackProps { readonly enableViews?: boolean; /** Use Production Agents */ readonly useProdAgents?: boolean; + /** Fine grain access control specifications */ + readonly fineGrainedAccessSpecs?: FineGrainedAccessSpecs[]; } function getServerAccess(serverAccessType: string, restrictServerAccessTo: string): IPeer { @@ -190,6 +193,7 @@ export class CIStack extends Stack { authCredsSecretsArn: importedAuthConfigValuesSecretBucketValue.toString(), useSsl, authType, + fineGrainedAccessSpecs: props?.fineGrainedAccessSpecs, failOnCloudInitError: props?.ignoreResourcesFailures, adminUsers: props?.adminUsers, agentNodeSecurityGroup: this.securityGroups.agentNodeSG.securityGroupId, diff --git a/lib/compute/auth-config.ts b/lib/compute/auth-config.ts index 385cc36b..a46bacd5 100644 --- a/lib/compute/auth-config.ts +++ b/lib/compute/auth-config.ts @@ -6,6 +6,13 @@ * compatible open source license. */ +export interface FineGrainedAccessSpecs { + users: string[], + roleName: string, + pattern: string, + templateName: string +} + export class AuthConfig { private static readonly adminRolePermissions: string[] = [ 'Overall/Administer', @@ -50,10 +57,21 @@ export class AuthConfig { 'View/Read', ]; - public static addOidcConfigToJenkinsYaml(yamlObject: any, authType: string, admins?: string[]): any { + private static readonly builderTemplatePermissions: string[] = [ + 'Job/Build', + 'Job/Cancel', + 'Job/Discover', + 'Job/Read', + 'Lockable Resources/View', + 'Run/Replay', + 'Metrics/View', + 'View/Read', + ]; + + public static addOidcConfigToJenkinsYaml(yamlObject: any, authType: string, admins?: string[], fineGrainedAccessItems?: FineGrainedAccessSpecs[]): any { const jenkinsYaml: any = yamlObject; let adminUsers: string[] = ['admin']; - const readOnlyUsers: string[] = ['anonymous']; + const readOnlyUsers: string[] = ['anonymous', 'authenticated']; if (admins) { adminUsers = adminUsers.concat(admins); @@ -89,6 +107,10 @@ export class AuthConfig { const rolesAndPermissions: { [x: string]: any; } = { roleBased: { + permissionTemplates: [{ + name: 'builder-template', + permissions: AuthConfig.builderTemplatePermissions, + }], roles: { global: [{ entries: adminUsers.map((user) => ({ user })), @@ -103,14 +125,20 @@ export class AuthConfig { pattern: '.*', permissions: AuthConfig.readOnlyRolePermissions, }, - ], }, }, }; jenkinsYaml.jenkins.authorizationStrategy = rolesAndPermissions; - + if (typeof fineGrainedAccessItems !== 'undefined') { + jenkinsYaml.jenkins.authorizationStrategy.roleBased.roles.items = fineGrainedAccessItems.map((item) => ({ + entries: item.users.map((user) => ({ user })), + name: item.roleName, + pattern: item.pattern, + templateName: item.templateName, + })); + } if (authType === 'github') { jenkinsYaml.jenkins.securityRealm = githubAuthConfig; } else { diff --git a/lib/compute/jenkins-main-node.ts b/lib/compute/jenkins-main-node.ts index 505b9672..2cd3874d 100644 --- a/lib/compute/jenkins-main-node.ts +++ b/lib/compute/jenkins-main-node.ts @@ -36,7 +36,7 @@ import { join } from 'path'; import { CloudwatchAgent } from '../constructs/cloudwatch-agent'; import { AgentNodeConfig, AgentNodeNetworkProps, AgentNodeProps } from './agent-node-config'; import { EnvConfig } from './env-config'; -import { AuthConfig } from './auth-config'; +import { AuthConfig, FineGrainedAccessSpecs } from './auth-config'; import { ViewsConfig } from './views'; interface HttpConfigProps { @@ -51,6 +51,7 @@ interface LoginAuthProps { readonly authCredsSecretsArn: string; readonly authType: string; readonly adminUsers?: string[]; + readonly fineGrainedAccessSpecs?: FineGrainedAccessSpecs[]; } interface DataRetentionProps { @@ -452,7 +453,8 @@ export class JenkinsMainNode { agentNodeObject: AgentNodeConfig, props: AgentNodeNetworkProps, agentNode: AgentNodeProps[], macAgent: string): string { let updatedConfig = agentNodeObject.addAgentConfigToJenkinsYaml(stack, agentNode, props, macAgent); if (loginAuthProps.authType !== 'default') { - updatedConfig = AuthConfig.addOidcConfigToJenkinsYaml(updatedConfig, loginAuthProps.authType, loginAuthProps.adminUsers); + updatedConfig = AuthConfig.addOidcConfigToJenkinsYaml(updatedConfig, loginAuthProps.authType, + loginAuthProps.adminUsers, loginAuthProps.fineGrainedAccessSpecs); } if (jenkinsMainNodeProps.envVarsFilePath !== '' && jenkinsMainNodeProps.envVarsFilePath != null) { updatedConfig = EnvConfig.addEnvConfigToJenkinsYaml(updatedConfig, jenkinsMainNodeProps.envVarsFilePath); diff --git a/test/ci-stack.test.ts b/test/ci-stack.test.ts index 171f81a6..9d268dc3 100644 --- a/test/ci-stack.test.ts +++ b/test/ci-stack.test.ts @@ -14,7 +14,7 @@ import { CIStack } from '../lib/ci-stack'; 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', + useSsl: 'true', serverAccessType: 'ipv4', restrictServerAccessTo: '10.10.10.10/32', additionalCommands: './test/data/hello-world.py', }, }); @@ -45,7 +45,7 @@ test('CI Stack Basic Resources', () => { test('External security group is open', () => { const app = new App({ context: { - useSsl: 'true', runWithOidc: 'true', serverAccessType: 'ipv4', restrictServerAccessTo: 'all', + useSsl: 'true', serverAccessType: 'ipv4', restrictServerAccessTo: 'all', }, }); @@ -89,7 +89,7 @@ test('External security group is open', () => { test('External security group is restricted', () => { const app = new App({ context: { - useSsl: 'true', runWithOidc: 'true', serverAccessType: 'ipv4', restrictServerAccessTo: '10.0.0.0/24', + useSsl: 'true', serverAccessType: 'ipv4', restrictServerAccessTo: '10.0.0.0/24', }, }); @@ -135,7 +135,7 @@ test('External security group is restricted', () => { test('MainNode', () => { const app = new App({ context: { - useSsl: 'true', runWithOidc: 'true', serverAccessType: 'ipv4', restrictServerAccessTo: '10.10.10.10/32', + useSsl: 'true', authType: 'oidc', serverAccessType: 'ipv4', restrictServerAccessTo: '10.10.10.10/32', }, }); @@ -167,7 +167,7 @@ test('MainNode', () => { test('LoadBalancer', () => { const app = new App({ context: { - useSsl: 'true', runWithOidc: 'true', serverAccessType: 'ipv4', restrictServerAccessTo: 'all', + useSsl: 'true', authType: 'oidc', serverAccessType: 'ipv4', restrictServerAccessTo: 'all', }, }); @@ -192,7 +192,7 @@ test('LoadBalancer', () => { test('CloudwatchCpuAlarm', () => { const app = new App({ context: { - useSsl: 'false', runWithOidc: 'false', serverAccessType: 'ipv4', restrictServerAccessTo: '10.10.10.10/32', + useSsl: 'false', serverAccessType: 'ipv4', restrictServerAccessTo: '10.10.10.10/32', }, }); @@ -211,7 +211,7 @@ test('CloudwatchCpuAlarm', () => { test('CloudwatchMemoryAlarm', () => { const app = new App({ context: { - useSsl: 'false', runWithOidc: 'false', serverAccessType: 'ipv4', restrictServerAccessTo: '10.10.10.10/32', + useSsl: 'false', serverAccessType: 'ipv4', restrictServerAccessTo: '10.10.10.10/32', }, }); @@ -230,7 +230,7 @@ test('CloudwatchMemoryAlarm', () => { test('LoadBalancer Access Logging', () => { const app = new App({ context: { - useSsl: 'false', runWithOidc: 'false', serverAccessType: 'ipv4', restrictServerAccessTo: '10.10.10.10/32', + useSsl: 'false', serverAccessType: 'ipv4', restrictServerAccessTo: '10.10.10.10/32', }, }); @@ -367,7 +367,7 @@ test('LoadBalancer Access Logging', () => { test('WAF rules', () => { const app = new App({ context: { - useSsl: 'false', runWithOidc: 'false', serverAccessType: 'ipv4', restrictServerAccessTo: '0.0.0.0/0', + useSsl: 'false', serverAccessType: 'ipv4', restrictServerAccessTo: '0.0.0.0/0', }, }); @@ -452,7 +452,7 @@ test('WAF rules', () => { test('Test WAF association with ALB', () => { const app = new App({ context: { - useSsl: 'false', runWithOidc: 'false', serverAccessType: 'ipv4', restrictServerAccessTo: '0.0.0.0/0', + useSsl: 'false', serverAccessType: 'ipv4', restrictServerAccessTo: '0.0.0.0/0', }, }); @@ -478,7 +478,7 @@ test('Test WAF association with ALB', () => { test('Test configElement jenkins content to use X-Forwarded-For header on port 443', () => { const app = new App({ context: { - useSsl: 'true', runWithOidc: 'false', serverAccessType: 'ipv4', restrictServerAccessTo: '0.0.0.0/0', + useSsl: 'true', serverAccessType: 'ipv4', restrictServerAccessTo: '0.0.0.0/0', }, }); @@ -512,7 +512,7 @@ test('Test configElement jenkins content to use X-Forwarded-For header on port 4 test('Test configElement jenkins content to use X-Forwarded-For header on port 80', () => { const app = new App({ context: { - useSsl: 'false', runWithOidc: 'false', serverAccessType: 'ipv4', restrictServerAccessTo: '0.0.0.0/0', + useSsl: 'false', serverAccessType: 'ipv4', restrictServerAccessTo: '0.0.0.0/0', }, }); diff --git a/test/compute/auth-config.test.ts b/test/compute/auth-config.test.ts index 754df0ed..5f4c4cc5 100644 --- a/test/compute/auth-config.test.ts +++ b/test/compute/auth-config.test.ts @@ -9,31 +9,28 @@ import { readFileSync } from 'fs'; import { load } from 'js-yaml'; import { JenkinsMainNode } from '../../lib/compute/jenkins-main-node'; -import { AuthConfig } from '../../lib/compute/auth-config'; +import { AuthConfig, FineGrainedAccessSpecs } from '../../lib/compute/auth-config'; describe('Test authType OIDC', () => { // WHEN - const testYaml = 'test/data/jenkins.yaml'; - const oidcConfig = { - authorizationServerUrl: 'http://localhost', clientId: 'clientId', - disableSslVerification: true, - emailFieldName: 'emailFieldName', - escapeHatchEnabled: true, - escapeHatchGroup: 'escapeHatchGroup', - escapeHatchUsername: 'escapeHatchUsername', - fullNameFieldName: 'fullNameFieldName', - groupsFieldName: 'groupsFieldName', + clientSecret: 'clientSecret', + authorizationServerUrl: 'http://localhost', + wellKnownOpenIDConfigurationUrl: 'wellKnownOpenIDConfigurationUrl', + tokenServerUrl: 'tokenServerUrl', + userInfoServerUrl: 'userInfoServerUrl', + disableSslVerification: false, + userNameField: 'sub', + escapeHatchEnabled: false, logoutFromOpenidProvider: true, - scopes: 'scopes', - tokenServerUrl: 'http://localhost', - userNameField: 'userNameField', + postLogoutRedirectUrl: '', + scopes: 'openid', + escapeHatchSecret: 'random', }; const admins = ['admin1', 'admin2']; - const jenkinsYaml = load(readFileSync(JenkinsMainNode.BASE_JENKINS_YAML_PATH, 'utf-8')); - AuthConfig.addOidcConfigToJenkinsYaml(jenkinsYaml, 'oidc', admins); - const yml: any = load(readFileSync(testYaml, 'utf-8')); + const yml : any = load(readFileSync(JenkinsMainNode.BASE_JENKINS_YAML_PATH, 'utf-8')); + AuthConfig.addOidcConfigToJenkinsYaml(yml, 'oidc', admins); // THEN test('Verify oidcConfig', async () => { @@ -41,21 +38,25 @@ describe('Test authType OIDC', () => { expect(addedOidcConfig).toEqual(oidcConfig); }); - test('Verify oidcConfig', async () => { - const adminRole = yml.jenkins.authorizationStrategy.roleBased.roles.global[0].assignments; - expect(adminRole).toEqual(['admin', 'admin1', 'admin2']); + test('Verify admins', async () => { + const adminRole = yml.jenkins.authorizationStrategy.roleBased.roles.global[0].entries; + const adminUsernames = adminRole.map((entry: { user: any; }) => entry.user); + expect(adminUsernames).toEqual(['admin', 'admin1', 'admin2']); }); }); describe('Test authType github', () => { // WHEN - const testYaml = 'test/data/github_auth.yaml'; const admins = ['foo', 'bar']; - const jenkinsYaml = load(readFileSync(JenkinsMainNode.BASE_JENKINS_YAML_PATH, 'utf-8')); - AuthConfig.addOidcConfigToJenkinsYaml(jenkinsYaml, 'github', admins); - const yml: any = load(readFileSync(testYaml, 'utf-8')); + const yml: any = load(readFileSync(JenkinsMainNode.BASE_JENKINS_YAML_PATH, 'utf-8')); + const fineGrainedAccess: FineGrainedAccessSpecs = { + users: ['user1', 'user2', 'user3'], + roleName: 'builder-job-role', + pattern: '((distribution|integ).*)', + templateName: 'builder-template', + }; + AuthConfig.addOidcConfigToJenkinsYaml(yml, 'github', admins, [fineGrainedAccess]); const globalRoles = yml.jenkins.authorizationStrategy.roleBased.roles.global; - const githubAuthConfig: { [x: string]: any; } = { githubWebUri: 'https://github.com', githubApiUri: 'https://api.github.com', @@ -81,16 +82,25 @@ describe('Test authType github', () => { // Check admin users const adminUsers = adminRole.entries.map((entry: any) => entry.user); - expect(adminUsers).toEqual(['bar', 'foo']); + expect(adminUsers).toEqual(['admin', 'foo', 'bar']); }); - test('Verify read only', async () => { + test('Verify read only access', async () => { // Find the read role const readRole = globalRoles.find((role: any) => role.name === 'read'); expect(readRole).toBeTruthy(); // Check read users const readUsers = readRole.entries.map((entry: any) => entry.user); - expect(readUsers).toEqual(['anonymous']); + expect(readUsers).toEqual(['anonymous', 'authenticated']); + }); + + test('Verify fine grained access', async () => { + // Find the builder role + const builderRole = yml.jenkins.authorizationStrategy.roleBased.roles.items.find((role: any) => role.name === 'builder-job-role'); + + // Check users + const buildUsers = builderRole.entries.map((entry: any) => entry.user); + expect(buildUsers).toEqual(['user1', 'user2', 'user3']); }); }); diff --git a/test/compute/config.test.ts b/test/compute/config.test.ts index 0c6e09c3..b2765090 100644 --- a/test/compute/config.test.ts +++ b/test/compute/config.test.ts @@ -14,15 +14,11 @@ import { ViewsConfig } from '../../lib/compute/views'; describe('Config', () => { // WHEN - const testYaml = 'test/data/test_env.yaml'; - const jenkinsYaml = load(readFileSync(JenkinsMainNode.BASE_JENKINS_YAML_PATH, 'utf-8')); + const jenkinsYaml: any = load(readFileSync(JenkinsMainNode.BASE_JENKINS_YAML_PATH, 'utf-8')); const envVarConfig = EnvConfig.addEnvConfigToJenkinsYaml(jenkinsYaml, 'test/data/env.yaml'); const viewsConfig = ViewsConfig.addViewsConfigToJenkinsYaml(jenkinsYaml); - const newConfig = dump(envVarConfig); - writeFileSync(testYaml, newConfig, 'utf-8'); - const yml: any = load(readFileSync(testYaml, 'utf-8')); // THEN test('Verify env variables', async () => { const envConfig = { @@ -35,7 +31,7 @@ describe('Config', () => { ], }, }; - const addedEnvConfig = yml.jenkins.globalNodeProperties; + const addedEnvConfig = jenkinsYaml.jenkins.globalNodeProperties; expect(addedEnvConfig).toEqual([envConfig]); }); @@ -52,7 +48,7 @@ describe('Config', () => { }, ], }; - const getlog = yml.jenkins.log; + const getlog = jenkinsYaml.jenkins.log; expect(getlog).toEqual(workflowLogger); }); @@ -121,7 +117,7 @@ describe('Config', () => { name: 'Misc', }, }; - const getViews = yml.jenkins.views; + const getViews = jenkinsYaml.jenkins.views; expect(getViews).toContainEqual(buildView); expect(getViews).toContainEqual(testView); expect(getViews).toContainEqual(releaseView); diff --git a/test/data/github_auth.yaml b/test/data/github_auth.yaml deleted file mode 100644 index 4682adb1..00000000 --- a/test/data/github_auth.yaml +++ /dev/null @@ -1,572 +0,0 @@ -jenkins: - agentProtocols: - - "JNLP4-connect" - - "Ping" - authorizationStrategy: - roleBased: - roles: - global: - - entries: - - user: "bar" - - user: "foo" - name: "admin" - pattern: ".*" - permissions: - - "Job/Move" - - "Job/Build" - - "Credentials/Delete" - - "Credentials/ManageDomains" - - "View/Create" - - "Agent/Configure" - - "Job/Read" - - "Credentials/Update" - - "Agent/Create" - - "Manage ownership/Nodes" - - "Job/Delete" - - "Agent/Build" - - "View/Configure" - - "Agent/Provision" - - "SCM/Tag" - - "Job/Create" - - "Job/Discover" - - "Manage ownership/Jobs" - - "Credentials/View" - - "Agent/Connect" - - "Agent/Delete" - - "Run/Replay" - - "Agent/Disconnect" - - "Run/Delete" - - "Job/Cancel" - - "Overall/Read" - - "Run/Update" - - "Credentials/Create" - - "Overall/Administer" - - "View/Delete" - - "Job/Configure" - - "Job Config History/DeleteEntry" - - "Job/Workspace" - - "View/Read" - - entries: - - user: "anonymous" - name: "read" - pattern: ".*" - permissions: - - "Overall/Read" - - "Job/Read" - clouds: - - amazonEC2: - name: "Amazon_ec2_cloud" - region: "us-west-2" - sshKeysCredentialsId: "jenkins-agent-node-key-pair" - templates: - - ami: "ami-046b5b8111c19b3ac" - 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 - hostKeyVerificationStrategy: 'OFF' - iamInstanceProfile: "arn:aws:iam::529213360297:instance-profile/OpenSearch-CI-lol-JenkinsAgentNodeInstanceProfile-ntMjuYvZaWaR" - 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" - javaPath: "java" - labelString: "Jenkins-Default-Agent-X64-C5xlarge-Single-Host" - launchTimeoutStr: "300" - maxTotalUses: -1 - metadataEndpointEnabled: true - metadataHopsLimit: 2 - metadataSupported: true - metadataTokensRequired: true - minimumNumberOfInstances: 0 - minimumNumberOfSpareInstances: 1 - mode: EXCLUSIVE - monitoring: true - nodeProperties: - - envVars: - env: - - 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" - - key: "JAVA23_HOME" - value: "/usr/lib/jvm/temurin-23-jdk-amd64" - - key: "JAVA8_HOME" - value: "/usr/lib/jvm/temurin-8-jdk-amd64" - - key: "JENKINS_HOME_PATH" - value: "/home/ec2-user" - numExecutors: 1 - remoteAdmin: "ec2-user" - remoteFS: "/home/ec2-user" - securityGroups: "sg-0a7de99890e29657e" - stopOnTerminate: false - subnetId: "subnet-0fd55c2bd295b5216" - t2Unlimited: false - tags: - - name: "Name" - value: "OpenSearch-CI-lol/AgentNode/Jenkins-Default-Agent-X64-C5xlarge-Single-Host" - - name: "type" - value: "jenkinsAgentNode-Jenkins-Default-Agent-X64-C5xlarge-Single-Host" - tenancy: Default - type: C54xlarge - useEphemeralDevices: false - - ami: "ami-0334ab986e1816201" - 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 - hostKeyVerificationStrategy: 'OFF' - iamInstanceProfile: "arn:aws:iam::529213360297:instance-profile/OpenSearch-CI-lol-JenkinsAgentNodeInstanceProfile-ntMjuYvZaWaR" - 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" - javaPath: "java" - labelString: "Jenkins-Default-Agent-ARM64-C5xlarge-Single-Host" - launchTimeoutStr: "300" - maxTotalUses: -1 - metadataEndpointEnabled: true - metadataHopsLimit: 2 - metadataSupported: true - metadataTokensRequired: true - minimumNumberOfInstances: 0 - minimumNumberOfSpareInstances: 1 - mode: EXCLUSIVE - monitoring: true - nodeProperties: - - envVars: - env: - - 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" - - key: "JAVA23_HOME" - value: "/usr/lib/jvm/temurin-23-jdk-amd64" - - key: "JAVA8_HOME" - value: "/usr/lib/jvm/temurin-8-jdk-amd64" - - key: "JENKINS_HOME_PATH" - value: "/home/ec2-user" - numExecutors: 1 - remoteAdmin: "ec2-user" - remoteFS: "/home/ec2-user" - securityGroups: "sg-0a7de99890e29657e" - stopOnTerminate: false - subnetId: "subnet-0fd55c2bd295b5216" - t2Unlimited: false - tags: - - name: "Name" - value: "OpenSearch-CI-lol/AgentNode/Jenkins-Default-Agent-ARM64-C5xlarge-Single-Host" - - name: "type" - value: "jenkinsAgentNode-Jenkins-Default-Agent-ARM64-C5xlarge-Single-Host" - tenancy: Default - type: C6g4xlarge - useEphemeralDevices: false - useInstanceProfileForCredentials: true - crumbIssuer: - standard: - excludeClientIPFromCrumb: false - disableRememberMe: false - labelAtoms: - - name: "built-in" - - name: "main-node" - labelString: "main-node" - log: - recorders: - - loggers: - - level: "FINE" - name: "org.jenkinsci.plugins.workflow.job.WorkflowRun" - name: "workflowRun" - markupFormatter: - rawHtml: - disableSyntaxHighlighting: true - mode: EXCLUSIVE - myViewsTabBar: "standard" - nodeMonitors: - - diskSpaceMonitor: - freeSpaceThreshold: "1GB" - - tmpSpace: - freeSpaceThreshold: "1GB" - numExecutors: 4 - primaryView: - all: - name: "all" - projectNamingStrategy: "standard" - quietPeriod: 5 - remotingSecurity: - enabled: true - scmCheckoutRetryCount: 0 - securityRealm: - github: - clientID: "clientID" - clientSecret: "clientSecret" - githubApiUri: "https://api.github.com" - githubWebUri: "https://github.com" - oauthScopes: "read:org,user:email" - slaveAgentPort: 50000 - updateCenter: - sites: - - id: "default" - url: "https://updates.jenkins.io/update-center.json" - views: - - all: - name: "all" - viewsTabBar: "standard" -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 -globalCredentialsConfiguration: - configuration: - providerFilter: "none" - typeFilter: "none" -appearance: - loginTheme: - useDefaultTheme: true - prism: - theme: PRISM -security: - anonymizeSupportBundle: - enabled: false - apiToken: - creationOfLegacyTokenEnabled: false - tokenGenerationOnCreationEnabled: false - usageStatisticsEnabled: true - copyartifact: - mode: PRODUCTION - cps: - hideSandbox: false - gitHooks: - allowedOnAgents: false - allowedOnController: false - gitHostKeyVerificationConfiguration: - sshHostKeyVerificationStrategy: "knownHostsFileVerificationStrategy" - globalJobDslSecurityConfiguration: - useScriptSecurity: true - sSHD: - port: -1 - scriptApproval: - forceSandbox: false -unclassified: - ansiColorBuildWrapper: - colorMaps: - - black: "#000000" - blackB: "#4C4C4C" - blue: "#1E90FF" - blueB: "#4682B4" - cyan: "#00CDCD" - cyanB: "#00FFFF" - green: "#00CD00" - greenB: "#00FF00" - magenta: "#CD00CD" - magentaB: "#FF00FF" - name: "xterm" - red: "#CD0000" - redB: "#FF0000" - white: "#E5E5E5" - whiteB: "#FFFFFF" - yellow: "#CDCD00" - yellowB: "#FFFF00" - - black: "#000000" - blackB: "#555555" - blue: "#0000AA" - blueB: "#5555FF" - cyan: "#00AAAA" - cyanB: "#55FFFF" - defaultBackground: 0 - defaultForeground: 7 - green: "#00AA00" - greenB: "#55FF55" - magenta: "#AA00AA" - magentaB: "#FF55FF" - name: "vga" - red: "#AA0000" - redB: "#FF5555" - white: "#AAAAAA" - whiteB: "#FFFFFF" - yellow: "#AA5500" - yellowB: "#FFFF55" - - black: "black" - blackB: "black" - blue: "blue" - blueB: "blue" - cyan: "cyan" - cyanB: "cyan" - green: "green" - greenB: "green" - magenta: "magenta" - magentaB: "magenta" - name: "css" - red: "red" - redB: "red" - white: "white" - whiteB: "white" - yellow: "yellow" - yellowB: "yellow" - - black: "#2E3436" - blackB: "#2E3436" - blue: "#3465A4" - blueB: "#3465A4" - cyan: "#06989A" - cyanB: "#06989A" - defaultBackground: 0 - defaultForeground: 7 - green: "#4E9A06" - greenB: "#4E9A06" - magenta: "#75507B" - magentaB: "#75507B" - name: "gnome-terminal" - red: "#CC0000" - redB: "#CC0000" - white: "#D3D7CF" - whiteB: "#D3D7CF" - yellow: "#C4A000" - yellowB: "#C4A000" - audit-trail: - displayUserName: false - logBuildCause: true - logCredentialsUsage: 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" - bitbucketEndpointConfiguration: - endpoints: - - bitbucketCloudEndpoint: - enableCache: false - manageHooks: false - repositoriesCacheDuration: 0 - teamCacheDuration: 0 - 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 - enrichedSummaryConfig: - enrichedSummaryEnabled: false - httpClientDelayBetweenRetriesInSeconds: 1 - httpClientMaxRetries: 3 - httpClientTimeoutInSeconds: 1 - 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/" - globalTimeOutConfiguration: - operations: - - "abortOperation" - overwriteable: false - injectionConfig: - allowUntrusted: false - checkForBuildAgentErrors: false - enabled: false - enforceUrl: false - injectCcudExtension: false - injectMavenExtension: false - ivyBuildTrigger: - extendedVersionMatching: false - jobConfigHistory: - excludePattern: "queue\\.xml|nodeMonitors\\.xml|UpdateCenter\\.xml|global-build-stats|LockableResourcesManager\\\ - .xml|MilestoneStep\\.xml|cloudbees-disk-usage-simple\\.xml" - saveModuleConfiguration: false - showBuildBadges: "always" - showChangeReasonCommentWindow: true - skipDuplicateHistory: true - junitTestResultStorage: - storage: "file" - location: - adminAddress: "address not configured yet " - mailer: - charset: "UTF-8" - useSsl: false - useTls: false - pipelineStepsAWS: - enableCredentialsFromNode: false - pollSCM: - pollingThreadCount: 10 - scmGit: - addGitTagAction: false - allowSecondFetch: false - createAccountBasedOnEmail: false - disableGitToolChooser: false - hideCredentials: false - showEntireCommitSummaryInChanges: false - useExistingAccountWithSameEmail: false - 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" diff --git a/test/data/test_env.yaml b/test/data/test_env.yaml deleted file mode 100644 index b5ecbcab..00000000 --- a/test/data/test_env.yaml +++ /dev/null @@ -1,318 +0,0 @@ -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 - - list: - columns: - - status - - weather - - jobName - - lastSuccess - - lastFailure - - lastDuration - - buildButton - - favoriteColumn - includeRegex: .*build.* - name: Build - - list: - columns: - - status - - weather - - jobName - - lastSuccess - - lastFailure - - lastDuration - - buildButton - - favoriteColumn - includeRegex: .*test.* - name: Test - - list: - columns: - - status - - weather - - jobName - - lastSuccess - - lastFailure - - lastDuration - - buildButton - - favoriteColumn - includeRegex: .*release.* - name: Release - - list: - columns: - - status - - weather - - jobName - - lastSuccess - - lastFailure - - lastDuration - - buildButton - - favoriteColumn - includeRegex: (?!.*(test|build|release).*).* - name: Misc - viewsTabBar: standard - globalNodeProperties: - - envVars: - env: - - key: s3Bucket - value: artifactBucket - - key: account - value: 1234 - - key: isStaging - value: true - - key: url - value: https://url.com -globalCredentialsConfiguration: - configuration: - providerFilter: none - typeFilter: none -appearance: - loginTheme: - useDefaultTheme: true -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/ - scmGit: - 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 - mailer: - charset: UTF-8 - useSsl: false - useTls: 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