├── Jenkinsfile
├── src
├── main
│ ├── resources
│ │ ├── lib
│ │ │ └── ecs
│ │ │ │ ├── taglib
│ │ │ │ └── blockWrapper.jelly
│ │ ├── com
│ │ │ └── alibabacloud
│ │ │ │ └── jenkins
│ │ │ │ └── ecs
│ │ │ │ ├── AlibabaEcsFollowerTemplate
│ │ │ │ ├── help-systemDiskSize_zh_CN.html
│ │ │ │ ├── help-systemDiskSize.html
│ │ │ │ ├── help-minimumNumberOfInstances_zh_CN.html
│ │ │ │ ├── help-snapshotId_zh_CN.html
│ │ │ │ ├── help-instanceCapStr_zh_CN.html
│ │ │ │ ├── help-launchTimeoutStr_zh_CN.html
│ │ │ │ ├── help-instanceType_zh_CN.html
│ │ │ │ ├── help-remoteAdmin_zh_CN.html
│ │ │ │ ├── help-launchTimeoutStr.html
│ │ │ │ ├── help-userData_zh_CN.html
│ │ │ │ ├── help-image_zh_CN.html
│ │ │ │ ├── help-instanceNamePrefix_zh_CN.html
│ │ │ │ ├── help-minimumNumberOfInstances.html
│ │ │ │ ├── help-zone_zh_CN.html
│ │ │ │ ├── help-vsw_zh_CN.html
│ │ │ │ ├── help-instanceType.html
│ │ │ │ ├── help-maxTotalUses_zh_CN.html
│ │ │ │ ├── help-initScript_zh_CN.html
│ │ │ │ ├── help-image.html
│ │ │ │ ├── help-userData.html
│ │ │ │ ├── help-systemDiskCategory_zh_CN.html
│ │ │ │ ├── help-zone.html
│ │ │ │ ├── help-instanceCapStr.html
│ │ │ │ ├── help-initScript.html
│ │ │ │ ├── help-numExecutors_zh_CN.html
│ │ │ │ ├── help-systemDiskCategory.html
│ │ │ │ ├── help-vsw.html
│ │ │ │ ├── help-dataDiskId_zh_CN.html
│ │ │ │ ├── help-remoteFs_zh_CN.html
│ │ │ │ ├── help-numExecutors.html
│ │ │ │ ├── config.properties
│ │ │ │ ├── help-ecsType_zh_CN.html
│ │ │ │ ├── config_zh_CN.properties
│ │ │ │ ├── help-ecsType.html
│ │ │ │ ├── help-remoteFs.html
│ │ │ │ └── config.jelly
│ │ │ │ ├── AlibabaCloud
│ │ │ │ ├── help-vpc_zh_CN.html
│ │ │ │ ├── help-securityGroup_zh_CN.html
│ │ │ │ ├── help-region_zh_CN.html
│ │ │ │ ├── help-attachPublicIp_zh_CN.html
│ │ │ │ ├── help-sshKey_zh_CN.html
│ │ │ │ ├── help-vpc.html
│ │ │ │ ├── help-noDelayProvisioning_zh_CN.html
│ │ │ │ ├── help-securityGroup.html
│ │ │ │ ├── help-noDelayProvisioning.html
│ │ │ │ ├── help-intranetMaster_zh_CN.html
│ │ │ │ ├── help-region.html
│ │ │ │ ├── help-sshKey.html
│ │ │ │ ├── help-attachPublicIp.html
│ │ │ │ ├── config.properties
│ │ │ │ ├── config_zh_CN.properties
│ │ │ │ ├── help-intranetMaster.html
│ │ │ │ ├── computerSet.jelly
│ │ │ │ └── config.jelly
│ │ │ │ ├── WindowsData
│ │ │ │ ├── help-password_zh_CN.html
│ │ │ │ ├── config.properties
│ │ │ │ ├── config_zh_CN.properties
│ │ │ │ ├── help-password.html
│ │ │ │ ├── help-bootDelay_zh_CN.html
│ │ │ │ ├── help-bootDelay.html
│ │ │ │ └── config.jelly
│ │ │ │ ├── AlibabaEcsStep
│ │ │ │ ├── help.html
│ │ │ │ └── config.jelly
│ │ │ │ ├── Messages_en.properties
│ │ │ │ ├── Messages.properties
│ │ │ │ ├── AlibabaEcsTag
│ │ │ │ └── config.jelly
│ │ │ │ ├── Messages_zh_CN.properties
│ │ │ │ ├── AlibabaEcsComputer
│ │ │ │ └── configure.jelly
│ │ │ │ └── EcsTemplateStep
│ │ │ │ └── config.jelly
│ │ └── index.jelly
│ └── java
│ │ └── com
│ │ └── alibabacloud
│ │ └── jenkins
│ │ └── ecs
│ │ ├── win
│ │ ├── winrm
│ │ │ ├── request
│ │ │ │ ├── WinRMRequest.java
│ │ │ │ ├── DeleteShellRequest.java
│ │ │ │ ├── GetOutputRequest.java
│ │ │ │ ├── SignalRequest.java
│ │ │ │ ├── OpenShellRequest.java
│ │ │ │ ├── ExecuteCommandRequest.java
│ │ │ │ ├── SendInputRequest.java
│ │ │ │ ├── AbstractWinRMRequest.java
│ │ │ │ └── RequestFactory.java
│ │ │ ├── WinRMConnectException.java
│ │ │ ├── soap
│ │ │ │ ├── Option.java
│ │ │ │ ├── MessageBuilder.java
│ │ │ │ ├── Namespaces.java
│ │ │ │ ├── HeaderBuilder.java
│ │ │ │ └── Header.java
│ │ │ ├── RuntimeIOException.java
│ │ │ ├── NegotiateNTLMSchemaFactory.java
│ │ │ ├── WinRMConnectionManagerFactory.java
│ │ │ ├── WinRM.java
│ │ │ └── WindowsProcess.java
│ │ └── WinConnection.java
│ │ ├── AlibabaEcsComputerListener.java
│ │ ├── util
│ │ ├── AlibabaEcsFactoryImpl.java
│ │ ├── EcsInstanceHelper.java
│ │ ├── Closeables.java
│ │ ├── AlibabaEcsFactory.java
│ │ ├── DateUtils.java
│ │ ├── LogHelper.java
│ │ ├── MinimumInstanceChecker.java
│ │ ├── NetworkUtils.java
│ │ └── CloudHelper.java
│ │ ├── exception
│ │ └── AlibabaEcsException.java
│ │ ├── EcsTypeData.java
│ │ ├── enums
│ │ ├── DataDiskCategory.java
│ │ └── SystemDiskCategory.java
│ │ ├── ConnectionStrategy.java
│ │ ├── UnixData.java
│ │ ├── EcsHostAddressProvider.java
│ │ ├── AlibabaEcsComputerLauncher.java
│ │ ├── AlibabaEcsTag.java
│ │ ├── monitor
│ │ ├── AlibabaEcsFollowerMonitor.java
│ │ └── FreeMemMonitor.java
│ │ ├── AlibabaEcsComputer.java
│ │ ├── NoDelayProvisionerStrategy.java
│ │ ├── WindowsData.java
│ │ └── AlibabaEcsStep.java
└── test
│ └── java
│ └── com
│ └── alibabacloud
│ └── jenkins
│ └── ecs
│ ├── util
│ ├── DateUtilsTest.java
│ ├── AlibabaEcsFactoryTest.java
│ └── NetworkUtilsTest.java
│ ├── EcsTemplateStepExecutionTest.java
│ ├── AlibabaCloudTest.java
│ ├── AlibabaEcsUnixComputerLauncherTest.java
│ ├── AlibabaEcsComputerTest.java
│ ├── AlibabaEcsStepTest.java
│ └── AlibabaEcsFollowerTemplateTest.java
├── docs
└── images
│ ├── nas.png
│ ├── nas_1.png
│ ├── nas_2.png
│ ├── nas_3.png
│ ├── nas_4.png
│ ├── nas_5.png
│ ├── nas_6.png
│ ├── nas_7.png
│ ├── qrcode.png
│ ├── jenkins.SSH.png
│ ├── jenkins.avail.png
│ ├── jenkins.conn.png
│ ├── jenkins.error.png
│ ├── jenkins.item.png
│ ├── jenkins.nodes.png
│ ├── jenkins.right.png
│ ├── jenkins.spot.png
│ ├── jenkins.script.png
│ ├── jenkins.testCre.png
│ ├── alibabacloud_ak_sk.png
│ ├── jenkins.cloudName.png
│ ├── jenkins.configSpot.png
│ ├── jenkins.provision.png
│ ├── jenkins.rightspot.png
│ ├── jenkins.sampleStep.png
│ ├── jenkins_config_vpc.png
│ ├── nail_group_qr_code.png
│ ├── alibabacloud.keypair.png
│ ├── jenkins.Credentials.png
│ ├── jenkins.cloudDetail.png
│ ├── jenkins.groovySandbox.png
│ ├── jenkins.templateName.png
│ ├── jenkins_configure_az.png
│ ├── jenkins_configure_sg.png
│ ├── configure_credentials_1.png
│ ├── jenkins.cloudsConfigure.png
│ ├── jenkins.detailFollower.png
│ ├── jenkins.pipelineSyntax.png
│ ├── jenkins_cloud_mechanism.png
│ ├── jenkins_configure_image.png
│ ├── jenkins_configure_name.png
│ ├── alibabacloud.keypairgene.png
│ ├── alibabacloud_plugin_market.png
│ ├── jenkins.Credentials.check.png
│ ├── jenkins.cloud.primaryKey.png
│ ├── jenkins_configure_clouds.png
│ ├── jenkins_configure_flavor.png
│ ├── jenkins_configure_region.png
│ ├── jenkins_configure_ssh_key.png
│ ├── jenkins_credentials_kind.png
│ ├── jenkins.generatePipelineScript.png
│ └── jenkins_configure_instance_count.png
├── .gitignore
├── .github
└── workflows
│ └── jenkins-security-scan.yml
├── PIPELINESTEP.md
├── CHANGELOG.md
├── nasData.md
└── LICENSE
/Jenkinsfile:
--------------------------------------------------------------------------------
1 | buildPlugin()
--------------------------------------------------------------------------------
/src/main/resources/lib/ecs/taglib:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/images/nas.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/nas.png
--------------------------------------------------------------------------------
/docs/images/nas_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/nas_1.png
--------------------------------------------------------------------------------
/docs/images/nas_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/nas_2.png
--------------------------------------------------------------------------------
/docs/images/nas_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/nas_3.png
--------------------------------------------------------------------------------
/docs/images/nas_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/nas_4.png
--------------------------------------------------------------------------------
/docs/images/nas_5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/nas_5.png
--------------------------------------------------------------------------------
/docs/images/nas_6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/nas_6.png
--------------------------------------------------------------------------------
/docs/images/nas_7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/nas_7.png
--------------------------------------------------------------------------------
/docs/images/qrcode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/qrcode.png
--------------------------------------------------------------------------------
/docs/images/jenkins.SSH.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/jenkins.SSH.png
--------------------------------------------------------------------------------
/docs/images/jenkins.avail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/jenkins.avail.png
--------------------------------------------------------------------------------
/docs/images/jenkins.conn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/jenkins.conn.png
--------------------------------------------------------------------------------
/docs/images/jenkins.error.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/jenkins.error.png
--------------------------------------------------------------------------------
/docs/images/jenkins.item.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/jenkins.item.png
--------------------------------------------------------------------------------
/docs/images/jenkins.nodes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/jenkins.nodes.png
--------------------------------------------------------------------------------
/docs/images/jenkins.right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/jenkins.right.png
--------------------------------------------------------------------------------
/docs/images/jenkins.spot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/jenkins.spot.png
--------------------------------------------------------------------------------
/docs/images/jenkins.script.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/jenkins.script.png
--------------------------------------------------------------------------------
/docs/images/jenkins.testCre.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/jenkins.testCre.png
--------------------------------------------------------------------------------
/docs/images/alibabacloud_ak_sk.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/alibabacloud_ak_sk.png
--------------------------------------------------------------------------------
/docs/images/jenkins.cloudName.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/jenkins.cloudName.png
--------------------------------------------------------------------------------
/docs/images/jenkins.configSpot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/jenkins.configSpot.png
--------------------------------------------------------------------------------
/docs/images/jenkins.provision.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/jenkins.provision.png
--------------------------------------------------------------------------------
/docs/images/jenkins.rightspot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/jenkins.rightspot.png
--------------------------------------------------------------------------------
/docs/images/jenkins.sampleStep.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/jenkins.sampleStep.png
--------------------------------------------------------------------------------
/docs/images/jenkins_config_vpc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/jenkins_config_vpc.png
--------------------------------------------------------------------------------
/docs/images/nail_group_qr_code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/nail_group_qr_code.png
--------------------------------------------------------------------------------
/docs/images/alibabacloud.keypair.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/alibabacloud.keypair.png
--------------------------------------------------------------------------------
/docs/images/jenkins.Credentials.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/jenkins.Credentials.png
--------------------------------------------------------------------------------
/docs/images/jenkins.cloudDetail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/jenkins.cloudDetail.png
--------------------------------------------------------------------------------
/docs/images/jenkins.groovySandbox.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/jenkins.groovySandbox.png
--------------------------------------------------------------------------------
/docs/images/jenkins.templateName.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/jenkins.templateName.png
--------------------------------------------------------------------------------
/docs/images/jenkins_configure_az.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/jenkins_configure_az.png
--------------------------------------------------------------------------------
/docs/images/jenkins_configure_sg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/jenkins_configure_sg.png
--------------------------------------------------------------------------------
/docs/images/configure_credentials_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/configure_credentials_1.png
--------------------------------------------------------------------------------
/docs/images/jenkins.cloudsConfigure.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/jenkins.cloudsConfigure.png
--------------------------------------------------------------------------------
/docs/images/jenkins.detailFollower.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/jenkins.detailFollower.png
--------------------------------------------------------------------------------
/docs/images/jenkins.pipelineSyntax.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/jenkins.pipelineSyntax.png
--------------------------------------------------------------------------------
/docs/images/jenkins_cloud_mechanism.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/jenkins_cloud_mechanism.png
--------------------------------------------------------------------------------
/docs/images/jenkins_configure_image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/jenkins_configure_image.png
--------------------------------------------------------------------------------
/docs/images/jenkins_configure_name.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/jenkins_configure_name.png
--------------------------------------------------------------------------------
/docs/images/alibabacloud.keypairgene.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/alibabacloud.keypairgene.png
--------------------------------------------------------------------------------
/docs/images/alibabacloud_plugin_market.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/alibabacloud_plugin_market.png
--------------------------------------------------------------------------------
/docs/images/jenkins.Credentials.check.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/jenkins.Credentials.check.png
--------------------------------------------------------------------------------
/docs/images/jenkins.cloud.primaryKey.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/jenkins.cloud.primaryKey.png
--------------------------------------------------------------------------------
/docs/images/jenkins_configure_clouds.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/jenkins_configure_clouds.png
--------------------------------------------------------------------------------
/docs/images/jenkins_configure_flavor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/jenkins_configure_flavor.png
--------------------------------------------------------------------------------
/docs/images/jenkins_configure_region.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/jenkins_configure_region.png
--------------------------------------------------------------------------------
/docs/images/jenkins_configure_ssh_key.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/jenkins_configure_ssh_key.png
--------------------------------------------------------------------------------
/docs/images/jenkins_credentials_kind.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/jenkins_credentials_kind.png
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaEcsFollowerTemplate/help-systemDiskSize_zh_CN.html:
--------------------------------------------------------------------------------
1 |
2 | 系统盘大小,单位为GiB。取值范围:20~500。
3 |
--------------------------------------------------------------------------------
/docs/images/jenkins.generatePipelineScript.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/jenkins.generatePipelineScript.png
--------------------------------------------------------------------------------
/docs/images/jenkins_configure_instance_count.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alibabacloud-ecs-plugin/HEAD/docs/images/jenkins_configure_instance_count.png
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaCloud/help-vpc_zh_CN.html:
--------------------------------------------------------------------------------
1 |
2 | VPC ID 将用于创建ECS实例。
3 | 示例:"vpc-dweqdxdaadqdfadqw"
4 |
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.DS_Store
2 | *.classpath
3 | *.settings
4 | *.project
5 | *.iml
6 | *.idea
7 | target
8 | logs
9 | aliyun-ecs/test-output*
10 | aliyun-ecs/bin
11 | work
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaEcsFollowerTemplate/help-systemDiskSize.html:
--------------------------------------------------------------------------------
1 |
2 | The size of the system disk, in GiB. Ranges:20~500。
3 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaEcsFollowerTemplate/help-minimumNumberOfInstances_zh_CN.html:
--------------------------------------------------------------------------------
1 |
2 | 最小实例数是用于生成节点的数量。
3 | 此插件将根据填写的数字创建子节点。
4 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/WindowsData/help-password_zh_CN.html:
--------------------------------------------------------------------------------
1 |
2 | 实例登陆密码,8~30 个字符,必须同时包含三项(大写字母、小写字母、数字、 ()`~!@#$%^&*_-+=|{}[]:;'<>,.?/ 中的特殊符号),不能以斜线号(/)开头
3 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaEcsFollowerTemplate/help-snapshotId_zh_CN.html:
--------------------------------------------------------------------------------
1 |
2 | 创建数据盘的快照
3 | 实际创建的云盘大小为指定的快照的大小。不能使用早于2013年7月15日(含)创建的快照,请求会报错被拒绝。
4 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaCloud/help-securityGroup_zh_CN.html:
--------------------------------------------------------------------------------
1 |
2 | 新创建实例所属于的安全组ID。同一个安全组内的实例之间可以互相访问,一个安全组能容纳的实例数量视安全组类型而定。
3 | 示例:sg-bp15ed6xe1yxeycg7****
4 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaEcsFollowerTemplate/help-instanceCapStr_zh_CN.html:
--------------------------------------------------------------------------------
1 |
2 | 限制从该模板启动的正在运行的实例总数,为空表示不限制。主要用于及控制ECS使用成本。
3 | 一旦实例数到达上限,额外的负载将执行排队等待,构建频率将降低。
4 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaEcsFollowerTemplate/help-launchTimeoutStr_zh_CN.html:
--------------------------------------------------------------------------------
1 |
2 | 等待SSH与新的从节点实例的链接完成的秒数,不填或为零时表示没有超时时间。
3 | 如果在该时间范围内无法与worker节点建立SSH, 则默认会删除掉该节点.
4 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/WindowsData/config.properties:
--------------------------------------------------------------------------------
1 | Windows_Admin_Password=Windows Admin Password
2 | Use_HTTPS=Use HTTPS
3 | Allow_Self_Signed_Certificate=Allow Self Signed Certificate
4 | Boot_Delay=Boot Delay
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/win/winrm/request/WinRMRequest.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs.win.winrm.request;
2 |
3 | import org.dom4j.Document;
4 |
5 | public interface WinRMRequest {
6 | Document build();
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaEcsFollowerTemplate/help-instanceType_zh_CN.html:
--------------------------------------------------------------------------------
1 |
2 | 待创建实例的实例规格的ID。
3 |
4 | 已上线的实例规格请参见
选择实例规格
5 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaEcsStep/help.html:
--------------------------------------------------------------------------------
1 |
2 | Creates an Aliyun ECS Spot Instance object, from an already globally defined cloud name and template without registering it as a Jenkins agent.
3 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaCloud/help-region_zh_CN.html:
--------------------------------------------------------------------------------
1 |
2 | 标识阿里云应用所在的地域。
3 | 查看
阿里云地域 获取所有支持的地域列表
4 | 样例: cn-hangzhou
5 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaEcsFollowerTemplate/help-remoteAdmin_zh_CN.html:
--------------------------------------------------------------------------------
1 |
2 | 该字段是可选的。指定用于使用 ssh 访问实例的用户名。使用windows镜像时需要将该参数指定为Administrator。指定自定义的用户名,
3 | 使用自定义用户名时需要注意该用户名必须在使用的镜像中已经创建。不指定此项,默认为“root”
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaEcsFollowerTemplate/help-launchTimeoutStr.html:
--------------------------------------------------------------------------------
1 |
2 | Number of seconds to wait for the ssh connection to the new slave instance to
3 | finish. Blank or zero here indicates no timeout/wait forever.
4 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaEcsFollowerTemplate/help-userData_zh_CN.html:
--------------------------------------------------------------------------------
1 |
2 | 实例自定义数据,不需要进行Base64编码,源码传入即可,原始数据最多为16 KB.
3 | 示例:
4 |
5 | #!/bin/sh
6 | echo 'hello world!'
7 |
8 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaEcsFollowerTemplate/help-image_zh_CN.html:
--------------------------------------------------------------------------------
1 |
2 | 镜像ID,启动实例时选择的镜像资源。
3 | 示例值:ubuntu_22_04_x64_20G_alibase_20220628.vhd
4 | 更多镜像请参见
公共镜像
5 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaEcsFollowerTemplate/help-instanceNamePrefix_zh_CN.html:
--------------------------------------------------------------------------------
1 |
2 | 实例名称前缀,和需要运行的任务名称之和不能超过128个英文或中文字符。
3 | 必须以大小字母或中文开头。
4 | 不能以http://或https://开头。
5 | 可以包含数字、半角冒号(:)、下划线(_)或者短划线(-)。
6 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaEcsFollowerTemplate/help-minimumNumberOfInstances.html:
--------------------------------------------------------------------------------
1 |
2 | The minimum number of instances is the number used to spawn nodes.
3 | This plugin will create child nodes based on the numbers filled in.
4 |
--------------------------------------------------------------------------------
/src/main/resources/index.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 | This plugin integrates Jenkins with
4 |
Alibaba Cloud ECS
5 | or anything implementing
6 | the ECS API's such as an CentOS.
7 |
8 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaEcsFollowerTemplate/help-zone_zh_CN.html:
--------------------------------------------------------------------------------
1 |
2 | 待创建实例所属的可用区ID。
3 | 查看
阿里云地域&可用区 获取所有支持的可用区列表
4 | 示例:cn-hangzhou-a
5 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaEcsFollowerTemplate/help-vsw_zh_CN.html:
--------------------------------------------------------------------------------
1 |
2 | 虚拟交换机ID, 交换机(vSwitch)是组成专有网络的基础网络设备,用来连接不同的云资源实例。
3 | 专有网络是地域级别的资源,专有网络不可以跨地域,但包含所属地域的所有可用区。
4 | 您可以在每个可用区内创建一个或多个交换机来划分子网。
5 | 示例:vsw-bp1s5fnvk4gn2tws0****
6 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/WindowsData/config_zh_CN.properties:
--------------------------------------------------------------------------------
1 | Windows_Admin_Password=Windows \u7ba1\u7406\u5458\u5bc6\u7801
2 | Use_HTTPS=\u4f7f\u7528 HTTPS
3 | Allow_Self_Signed_Certificate=\u5141\u8bb8\u81ea\u7b7e\u540d\u8bc1\u4e66
4 | Boot_Delay=\u542f\u52a8\u5ef6\u8fdf
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/WindowsData/help-password.html:
--------------------------------------------------------------------------------
1 |
2 | Instance login password, 8~30 characters, including three items (uppercase, lowercase, numbers, numbers, ()`~@#$%^&*_=|{}[]:;'<> ,.?/ special symbols in the middle order), cannot be preceded by a slash (/)
3 |
4 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaCloud/help-attachPublicIp_zh_CN.html:
--------------------------------------------------------------------------------
1 |
2 | Cloud自动创建的worker节点, 是否需要自动设置公网IP.
3 |
如果您的JenkinsMaster部署在非阿里云环境, 则通常需要设置公网IP, 以便后续JenkinsMaster能够通过公网IP与worker节点通信
4 | 如果您的JenkinsMaster部署在阿里云上, 则无需配置该选项, 可以通过私网IP访问到worker节点
5 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaCloud/help-sshKey_zh_CN.html:
--------------------------------------------------------------------------------
1 |
2 | 阿里云SSH密钥对是一种安全便捷的登录认证方式,由公钥和私钥组成,仅支持Linux实例。
3 |
4 | 私钥使用未加密的PEM(Privacy-Enhanced Mail)编码的PKCS#8格式。
5 | ECS密钥对的私钥以 "-----BEGIN RSA PRIVATE KEY-----" 开头。
6 | 推荐设置凭据ID为在阿里云控制台上已创建密钥的名称。
7 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/WindowsData/help-bootDelay_zh_CN.html:
--------------------------------------------------------------------------------
1 |
2 | 在此指示插件检测到 WinRM 可用后等待机器准备就绪的时间(以秒为单位)。
3 | 但是在 Windows 上,在启动过程中,WinRM 服务可能会启动,然后几分钟后会重新启动。
4 | 如果在 Jenkins 执行代理配置期间发生此重新启动,Windows 将阻止 WinRM 客户端再次连接,并且
5 | 代理将无法正确配置。
6 |
7 | 已发现安全值是 3 分钟。
8 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaCloud/help-vpc.html:
--------------------------------------------------------------------------------
1 |
2 | Virtual Private Cloud (VPC for short),
3 | you need to specify the VPC ID that has been created in Alibaba CLOUD.
4 | The VPC ID will be used to create an ECS instance.
5 | Example:"vpc-dweqdxdaadqdfadqw"
6 |
7 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaEcsFollowerTemplate/help-instanceType.html:
--------------------------------------------------------------------------------
1 |
2 | ID of the instance type of the instance to be created.
3 |
4 | For online instance specifications, see
Choose an instance type
5 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaEcsFollowerTemplate/help-maxTotalUses_zh_CN.html:
--------------------------------------------------------------------------------
1 |
2 | 可选值为:
3 | - 0: 代表不限制复用次数, 即该节点会常驻, 可以执行不限次数的构建, 直到手动销毁或者空闲时长超过阈值导致销毁.
4 | - 1: 代表该节点只能执行一次构建, 构建结束, 节点就立即销毁.
5 | - 2~N: 代表节点可以执行2~N次构建. 没执行一次构建, 剩余构建次数递减1, 直到为0时销毁.
6 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaEcsFollowerTemplate/help-initScript_zh_CN.html:
--------------------------------------------------------------------------------
1 |
2 | Init脚本是在Jenkins开始启动跟随者节点之前在新启动的跟随者节点实例上运行的Shell脚本。
3 | 这也是安装构建和测试所需的其他软件包的好地方。
4 | 样例:
5 |
6 | #!/bin/bash
7 | sudo yum install -y net-tools
8 |
9 |
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/win/winrm/WinRMConnectException.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs.win.winrm;
2 |
3 | public class WinRMConnectException extends RuntimeIOException {
4 |
5 | public WinRMConnectException(String message, Throwable cause) {
6 | super(message, cause);
7 | }
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaEcsFollowerTemplate/help-image.html:
--------------------------------------------------------------------------------
1 |
2 | Image ID, the image resource selected when starting the instance.
3 | Example:ubuntu_22_04_x64_20G_alibase_20220628.vhd
4 | For more images, see
public image
5 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaEcsFollowerTemplate/help-userData.html:
--------------------------------------------------------------------------------
1 |
2 | Instance custom data, Base64 encoding is NOT required, the source code can be passed in, and the original data is up to 16 KB.
3 | Example:
4 |
5 | #!/bin/sh
6 | echo 'hello world!'
7 |
8 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaEcsFollowerTemplate/help-systemDiskCategory_zh_CN.html:
--------------------------------------------------------------------------------
1 |
2 | 系统盘的云盘种类,可选择的云盘种类如下:
3 |
cloud:普通云盘。
4 | cloud_efficiency:高效云盘。
5 | cloud_ssd:SSD云盘。
6 | cloud_essd:ESSD云盘。
7 | 注意: ECS的 c7/g7/r7 规格族只支持 cloud_essd 类型云盘
8 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaCloud/help-noDelayProvisioning_zh_CN.html:
--------------------------------------------------------------------------------
1 | 默认情况下, Jenkins会评估当前构建队列中的任务数量与worker节点的繁忙程度, 从而避免过多地新建worker节点;
2 | 如果勾选了该项, 则一旦构建队列中有任务积压, 则不会等待已有的worker节点结束工作, 而是会立刻新建worker节点来执行队里中的构建任务.
3 | 勾选该项, 优点是队列中任务能迅速引发新建节点从而迅速完成构建, 缺点是任务结束后可能导致闲置的worker节点数量过多从而引发额外费用问题; 此时建议调整节点的 "Idle Termination Time In Minutes" 参数来缓解该问题
4 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaCloud/help-securityGroup.html:
--------------------------------------------------------------------------------
1 |
2 | ID of the security group to which the newly created instance belongs.
3 | Instances in the same security group can access each other.
4 | The number of instances that a security group can accommodate depends on the security group type.
5 | Example: sg-bp15ed6xe1yxeycg7****
6 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaEcsFollowerTemplate/help-zone.html:
--------------------------------------------------------------------------------
1 |
2 | ID of the Availability Zone to which the instance to be created belongs.
3 | See
Alibaba Cloud documentation
4 | for more about what Availability Zone are.
5 | Example:cn-hangzhou-a
6 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaCloud/help-noDelayProvisioning.html:
--------------------------------------------------------------------------------
1 | By default Jenkins do estimate load to avoid over-provisioning of cloud nodes.
2 | With this option enabled, a new node is created on Alibaba Cloud as soon as NodeProvisioner detects need for more agents.
3 | In worse scenarios, this will results in some extra nodes provisioned on ECS, which will be shortly terminated.
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaEcsFollowerTemplate/help-instanceCapStr.html:
--------------------------------------------------------------------------------
1 |
2 | Limit the total number of running instances launched from this template, empty means no limit. Mainly used to
3 | control the cost of using ECS.
4 | Once the number of instances reaches the upper limit, additional loads will be queued and the build frequency will
5 | be reduced.
6 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaCloud/help-intranetMaster_zh_CN.html:
--------------------------------------------------------------------------------
1 |
2 | 当前Jenkins Master是否部署在VPC内网环境中(即是否有访问公网的权限).
3 |
如果您的Jenkins Master部署在内网环境中(即没有访问公网权限), 请勾选此项, 后续插件调用阿里云SDK会使用VPC私网域名进行请求.
4 | 如果在公网环境中(即有访问公网权限), 则无需勾选此项, 后续调用阿里云SDK会使用公网域名进行请求.
5 | 如果不勾选此项, 默认会使用公网域名进行访问, 如果无访问权限, 后续使用该插件会出现"ConnectTimeoutException"异常
6 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaCloud/help-region.html:
--------------------------------------------------------------------------------
1 |
2 | Specifies the geographic region in which your agents will run. Pick the region closest to you.
3 | Regions can be thought of as
4 | independent instances of ECS.
5 | See
Alibaba Cloud documentation
6 | for more about what regions are.
7 | Example: cn-hangzhou
8 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaEcsFollowerTemplate/help-initScript.html:
--------------------------------------------------------------------------------
1 |
2 | The Init script is a shell script that runs on the newly started follower node instance before Jenkins starts to start the follower node.
3 | This is also a good place to install other packages needed for building and testing.
4 | Example:
5 |
6 | #!/bin/bash
7 | sudo yum install -y net-tools
8 |
9 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaEcsFollowerTemplate/help-numExecutors_zh_CN.html:
--------------------------------------------------------------------------------
1 |
2 | Jenkins可以在此节点上执行的并发构建的最大数量。默认为4
3 | 首先,一个不错的值是机器上的CPU内核数(查看
《实例规格说明》 获取CPU核数)。
4 | 设置较高的值将导致每个构建花费更长的时间,但可能会增加整体吞吐量。
5 | 例如,一个构建可能受CPU限制,而同时运行的第二个构建可能受I/O限制-因此,第二个构建此时可以利用备用I/O容量。
6 |
7 | 对于主服务器,请将执行程序的数量设置为零,以防止其在本地执行构建。 注意:master始终可以运行包括管道的顶级任务在内的轻量级任务。
8 |
9 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaEcsFollowerTemplate/help-systemDiskCategory.html:
--------------------------------------------------------------------------------
1 |
2 | The cloud disk type of the system disk. The cloud disk types that can be selected are as follows:
3 |
cloud_efficiency:Efficient cloud disk.
4 | cloud_ssd:SSD cloud disk.
5 | cloud_essd:ESSD cloud disk.
6 | cloud:Ordinary cloud disk.
7 | Attention: c7/g7/r7 only support cloud_essd
8 |
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/win/winrm/soap/Option.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs.win.winrm.soap;
2 |
3 | public class Option {
4 | private final String name;
5 | private final String value;
6 |
7 | public Option(String name, String value) {
8 | this.name = name;
9 | this.value = value;
10 | }
11 |
12 | public String getValue() {
13 | return value;
14 | }
15 |
16 | public String getName() {
17 | return name;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaEcsFollowerTemplate/help-vsw.html:
--------------------------------------------------------------------------------
1 |
2 | Virtual switch ID, a switch (vSwitch) is a basic network device that forms a VPC and is used to connect different cloud resource instances.
3 | A VPC is a region-level resource. A VPC cannot span regions, but includes all the availability zones in the region to which it belongs.
4 | You can create one or more switches within each Availability Zone to divide the subnets.
5 | Example:vsw-bp1s5fnvk4gn2tws0****
6 |
--------------------------------------------------------------------------------
/src/main/resources/lib/ecs/blockWrapper.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaEcsFollowerTemplate/help-dataDiskId_zh_CN.html:
--------------------------------------------------------------------------------
1 |
2 | 已有的云盘id
3 | 当云盘需要挂载在多个实例上时的限制详见
NVMe云盘概述
4 | 当前使用的时块存储EBS,时延极低。吞吐数十Gbps,单ECS通过POSIX接口访问,随机读写
5 | 当需要挂载的云盘为NVMe云盘时需要在初始化脚本,选择的已有云盘若为新云盘需要先格式化云盘
6 |
7 | #!/bin/bash
8 | mkdir /dev/xvdb
9 | mount /dev/nvme0n1 /dev/xvdb
10 | mount /dev/nvme1n1 /dev/xvdb
11 |
12 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaCloud/help-sshKey.html:
--------------------------------------------------------------------------------
1 |
2 | Alibaba Cloud SSH key pair is a safe and convenient way of login authentication. It consists of a public key and a private key. It only supports Linux instances.
3 |
4 | The private key is in unencrypted PEM (Privacy-Enhanced Mail) encoded PKCS#8 format.
5 | The private key of an ECS key pair starts with "-----BEGIN RSA PRIVATE KEY-----" .
6 | It is recommended to set the credential ID to the name of the key created on the Alibaba Cloud console.
7 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaCloud/help-attachPublicIp.html:
--------------------------------------------------------------------------------
1 |
2 | Whether the worker node automatically created by Cloud needs to set the public IP.
3 |
If your JenkinsMaster is deployed in a non-Alibaba cloud environment, you usually need to set the public IP so that the subsequent JenkinsMaster can communicate with the worker nodes through the public IP
4 | If your JenkinsMaster is deployed on Alibaba Cloud, you do not need to configure this option, you can access the worker node through the private IP
5 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/WindowsData/help-bootDelay.html:
--------------------------------------------------------------------------------
1 |
2 | Indicate here the time in seconds to wait for the machine to be ready once the plugin detects WinRM is available.
3 | Unfortunately, on Windows during the boot, the WinRM service might be started, and then several minutes after will be restarted.
4 | If this restart happens during the agent provisioning that Jenkins does, Windows will prevent the WinRM client to connect again and the
5 | agent will not be correctly provisioned.
6 |
7 | A safe value has been found to be 3 minutes.
8 |
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/win/winrm/RuntimeIOException.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs.win.winrm;
2 |
3 | @SuppressWarnings("serial")
4 | public class RuntimeIOException extends RuntimeException {
5 | public RuntimeIOException() {
6 | super();
7 | }
8 |
9 | public RuntimeIOException(String message) {
10 | super(message);
11 | }
12 |
13 | public RuntimeIOException(Throwable cause) {
14 | super(cause);
15 | }
16 |
17 | public RuntimeIOException(String message, Throwable cause) {
18 | super(message, cause);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/AlibabaEcsComputerListener.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs;
2 |
3 | import hudson.Extension;
4 | import hudson.model.Computer;
5 | import hudson.model.TaskListener;
6 | import hudson.slaves.ComputerListener;
7 |
8 | /**
9 | * Created by kunlun.ykl on 2020/9/11.
10 | */
11 | @Extension
12 | public class AlibabaEcsComputerListener extends ComputerListener {
13 | @Override
14 | public void onOnline(Computer c, TaskListener listener) {
15 | if (c instanceof AlibabaEcsComputer) {
16 | ((AlibabaEcsComputer)c).onConnected();
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaCloud/config.properties:
--------------------------------------------------------------------------------
1 | Alibaba_Cloud_Credentials=Alibaba Cloud Credentials
2 | CredentialsDescription=Alibaba Cloud IAM Access Key used to connect to ECS. If not specified, implicit authentication mechanisms are used (IAM roles...)
3 | Region=Region
4 | VPC=VPC
5 | Security_Group=Security Group
6 | ECS_SSH_Key=ECS SSH Key
7 | Advanced=Advanced
8 | Assign_Public_Ip=Assign Public Ip
9 | Instance_Cap=Instance Cap
10 | No_delay_provisioning=No delay provisioning
11 | Test_Connection=Test Connection
12 | Images=Images
13 | ImagesDescription=List of Images to be launched as agents
14 | Name=Name
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/util/AlibabaEcsFactoryImpl.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs.util;
2 |
3 | import com.aliyuncs.auth.AlibabaCloudCredentials;
4 | import hudson.Extension;
5 | import com.alibabacloud.jenkins.ecs.client.AlibabaEcsClient;
6 |
7 | /**
8 | * Created by kunlun.ykl on 2020/8/26.
9 | */
10 | @Extension
11 | public class AlibabaEcsFactoryImpl implements AlibabaEcsFactory {
12 |
13 | @Override
14 | public AlibabaEcsClient connect(AlibabaCloudCredentials credentials, String regionNo, Boolean intranetMaster) {
15 | return new AlibabaEcsClient(credentials, regionNo, intranetMaster);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/.github/workflows/jenkins-security-scan.yml:
--------------------------------------------------------------------------------
1 | name: Jenkins Security Scan
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 | types: [ opened, synchronize, reopened ]
9 | workflow_dispatch:
10 |
11 | permissions:
12 | security-events: write
13 | contents: read
14 | actions: read
15 |
16 | jobs:
17 | security-scan:
18 | uses: jenkins-infra/jenkins-security-scan/.github/workflows/jenkins-security-scan.yaml@v2
19 | with:
20 | java-cache: 'maven' # Optionally enable use of a build dependency cache. Specify 'maven' or 'gradle' as appropriate.
21 | # java-version: 21 # Optionally specify what version of Java to set up for the build, or remove to use a recent default.
22 |
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/util/EcsInstanceHelper.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs.util;
2 |
3 | import com.aliyuncs.ecs.model.v20140526.DescribeInstancesResponse;
4 |
5 | import java.util.Date;
6 |
7 | public class EcsInstanceHelper {
8 |
9 | public static long getStartCostInSeconds(DescribeInstancesResponse.Instance instance) {
10 | String creationTime = instance.getCreationTime();
11 | Date create = DateUtils.parse(creationTime);
12 | String startTime = instance.getStartTime();
13 | Date start = DateUtils.parse(startTime);
14 | if (create == null || start == null) {
15 | return 0;
16 | }
17 | return (start.getTime() - create.getTime()) / 1000;
18 | }
19 |
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/PIPELINESTEP.md:
--------------------------------------------------------------------------------
1 | # AlibabaEcs Step pipeline
2 |
3 | ## 使用步骤
4 | 1. 配置ecs 模版配置
5 | 
6 | 2. 新建一个流水线任务
7 | 
8 | 3. 点击流水线语法
9 | 
10 | 4. 示例步骤 选择 alibabaEcs: Cloud template provisioning
11 | 
12 | 5. Alibaba Cloud name 选择刚创建模版名词
13 | 
14 | 6. Template name 选择刚创建模版配置中的ecs模版描述
15 | 
16 | 7. 点击生成流水线脚本
17 | 
18 | 8. 复制框中生成的脚本
19 | 9. 粘贴脚本到流水线中的脚本文本框内
20 | 
21 | 10. 不使用Groovy沙盘
22 |
23 | 
24 | 11. 点击保存
25 | 12. 点击立即构建
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/exception/AlibabaEcsException.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs.exception;
2 |
3 | /**
4 | * Created by kunlun.ykl on 2020/9/25.
5 | */
6 | public class AlibabaEcsException extends Exception {
7 | public AlibabaEcsException() {
8 | }
9 |
10 | public AlibabaEcsException(String message) {
11 | super(message);
12 | }
13 |
14 | public AlibabaEcsException(String message, Throwable cause) {
15 | super(message, cause);
16 | }
17 |
18 | public AlibabaEcsException(Throwable cause) {
19 | super(cause);
20 | }
21 |
22 | public AlibabaEcsException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
23 | super(message, cause, enableSuppression, writableStackTrace);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaCloud/config_zh_CN.properties:
--------------------------------------------------------------------------------
1 | Alibaba_Cloud_Credentials=\u963f\u91cc ECS \u51ed\u8bc1
2 | CredentialsDescription=\u7528\u4e8e\u8fde\u63a5 ECS \u7684\u963f\u91cc\u4e91 iam access key\u3002 \u5982\u679c\u672a\u6307\u5b9a\uff0c\u5219\u4f7f\u7528\u9690\u5f0f\u8eab\u4efd\u9a8c\u8bc1\u673a\u5236\uff08iam \u89d2\u8272...\uff09
3 | Region=\u5730\u57df
4 | VPC=\u865A\u62DF\u79C1\u6709\u4E91ID
5 | Security_Group=\u5b89\u5168\u7ec4ID
6 | ECS_SSH_Key=\u963f\u91cc\u4e91SSH\u5bc6\u94a5
7 | Advanced=\u9AD8\u7EA7...
8 | Assign_Public_Ip=\u662f\u5426\u8bbe\u7f6e\u516c\u7f51IP
9 | Instance_Cap=\u5b9e\u4f8b\u4e0a\u9650
10 | No_delay_provisioning=\u65e0\u5ef6\u8fdf\u4f9b\u5e94
11 | Test_Connection=\u6D4B\u8BD5\u8FDE\u63A5
12 | Images=ECS\u6a21\u7248
13 | ImagesDescription=List of Images to be launched as agents
14 | Name=\u540D\u5B57
--------------------------------------------------------------------------------
/src/test/java/com/alibabacloud/jenkins/ecs/util/DateUtilsTest.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs.util;
2 |
3 | import org.junit.Test;
4 |
5 | import java.util.Calendar;
6 | import java.util.Date;
7 |
8 | public class DateUtilsTest {
9 | @Test
10 | public void parseTest() {
11 | String createTime = "2017-12-10T04:04Z";
12 | Date parse = DateUtils.parse(createTime);
13 | System.out.println(parse);
14 |
15 | createTime = "2022-08-01T03:25Z";
16 | parse = DateUtils.parse(createTime);
17 | System.out.println(parse.getTime());
18 | }
19 |
20 | @Test
21 | public void formatTest() {
22 | String format = DateUtils.format(Calendar.getInstance().getTime());
23 | System.out.println(format);
24 | Date parse = DateUtils.parse(format);
25 | System.out.println(parse);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/EcsTypeData.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs;
2 |
3 | import java.io.Serializable;
4 | import java.util.concurrent.TimeUnit;
5 |
6 | import hudson.model.AbstractDescribableImpl;
7 |
8 | public abstract class EcsTypeData extends AbstractDescribableImpl implements Serializable {
9 | private static final long serialVersionUID = 558886106677617487L;
10 |
11 | public abstract boolean isWindows();
12 |
13 | public abstract boolean isUnix();
14 |
15 | public abstract String getBootDelay();
16 |
17 | public int getBootDelayInMillis() {
18 | if (getBootDelay() == null) { return 0; }
19 | try {
20 | return (int) TimeUnit.SECONDS.toMillis(Integer.parseInt(getBootDelay()));
21 | } catch (NumberFormatException nfe) {
22 | return 0;
23 | }
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/WindowsData/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaEcsFollowerTemplate/help-remoteFs_zh_CN.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | 代理需要有一个专用于 Jenkins 的目录。指定代理上此目录的路径。最好用绝对路径,
4 | 例如 /var/jenkins 或 c:\jenkins.
5 | 这应该是代理计算机的本地路径。无需从控制器中看到此路径。
6 |
7 | 代理不维护重要数据;所有作业配置、构建日志和工件存储在控制器上,因此可以使用临时目录作为代理根目录。
8 |
9 | 通过为代理提供一个在机器重启后不会删除的目录,例如,代理可以缓存数据,例如工具安装,或
10 | 构建工作区。这可以防止不必要的工具下载或检查当构建开始在此代理上再次运行时再次输出源代码重启。
11 |
12 | 如果您使用相对路径,例如 ./jenkins-agent, 路径将是相对于由提供的工作目录
13 | Launch method .
14 |
15 | 对于 Jenkins 控制启动代理进程的启动器,例如作为 SSH,当前工作目录通常是一致的,例如用户的主目录。
16 | 对于 Jenkins 无法控制启动代理进程的启动器,例如从命令行启动的入站代理,当前工作目录可能会在
17 | 代理的启动和相对路径的使用可能会出现问题。
18 |
19 | 将相对路径与入站启动器一起使用时遇到的主要问题是陈旧的工作空间和工具安装的激增在代理机器上。
20 | 这可能会导致磁盘空间问题。
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/win/winrm/request/DeleteShellRequest.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs.win.winrm.request;
2 |
3 | import java.net.URI;
4 | import java.net.URISyntaxException;
5 | import java.net.URL;
6 |
7 | public class DeleteShellRequest extends AbstractWinRMRequest {
8 |
9 | private final String shellId;
10 |
11 | public DeleteShellRequest(URL url, String shellId) {
12 | super(url);
13 | this.shellId = shellId;
14 | }
15 |
16 | @Override
17 | protected void construct() {
18 | try {
19 | defaultHeader().action(new URI("http://schemas.xmlsoap.org/ws/2004/09/transfer/Delete")).shellId(shellId).resourceURI(new URI("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd"));
20 |
21 | setBody(null);
22 | } catch (URISyntaxException e) {
23 | throw new RuntimeException("Error while building request content", e);
24 | }
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaCloud/help-intranetMaster.html:
--------------------------------------------------------------------------------
1 |
2 | Whether the current Jenkins Master is deployed in the VPC intranet environment (that is, whether it has access to the public network).
3 |
If Jenkins Master is deployed in the intranet environment (that is, Jenkins Master do not have access to the public network), please check this option, and the subsequent plug-in calls to Alibaba Cloud SDK will use the VPC Private EndPoint name for requests.
4 | If Jenkins Master is in a public network environment (that is, you have access to the public network), you do not need to check this option. Subsequent calls to the Alibaba Cloud SDK will use the public Endpoint for requests.
5 | If you do not check this option, public API Endpoint will be used for access by default, which may lead to unreachable access, so the "ConnectTimeoutException" exception will occur when the plug-in is used later.
6 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ### Version 1.5 (July 19, 2022)
4 | ### New features and improvements
5 | ***
6 | * Add support for vpc endpoint
7 |
8 | ### Dependency updates
9 | ***
10 | * alibabacloud-credentials from 1.0 to 1.1
11 |
12 | ## Version 1.5 (July 19, 2022)
13 | ### New features and improvements
14 | ***
15 | * Add noun notes
16 |
17 | ### Bug fixes
18 | ***
19 | * configuration cluster save updated System Disk Category and System Disk size
20 |
21 | ## Version 1.4 (July 18, 2022)
22 | ### New features and improvements
23 | ***
24 | * Add support system disk category
25 | * Add support system disk size
26 | * Add attach Public Ip
27 |
28 |
29 | ## Version 1.3 (Dec 22, 2020)
30 | - Add chinese readme index.
31 | - Delete allocate public ip method.
32 |
33 | ### Version 1.2 (Dec 3, 2020)
34 | - Fixed public IP connection error.
35 |
36 | ### Version 1.1 (Dec 2, 2020)
37 | - Added auto generate vsw function for easier node provisioning.
38 |
39 | ### Version 1.0 (Nov 20, 2020)
40 | - Initial release
--------------------------------------------------------------------------------
/src/test/java/com/alibabacloud/jenkins/ecs/EcsTemplateStepExecutionTest.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs;
2 |
3 | import jenkins.model.Jenkins;
4 | import org.junit.Test;
5 | import org.junit.runner.RunWith;
6 | import org.mockito.Mock;
7 | import org.mockito.junit.MockitoJUnitRunner;
8 |
9 | /**
10 | * @author Alicia Doblas
11 | */
12 | @RunWith(MockitoJUnitRunner.class)
13 | public class EcsTemplateStepExecutionTest {
14 | @Mock
15 | Jenkins jenkins;
16 |
17 | @Test
18 | public void tets() throws Exception {
19 | EcsTemplateStep step = new EcsTemplateStep();
20 | step.setSystemDiskCategory("cloud_essd_PL0");
21 | step.setDataDiskCategory("cloud_essd_PL0");
22 | step.setNewDataDisk(true);
23 | step.setDataDiskSize("1");
24 | step.setMountQuantity("1");
25 | step.setMinimumNumberOfInstances("1");
26 | EcsTemplateStepExecution stepExecution = new EcsTemplateStepExecution(step, null);
27 | stepExecution.start();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaEcsFollowerTemplate/help-numExecutors.html:
--------------------------------------------------------------------------------
1 |
2 | The maximum number of concurrent builds that Jenkins may perform on this node.default: 4
3 | A good value to start with would be the number of CPU cores on the machine(see the "Instance Types and
4 | Specifications" chapter of
"Elastic Cloud Server Product
5 | Introduction" get the cpu number).
6 | Setting a higher value would cause each build to take longer, but could increase the overall throughput. For
7 | example, one build might be CPU-bound, while a second build running at the same time might be I/O-bound — so the
8 | second build could take advantage of the spare I/O capacity at that moment.
9 |
10 | For the master, set the number of executors to zero to prevent it from executing builds locally. Note: master will
11 | always be able to run flyweight tasks including Pipeline's top-level task.
12 |
13 |
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/enums/DataDiskCategory.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs.enums;
2 |
3 | public enum DataDiskCategory {
4 | cloud_ssd,
5 | cloud_efficiency,
6 | cloud,
7 | cloud_essd_PL0,
8 | cloud_essd_PL1,
9 | cloud_essd_PL2,
10 | cloud_essd_PL3;
11 |
12 | public static DataDiskCategory fromValue(String value) {
13 | if (value != null && !"".equals(value)) {
14 | DataDiskCategory[] var1 = values();
15 | int var2 = var1.length;
16 |
17 | for (int var3 = 0; var3 < var2; ++var3) {
18 | DataDiskCategory enumEntry = var1[var3];
19 | if (enumEntry.toString().equals(value)) {
20 | return enumEntry;
21 | }
22 | }
23 | throw new IllegalArgumentException("Cannot create enum from " + value + " value!");
24 | } else {
25 | throw new IllegalArgumentException("Value cannot be null or empty!");
26 | }
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/enums/SystemDiskCategory.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs.enums;
2 |
3 | public enum SystemDiskCategory {
4 | cloud_ssd,
5 | cloud_efficiency,
6 | cloud,
7 | cloud_essd_PL0,
8 | cloud_essd_PL1,
9 | cloud_essd_PL2,
10 | cloud_essd_PL3;
11 |
12 | public static SystemDiskCategory fromValue(String value) {
13 | if (value != null && !"".equals(value)) {
14 | SystemDiskCategory[] var1 = values();
15 | int var2 = var1.length;
16 |
17 | for (int var3 = 0; var3 < var2; ++var3) {
18 | SystemDiskCategory enumEntry = var1[var3];
19 | if (enumEntry.toString().equals(value)) {
20 | return enumEntry;
21 | }
22 | }
23 | throw new IllegalArgumentException("Cannot create enum from " + value + " value!");
24 | } else {
25 | throw new IllegalArgumentException("Value cannot be null or empty!");
26 | }
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/util/Closeables.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs.util;
2 |
3 | import java.io.Closeable;
4 | import java.io.IOException;
5 | import java.util.logging.Level;
6 | import java.util.logging.Logger;
7 |
8 | import edu.umd.cs.findbugs.annotations.Nullable;
9 |
10 | public class Closeables {
11 | private static Logger log = Logger.getLogger(Closeables.class.getCanonicalName());
12 |
13 | /**
14 | * Quietly close a {@link Closeable}, logging any {@link IOException}s instead of throwing them.
15 | *
16 | * @param closeable The {@link Closeable} to close quietly. If null, then this method is a no-op.
17 | */
18 | public static void closeQuietly(@Nullable Closeable closeable) {
19 | if (closeable == null) {
20 | return;
21 | }
22 |
23 | try {
24 | closeable.close();
25 | } catch (IOException e) {
26 | log.log(Level.WARNING, "Failed to close resource, ignoring", e);
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/ConnectionStrategy.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs;
2 |
3 | public enum ConnectionStrategy {
4 |
5 | PUBLIC_IP("Public IP"),
6 |
7 | PRIVATE_IP("Private IP");
8 |
9 | private final String displayText;
10 |
11 | ConnectionStrategy(String displayText) {
12 | this.displayText = displayText;
13 | }
14 |
15 | /**
16 | * For backwards compatibility.
17 | * @param connectUsingPublicIp whether or not to use a public ip to establish a connection.
18 | * @param associatePublicIp whether or not to associate to a public ip.
19 | * @return an {@link ConnectionStrategy} based on provided parameters.
20 | */
21 | public static ConnectionStrategy backwardsCompatible(boolean connectUsingPublicIp, boolean associatePublicIp) {
22 | if (connectUsingPublicIp || associatePublicIp) {
23 | return PUBLIC_IP;
24 | } else {
25 | return PRIVATE_IP;
26 | }
27 | }
28 |
29 | public String getDisplayText() {
30 | return this.displayText;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/util/AlibabaEcsFactory.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs.util;
2 |
3 | import com.aliyuncs.auth.AlibabaCloudCredentials;
4 | import hudson.ExtensionPoint;
5 | import com.alibabacloud.jenkins.ecs.client.AlibabaEcsClient;
6 | import jenkins.model.Jenkins;
7 |
8 | /**
9 | * Created by kunlun.ykl on 2020/8/26.
10 | */
11 | public interface AlibabaEcsFactory extends ExtensionPoint {
12 | static AlibabaEcsFactory getInstance() {
13 | AlibabaEcsFactory instance = null;
14 | for (AlibabaEcsFactory implementation : Jenkins.get().getExtensionList(AlibabaEcsFactory.class)) {
15 | if (instance != null) {
16 | throw new IllegalStateException("Multiple implementations of " + AlibabaEcsFactory.class.getName()
17 | + " found. If overriding, please consider using ExtensionFilter");
18 | }
19 | instance = implementation;
20 | }
21 | return instance;
22 | }
23 |
24 | AlibabaEcsClient connect(AlibabaCloudCredentials credentials, String regionNo, Boolean intranetMaster);
25 | }
26 |
--------------------------------------------------------------------------------
/nasData.md:
--------------------------------------------------------------------------------
1 | # 加速Java Maven构建
2 |
3 | 使用挂载 [NAS(文件存储)](https://help.aliyun.com/zh/nas/product-overview/what-is-nas?spm=a2c4g.11186623.0.0.17da61fd36UpTC) 加速
4 | Java Maven构建
5 |
6 | ## 使用步骤
7 |
8 | 1. 创建ecs 下载 maven 并配置maven的setting缓存目录为/data/nas/maven/cache
9 | 2. 在ecs上创建/data/nas目录
10 | 
11 | 3. 在[NAS控制台](https://nasnext.console.aliyun.com/cn-hangzhou/filesystem) 创建文件系统
12 | 
13 | 4. 点击创建出的文件系统
14 | 
15 | 5. 点击挂载使用,点击添加到ECS
16 | 
17 | 6. 点击挂载,选择刚配置maven的ecs
18 | 
19 | 7. 填写挂载路径为/data/nas, 默认开机自动挂载,选择协议类型为NFSv4.0(多台ECS同时编辑一个文件,请使用NFSv4.0),点击挂载
20 | 
21 | 8. 登录ecs使用 df-h 命令查看nas是否挂载
22 | 
23 | 9.
24 | 返回[ecs控制台](https://www.aliyun.com/product/ecs?spm=5176.28055625.J_3207526240.33.5861154aEUhMD6&scm=20140722.M_5288647._.V_1)
25 | ,点击更多,选择云盘和镜像创建自定义镜像
26 | 
27 | 10.使用jenkins插件创建ecs时镜像填写刚创建的镜像Id
28 |
29 | ## 注意
30 |
31 | 1. ECS 关联 VPC 必须跟挂载地址所在 VPC 相同,NAS 存储配置才能生效。
32 | 2. 文件系统所在可用区:华东 1 可用区 G。通用型 NAS 可跨可用区挂载,极速型不推荐跨可用区。
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Alibaba Cloud
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaEcsFollowerTemplate/config.properties:
--------------------------------------------------------------------------------
1 | TestCreateEcs=TestCreateEcs
2 | Description=Description
3 | Image=Image
4 | Availability_Zone=Availability Zone
5 | VSW=VSW
6 | Instance_Charge_Type=Instance Charge Type
7 | Choose_Instance_Type=Choose Instance Type
8 | User_Data=User Data
9 | Remote_user=Remote user
10 | Init_Script=Init Script
11 | Labels=Labels
12 | Remote_FS_root=Remote FS root
13 | System_Disk_Category=System Disk Category
14 | System_Disk_Size=System Disk Size
15 | Instance_Cap=Instance Cap
16 | Minimum_number_of_instances=Minimum number of instances
17 | Idle_Termination_Time_In_Minutes=Idle Termination Time In Minutes
18 | IdleTTDesc=IdleTTDesc
19 | Number_of_Executors=Number of Executors
20 | Launch_Timeout_In_Seconds=Launch Timeout In Seconds
21 | Advanced=Advanced
22 | MountDataDisk=Mount Data Volume
23 | DataDiskCategory=DataVolumeCategory
24 | DataDiskSize=DataVolumeSize
25 | MountQuantity=MountQuantity
26 | ECS_Type=ECS Type
27 | Maximum_Total_Uses=Maximum Total Uses
28 | Instance_Name_Prefix=Instance Name Prefix
29 | DiskId=Disk Id
30 | NewDisk=Create new data volume
31 | SnapshotId=Snapshot Id
32 |
33 |
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/win/winrm/soap/MessageBuilder.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs.win.winrm.soap;
2 |
3 | import org.dom4j.Document;
4 | import org.dom4j.DocumentHelper;
5 | import org.dom4j.Element;
6 | import org.dom4j.Namespace;
7 | import org.dom4j.QName;
8 |
9 | public class MessageBuilder {
10 | private final Document doc = DocumentHelper.createDocument();
11 | private final Element envelope = doc.addElement(QName.get("Envelope", Namespaces.NS_SOAP_ENV));
12 |
13 | public MessageBuilder() {
14 | for (Namespace ns : Namespaces.mostUsed()) {
15 | envelope.add(ns);
16 | }
17 | }
18 |
19 | public HeaderBuilder newHeader() {
20 | return new HeaderBuilder();
21 | }
22 |
23 | public void addHeader(Header header) {
24 | Element elem = envelope.addElement(QName.get("Header", Namespaces.NS_SOAP_ENV));
25 | header.toElement(elem);
26 | }
27 |
28 | public void addBody(Element body) {
29 | Element elem = envelope.addElement(QName.get("Body", Namespaces.NS_SOAP_ENV));
30 | if (body != null)
31 | elem.add(body);
32 | }
33 |
34 | public Document build() {
35 | return doc;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/util/DateUtils.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs.util;
2 |
3 | import lombok.extern.slf4j.Slf4j;
4 |
5 | import java.text.DateFormat;
6 | import java.text.ParseException;
7 | import java.text.SimpleDateFormat;
8 | import java.util.Date;
9 | import java.util.TimeZone;
10 |
11 |
12 | @Slf4j
13 | public class DateUtils {
14 | private static final String pattern = "yyyy-MM-dd'T'HH:mm'Z'";
15 |
16 | public static Date parse(String time) {
17 | TimeZone tz = TimeZone.getTimeZone("UTC");
18 | try {
19 | DateFormat df = new SimpleDateFormat(pattern);
20 | df.setTimeZone(tz);
21 | return df.parse(time);
22 | } catch (ParseException e) {
23 | log.error("parse error. {}", time, e);
24 | }
25 | return null;
26 | }
27 |
28 | public static String format(Date time) {
29 | TimeZone tz = TimeZone.getTimeZone("UTC");
30 | DateFormat df = new SimpleDateFormat(pattern);
31 | try {
32 | df.setTimeZone(tz);
33 | return df.format(time);
34 | } catch (Exception e) {
35 | log.error("format error. {}", time, e);
36 | }
37 | return null;
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaCloud/computerSet.jelly:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | ${t.templateName}
12 |
13 |
14 |
15 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/test/java/com/alibabacloud/jenkins/ecs/util/AlibabaEcsFactoryTest.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs.util;
2 |
3 | import com.alibabacloud.jenkins.ecs.client.AlibabaEcsClient;
4 | import com.aliyuncs.auth.AlibabaCloudCredentials;
5 | import com.aliyuncs.auth.BasicCredentials;
6 | import lombok.extern.slf4j.Slf4j;
7 | import org.junit.Rule;
8 | import org.junit.Test;
9 | import org.jvnet.hudson.test.JenkinsRule;
10 |
11 | import static org.junit.Assert.assertNotNull;
12 | import static org.junit.Assert.assertTrue;
13 |
14 | /**
15 | * Created by kunlun.ykl on 2020/8/26.
16 | */
17 | @Slf4j
18 | public class AlibabaEcsFactoryTest {
19 | @Rule
20 | public JenkinsRule r = new JenkinsRule();
21 |
22 | @Test
23 | public void getInstanceTest() {
24 | AlibabaEcsFactory instance = AlibabaEcsFactory.getInstance();
25 | assertTrue(instance instanceof AlibabaEcsFactoryImpl);
26 | }
27 |
28 | @Test
29 | public void connectTest() {
30 | String ak = "";
31 | String sk = "";
32 | AlibabaCloudCredentials credentials = new BasicCredentials(ak, sk);
33 | String endpointName = "cn-hangzhou";
34 | AlibabaEcsClient connect = AlibabaEcsFactory.getInstance().connect(credentials, endpointName, true);
35 | assertNotNull(connect);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/UnixData.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs;
2 |
3 | import java.io.Serializable;
4 | import java.util.Objects;
5 |
6 | import hudson.Extension;
7 | import hudson.model.Descriptor;
8 | import org.kohsuke.stapler.DataBoundConstructor;
9 |
10 | public class UnixData extends EcsTypeData implements Serializable {
11 |
12 | @DataBoundConstructor
13 | public UnixData() {}
14 |
15 | @Override
16 | public boolean isWindows() {
17 | return false;
18 | }
19 |
20 | @Override
21 | public boolean isUnix() {
22 | return true;
23 | }
24 |
25 | @Override
26 | public String getBootDelay() {
27 | return null;
28 | }
29 |
30 | @Override
31 | public int hashCode() {
32 | return Objects.hash();
33 | }
34 |
35 | @Override
36 | public boolean equals(Object obj) {
37 | if (this == obj){
38 | return true;
39 | }
40 | if (obj == null){
41 | return false;
42 | }
43 | if (this.getClass() != obj.getClass()){
44 | return false;
45 | }
46 | return false;
47 |
48 | }
49 |
50 | @Extension
51 | public static class DescriptorImpl extends Descriptor {
52 | @Override
53 | public String getDisplayName() {
54 | return "unix";
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/win/winrm/request/GetOutputRequest.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs.win.winrm.request;
2 |
3 | import java.net.URI;
4 | import java.net.URISyntaxException;
5 | import java.net.URL;
6 |
7 | import com.alibabacloud.jenkins.ecs.win.winrm.soap.Namespaces;
8 | import org.dom4j.DocumentHelper;
9 | import org.dom4j.Element;
10 | import org.dom4j.QName;
11 |
12 | public class GetOutputRequest extends AbstractWinRMRequest {
13 |
14 | private final String shellId, commandId;
15 |
16 | public GetOutputRequest(URL url, String shellId, String commandId) {
17 | super(url);
18 | this.shellId = shellId;
19 | this.commandId = commandId;
20 | }
21 |
22 | @Override
23 | protected void construct() {
24 | try {
25 | defaultHeader().action(new URI("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Receive")).resourceURI(new URI("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd")).shellId(shellId);
26 |
27 | Element body = DocumentHelper.createElement(QName.get("Receive", Namespaces.NS_WIN_SHELL));
28 | body.addElement(QName.get("DesiredStream", Namespaces.NS_WIN_SHELL)).addAttribute("CommandId", commandId).addText("stdout stderr");
29 | setBody(body);
30 | } catch (URISyntaxException e) {
31 | throw new RuntimeException("Error while building request content", e);
32 | }
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaEcsFollowerTemplate/help-ecsType_zh_CN.html:
--------------------------------------------------------------------------------
1 |
2 | 选择此代理使用的 ECS 类型:
3 |
4 | Unix:通过 ssh 连接。
5 | Windows:通过 CIFS 和 WinRM/WinRS 连接。
6 |
7 |
8 |
9 | Windows ECS注意事项:
10 |
11 | 通过 CIFS(发送初始 Jenkins agent.jar)和 WinRM 访问阿里巴巴 Windows 代理以启动和连接
12 | 之后给代理。
13 |
14 | 此 windows ECS 必须配置:
15 |
16 | 允许 SMB over TCP(传入 TCP 端口 445)和 WinRM(传入 TCP 端口 5985)的安全组
17 | Windows 防火墙应允许通过 TCP 传入 SMB
18 | java 应该已安装并在 %PATH% 中可用
19 | 应使用以下命令启用 WinRM(有关详细信息,请参阅:Microsoft article 555966 ):
20 |
21 | winrm quickconfig
22 | winrm set winrm/config/service/Auth '@{Basic="true"}'
23 | winrm set winrm/config/service '@{AllowUnencrypted="true"}'
24 | winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="10240"}'
25 | 对于 https:
26 |
27 | 生成 Windows 证书
28 | 安装证书
29 | winrm create winrm/config/Listener?Address=*+Transport=HTTPS '@{Hostname="HOSTNAME"; CertificateThumbprint="THUMBPRINT"}'
30 |
31 |
32 |
33 |
34 |
35 |
36 | 最后确保将用户名设置为管理员并输入管理员密码。
37 |
38 |
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/win/winrm/request/SignalRequest.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs.win.winrm.request;
2 |
3 | import java.net.URI;
4 | import java.net.URISyntaxException;
5 | import java.net.URL;
6 |
7 | import com.alibabacloud.jenkins.ecs.win.winrm.soap.Namespaces;
8 | import org.dom4j.DocumentHelper;
9 | import org.dom4j.Element;
10 | import org.dom4j.QName;
11 |
12 | public class SignalRequest extends AbstractWinRMRequest {
13 |
14 | private final String commandId, shellId;
15 |
16 | public SignalRequest(URL url, String shellId, String commandId) {
17 | super(url);
18 | this.commandId = commandId;
19 | this.shellId = shellId;
20 | }
21 |
22 | @Override
23 | protected void construct() {
24 | try {
25 | defaultHeader().action(new URI("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Command")).resourceURI(new URI("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd")).shellId(shellId);
26 |
27 | Element body = DocumentHelper.createElement(QName.get("Signal", Namespaces.NS_WIN_SHELL)).addAttribute("CommandId", commandId);
28 |
29 | body.addElement(QName.get("Code", Namespaces.NS_WIN_SHELL)).addText("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/signal/terminate");
30 | setBody(body);
31 | } catch (URISyntaxException e) {
32 | throw new RuntimeException("Error while building request content", e);
33 | }
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/win/winrm/request/OpenShellRequest.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs.win.winrm.request;
2 |
3 | import java.net.URI;
4 | import java.net.URISyntaxException;
5 | import java.net.URL;
6 | import java.util.Arrays;
7 |
8 | import com.alibabacloud.jenkins.ecs.win.winrm.soap.Namespaces;
9 | import com.alibabacloud.jenkins.ecs.win.winrm.soap.Option;
10 | import org.dom4j.DocumentHelper;
11 | import org.dom4j.Element;
12 | import org.dom4j.QName;
13 |
14 | public class OpenShellRequest extends AbstractWinRMRequest {
15 |
16 | public OpenShellRequest(URL url) {
17 | super(url);
18 | }
19 |
20 | protected void construct() {
21 | try {
22 | defaultHeader().action(new URI("http://schemas.xmlsoap.org/ws/2004/09/transfer/Create")).resourceURI(new URI("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd")).options(Arrays.asList(new Option("WINRS_NOPROFILE", "FALSE"), new Option("WINRS_CODEPAGE", "437")));
23 |
24 | Element body = DocumentHelper.createElement(QName.get("Shell", Namespaces.NS_WIN_SHELL));
25 | body.addElement(QName.get("InputStreams", Namespaces.NS_WIN_SHELL)).addText("stdin");
26 | body.addElement(QName.get("OutputStreams", Namespaces.NS_WIN_SHELL)).addText("stdout stderr");
27 | setBody(body);
28 | } catch (URISyntaxException e) {
29 | throw new RuntimeException("Error while building request content", e);
30 | }
31 |
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaEcsStep/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/win/winrm/request/ExecuteCommandRequest.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs.win.winrm.request;
2 |
3 | import java.net.URI;
4 | import java.net.URISyntaxException;
5 | import java.net.URL;
6 | import java.util.Collections;
7 |
8 | import com.alibabacloud.jenkins.ecs.win.winrm.soap.Namespaces;
9 | import com.alibabacloud.jenkins.ecs.win.winrm.soap.Option;
10 | import org.dom4j.DocumentHelper;
11 | import org.dom4j.Element;
12 | import org.dom4j.QName;
13 |
14 | public class ExecuteCommandRequest extends AbstractWinRMRequest {
15 |
16 | private final String shellId;
17 | private final String command;
18 |
19 | public ExecuteCommandRequest(URL url, String shellId, String command) {
20 | super(url);
21 | this.command = command;
22 | this.shellId = shellId;
23 | }
24 |
25 | @Override
26 | protected void construct() {
27 | try {
28 | defaultHeader().action(new URI("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Command")).resourceURI(new URI("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd")).shellId(shellId).options(Collections.singletonList(new Option("WINRS_CONSOLEMODE_STDIN", "FALSE")));
29 |
30 | Element body = DocumentHelper.createElement(QName.get("CommandLine", Namespaces.NS_WIN_SHELL));
31 | body.addElement(QName.get("Command", Namespaces.NS_WIN_SHELL)).addText("\"" + command + "\"");
32 | setBody(body);
33 | } catch (URISyntaxException e) {
34 | throw new RuntimeException("Error while building request content", e);
35 | }
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/EcsHostAddressProvider.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs;
2 |
3 | import java.util.List;
4 | import java.util.Optional;
5 |
6 | import com.aliyuncs.ecs.model.v20140526.DescribeInstancesResponse.Instance.VpcAttributes;
7 | import org.apache.commons.lang.StringUtils;
8 |
9 | import com.aliyuncs.ecs.model.v20140526.DescribeInstancesResponse.Instance;
10 |
11 | import static com.alibabacloud.jenkins.ecs.ConnectionStrategy.*;
12 |
13 |
14 | public class EcsHostAddressProvider {
15 |
16 | public static String windows(Instance instance, ConnectionStrategy strategy) {
17 | if (strategy.equals(PRIVATE_IP)) {
18 | return getPrivateIpAddress(instance);
19 | } else if (strategy.equals(PUBLIC_IP)) {
20 | return getPublicIpAddress(instance);
21 | } else {
22 | throw new IllegalArgumentException("Could not windows host address for strategy = " + strategy.toString());
23 | }
24 | }
25 |
26 |
27 | private static String getPublicIpAddress(Instance instance) {
28 | List publicIpAddress = instance.getPublicIpAddress();
29 | return publicIpAddress.get(0);
30 | }
31 |
32 |
33 | private static String getPrivateIpAddress(Instance instance) {
34 | VpcAttributes vpcAttributes = instance.getVpcAttributes();
35 | List privateIpAddress = vpcAttributes.getPrivateIpAddress();
36 | return privateIpAddress.get(0);
37 | }
38 |
39 | private static Optional filterNonEmpty(String value) {
40 | return Optional.ofNullable(value).filter(StringUtils::isNotEmpty);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/win/winrm/request/SendInputRequest.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs.win.winrm.request;
2 |
3 | import java.net.URI;
4 | import java.net.URISyntaxException;
5 | import java.net.URL;
6 | import java.util.Base64;
7 |
8 | import com.alibabacloud.jenkins.ecs.win.winrm.soap.Namespaces;
9 | import org.dom4j.DocumentHelper;
10 | import org.dom4j.Element;
11 | import org.dom4j.QName;
12 |
13 | public class SendInputRequest extends AbstractWinRMRequest {
14 |
15 | private final String commandId, shellId;
16 | byte[] input;
17 |
18 | public SendInputRequest(URL url, byte[] input, String shellId, String commandId) {
19 | super(url);
20 | this.input = input.clone();
21 | this.commandId = commandId;
22 | this.shellId = shellId;
23 | }
24 |
25 | @Override
26 | protected void construct() {
27 | try {
28 | defaultHeader().action(new URI("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Send")).resourceURI(new URI("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd")).shellId(shellId);
29 |
30 | Element body = DocumentHelper.createElement(QName.get("Send", Namespaces.NS_WIN_SHELL));
31 | body.addElement(QName.get("Stream", Namespaces.NS_WIN_SHELL))
32 | .addAttribute("Name", "stdin")
33 | .addAttribute("CommandId", commandId)
34 | .addText(Base64.getEncoder().encodeToString(input));
35 | setBody(body);
36 | } catch (URISyntaxException e) {
37 | throw new RuntimeException("Error while building request content", e);
38 | }
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaEcsFollowerTemplate/config_zh_CN.properties:
--------------------------------------------------------------------------------
1 | TestCreateEcs=\u6D4B\u8BD5\u521B\u5EFAECS
2 | Description=\u63cf\u8ff0
3 | Image=\u955c\u50cfID
4 | Availability_Zone=\u53ef\u7528\u533a
5 | VSW=\u865a\u62df\u4ea4\u6362\u673aID
6 | Instance_Charge_Type=\u4ed8\u8d39\u7c7b\u578b
7 | Choose_Instance_Type=\u5b9e\u4f8b\u89c4\u683c
8 | User_Data=\u81ea\u5b9a\u4e49\u6570\u636e
9 | Init_Script=\u521d\u59cb\u5316\u811a\u672c
10 | Labels=\u8282\u70b9\u6807\u7b7e
11 | Remote_FS_root=\u8fdc\u7a0b\u6839\u76ee\u5f55
12 | Remote_user=\u8fdc\u7a0b\u7528\u6237
13 | System_Disk_Category=\u7cfb\u7edf\u76d8\u7c7b\u578b
14 | System_Disk_Size=\u7cfb\u7edf\u76d8\u5927\u5c0f
15 | Instance_Cap=\u5b9e\u4f8b\u4e0a\u9650
16 | Minimum_number_of_instances=\u6700\u5C0F\u5B9E\u4F8B\u6570
17 | Idle_Termination_Time_In_Minutes=\u7a7a\u95f2\u7ec8\u6b62\u65f6\u95f4
18 | IdleTTDesc=\u4ECE\u8282\u70B9\u5728\u88AB\u7EC8\u6B62\u4E4B\u524D\u4FDD\u6301\u7A7A\u95F2\u72B6\u6001\u7684\u65F6\u95F4\uFF08\u5355\u4F4D\uFF1A\u5206\u949F\uFF09
19 | Number_of_Executors=\u6267\u884c\u8005\u6570\u91cf
20 | Launch_Timeout_In_Seconds=\u542f\u52a8\u8d85\u65f6\uff08\u5355\u4f4d\u4e3a\u79d2\uff09
21 | Advanced=\u9AD8\u7EA7...
22 | MountDataDisk=\u6302\u8f7d\u6570\u636e\u5377
23 | DataDiskCategory=\u6570\u636e\u5377\u7c7b\u578b
24 | DataDiskSize=\u6570\u636e\u5377\u5927\u5c0f
25 | MountQuantity=\u6570\u636e\u5377\u5927\u5c0f
26 | ECS_Type=\u7c7b\u578b
27 | Maximum_Total_Uses=\u5355\u8282\u70b9\u6700\u5927\u53ef\u590d\u7528\u6b21\u6570
28 | Instance_Name_Prefix=\u5b9e\u4f8b\u540d\u79f0\u524d\u7f00
29 | DiskId=\u4e91\u76d8id
30 | NewDisk=\u521b\u5efa\u65b0\u7684\u6570\u636e\u5377
31 | SnapshotId=\u5feb\u7167id
32 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/Messages_en.properties:
--------------------------------------------------------------------------------
1 | shutdown=Jenkins instance is quieting down
2 | terminating=Jenkins instance is terminating
3 | viaContextPath=/computer/
4 | slaveTemplate_provision_error=slaveTemplate.provision error
5 |
6 | AlibabaECSCloud.NonUniqName=Duplicate Cloud Name
7 | AlibabaECSCloud.PermissionError=permission is error
8 | AlibabaECSCloud.NotSpecifiedCredentials=Credentials not specified
9 | AlibabaECSCloud.NotFoundCredentials=Credentials not found
10 | AlibabaECSCloud.IllegalAkSk=Illegal ak/sk:
11 | AlibabaECSCloud.NotSpecifiedSSHPrivateKey=SSH PrivateKey not specified
12 | AlibabaECSCloud.IllegalSSHPrivateKey=Illegal SSH PrivateKey:
13 | AlibabaECSCloud.ConnectionSuccess=Connection OK
14 | AlibabaECSCloud.SSHPrivateKeyValidateError=SSH PrivateKey validate error
15 | AlibabaECSCloud.NotSpecifiedDescription=Description not specified
16 | AlibabaECSCloud.MinimumNumberOfInstancesCheckError=Minimum number of instances must not be larger than AMI Instance Cap %d
17 | AlibabaECSCloud.MinimumNumberOfInstancesError=Minimum number of instances must be a non-negative integer (or null)
18 | AlibabaECSCloud.Success=Success
19 | AlibabaECSCloud.Error=Error
20 | AlibabaECSCloud.NotFoundInstanceType=instanceType not found
21 | AlibabaECSCloud.DiskQuantityError=Please fill in the correct data volume quantity。Current specs max support count:
22 | AlibabaECSCloud.NotFoundMountQuantity=Mount Quantity not specified
23 | AlibabaECSCloud.NotSpecifiedPassword=Password not specified
24 | abaECSCloud.DiskDoesNotExist=disk does not exist
25 | AlibabaECSCloud.MountMultipleDisksError=Disk does not support multiple attach
26 | AlibabaECSCloud.InstanceTypeDoesNotSupportMultiAttach=The instanceType of the specified instance does not support multi attach disk
27 |
28 |
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/win/winrm/soap/Namespaces.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs.win.winrm.soap;
2 |
3 | import java.util.Arrays;
4 | import java.util.Collections;
5 | import java.util.List;
6 |
7 | import org.dom4j.Namespace;
8 |
9 | public class Namespaces {
10 | public static final Namespace NS_SOAP_ENV = Namespace.get("env", "http://www.w3.org/2003/05/soap-envelope");
11 | public static final Namespace NS_ADDRESSING = Namespace.get("a", "http://schemas.xmlsoap.org/ws/2004/08/addressing");
12 | public static final Namespace NS_CIMBINDING = Namespace.get("b", "http://schemas.dmtf.org/wbem/wsman/1/cimbinding.xsd");
13 | public static final Namespace NS_ENUM = Namespace.get("n", "http://schemas.xmlsoap.org/ws/2004/09/enumeration");
14 | public static final Namespace NS_TRANSFER = Namespace.get("x", "http://schemas.xmlsoap.org/ws/2004/09/transfer");
15 | public static final Namespace NS_WSMAN_DMTF = Namespace.get("w", "http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd");
16 | public static final Namespace NS_WSMAN_MSFT = Namespace.get("p", "http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd");
17 | public static final Namespace NS_SCHEMA_INST = Namespace.get("xsi", "http://www.w3.org/2001/XMLSchema-instance");
18 | public static final Namespace NS_WIN_SHELL = Namespace.get("rsp", "http://schemas.microsoft.com/wbem/wsman/1/windows/shell");
19 | public static final Namespace NS_WSMAN_FAULT = Namespace.get("f", "http://schemas.microsoft.com/wbem/wsman/1/wsmanfault");
20 |
21 | private Namespaces() {
22 | }
23 |
24 | public static final List mostUsed() {
25 | return Collections.unmodifiableList(Arrays.asList(NS_SOAP_ENV, NS_ADDRESSING, NS_WIN_SHELL, NS_WSMAN_DMTF, NS_WSMAN_MSFT));
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/Messages.properties:
--------------------------------------------------------------------------------
1 | shutdown=Jenkins instance is quieting down
2 | terminating=Jenkins instance is terminating
3 | viaContextPath=/computer/
4 | slaveTemplate_provision_error=slaveTemplate.provision error
5 |
6 | AlibabaECSCloud.NonUniqName=Duplicate Cloud Name
7 | AlibabaECSCloud.PermissionError=permission is error
8 | AlibabaECSCloud.NotSpecifiedCredentials=Credentials not specified
9 | AlibabaECSCloud.NotFoundCredentials=Credentials not found
10 | AlibabaECSCloud.IllegalAkSk=Illegal ak/sk:
11 | AlibabaECSCloud.NotSpecifiedSSHPrivateKey=SSH PrivateKey not specified
12 | AlibabaECSCloud.IllegalSSHPrivateKey=Illegal SSH PrivateKey:
13 | AlibabaECSCloud.ConnectionSuccess=Connection OK
14 | AlibabaECSCloud.SSHPrivateKeyValidateError=SSH PrivateKey validate error
15 | AlibabaECSCloud.NotSpecifiedDescription=Description not specified
16 | AlibabaECSCloud.MinimumNumberOfInstancesCheckError=Minimum number of instances must not be larger than AMI Instance Cap %d
17 | AlibabaECSCloud.MinimumNumberOfInstancesError=Minimum number of instances must be a non-negative integer (or null)
18 | AlibabaECSCloud.Success=Success
19 | AlibabaECSCloud.Error=Error
20 | AlibabaECSCloud.NotFoundInstanceType=instanceType not found
21 | AlibabaECSCloud.DiskQuantityError=Please fill in the correct data volume quantity。Current specs max support count:
22 | AlibabaECSCloud.NotFoundMountQuantity=Mount Quantity not specified
23 | OfflineCause.SSLException=The instance SSL Key check failed
24 | AlibabaECSCloud.NotSpecifiedPassword=Password not specified
25 | AlibabaECSCloud.DiskDoesNotExist=disk does not exist
26 | AlibabaECSCloud.MountMultipleDisksError=Disk does not support multiple attach
27 | AlibabaECSCloud.InstanceTypeDoesNotSupportMultiAttach=The instanceType of the specified instance does not support multi attach disk
28 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaEcsTag/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaEcsFollowerTemplate/help-ecsType.html:
--------------------------------------------------------------------------------
1 |
2 | Choose the type of ECS this agent uses:
3 |
4 | Unix: connected to with ssh.
5 | Windows: connected to with CIFS and WinRM/WinRS.
6 |
7 |
8 |
9 | Notes for Windows ECS:
10 |
11 | Alibaba Windows agents are accessed with CIFS (to send the initial Jenkins agent.jar) and WinRM to launch and connect
12 | to the agent afterward.
13 |
14 | This windows ECS must be configured with:
15 |
16 | a security group allowing SMB over TCP (incoming TCP port 445) and WinRM (incoming TCP port 5985)
17 | windows firewall should allow incoming SMB over TCP
18 | java should be installed and available in the %PATH%
19 | WinRM should be enabled with the following commands (for more information see: Microsoft article 555966 ):
20 |
21 | winrm quickconfig
22 | winrm set winrm/config/service/Auth '@{Basic="true"}'
23 | winrm set winrm/config/service '@{AllowUnencrypted="true"}'
24 | winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="10240"}'
25 | For https:
26 |
31 |
32 |
33 |
34 |
35 |
36 | Finally make sure to set the username to Administrator and enter the administrator password.
37 |
38 |
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/util/LogHelper.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs.util;
2 |
3 | import java.io.PrintStream;
4 | import java.util.logging.Level;
5 | import java.util.logging.LogRecord;
6 | import java.util.logging.SimpleFormatter;
7 |
8 | import hudson.model.TaskListener;
9 | import org.slf4j.Logger;
10 |
11 | /**
12 | * Created by kunlun.ykl on 2020/9/25.
13 | */
14 | public class LogHelper {
15 | private static final SimpleFormatter sf = new SimpleFormatter();
16 |
17 | public static void debug(Logger logger, TaskListener listener, String message, Throwable e) {
18 | logger.debug(message, e);
19 | remoteLog(logger.getName(), Level.FINEST, listener, message, e);
20 | }
21 |
22 | public static void info(Logger logger, TaskListener listener, String message, Throwable e) {
23 | logger.info(message, e);
24 | remoteLog(logger.getName(), Level.INFO, listener, message, e);
25 | }
26 |
27 | public static void warn(Logger logger, TaskListener listener, String message, Throwable e) {
28 | logger.warn(message, e);
29 | remoteLog(logger.getName(), Level.WARNING, listener, message, e);
30 | }
31 |
32 | public static void error(Logger logger, TaskListener listener, String message, Throwable e) {
33 | logger.error(message, e);
34 | remoteLog(logger.getName(), Level.SEVERE, listener, message, e);
35 | }
36 |
37 | public static void remoteLog(String loggerName, Level level, TaskListener listener, String message, Throwable e) {
38 | if (listener != null) {
39 | if (e != null) {
40 | message += " Exception: " + e;
41 | }
42 | LogRecord lr = new LogRecord(level, message);
43 | lr.setLoggerName(loggerName);
44 | PrintStream printStream = listener.getLogger();
45 | printStream.print(sf.format(lr));
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/win/winrm/NegotiateNTLMSchemaFactory.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs.win.winrm;
2 |
3 | import org.apache.http.Header;
4 | import org.apache.http.HttpRequest;
5 | import org.apache.http.auth.AuthScheme;
6 | import org.apache.http.auth.AuthSchemeProvider;
7 | import org.apache.http.auth.AuthenticationException;
8 | import org.apache.http.auth.Credentials;
9 | import org.apache.http.auth.NTCredentials;
10 | import org.apache.http.client.config.AuthSchemes;
11 | import org.apache.http.impl.auth.NTLMScheme;
12 | import org.apache.http.message.BufferedHeader;
13 | import org.apache.http.protocol.HttpContext;
14 | import org.apache.http.util.CharArrayBuffer;
15 |
16 | public class NegotiateNTLMSchemaFactory implements AuthSchemeProvider {
17 |
18 | public AuthScheme create(HttpContext context) {
19 | return new NegotiateNTLM();
20 | }
21 |
22 | public static class NegotiateNTLM extends NTLMScheme {
23 | @Override
24 | public String getSchemeName() {
25 | return AuthSchemes.SPNEGO;
26 | }
27 |
28 | @Override
29 | public Header authenticate(Credentials credentials, HttpRequest request) throws AuthenticationException {
30 | Credentials ntCredentials = credentials;
31 | if (!(credentials instanceof NTCredentials)) {
32 | ntCredentials = new NTCredentials(credentials.getUserPrincipal().getName(), credentials.getPassword(), null, null);
33 | }
34 | Header header = super.authenticate(ntCredentials, request);
35 | //need replace NTLM with Negotiate
36 | CharArrayBuffer buffer = new CharArrayBuffer(512);
37 | buffer.append(header.getName());
38 | buffer.append(": ");
39 | buffer.append(header.getValue().replaceFirst("NTLM", "Negotiate"));
40 | return new BufferedHeader(buffer);
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/Messages_zh_CN.properties:
--------------------------------------------------------------------------------
1 | shutdown=Jenkins instance is quieting down
2 | terminating=jenkins \u5b9e\u4f8b\u6b63\u5728\u7ec8\u6b62
3 | viaContextPath=/computer/
4 | slaveTemplate_provision_error=slavetemplate.provision \u9519\u8bef
5 |
6 | AlibabaECSCloud.NonUniqName=\u91cd\u590d\u7684\u4e91\u540d\u79f0
7 | AlibabaECSCloud.PermissionError=\u6743\u9650\u662f\u9519\u8bef\u7684
8 | AlibabaECSCloud.NotSpecifiedCredentials=\u672a\u6307\u5b9a\u51ed\u636e
9 | AlibabaECSCloud.NotFoundCredentials=\u672a\u627e\u5230\u51ed\u636e
10 | AlibabaECSCloud.IllegalAkSk=\u975e\u6cd5ak/sk\uff1a
11 | AlibabaECSCloud.NotSpecifiedSSHPrivateKey=\u672a\u6307\u5b9a SSH \u79c1\u94a5
12 | AlibabaECSCloud.IllegalSSHPrivateKey=\u975e\u6cd5 SSH \u79c1\u94a5\uff1a
13 | AlibabaECSCloud.ConnectionSuccess=\u8fde\u63a5\u6b63\u5e38
14 | AlibabaECSCloud.SSHPrivateKeyValidateError=SSH \u79c1\u94a5\u9a8c\u8bc1\u9519\u8bef
15 | AlibabaECSCloud.NotSpecifiedDescription=\u672a\u6307\u5b9a\u63cf\u8ff0
16 | AlibabaECSCloud.MinimumNumberOfInstancesCheckError=\u6700\u5c0f\u5b9e\u4f8b\u6570\u4e0d\u5f97\u5927\u4e8e AMI \u5b9e\u4f8b\u4e0a\u9650 %d
17 | AlibabaECSCloud.MinimumNumberOfInstancesError=\u6700\u5c0f\u5b9e\u4f8b\u6570\u5fc5\u987b\u662f\u975e\u8d1f\u6574\u6570\uff08\u6216 null\uff09
18 | AlibabaECSCloud.Success=\u6210\u529f
19 | AlibabaECSCloud.Error=\u5f02\u5e38
20 | AlibabaECSCloud.NotFoundInstanceType=\u672a\u627e\u5230\u5b9e\u4f8b\u89c4\u683c
21 | AlibabaECSCloud.DiskQuantityError=\u8bf7\u586b\u5199\u6b63\u786e\u7684\u6570\u636e\u91cf\u3002\u5f53\u524d\u89c4\u683c\u6700\u5927\u652f\u6301\u6570\uff1a
22 | AlibabaECSCloud.NotFoundMountQuantity=\u672a\u6307\u5b9a\u6302\u8f7d\u6570\u91cf
23 | AlibabaECSCloud.NotSpecifiedPassword=\u672a\u6307\u5b9a\u5bc6\u7801
24 | abaECSCloud.DiskDoesNotExist=\u78c1\u76d8\u4e0d\u5b58\u5728
25 | AlibabaECSCloud.MountMultipleDisksError=\u6570\u636e\u5377\u4e0d\u652f\u6301\u591a\u6302\u8f7d
26 | AlibabaECSCloud.InstanceTypeDoesNotSupportMultiAttach=\u6307\u5b9a\u5b9e\u4f8b\u89c4\u683c\u4e0d\u652f\u6301\u591a\u6302\u76d8
--------------------------------------------------------------------------------
/src/test/java/com/alibabacloud/jenkins/ecs/AlibabaCloudTest.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs;
2 |
3 | import java.util.List;
4 |
5 | import com.alibabacloud.credentials.plugin.auth.AlibabaCredentials;
6 | import com.alibabacloud.credentials.plugin.util.CredentialsHelper;
7 | import com.google.common.collect.Lists;
8 | import org.junit.Rule;
9 | import org.junit.Test;
10 | import org.junit.runner.RunWith;
11 | import org.jvnet.hudson.test.JenkinsRule;
12 | import org.mockito.BDDMockito;
13 | import org.powermock.api.mockito.PowerMockito;
14 | import org.powermock.core.classloader.annotations.PowerMockIgnore;
15 | import org.powermock.core.classloader.annotations.PrepareForTest;
16 | import org.powermock.modules.junit4.PowerMockRunner;
17 |
18 | import static org.junit.Assert.assertNotNull;
19 | import static org.powermock.api.mockito.PowerMockito.when;
20 |
21 | /**
22 | * Created by kunlun.ykl on 2020/9/29.
23 | */
24 | @PowerMockIgnore(
25 | {"javax.crypto.*", "org.hamcrest.*", "javax.net.ssl.*", "com.sun.org.apache.xerces.*", "javax.xml.*", "org.xml.*"})
26 | @RunWith(PowerMockRunner.class)
27 | @PrepareForTest({CredentialsHelper.class})
28 | public class AlibabaCloudTest {
29 | @Rule
30 | public JenkinsRule r = new JenkinsRule();
31 |
32 | @Test
33 | public void cloudTest() {
34 | String credentialsId = "sampleCredentialsId";
35 | String sshKey = null;
36 | AlibabaCredentials credentials = new AlibabaCredentials("ak",
37 | "sk");
38 | PowerMockito.mockStatic(CredentialsHelper.class);
39 | BDDMockito.given(CredentialsHelper.getCredentials(credentialsId)).willReturn(credentials);
40 | when(CredentialsHelper.getCredentials(credentialsId)).thenReturn(credentials);
41 | List tags = Lists.newArrayList();
42 | // AlibabaCloud cloud = new AlibabaCloud("testCloud", credentialsId, sshKey, "cn-beijing", "centos", "test-vpc",
43 | // "test-sg", "cn-beijing-a", "test-vsw", "ecs.c5.large", 1,
44 | // "", "", "", "", 20, false, false, tags ,"Spot");
45 | // assertNotNull(cloud);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaEcsFollowerTemplate/help-remoteFs.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | An agent needs to have a directory dedicated to Jenkins. Specify
4 | the path to this directory on the agent. It is best to use
5 | an absolute path, such as /var/jenkins or c:\jenkins.
6 | This should be a path local to the agent machine. There is no need for
7 | this path to be visible from the controller.
8 |
9 | Agents do not maintain important data; all job configurations, build logs and
10 | artifacts are stored on the controller, so it would be possible to use a temporary
11 | directory as the agent root directory.
12 |
13 | However, by giving an agent a directory that is not deleted after a machine
14 | reboot, for example, the agent can cache data such as tool installations, or
15 | build workspaces. This prevents unnecessary downloading of tools, or checking
16 | out source code again when builds start to run on this agent again after a
17 | reboot.
18 |
19 | If you use a relative path, such as ./jenkins-agent, the path will be
20 | relative to the working directory provided by the Launch method .
21 |
22 | For launchers where Jenkins controls starting the agent process, such
23 | as SSH, the current working directory will typically be consistent,
24 | e.g. the user's home directory.
25 |
26 | For launchers where Jenkins has no control over starting the agent process,
27 | such as inbound agents launched from the command line,
28 | the current working directory may change between
29 | launches of the agent and use of a relative path may prove problematic.
30 |
31 | The principal issue encountered when using relative paths with inbound launchers
32 | is the proliferation of stale workspaces and tool installation
33 | on the agent machine. This can cause disk space issues.
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/src/test/java/com/alibabacloud/jenkins/ecs/AlibabaEcsUnixComputerLauncherTest.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs;
2 |
3 | import java.io.IOException;
4 | import java.io.PrintStream;
5 | import java.util.List;
6 |
7 | import javax.annotation.Nonnull;
8 |
9 | import com.google.common.collect.Lists;
10 | import hudson.model.Descriptor.FormException;
11 | import hudson.model.TaskListener;
12 | import hudson.slaves.SlaveComputer;
13 | import org.junit.Ignore;
14 | import org.junit.Rule;
15 | import org.junit.Test;
16 | import org.jvnet.hudson.test.JenkinsRule;
17 |
18 | /**
19 | * Created by kunlun.ykl on 2020/8/25.
20 | */
21 | @Ignore
22 | public class AlibabaEcsUnixComputerLauncherTest {
23 | @Rule
24 | public JenkinsRule r = new JenkinsRule();
25 |
26 | @Test
27 | public void launchTest() throws IOException, FormException {
28 | AlibabaEcsUnixComputerLauncher launcher = new AlibabaEcsUnixComputerLauncher();
29 | String ecsInstanceId = "i-abc";
30 | String name = "hanting-test";
31 | String remoteFS = "/root";
32 | String cloudName = "alibaba-ecs-cloud";
33 | String labelString = "myCI";
34 | String initScript = "";
35 | String templateName = "alibaba-ecs-cloud-t1";
36 | String userData = "userData-test";
37 | int numExecutors = 1;
38 | int launchTimeout = 10000;
39 | List tags = Lists.newArrayList();
40 | String idleTerminationMinutes = "30";
41 | UnixData unixData = new UnixData();
42 | AlibabaEcsSpotFollower follower = new AlibabaEcsSpotFollower(ecsInstanceId, name, remoteFS, cloudName,
43 | labelString, initScript, templateName, numExecutors, launchTimeout, tags, idleTerminationMinutes, userData,
44 | unixData, "", 1, "");
45 | r.jenkins.addNode(follower);
46 |
47 | SlaveComputer slaveComputer = new SlaveComputer(follower);
48 | TaskListener listener = new TaskListener() {
49 | @Nonnull
50 | @Override
51 | public PrintStream getLogger() {
52 | return System.out;
53 | }
54 | };
55 | launcher.launch(slaveComputer, listener);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/AlibabaEcsComputerLauncher.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs;
2 |
3 | import com.alibabacloud.jenkins.ecs.exception.AlibabaEcsException;
4 | import hudson.model.Slave;
5 | import hudson.model.TaskListener;
6 | import hudson.slaves.ComputerLauncher;
7 | import hudson.slaves.SlaveComputer;
8 |
9 | import java.io.IOException;
10 | import java.util.logging.Level;
11 | import java.util.logging.Logger;
12 |
13 | /**
14 | * Created by kunlun.ykl on 2020/8/24.
15 | */
16 | public abstract class AlibabaEcsComputerLauncher extends ComputerLauncher {
17 | private static final Logger LOGGER = Logger.getLogger(AlibabaEcsComputerLauncher.class.getName());
18 |
19 | @Override
20 | public void launch(SlaveComputer slaveComputer, TaskListener listener) {
21 | try {
22 | launchScript((AlibabaEcsComputer) slaveComputer, listener);
23 | } catch (AlibabaEcsException | IOException e) {
24 | e.printStackTrace(listener.error(e.getMessage()));
25 | Slave node = slaveComputer.getNode();
26 | if (node != null && node instanceof AlibabaEcsSpotFollower) {
27 | LOGGER.log(Level.WARNING, String.format("Terminating the ecs agent %s due a problem launching or connecting to it", slaveComputer.getName()), e);
28 | ((AlibabaEcsSpotFollower) node).terminate();
29 | }
30 | } catch (InterruptedException e) {
31 | Thread.currentThread().interrupt();
32 | e.printStackTrace(listener.error(e.getMessage()));
33 | Slave node = slaveComputer.getNode();
34 | if (node != null && node instanceof AlibabaEcsSpotFollower) {
35 | LOGGER.log(Level.WARNING, String.format("Terminating the ecs agent %s due a problem launching or connecting to it", slaveComputer.getName()), e);
36 | ((AlibabaEcsSpotFollower) node).terminate();
37 | }
38 | }
39 | }
40 |
41 | /**
42 | * Stage 2 of the launch. Called after the ECS instance comes up.
43 | */
44 | protected abstract void launchScript(AlibabaEcsComputer computer, TaskListener listener) throws AlibabaEcsException, IOException, InterruptedException;
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaCloud/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/win/winrm/request/AbstractWinRMRequest.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs.win.winrm.request;
2 |
3 | import java.net.URI;
4 | import java.net.URISyntaxException;
5 | import java.net.URL;
6 | import java.util.UUID;
7 |
8 | import com.alibabacloud.jenkins.ecs.win.winrm.soap.HeaderBuilder;
9 | import com.alibabacloud.jenkins.ecs.win.winrm.soap.MessageBuilder;
10 | import org.dom4j.Document;
11 | import org.dom4j.Element;
12 |
13 | public abstract class AbstractWinRMRequest implements WinRMRequest {
14 |
15 | protected MessageBuilder message = new MessageBuilder();
16 | protected HeaderBuilder header = message.newHeader();
17 |
18 | protected String timeout = "PT60S";
19 | protected int envelopSize = 153600;
20 | protected String locale = "en-US";
21 |
22 | protected URL url;
23 |
24 | public AbstractWinRMRequest(URL url) {
25 | this.url = url;
26 | }
27 |
28 | protected abstract void construct();
29 |
30 | public Document build() {
31 | construct();
32 | return message.build();
33 | }
34 |
35 | protected HeaderBuilder defaultHeader() throws URISyntaxException {
36 | return header.to(url.toURI()).replyTo(new URI("http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous")).maxEnvelopeSize(envelopSize).id(generateUUID()).locale(locale).timeout(timeout);
37 | }
38 |
39 | protected void setBody(Element body) {
40 | message.addHeader(header.build());
41 | message.addBody(body);
42 | }
43 |
44 | protected String generateUUID() {
45 | return "uuid:" + UUID.randomUUID().toString().toUpperCase();
46 | }
47 |
48 | public String getTimeout() {
49 | return timeout;
50 | }
51 |
52 | public void setTimeout(String timeout) {
53 | this.timeout = timeout;
54 | }
55 |
56 | public int getEnvelopSize() {
57 | return envelopSize;
58 | }
59 |
60 | public void setEnvelopSize(int envelopSize) {
61 | this.envelopSize = envelopSize;
62 | }
63 |
64 | public String getLocale() {
65 | return locale;
66 | }
67 |
68 | public void setLocale(String locale) {
69 | this.locale = locale;
70 | }
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/win/winrm/soap/HeaderBuilder.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs.win.winrm.soap;
2 |
3 | import java.net.URI;
4 | import java.util.ArrayList;
5 | import java.util.Collections;
6 | import java.util.List;
7 |
8 | public class HeaderBuilder {
9 | private String to;
10 | private String replyTo;
11 | private String maxEnvelopeSize;
12 | private String timeout;
13 | private String locale;
14 | private String id;
15 | private String action;
16 | private String shellId;
17 | private String resourceURI;
18 | private List optionSet;
19 |
20 | HeaderBuilder() {
21 | }
22 |
23 | public HeaderBuilder to(URI address) {
24 | to = address.toString();
25 | return this;
26 | }
27 |
28 | public HeaderBuilder replyTo(URI address) {
29 | replyTo = address.toString();
30 | return this;
31 | }
32 |
33 | public HeaderBuilder maxEnvelopeSize(int size) {
34 | maxEnvelopeSize = Integer.toString(size);
35 | return this;
36 | }
37 |
38 | public HeaderBuilder id(String id) {
39 | this.id = id;
40 | return this;
41 | }
42 |
43 | public HeaderBuilder locale(String locale) {
44 | this.locale = locale;
45 | return this;
46 | }
47 |
48 | public HeaderBuilder timeout(String timeout) {
49 | this.timeout = timeout;
50 | return this;
51 | }
52 |
53 | public HeaderBuilder action(URI uri) {
54 | this.action = uri.toString();
55 | return this;
56 | }
57 |
58 | public HeaderBuilder shellId(String shellId) {
59 | this.shellId = shellId;
60 | return this;
61 | }
62 |
63 | public HeaderBuilder resourceURI(URI uri) {
64 | this.resourceURI = uri.toString();
65 | return this;
66 | }
67 |
68 | public HeaderBuilder options(List options) {
69 | this.optionSet = options != null ? Collections.unmodifiableList(new ArrayList<>(options)) : Collections.emptyList();
70 | return this;
71 | }
72 |
73 | public Header build() {
74 | return new Header(to, replyTo, maxEnvelopeSize, timeout, locale, id, action, shellId, resourceURI, optionSet);
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/test/java/com/alibabacloud/jenkins/ecs/AlibabaEcsComputerTest.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs;
2 |
3 | import java.io.IOException;
4 | import java.util.List;
5 |
6 | import com.aliyuncs.ecs.model.v20140526.DescribeInstancesResponse;
7 | import com.google.common.collect.Lists;
8 | import hudson.slaves.SlaveComputer;
9 | import org.junit.Before;
10 | import org.junit.Rule;
11 | import org.junit.Test;
12 | import org.junit.runner.RunWith;
13 | import org.jvnet.hudson.test.JenkinsRule;
14 | import org.mockito.Mock;
15 | import org.powermock.core.classloader.annotations.PowerMockIgnore;
16 | import org.powermock.core.classloader.annotations.PrepareForTest;
17 | import org.powermock.modules.junit4.PowerMockRunner;
18 |
19 | import static org.junit.Assert.assertEquals;
20 | import static org.mockito.Mockito.when;
21 |
22 | /**
23 | * Created by kunlun.ykl on 2020/9/30.
24 | */
25 | @PowerMockIgnore(
26 | {"javax.crypto.*", "org.hamcrest.*", "javax.net.ssl.*", "com.sun.org.apache.xerces.*", "javax.xml.*", "org.xml.*"})
27 | @RunWith(PowerMockRunner.class)
28 | @PrepareForTest(
29 | {AlibabaEcsSpotFollower.class, AlibabaEcsComputerLauncher.class, SlaveComputer.class})
30 | public class AlibabaEcsComputerTest {
31 | @Rule
32 | public JenkinsRule r = new JenkinsRule();
33 |
34 | @Mock
35 | private AlibabaEcsSpotFollower follower;
36 |
37 | @Before
38 | public void setUp() throws IOException {
39 | when(follower.getNodeName()).thenReturn("i-xxx");
40 | when(follower.getEcsInstanceId()).thenReturn("i-xxx");
41 | when(follower.getInstanceType()).thenReturn("ecs.c5.large");
42 | DescribeInstancesResponse.Instance instance = new DescribeInstancesResponse.Instance();
43 | when(follower.describeNode()).thenReturn(instance);
44 | when(follower.getNumExecutors()).thenReturn(1);
45 | r.jenkins.addNode(follower);
46 | }
47 |
48 | @Test
49 | public void getBaseInfoTest() {
50 | AlibabaEcsComputer computer = new AlibabaEcsComputer(follower);
51 | assertEquals("ecs.c5.large", computer.getEcsType());
52 | assertEquals("i-xxx", computer.getInstanceId());
53 | }
54 |
55 | @Test
56 | public void test() {
57 | List objects = Lists.newArrayList();
58 | for (Object object : objects) {
59 | System.out.println("i");
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/AlibabaEcsTag.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs;
2 |
3 | import java.io.Serializable;
4 | import java.util.LinkedList;
5 | import java.util.List;
6 | import java.util.Objects;
7 |
8 | import com.aliyuncs.ecs.model.v20140526.RunInstancesRequest.Tag;
9 | import hudson.Extension;
10 | import hudson.model.AbstractDescribableImpl;
11 | import hudson.model.Descriptor;
12 | import org.antlr.v4.runtime.misc.NotNull;
13 | import org.kohsuke.stapler.DataBoundConstructor;
14 |
15 | public class AlibabaEcsTag extends AbstractDescribableImpl implements Serializable {
16 | private final String name;
17 | private final String value;
18 |
19 | public static final String TAG_NAME_CREATED_FROM = "CreatedFrom";
20 | public static final String TAG_VALUE_JENKINS_PLUGIN = "jenkins-plugin";
21 |
22 | @DataBoundConstructor
23 | public AlibabaEcsTag(String name, String value) {
24 | this.name = name;
25 | this.value = value;
26 | }
27 |
28 | public static List formInstanceTags(List serverTags) {
29 | if (null == serverTags)
30 | return null;
31 | LinkedList result = new LinkedList<>();
32 | for (Tag tag : serverTags) {
33 | result.add(new AlibabaEcsTag(tag.getKey(), tag.getValue()));
34 | }
35 | return result;
36 | }
37 |
38 | public String getName() {
39 | return name;
40 | }
41 |
42 | public String getValue() {
43 | return value;
44 | }
45 |
46 | @Override
47 | public String toString() {
48 | return "AlibabaEcsTag: " + name + "->" + value;
49 | }
50 |
51 | @Override
52 | public boolean equals(Object o) {
53 | if (o == null)
54 | return false;
55 | if (this.getClass() != o.getClass())
56 | return false;
57 |
58 | AlibabaEcsTag other = (AlibabaEcsTag) o;
59 | if ((name == null && other.name != null) || (name != null && !name.equals(other.name)))
60 | return false;
61 | return (value != null || other.value == null) && (value == null || value.equals(other.value));
62 | }
63 |
64 | @Override
65 | public int hashCode() {
66 | return Objects.hash(name, value);
67 | }
68 |
69 | @Extension
70 | public static class DescriptorImpl extends Descriptor {
71 | @Override
72 | public @NotNull String getDisplayName() {
73 | return "";
74 | }
75 |
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/win/winrm/request/RequestFactory.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs.win.winrm.request;
2 |
3 | import java.net.URL;
4 |
5 | public class RequestFactory {
6 | private final URL url;
7 | private String timeout = "PT60S";
8 | private int envelopSize = 153600;
9 | private String locale = "en-US";
10 |
11 | public RequestFactory(URL url) {
12 | this.url = url;
13 | }
14 |
15 | public OpenShellRequest newOpenShellRequest() {
16 | OpenShellRequest r = new OpenShellRequest(url);
17 | setDefaults(r);
18 | return r;
19 | }
20 |
21 | public ExecuteCommandRequest newExecuteCommandRequest(String shellId, String command) {
22 | ExecuteCommandRequest r = new ExecuteCommandRequest(url, shellId, command);
23 | setDefaults(r);
24 | return r;
25 | }
26 |
27 | public DeleteShellRequest newDeleteShellRequest(String shellId) {
28 | DeleteShellRequest r = new DeleteShellRequest(url, shellId);
29 | setDefaults(r);
30 | return r;
31 | }
32 |
33 | public SignalRequest newSignalRequest(String shellId, String commandId) {
34 | SignalRequest r = new SignalRequest(url, shellId, commandId);
35 | setDefaults(r);
36 | return r;
37 | }
38 |
39 | public SendInputRequest newSendInputRequest(byte[] input, String shellId, String commandId) {
40 | SendInputRequest r = new SendInputRequest(url, input, shellId, commandId);
41 | setDefaults(r);
42 | return r;
43 | }
44 |
45 | public GetOutputRequest newGetOutputRequest(String shellId, String commandId) {
46 | GetOutputRequest r = new GetOutputRequest(url, shellId, commandId);
47 | setDefaults(r);
48 | return r;
49 | }
50 |
51 | private void setDefaults(AbstractWinRMRequest r) {
52 | r.setTimeout(timeout);
53 | r.setLocale(locale);
54 | r.setEnvelopSize(envelopSize);
55 | }
56 |
57 | public String getTimeout() {
58 | return timeout;
59 | }
60 |
61 | public void setTimeout(String timeout) {
62 | this.timeout = timeout;
63 | }
64 |
65 | public int getEnvelopSize() {
66 | return envelopSize;
67 | }
68 |
69 | public void setEnvelopSize(int envelopSize) {
70 | this.envelopSize = envelopSize;
71 | }
72 |
73 | public String getLocale() {
74 | return locale;
75 | }
76 |
77 | public void setLocale(String locale) {
78 | this.locale = locale;
79 | }
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/monitor/AlibabaEcsFollowerMonitor.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs.monitor;
2 |
3 | import com.alibabacloud.jenkins.ecs.AlibabaEcsSpotFollower;
4 | import com.alibabacloud.jenkins.ecs.util.MinimumInstanceChecker;
5 | import hudson.Extension;
6 | import hudson.model.AsyncPeriodicWork;
7 | import hudson.model.Computer;
8 | import hudson.model.Node;
9 | import hudson.model.TaskListener;
10 | import jenkins.model.Jenkins;
11 |
12 | import java.io.IOException;
13 | import java.util.concurrent.TimeUnit;
14 | import java.util.logging.Level;
15 | import java.util.logging.Logger;
16 |
17 | @Extension
18 | public class AlibabaEcsFollowerMonitor extends AsyncPeriodicWork {
19 | private static final Logger LOGGER = Logger.getLogger(AlibabaEcsFollowerMonitor.class.getName());
20 |
21 | private final Long recurrencePeriod;
22 |
23 | public AlibabaEcsFollowerMonitor() {
24 | super("AlibabaEcsFollowerMonitor alive agents monitor");
25 | recurrencePeriod = Long.getLong("jenkins.ecs.checkAlivePeriod", TimeUnit.MINUTES.toMillis(10));
26 | LOGGER.log(Level.FINE, "ECS check alive period is {0}ms", recurrencePeriod);
27 | }
28 |
29 | @Override
30 | public long getRecurrencePeriod() {
31 | return recurrencePeriod;
32 | }
33 |
34 | @Override
35 | protected void execute(TaskListener listener) throws IOException, InterruptedException {
36 | removeDeadNodes();
37 | MinimumInstanceChecker.checkForMinimumInstances();
38 | }
39 |
40 | private void removeDeadNodes() {
41 | for (Node node : Jenkins.get().getNodes()) {
42 | if (node instanceof AlibabaEcsSpotFollower) {
43 | final AlibabaEcsSpotFollower ecsSlave = (AlibabaEcsSpotFollower) node;
44 | Computer computer = ecsSlave.toComputer();
45 | try {
46 | if (!ecsSlave.isAlive()
47 | && !computer.isConnecting()
48 | && computer.isOffline()) {
49 | LOGGER.info("ECS instance is dead: " + ecsSlave.getEcsInstanceId());
50 | ecsSlave.terminate();
51 | }
52 | } catch (Exception e) {
53 | LOGGER.info("ECS instance is dead and failed to terminate: " + ecsSlave.getEcsInstanceId());
54 | removeNode(ecsSlave);
55 | }
56 | }
57 | }
58 | }
59 |
60 | private void removeNode(AlibabaEcsSpotFollower ecsFollower) {
61 | try {
62 | Jenkins.get().removeNode(ecsFollower);
63 | } catch (IOException e) {
64 | LOGGER.log(Level.WARNING, "Failed to remove node: " + ecsFollower.getEcsInstanceId());
65 | }
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/src/test/java/com/alibabacloud/jenkins/ecs/AlibabaEcsStepTest.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs;
2 |
3 | import java.util.Collections;
4 | import java.util.List;
5 |
6 | import com.google.common.collect.Lists;
7 | import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
8 | import org.jenkinsci.plugins.workflow.job.WorkflowJob;
9 | import org.jenkinsci.plugins.workflow.job.WorkflowRun;
10 | import org.junit.Assert;
11 | import org.junit.Before;
12 | import org.junit.Rule;
13 | import org.junit.Test;
14 | import org.junit.runner.RunWith;
15 | import org.jvnet.hudson.test.JenkinsRule;
16 | import org.mockito.Mock;
17 | import org.powermock.core.classloader.annotations.PowerMockIgnore;
18 | import org.powermock.core.classloader.annotations.PrepareForTest;
19 | import org.powermock.modules.junit4.PowerMockRunner;
20 |
21 | import static org.mockito.ArgumentMatchers.anyString;
22 | import static org.mockito.Mockito.when;
23 |
24 | /**
25 | * Created by kunlun.ykl on 2020/9/27.
26 | */
27 | @PowerMockIgnore(
28 | {"javax.crypto.*", "org.hamcrest.*", "javax.net.ssl.*", "com.sun.org.apache.xerces.*", "javax.xml.*", "org.xml.*"})
29 | @RunWith(PowerMockRunner.class)
30 | @PrepareForTest({AlibabaEcsSpotFollower.class, AlibabaEcsFollowerTemplate.class})
31 | public class AlibabaEcsStepTest {
32 | @Rule
33 | public JenkinsRule r = new JenkinsRule();
34 | @Mock
35 | private AlibabaCloud cl;
36 |
37 | @Mock
38 | AlibabaEcsSpotFollower follower;
39 |
40 | @Mock
41 | AlibabaEcsFollowerTemplate template;
42 |
43 | @Before
44 | public void setup() throws Exception {
45 | List templates = Lists.newArrayList();
46 | templates.add(template);
47 | when(cl.getDisplayName()).thenReturn("Alibaba Cloud ECS");
48 | when(cl.getCloudName()).thenReturn("Alibaba Cloud ECS");
49 | when(cl.getTemplates()).thenReturn(templates);
50 | when(cl.getTemplate(anyString())).thenReturn(template);
51 | r.jenkins.clouds.add(cl);
52 |
53 | when(follower.getNodeName()).thenReturn("nodeName");
54 | when(follower.getEcsInstanceId()).thenReturn("i-ddddd");
55 | List slaves = Collections.singletonList(follower);
56 | when(cl.createNodes("cn-beijing-h-ecs.g5.large")).thenReturn(slaves);
57 | }
58 |
59 | @Test
60 | public void bootInstance() throws Exception {
61 | WorkflowJob boot = r.jenkins.createProject(WorkflowJob.class, "AlibabaCloudEcsTest");
62 | boot.setDefinition(new CpsFlowDefinition(
63 | " node('master') {\n" +
64 | " def X = alibabaEcs cloud: 'ALI-Alibaba Cloud ECS', template: 'cn-beijing-h-ecs.g5.large'\n" +
65 | "}", true));
66 | WorkflowRun b = r.assertBuildStatusSuccess(boot.scheduleBuild2(0));
67 | r.assertLogContains("SUCCESS", b);
68 | }
69 |
70 | @Test
71 | public void test() {
72 | EcsTemplateStep ecsTemplateStep = new EcsTemplateStep();
73 | ecsTemplateStep.setDataDiskId("i-ssss");
74 | ecsTemplateStep.setSnapshotId("s-qqqqqqq");
75 | String snapshotId = ecsTemplateStep.getSnapshotId();
76 | Assert.assertEquals(snapshotId, "s-qqqqqqq");
77 | }
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/AlibabaEcsComputer.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs;
2 |
3 | import edu.umd.cs.findbugs.annotations.CheckForNull;
4 | import hudson.model.Slave;
5 | import hudson.slaves.SlaveComputer;
6 | import lombok.extern.slf4j.Slf4j;
7 | import org.kohsuke.stapler.HttpRedirect;
8 | import org.kohsuke.stapler.HttpResponse;
9 |
10 | /**
11 | * Created by kunlun.ykl on 2020/8/25.
12 | */
13 | @Slf4j
14 | public class AlibabaEcsComputer extends SlaveComputer {
15 |
16 | public AlibabaEcsComputer(Slave follower) {
17 | super(follower);
18 | }
19 |
20 | public AlibabaEcsFollowerTemplate getSlaveTemplate() {
21 | AlibabaEcsSpotFollower node = getNode();
22 | if (node == null) {
23 | log.error("getSlaveTemplate error. node is null. computerName: {}", this.getName());
24 | return null;
25 | }
26 | if (null == node.getCloud()) {
27 | log.error("getCloud error. cloud null. computerName: {} cloudName: {}", this.getName(), node.getCloudName());
28 | return null;
29 | }
30 | return node.getCloud().getTemplate(node.getTemplateName());
31 | }
32 |
33 | public String getEcsType() {
34 | AlibabaEcsSpotFollower node = getNode();
35 | return node == null ? null : node.getInstanceType();
36 | }
37 |
38 | @CheckForNull
39 | public String getInstanceId() {
40 | AlibabaEcsSpotFollower node = getNode();
41 | return node == null ? null : node.getEcsInstanceId();
42 | }
43 |
44 | public AlibabaCloud getCloud() {
45 | AlibabaEcsSpotFollower node = getNode();
46 | return node == null ? null : node.getCloud();
47 | }
48 |
49 | public String status() {
50 | AlibabaEcsSpotFollower node = getNode();
51 | return node == null ? null : node.status();
52 | }
53 |
54 | public boolean isAlive() {
55 | AlibabaEcsSpotFollower node = getNode();
56 | return node == null ? false : node.isAlive();
57 | }
58 |
59 | public long getUptime() {
60 | AlibabaEcsSpotFollower node = getNode();
61 | return node == null ? 0 : node.getUptime();
62 | }
63 |
64 | public long getUptimeInSeconds() {
65 | return getUptime() / 1000;
66 | }
67 |
68 | @Override
69 | public AlibabaEcsSpotFollower getNode() {
70 | return (AlibabaEcsSpotFollower) super.getNode();
71 | }
72 |
73 | @Override
74 | public HttpResponse doDoDelete() {
75 | checkPermission(DELETE);
76 | log.info("doDoDelete node");
77 | AlibabaEcsSpotFollower node = getNode();
78 | if (node != null) {
79 | log.info("doDelete node: {}", node.getNodeName());
80 | node.terminate();
81 | }
82 | return new HttpRedirect("..");
83 | }
84 |
85 | public void onConnected() {
86 | log.info("AlibabaEcsComputer onConnected. {}", getInstanceId());
87 | AlibabaEcsSpotFollower node = getNode();
88 | if (node != null) {
89 | node.onConnected();
90 | }
91 | }
92 |
93 | public void modifyInstanceName(String currentBuildName) {
94 | AlibabaEcsSpotFollower node = getNode();
95 | if (null == node) {
96 | log.error("modifyInstanceName skipped. node is null");
97 | return;
98 | }
99 | node.modifyInstanceName(currentBuildName);
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/NoDelayProvisionerStrategy.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs;
2 |
3 | import hudson.Extension;
4 | import hudson.model.Label;
5 | import hudson.model.LoadStatistics;
6 | import hudson.slaves.Cloud;
7 | import hudson.slaves.NodeProvisioner;
8 | import jenkins.model.Jenkins;
9 |
10 | import java.util.Collection;
11 | import java.util.logging.Level;
12 | import java.util.logging.Logger;
13 |
14 | /**
15 | * Implementation of {@link NodeProvisioner.Strategy} which will provision a new node immediately as
16 | * a task enter the queue.
17 | * Now that ECS is billed by the minute, we don't really need to wait before provisioning a new node.
18 | *
19 | * @author Nicolas De Loof
20 | */
21 | @Extension(ordinal = 100)
22 | public class NoDelayProvisionerStrategy extends NodeProvisioner.Strategy {
23 |
24 | private static final Logger LOGGER = Logger.getLogger(NoDelayProvisionerStrategy.class.getName());
25 |
26 | @Override
27 | public NodeProvisioner.StrategyDecision apply(NodeProvisioner.StrategyState strategyState) {
28 | final Label label = strategyState.getLabel();
29 |
30 | LoadStatistics.LoadStatisticsSnapshot snapshot = strategyState.getSnapshot();
31 | int availableCapacity = snapshot.getAvailableExecutors() // live executors
32 | + snapshot.getConnectingExecutors() // executors present but not yet connected
33 | + strategyState.getPlannedCapacitySnapshot() // capacity added by previous strategies from previous rounds
34 | + strategyState.getAdditionalPlannedCapacity(); // capacity added by previous strategies _this round_
35 | int currentDemand = snapshot.getQueueLength();
36 | LOGGER.log(Level.INFO, "Available capacity={0}, currentDemand={1}", new Object[]{availableCapacity, currentDemand});
37 | if (availableCapacity < currentDemand) {
38 | Jenkins jenkinsInstance = Jenkins.get();
39 | for (Cloud cloud : jenkinsInstance.clouds) {
40 | if (!(cloud instanceof AlibabaCloud)) continue;
41 | if (!cloud.canProvision(label)) continue;
42 | AlibabaCloud alibabaCloud = (AlibabaCloud) cloud;
43 | if (!alibabaCloud.isNoDelayProvisioning()) continue;
44 |
45 | Collection plannedNodes = cloud.provision(label, currentDemand - availableCapacity);
46 | LOGGER.log(Level.INFO, "Planned {0} new nodes", plannedNodes.size());
47 | strategyState.recordPendingLaunches(plannedNodes);
48 | availableCapacity += sumCntOfExecutors(plannedNodes);
49 | LOGGER.log(Level.INFO, "After provisioning, available capacity={0}, currentDemand={1}", new Object[]{availableCapacity, currentDemand});
50 | break;
51 | }
52 | }
53 | if (availableCapacity >= currentDemand) {
54 | LOGGER.log(Level.INFO, "Provisioning completed");
55 | return NodeProvisioner.StrategyDecision.PROVISIONING_COMPLETED;
56 | } else {
57 | LOGGER.log(Level.INFO, "Provisioning not complete, consulting remaining strategies");
58 | return NodeProvisioner.StrategyDecision.CONSULT_REMAINING_STRATEGIES;
59 | }
60 | }
61 |
62 | private int sumCntOfExecutors(Collection plannedNodes) {
63 | int cntOfExecutors = 0;
64 | for (NodeProvisioner.PlannedNode plannedNode : plannedNodes) {
65 | cntOfExecutors += plannedNode.numExecutors;
66 | }
67 | return cntOfExecutors;
68 | }
69 |
70 | }
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/win/winrm/WinRMConnectionManagerFactory.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs.win.winrm;
2 |
3 | import org.apache.http.config.Registry;
4 | import org.apache.http.config.RegistryBuilder;
5 | import org.apache.http.conn.socket.ConnectionSocketFactory;
6 | import org.apache.http.conn.ssl.NoopHostnameVerifier;
7 | import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
8 | import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
9 | import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
10 | import org.apache.http.ssl.SSLContextBuilder;
11 |
12 | import java.security.KeyManagementException;
13 | import java.security.KeyStoreException;
14 | import java.security.NoSuchAlgorithmException;
15 | import java.util.logging.Level;
16 | import java.util.logging.Logger;
17 |
18 | public class WinRMConnectionManagerFactory {
19 | private static final Logger log = Logger.getLogger(WinRMClient.class.getName());
20 |
21 | static final WinRMHttpConnectionManager DEFAULT = new WinRMHttpConnectionManager();
22 | static final WinRMHttpConnectionManager SSL = new WinRMHttpConnectionManager(false);
23 | static final WinRMHttpConnectionManager SSL_ALLOW_SELF_SIGNED = new WinRMHttpConnectionManager(true);
24 |
25 | static class WinRMHttpConnectionManager {
26 | private final PoolingHttpClientConnectionManager connectionManager;
27 | private SSLConnectionSocketFactory socketFactory;
28 |
29 | final static int DEFAULT_MAX_PER_ROUTE = 50;
30 | final static int MAX_TOTAL = 2500;
31 |
32 | WinRMHttpConnectionManager() {
33 | connectionManager = new PoolingHttpClientConnectionManager();
34 | connectionManager.setDefaultMaxPerRoute(DEFAULT_MAX_PER_ROUTE);
35 | connectionManager.setMaxTotal(MAX_TOTAL);
36 | }
37 |
38 | WinRMHttpConnectionManager(boolean allowSelfSignedCertificate) {
39 | connectionManager = new PoolingHttpClientConnectionManager(getSslSocketFactory(allowSelfSignedCertificate));
40 | connectionManager.setDefaultMaxPerRoute(DEFAULT_MAX_PER_ROUTE);
41 | connectionManager.setMaxTotal(MAX_TOTAL);
42 | }
43 |
44 | public PoolingHttpClientConnectionManager getConnectionManager() {
45 | return connectionManager;
46 | }
47 |
48 | public SSLConnectionSocketFactory getSocketFactory() {
49 | return socketFactory;
50 | }
51 |
52 | private Registry getSslSocketFactory(boolean allowSelfSignedCertificate) {
53 | log.log(Level.FINE, "Setting up getSslSocketFactory");
54 | try {
55 | if (allowSelfSignedCertificate) {
56 | this.socketFactory = new SSLConnectionSocketFactory(
57 | new SSLContextBuilder().loadTrustMaterial(null, new TrustSelfSignedStrategy()).build(),
58 | NoopHostnameVerifier.INSTANCE);
59 | log.log(Level.FINE, "Allowing self-signed certificates");
60 | } else {
61 | this.socketFactory = SSLConnectionSocketFactory.getSystemSocketFactory();
62 | log.log(Level.FINE, "Using system socket factory");
63 | }
64 | } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {
65 | log.log(Level.WARNING, "Exception when creating socket factory, using system socket factory");
66 | this.socketFactory = SSLConnectionSocketFactory.getSystemSocketFactory();
67 | }
68 | return RegistryBuilder.create()
69 | .register("https", this.socketFactory)
70 | .build();
71 | }
72 | }
73 | }
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/win/winrm/soap/Header.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs.win.winrm.soap;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Collections;
5 | import java.util.List;
6 |
7 | import org.dom4j.Element;
8 | import org.dom4j.QName;
9 |
10 | public class Header {
11 | private final String to;
12 | private final String replyTo;
13 | private final String maxEnvelopeSize;
14 | private final String timeout;
15 | private final String locale;
16 | private final String id;
17 | private final String action;
18 | private final String shellId;
19 | private final String resourceURI;
20 | private final List optionSet;
21 |
22 | Header(String to, String replyTo, String maxEnvelopeSize, String timeout, String locale, String id, String action, String shellId, String resourceURI, List optionSet) {
23 | this.to = to;
24 | this.replyTo = replyTo;
25 | this.maxEnvelopeSize = maxEnvelopeSize;
26 | this.timeout = timeout;
27 | this.locale = locale;
28 | this.id = id;
29 | this.action = action;
30 | this.shellId = shellId;
31 | this.resourceURI = resourceURI;
32 | this.optionSet = optionSet != null ? Collections.unmodifiableList(new ArrayList<>(optionSet)) : Collections.emptyList();
33 | }
34 |
35 | private static Element mustUnderstand(Element e) {
36 | return e.addAttribute("mustUnderstand", "true");
37 | }
38 |
39 | private static Element mustNotUnderstand(Element e) {
40 | return e.addAttribute("mustUnderstand", "false");
41 | }
42 |
43 | void toElement(Element header) {
44 | if (to != null) {
45 | header.addElement(QName.get("To", Namespaces.NS_ADDRESSING)).addText(to);
46 | }
47 |
48 | if (replyTo != null) {
49 | mustUnderstand(header.addElement(QName.get("ReplyTo", Namespaces.NS_ADDRESSING)).addElement(QName.get("Address", Namespaces.NS_ADDRESSING))).addText(replyTo);
50 | }
51 |
52 | if (maxEnvelopeSize != null) {
53 | mustUnderstand(header.addElement(QName.get("MaxEnvelopeSize", Namespaces.NS_WSMAN_DMTF))).addText(maxEnvelopeSize);
54 | }
55 |
56 | if (id != null) {
57 | header.addElement(QName.get("MessageID", Namespaces.NS_ADDRESSING)).addText(id);
58 | }
59 |
60 | if (locale != null) {
61 | mustNotUnderstand(header.addElement(QName.get("Locale", Namespaces.NS_WSMAN_DMTF))).addAttribute("xml:lang", locale);
62 | mustNotUnderstand(header.addElement(QName.get("DataLocale", Namespaces.NS_WSMAN_MSFT))).addAttribute("xml:lang", locale);
63 | }
64 |
65 | if (timeout != null) {
66 | header.addElement(QName.get("OperationTimeout", Namespaces.NS_WSMAN_DMTF)).addText(timeout);
67 | }
68 |
69 | if (action != null) {
70 | mustUnderstand(header.addElement(QName.get("Action", Namespaces.NS_ADDRESSING))).addText(action);
71 | }
72 |
73 | if (shellId != null) {
74 | header.addElement(QName.get("SelectorSet", Namespaces.NS_WSMAN_DMTF)).addElement(QName.get("Selector", Namespaces.NS_WSMAN_DMTF)).addAttribute("Name", "ShellId").addText(shellId);
75 | }
76 |
77 | if (resourceURI != null) {
78 | mustUnderstand(header.addElement(QName.get("ResourceURI", Namespaces.NS_WSMAN_DMTF))).addText(resourceURI);
79 | }
80 |
81 | if (optionSet != null) {
82 | final Element set = header.addElement(QName.get("OptionSet", Namespaces.NS_WSMAN_DMTF));
83 | for (Option p : optionSet) {
84 | set.addElement(QName.get("Option", Namespaces.NS_WSMAN_DMTF)).addAttribute("Name", p.getName()).addText(p.getValue());
85 | }
86 | }
87 | }
88 |
89 | }
90 |
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/monitor/FreeMemMonitor.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs.monitor;
2 |
3 | import java.io.IOException;
4 |
5 | import hudson.Extension;
6 | import hudson.Functions;
7 | import hudson.Util;
8 | import hudson.model.Computer;
9 | import hudson.node_monitors.AbstractAsyncNodeMonitorDescriptor;
10 | import hudson.node_monitors.AbstractNodeMonitorDescriptor;
11 | import hudson.node_monitors.NodeMonitor;
12 | import hudson.node_monitors.SwapSpaceMonitor;
13 | import jenkins.security.MasterToSlaveCallable;
14 | import net.sf.json.JSONObject;
15 | import org.jenkinsci.Symbol;
16 | import org.jvnet.hudson.MemoryMonitor;
17 | import org.jvnet.hudson.MemoryUsage;
18 | import org.kohsuke.stapler.StaplerRequest;
19 |
20 | /**
21 | * Created by kunlun.ykl on 2020/9/14.
22 | */
23 | public class FreeMemMonitor extends SwapSpaceMonitor {
24 | /**
25 | * Returns the HTML representation of the space.
26 | */
27 | public String toHtml(MemoryUsage usage) {
28 | if (usage.availablePhysicalMemory == -1) { return "N/A"; }
29 |
30 | String humanReadableSpace = Functions.humanReadableByteSize(usage.availablePhysicalMemory);
31 |
32 | long free = usage.availablePhysicalMemory;
33 | free /= 1024L; // convert to KB
34 | free /= 1024L; // convert to MB
35 | if (free > 256 || usage.totalPhysicalMemory < usage.availablePhysicalMemory * 2) {
36 | return humanReadableSpace; // if we have more than 256MB free or less than 80% filled up, it's OK
37 | }
38 |
39 | // Otherwise considered dangerously low.
40 | return Util.wrapToErrorSpan(humanReadableSpace);
41 | }
42 |
43 | public long toMB(MemoryUsage usage) {
44 | if (usage.availablePhysicalMemory == -1) { return -1; }
45 |
46 | long free = usage.availablePhysicalMemory;
47 | free /= 1024L; // convert to KB
48 | free /= 1024L; // convert to MB
49 | return free;
50 | }
51 |
52 | @Override
53 | public String getColumnCaption() {
54 | // Hide this column from non-admins
55 | return "Free Mem Space";
56 | }
57 |
58 | @Extension
59 | @Symbol("memorySpace")
60 | public static class DescriptorImpl extends AbstractAsyncNodeMonitorDescriptor {
61 | public DescriptorImpl() {
62 | setDescriptor(this);
63 | }
64 |
65 | private static void setDescriptor(AbstractNodeMonitorDescriptor descriptor){
66 | DESCRIPTOR = descriptor;
67 | }
68 |
69 | @Override
70 | protected MonitorTask createCallable(Computer c) {
71 | return new MonitorTask();
72 | }
73 |
74 | public String getDisplayName() {
75 | return "Free Mem Space";
76 | }
77 |
78 | @Override
79 | public NodeMonitor newInstance(StaplerRequest req, JSONObject formData) throws FormException {
80 | return new FreeMemMonitor();
81 | }
82 | }
83 |
84 | private static class MonitorTask extends MasterToSlaveCallable {
85 | public MemoryUsage call() throws IOException {
86 | MemoryMonitor mm;
87 | try {
88 | mm = MemoryMonitor.get();
89 | } catch (IOException e) {
90 | return report(e);
91 | } catch (LinkageError e) { // JENKINS-15796
92 | return report(e);
93 | }
94 | return new MemoryUsage2(mm.monitor());
95 | }
96 |
97 | private MemoryUsage report(T e) throws T {
98 | if (!warned) {
99 | warned = true;
100 | throw e;
101 | } else { // JENKINS-2194
102 | return null;
103 | }
104 | }
105 |
106 | private static final long serialVersionUID = 1L;
107 |
108 | private static boolean warned = false;
109 | }
110 |
111 | }
112 |
--------------------------------------------------------------------------------
/src/test/java/com/alibabacloud/jenkins/ecs/AlibabaEcsFollowerTemplateTest.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | import com.alibabacloud.credentials.plugin.auth.AlibabaPrivateKey;
7 | import com.alibabacloud.jenkins.ecs.client.AlibabaEcsClient;
8 | import com.alibabacloud.jenkins.ecs.enums.DataDiskCategory;
9 | import com.alibabacloud.jenkins.ecs.enums.SystemDiskCategory;
10 | import com.google.common.collect.Lists;
11 | import org.junit.Assert;
12 | import org.junit.Before;
13 | import org.junit.Rule;
14 | import org.junit.Test;
15 | import org.junit.runner.RunWith;
16 | import org.jvnet.hudson.test.JenkinsRule;
17 | import org.mockito.Mock;
18 | import org.powermock.core.classloader.annotations.PowerMockIgnore;
19 | import org.powermock.core.classloader.annotations.PrepareForTest;
20 | import org.powermock.modules.junit4.PowerMockRunner;
21 |
22 | import static org.mockito.ArgumentMatchers.any;
23 | import static org.mockito.Mockito.when;
24 |
25 | /**
26 | * Created by kunlun.ykl on 2020/8/26.
27 | */
28 | @PowerMockIgnore(
29 | {"javax.crypto.*", "org.hamcrest.*", "javax.net.ssl.*", "com.sun.org.apache.xerces.*", "javax.xml.*", "org.xml.*"})
30 | @RunWith(PowerMockRunner.class)
31 | @PrepareForTest({AlibabaCloud.class, AlibabaEcsClient.class, AlibabaPrivateKey.class})
32 | public class AlibabaEcsFollowerTemplateTest {
33 | @Rule
34 | public JenkinsRule r = new JenkinsRule();
35 |
36 | @Mock
37 | AlibabaCloud alibabaCloud;
38 |
39 | @Mock
40 | AlibabaEcsClient client;
41 |
42 | @Mock
43 | AlibabaPrivateKey key;
44 | List instances = new ArrayList<>();
45 |
46 | @Before
47 | public void setup() {
48 | when(alibabaCloud.connect()).thenReturn(client);
49 | when(alibabaCloud.getPrivateKey()).thenReturn(key);
50 | when(key.getKeyPairName()).thenReturn("keyName");
51 | instances.add("hello");
52 | when(client.runInstances(any())).thenReturn(instances);
53 | }
54 |
55 | @Test
56 | public void provisionSpotTest() throws Exception {
57 | String remoteFS = "/root";
58 | // String cloudName = "alibaba-ecs-cloud";
59 | String labelString = "myCI";
60 | String initScript = "";
61 | String templateName = "alibaba-ecs-cloud-t1";
62 | int numExecutors = 1;
63 | String image = "ubuntu.vhd";
64 | int launchTimeout = 10000;
65 | List tags = Lists.newArrayList();
66 | String idleTerminationMinutes = "30";
67 | String zone = "cn-beijing-h";
68 | String chargeType = "PostPaid";
69 | String instanceType = "ecs.c6.large";
70 | SystemDiskCategory systemDiskCategory = SystemDiskCategory.cloud_essd_PL0;
71 | Integer systemDiskSize = 40;
72 | String vsw = "vsw-1";
73 | int minimumNumberOfInstances = 1;
74 | String instanceCapStr = "2";
75 | String dataDiskSize = "10";
76 | DataDiskCategory dataDiskCategory = DataDiskCategory.cloud;
77 | String mountQuantity = "4";
78 | boolean mountDataDisk = true;
79 | boolean newDataDisk = true;
80 | String dataDiskId = "d-sdcscscss";
81 | String snapshotId = "s-ddwqdad";
82 | WindowsData windowsData = new WindowsData("X123456x", false, "180", true, false);
83 | AlibabaEcsFollowerTemplate follower = new AlibabaEcsFollowerTemplate(templateName, image, zone, vsw, chargeType,
84 | instanceType, initScript, labelString, remoteFS, systemDiskCategory, systemDiskSize,
85 | minimumNumberOfInstances,
86 | idleTerminationMinutes, instanceCapStr, numExecutors + "", launchTimeout + "", tags, "userData",
87 | windowsData, ConnectionStrategy.PUBLIC_IP, "", dataDiskSize, dataDiskCategory, mountQuantity, snapshotId,
88 | mountDataDisk, newDataDisk, dataDiskId, 1, "", "");
89 | follower.setParent(alibabaCloud);
90 | List instanceIds = follower.provisionSpot(1, true);
91 | Assert.assertEquals(instanceIds, instances);
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaEcsComputer/configure.jelly:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/WindowsData.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs;
2 |
3 | import java.util.Objects;
4 | import java.util.concurrent.TimeUnit;
5 |
6 | import hudson.Extension;
7 | import hudson.model.Descriptor;
8 | import hudson.util.FormValidation;
9 | import hudson.util.Secret;
10 | import org.apache.commons.lang.StringUtils;
11 | import org.kohsuke.stapler.DataBoundConstructor;
12 | import org.kohsuke.stapler.QueryParameter;
13 |
14 | public class WindowsData extends EcsTypeData {
15 |
16 | private final Secret password;
17 | private final boolean useHTTPS;
18 | private final String bootDelay;
19 | private final boolean specifyPassword;
20 | private final Boolean allowSelfSignedCertificate;
21 |
22 | @DataBoundConstructor
23 | public WindowsData(String password, boolean useHTTPS, String bootDelay, boolean specifyPassword, boolean allowSelfSignedCertificate) {
24 | this.password = Secret.fromString(password);
25 | this.useHTTPS = useHTTPS;
26 | this.bootDelay = bootDelay;
27 | //Backwards compatibility
28 | if (!specifyPassword && !this.password.getPlainText().isEmpty()) {
29 | specifyPassword = true;
30 | }
31 | this.specifyPassword = specifyPassword;
32 |
33 | this.allowSelfSignedCertificate = allowSelfSignedCertificate;
34 | }
35 |
36 | //@Deprecated
37 | //public WindowsData(String password, boolean useHTTPS, String bootDelay, boolean specifyPassword) {
38 | // this(password, useHTTPS, bootDelay, specifyPassword, true);
39 | //}
40 | //
41 | //public WindowsData(String password, boolean useHTTPS, String bootDelay) {
42 | // this(password, useHTTPS, bootDelay, false);
43 | //}
44 |
45 | @Override
46 | public boolean isWindows() {
47 | return true;
48 | }
49 |
50 | @Override
51 | public boolean isUnix() {
52 | return false;
53 | }
54 |
55 | public Secret getPassword() {
56 | return password;
57 | }
58 |
59 | public boolean isUseHTTPS() {
60 | return useHTTPS;
61 | }
62 |
63 | public String getBootDelay() {
64 | return bootDelay;
65 | }
66 |
67 | public boolean isSpecifyPassword() {
68 | return specifyPassword;
69 | }
70 |
71 | @Override
72 | public int getBootDelayInMillis() {
73 | try {
74 | return (int) TimeUnit.SECONDS.toMillis(Integer.parseInt(bootDelay));
75 | } catch (NumberFormatException nfe) {
76 | return 0;
77 | }
78 | }
79 |
80 | public boolean isAllowSelfSignedCertificate(){
81 | return allowSelfSignedCertificate == null || allowSelfSignedCertificate;
82 | }
83 |
84 | @Override
85 | public int hashCode() {
86 | return Objects.hash(password,useHTTPS, bootDelay, specifyPassword);
87 | }
88 |
89 | @Override
90 | public boolean equals(Object obj) {
91 | if (this == obj)
92 | return true;
93 | if (obj == null)
94 | return false;
95 | if (this.getClass() != obj.getClass())
96 | return false;
97 | final WindowsData other = (WindowsData) obj;
98 | if (bootDelay == null) {
99 | if (other.bootDelay != null)
100 | return false;
101 | } else if (!bootDelay.equals(other.bootDelay))
102 | return false;
103 | if (password == null) {
104 | if (other.password != null)
105 | return false;
106 | } else if (!password.equals(other.password))
107 | return false;
108 | if (allowSelfSignedCertificate == null) {
109 | if (other.allowSelfSignedCertificate != null)
110 | return false;
111 | } else if (!allowSelfSignedCertificate.equals(other.allowSelfSignedCertificate))
112 | return false;
113 | return useHTTPS == other.useHTTPS && specifyPassword == other.specifyPassword;
114 | }
115 |
116 | @Extension
117 | public static class DescriptorImpl extends Descriptor {
118 | @Override
119 | public String getDisplayName() {
120 | return "windows";
121 | }
122 |
123 | public FormValidation doCheckPassword(@QueryParameter String password) {
124 | if (StringUtils.isBlank(password)) {
125 | return FormValidation.error(Messages.AlibabaECSCloud_NotSpecifiedPassword());
126 | }
127 | return FormValidation.ok();
128 | }
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/win/winrm/WinRM.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs.win.winrm;
2 |
3 | import java.io.IOException;
4 | import java.net.MalformedURLException;
5 | import java.net.URL;
6 | import java.util.logging.Logger;
7 |
8 | public class WinRM {
9 | private static final Logger log = Logger.getLogger(WinRM.class.getName());
10 |
11 | private final String host;
12 | private final String username;
13 | private final String password;
14 | private final boolean allowSelfSignedCertificate;
15 | private int timeout = 60;
16 | private boolean useHTTPS;
17 |
18 | @Deprecated
19 | public WinRM(String host, String username, String password) {
20 | this(host, username, password, true);
21 | }
22 |
23 | public WinRM(String host, String username, String password, boolean allowSelfSignedCertificate) {
24 | this.host = host;
25 | this.username = username;
26 | this.password = password;
27 | this.allowSelfSignedCertificate = allowSelfSignedCertificate;
28 | }
29 |
30 | /**
31 | * # Convert the number of seconds to an ISO8601 duration format # @see
32 | * http://tools.ietf.org/html/rfc2445#section-4.3.6 # @param [Fixnum] seconds The amount of seconds for this
33 | * duration
34 | *
35 | * @param seconds
36 | * @return
37 | */
38 | private static String secToDuration(int seconds) {
39 | StringBuilder iso = new StringBuilder("P");
40 | if (seconds > 604800) {
41 | // more than a week
42 | int weeks = seconds / 604800;
43 | seconds -= (604800 * weeks);
44 | iso.append(weeks).append('W');
45 | }
46 |
47 | if (seconds > 86400) {
48 | // more than a day
49 | int days = seconds / 86400;
50 | seconds -= (86400 * days);
51 | iso.append(days).append('D');
52 | }
53 |
54 | if (seconds > 0) {
55 | iso.append('T');
56 | if (seconds > 3600) { // more than an hour
57 | int hours = seconds / 3600;
58 | seconds -= (3600 * hours);
59 | iso.append(hours).append('H');
60 | }
61 | if (seconds > 60) { // more than a minute
62 | int minutes = seconds / 60;
63 | seconds -= (60 * minutes);
64 | iso.append(minutes).append('M');
65 | }
66 | iso.append(seconds).append('S');
67 | }
68 |
69 | return iso.toString();
70 | }
71 |
72 | public void ping() throws IOException {
73 | final WinRMClient client = new WinRMClient(buildURL(), username, password, allowSelfSignedCertificate);
74 | client.setTimeout(secToDuration(timeout));
75 | client.setUseHTTPS(isUseHTTPS());
76 | try {
77 | client.openShell();
78 | } finally {
79 | try {
80 | client.deleteShell();
81 | } catch (Exception e) {
82 | }
83 | }
84 | }
85 |
86 | public WindowsProcess execute(String commandLine) {
87 | final WinRMClient client = new WinRMClient(buildURL(), username, password, allowSelfSignedCertificate);
88 | client.setTimeout(secToDuration(timeout));
89 | client.setUseHTTPS(isUseHTTPS());
90 | try {
91 | client.openShell();
92 | client.executeCommand(commandLine);
93 |
94 | return new WindowsProcess(client, commandLine);
95 | } catch (IOException exc) {
96 | throw new RuntimeException("Cannot execute command " + commandLine + " on " + this, exc);
97 | }
98 | }
99 |
100 | public URL buildURL() {
101 | String scheme = useHTTPS ? "https" : "http";
102 | int port = useHTTPS ? 5986 : 5985;
103 |
104 | try {
105 | return new URL(scheme, host, port, "/wsman");
106 | } catch (MalformedURLException e) {
107 | throw new RuntimeException("Invalid winrm url");
108 | }
109 | }
110 |
111 | /**
112 | * @return the useHTTPS
113 | */
114 | public boolean isUseHTTPS() {
115 | return useHTTPS;
116 | }
117 |
118 | /**
119 | * @param useHTTPS
120 | * the useHTTPS to set
121 | */
122 | public void setUseHTTPS(boolean useHTTPS) {
123 | this.useHTTPS = useHTTPS;
124 | }
125 |
126 | /**
127 | * @return the timeout
128 | */
129 | public int getTimeout() {
130 | return timeout;
131 | }
132 |
133 | /**
134 | * @param timeout
135 | * the timeout to set
136 | */
137 | public void setTimeout(int timeout) {
138 | this.timeout = timeout;
139 | }
140 | }
141 |
142 |
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/AlibabaEcsStep.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs;
2 |
3 | import com.alibabacloud.jenkins.ecs.util.CloudHelper;
4 | import com.aliyuncs.ecs.model.v20140526.DescribeInstancesResponse.Instance;
5 | import hudson.Extension;
6 | import hudson.Util;
7 | import hudson.model.TaskListener;
8 | import hudson.slaves.Cloud;
9 | import hudson.util.ListBoxModel;
10 | import jenkins.model.Jenkins;
11 | import lombok.extern.slf4j.Slf4j;
12 | import org.apache.commons.collections.CollectionUtils;
13 | import org.jenkinsci.plugins.workflow.steps.*;
14 | import org.kohsuke.stapler.DataBoundConstructor;
15 | import org.kohsuke.stapler.QueryParameter;
16 |
17 | import java.util.Collections;
18 | import java.util.List;
19 | import java.util.Set;
20 |
21 | import static com.alibabacloud.jenkins.ecs.AlibabaCloud.CLOUD_ID_PREFIX;
22 |
23 | /**
24 | * Returns the instance provisioned. Returns the instance provisioned.
25 | *
26 | * Used like:
27 | *
28 | *
29 | * node {
30 | * def x = alibabaEcs cloud: 'ALI-myCloud', template: 'cn-beijing-a-ecs.c5.large'
31 | * }
32 | *
33 | *
34 | * Created by kunlun.ykl on 2020/9/27.
35 | */
36 | @Slf4j
37 | public class AlibabaEcsStep extends Step {
38 | private String cloud;
39 | private String template;
40 |
41 | @DataBoundConstructor
42 | public AlibabaEcsStep(String cloud, String template) {
43 | this.cloud = cloud;
44 | this.template = template;
45 | }
46 |
47 | public String getCloud() {
48 | return cloud;
49 | }
50 |
51 | public String getTemplate() {
52 | return template;
53 | }
54 |
55 | @Override
56 | public StepExecution start(StepContext stepContext) throws Exception {
57 | log.info("start AlibabaEcsStep execution");
58 | return new AlibabaEcsStep.Execution(this, stepContext);
59 | }
60 |
61 | @Extension
62 | public static final class DescriptorImpl extends StepDescriptor {
63 |
64 | @Override
65 | public String getFunctionName() {
66 | return "alibabaEcs";
67 | }
68 |
69 | @Override
70 | public String getDisplayName() {
71 | return "Cloud template provisioning";
72 | }
73 |
74 | public ListBoxModel doFillCloudItems() {
75 | ListBoxModel r = new ListBoxModel();
76 | r.add("", "");
77 | Jenkins.CloudList clouds = jenkins.model.Jenkins.get().clouds;
78 | for (Cloud cList : clouds) {
79 | if (cList instanceof AlibabaCloud) {
80 | r.add(cList.getDisplayName(), cList.getDisplayName());
81 | }
82 | }
83 | return r;
84 | }
85 |
86 | public ListBoxModel doFillTemplateItems(@QueryParameter String cloud) {
87 | cloud = Util.fixEmpty(cloud);
88 | ListBoxModel r = new ListBoxModel();
89 | for (Cloud cList : jenkins.model.Jenkins.get().clouds) {
90 | if (cList.getDisplayName().equals(cloud)) {
91 | List templates = ((AlibabaCloud) cList).getTemplates();
92 | for (AlibabaEcsFollowerTemplate template : templates) {
93 | r.add(template.getTemplateName(), template.getTemplateName());
94 | }
95 | }
96 | }
97 | return r;
98 | }
99 |
100 | @Override
101 | public Set extends Class>> getRequiredContext() {
102 | return Collections.singleton(TaskListener.class);
103 | }
104 |
105 | }
106 |
107 | public static class Execution extends SynchronousNonBlockingStepExecution {
108 | private final String cloud;
109 | private final String template;
110 |
111 | Execution(AlibabaEcsStep step, StepContext context) {
112 | super(context);
113 | this.cloud = step.cloud;
114 | this.template = step.template;
115 | }
116 |
117 | @Override
118 | protected Instance run() throws Exception {
119 | String cloudName = cloud.substring(CLOUD_ID_PREFIX.length());
120 | AlibabaCloud cl = CloudHelper.getCloud(cloudName);
121 | if (null == cl) {
122 | throw new IllegalArgumentException(
123 | "Error in Alibaba Cloud. Please review Alibaba ECS settings in Jenkins configuration.");
124 | }
125 | List instances = cl.createNodes(template);
126 | if (CollectionUtils.isEmpty(instances)) {
127 | throw new IllegalArgumentException(
128 | "Error in Alibaba Cloud. Please review Alibaba ECS defined in Jenkins configuration.");
129 | }
130 | AlibabaEcsSpotFollower follower = instances.get(0);
131 | return CloudHelper.getInstanceWithRetry(follower);
132 | }
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/EcsTemplateStep/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
--------------------------------------------------------------------------------
/src/main/resources/com/alibabacloud/jenkins/ecs/AlibabaEcsFollowerTemplate/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | ${it.name()}
38 |
39 |
40 |
41 |
42 |
44 |
46 |
47 | ${it.name()}
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/win/winrm/WindowsProcess.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs.win.winrm;
2 |
3 | import java.io.IOException;
4 | import java.io.InputStream;
5 | import java.io.OutputStream;
6 | import java.util.logging.Level;
7 | import java.util.logging.Logger;
8 |
9 | import com.alibabacloud.jenkins.ecs.util.Closeables;
10 | import hudson.remoting.FastPipedInputStream;
11 | import hudson.remoting.FastPipedOutputStream;
12 |
13 | public class WindowsProcess {
14 | private static final Logger LOGGER = Logger.getLogger(WindowsProcess.class.getName());
15 |
16 | private static final int INPUT_BUFFER = 16 * 1024;
17 | private final WinRMClient client;
18 |
19 | private final FastPipedInputStream toCallersStdin;
20 | private final FastPipedOutputStream callersStdin;
21 | private final FastPipedInputStream callersStdout;
22 | private final FastPipedOutputStream toCallersStdout;
23 | private final FastPipedInputStream callersStderr;
24 | private final FastPipedOutputStream toCallersStderr;
25 | private final String command;
26 | private boolean terminated;
27 | private Thread outputThread;
28 |
29 | private Thread inputThread;
30 |
31 | WindowsProcess(WinRMClient client, String command) throws IOException {
32 | this.client = client;
33 | this.command = command;
34 |
35 | toCallersStdin = new FastPipedInputStream();
36 | callersStdin = new FastPipedOutputStream(toCallersStdin);
37 | callersStdout = new FastPipedInputStream();
38 | toCallersStdout = new FastPipedOutputStream(callersStdout);
39 | callersStderr = new FastPipedInputStream();
40 | toCallersStderr = new FastPipedOutputStream(callersStderr);
41 |
42 | startStdoutCopyThread();
43 | try {
44 | Thread.sleep(1000);
45 | } catch (InterruptedException e) {
46 | Thread.currentThread().interrupt();
47 | }
48 | startStdinCopyThread();
49 | }
50 |
51 | public InputStream getStdout() {
52 | return callersStdout;
53 | }
54 |
55 | public OutputStream getStdin() {
56 | return callersStdin;
57 | }
58 |
59 | public InputStream getStderr() {
60 | return callersStderr;
61 | }
62 |
63 | public synchronized int waitFor() {
64 | if (terminated) {
65 | return client.exitCode();
66 | }
67 |
68 | try {
69 | try {
70 | outputThread.join();
71 | } finally {
72 | client.deleteShell();
73 | terminated = true;
74 | Closeables.closeQuietly(toCallersStdin);
75 | }
76 | return client.exitCode();
77 | } catch (InterruptedException exc) {
78 | Thread.currentThread().interrupt();
79 | throw new RuntimeException("Exception while executing command", exc);
80 | }
81 | }
82 |
83 | public synchronized void destroy() {
84 | if (terminated) {
85 | return;
86 | }
87 |
88 | client.signal();
89 | client.deleteShell();
90 | terminated = true;
91 | Closeables.closeQuietly(toCallersStdout);
92 | Closeables.closeQuietly(toCallersStdin);
93 | Closeables.closeQuietly(toCallersStderr);
94 | Closeables.closeQuietly(callersStdout);
95 | Closeables.closeQuietly(callersStdin);
96 | Closeables.closeQuietly(callersStderr);
97 | }
98 |
99 | private void startStdoutCopyThread() {
100 | outputThread = new Thread("output copy: " + command) {
101 | @Override
102 | public void run() {
103 | try {
104 | for (;;) {
105 | if (!client.slurpOutput(toCallersStdout, toCallersStderr)) {
106 | LOGGER.log(Level.FINE, () -> "no more output for " + command);
107 | break;
108 | }
109 | }
110 | } catch (Exception exc) {
111 | LOGGER.log(Level.WARNING, "ouch, stdout exception for " + command, exc);
112 | exc.printStackTrace();
113 | } finally {
114 | Closeables.closeQuietly(toCallersStdout);
115 | Closeables.closeQuietly(toCallersStderr);
116 | }
117 | }
118 | };
119 | outputThread.setDaemon(true);
120 | outputThread.start();
121 | }
122 |
123 | private void startStdinCopyThread() {
124 | inputThread = new Thread("input copy: " + command) {
125 | @Override
126 | public void run() {
127 | try {
128 | byte[] buf = new byte[INPUT_BUFFER];
129 | for (;;) {
130 | int n = toCallersStdin.read(buf);
131 |
132 | if (n == -1)
133 | break;
134 | if (n == 0)
135 | continue;
136 |
137 | byte[] bufToSend = new byte[n];
138 | System.arraycopy(buf, 0, bufToSend, 0, n);
139 | LOGGER.log(Level.FINE, () -> "piping " + bufToSend.length + " to input of " + command);
140 | client.sendInput(bufToSend);
141 | }
142 | } catch (Exception exc) {
143 | LOGGER.log(Level.WARNING, "ouch, STDIN exception for " + command, exc);
144 | } finally {
145 | Closeables.closeQuietly(callersStdin);
146 | }
147 | }
148 | };
149 | inputThread.setDaemon(true);
150 | inputThread.start();
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/util/MinimumInstanceChecker.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs.util;
2 |
3 | import com.alibabacloud.jenkins.ecs.AlibabaCloud;
4 | import com.alibabacloud.jenkins.ecs.AlibabaEcsComputer;
5 | import com.alibabacloud.jenkins.ecs.AlibabaEcsFollowerTemplate;
6 | import edu.umd.cs.findbugs.annotations.NonNull;
7 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
8 | import hudson.model.Computer;
9 | import hudson.model.Label;
10 | import hudson.model.Queue;
11 | import jenkins.model.Jenkins;
12 | import lombok.extern.slf4j.Slf4j;
13 | import org.kohsuke.accmod.Restricted;
14 | import org.kohsuke.accmod.restrictions.NoExternalUse;
15 |
16 | import java.time.Clock;
17 | import java.util.Arrays;
18 | import java.util.Objects;
19 | import java.util.stream.Stream;
20 |
21 | @Restricted(NoExternalUse.class)
22 | @Slf4j
23 | public class MinimumInstanceChecker {
24 |
25 | @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "Needs to be overridden from tests")
26 | public static Clock clock = Clock.systemDefaultZone();
27 |
28 | private static Stream agentsForTemplate(@NonNull AlibabaEcsFollowerTemplate agentTemplate) {
29 | return (Stream) Arrays.stream(Jenkins.get().getComputers()).filter(computer -> computer instanceof AlibabaEcsComputer).filter(computer -> {
30 | AlibabaEcsFollowerTemplate computerTemplate = ((AlibabaEcsComputer) computer).getSlaveTemplate();
31 | return computerTemplate != null && Objects.equals(computerTemplate.getTemplateName(), agentTemplate.getTemplateName());
32 | });
33 | }
34 |
35 | public static int countCurrentNumberOfAgents(@NonNull AlibabaEcsFollowerTemplate agentTemplate) {
36 | return (int) agentsForTemplate(agentTemplate).count();
37 | }
38 |
39 | public static int countCurrentNumberOfSpareAgents(@NonNull AlibabaEcsFollowerTemplate agentTemplate) {
40 | return (int) agentsForTemplate(agentTemplate).filter(computer -> computer.countBusy() == 0).filter(computer -> computer.isOnline()).count();
41 | }
42 |
43 | public static int countCurrentNumberOfProvisioningAgents(@NonNull AlibabaEcsFollowerTemplate agentTemplate) {
44 | return (int) agentsForTemplate(agentTemplate).filter(computer -> computer.countBusy() == 0).filter(computer -> computer.isOffline()).filter(computer -> computer.isConnecting()).count();
45 | }
46 |
47 | public static int countQueueItemsForAgentTemplate(@NonNull AlibabaEcsFollowerTemplate agentTemplate) {
48 | return (int) Queue.getInstance().getBuildableItems().stream().map((Queue.Item item) -> item.getAssignedLabel()).filter(Objects::nonNull).filter((Label label) -> label.matches(agentTemplate.getLabelSet())).count();
49 | }
50 |
51 |
52 | public static void checkForMinimumInstances() {
53 | Jenkins.get().clouds.stream().filter(cloud -> cloud instanceof AlibabaCloud).map(cloud -> (AlibabaCloud) cloud).forEach(cloud -> {
54 | cloud.getTemplates().forEach(agentTemplate -> {
55 | // Minimum instances now have a time range, check to see
56 | // if we are within that time range and return early if not.
57 | int requiredMinAgents = agentTemplate.getMinimumNumberOfInstances();
58 | // int requiredMinSpareAgents = agentTemplate.getMinimumNumberOfSpareInstances();
59 | int requiredMinSpareAgents = 0;
60 | int currentNumberOfAgentsForTemplate = countCurrentNumberOfAgents(agentTemplate);
61 | int currentNumberOfSpareAgentsForTemplate = countCurrentNumberOfSpareAgents(agentTemplate);
62 | int currentNumberOfProvisioningAgentsForTemplate = countCurrentNumberOfProvisioningAgents(agentTemplate);
63 | int currentBuildsWaitingForTemplate = countQueueItemsForAgentTemplate(agentTemplate);
64 | int provisionForMinAgents = 0;
65 | int provisionForMinSpareAgents = 0;
66 |
67 | // Check if we need to provision any agents because we
68 | // don't have the minimum number of agents
69 | provisionForMinAgents = requiredMinAgents - currentNumberOfAgentsForTemplate;
70 | if (provisionForMinAgents < 0) {
71 | provisionForMinAgents = 0;
72 | }
73 |
74 | // Check if we need to provision any agents because we
75 | // don't have the minimum number of spare agents.
76 | // Don't double provision if minAgents and minSpareAgents are set.
77 | provisionForMinSpareAgents = (requiredMinSpareAgents + currentBuildsWaitingForTemplate) - (currentNumberOfSpareAgentsForTemplate + provisionForMinAgents + currentNumberOfProvisioningAgentsForTemplate);
78 | if (provisionForMinSpareAgents < 0) {
79 | provisionForMinSpareAgents = 0;
80 | }
81 | int numberToProvision = provisionForMinAgents + provisionForMinSpareAgents;
82 | log.info("{} checkForMinimumInstances started. requiredMinAgents: {} " +
83 | "currentNumberOfAgentsForTemplate: {} currentNumberOfSpareAgentsForTemplate: {} currentNumberOfProvisioningAgentsForTemplate: {} " +
84 | "currentBuildsWaitingForTemplate: {} " +
85 | "provisionForMinSpareAgents: {} numberToProvision: {}",
86 | agentTemplate.getTemplateName(),
87 | requiredMinAgents,
88 | currentNumberOfAgentsForTemplate,
89 | currentNumberOfSpareAgentsForTemplate,
90 | currentNumberOfProvisioningAgentsForTemplate,
91 | currentBuildsWaitingForTemplate,
92 | provisionForMinSpareAgents,
93 | numberToProvision);
94 | if (numberToProvision > 0) {
95 | cloud.provision(agentTemplate, numberToProvision);
96 | }
97 | });
98 | });
99 | }
100 |
101 | }
102 |
--------------------------------------------------------------------------------
/src/test/java/com/alibabacloud/jenkins/ecs/util/NetworkUtilsTest.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs.util;
2 |
3 | import com.google.common.collect.Lists;
4 | import org.apache.commons.lang.StringUtils;
5 | import org.junit.Assert;
6 | import org.junit.Test;
7 |
8 | import java.util.HashSet;
9 | import java.util.List;
10 | import java.util.Set;
11 |
12 | /**
13 | * Created by kunlun.ykl on 2020/11/20.
14 | */
15 | public class NetworkUtilsTest {
16 |
17 | @Test
18 | public void autoGenerateSubnetTest() {
19 | List otherVswCidrBlocks = Lists.newArrayList();
20 | String vpcCidrBlock;
21 | String subnet;
22 |
23 | // Case 1 ~ 4: Single existing VSW
24 | // Case 1: VPC-"172.16.0.0/12", other cidrBlocks-{"172.16.0.0/16"}
25 | vpcCidrBlock = "172.16.0.0/12";
26 | otherVswCidrBlocks = Lists.newArrayList("172.16.0.0/16");
27 | checkExpectation(vpcCidrBlock, otherVswCidrBlocks);
28 |
29 | // Case 2: VPC-"192.168.0.0/16", other cidrBlocks-{"192.168.0.0/18"}
30 | vpcCidrBlock = "192.168.0.0/16";
31 | otherVswCidrBlocks = Lists.newArrayList("192.168.0.0/18");
32 | checkExpectation(vpcCidrBlock, otherVswCidrBlocks);
33 |
34 | // Case 3: VPC-"10.0.0.0/8", other cidrBlocks-{"10.0.0.0/16"}
35 | vpcCidrBlock = "10.0.0.0/8";
36 | otherVswCidrBlocks = Lists.newArrayList("10.0.0.0/16");
37 | checkExpectation(vpcCidrBlock, otherVswCidrBlocks);
38 |
39 | // Case 4: VPC-"10.0.0.0/8", other cidrBlocks-{"10.0.0.0/22"}
40 | vpcCidrBlock = "10.0.0.0/8";
41 | otherVswCidrBlocks = Lists.newArrayList("10.0.0.0/22");
42 | checkExpectation(vpcCidrBlock, otherVswCidrBlocks);
43 |
44 | // Case 5 ~ 7: No existing VSWs
45 | // Case 5: VPC-"192.168.0.0/16", other cidrBlocks-{}
46 | vpcCidrBlock = "192.168.0.0/16";
47 | otherVswCidrBlocks = Lists.newArrayList();
48 | checkExpectation(vpcCidrBlock, otherVswCidrBlocks);
49 |
50 | // Case 6: VPC-"172.16.0.0/12", other cidrBlocks-{}
51 | vpcCidrBlock = "172.16.0.0/12";
52 | otherVswCidrBlocks = Lists.newArrayList();
53 | checkExpectation(vpcCidrBlock, otherVswCidrBlocks);
54 |
55 | // Case 7: VPC-"10.0.0.0/8", other cidrBlocks-{}
56 | vpcCidrBlock = "10.0.0.0/8";
57 | otherVswCidrBlocks = Lists.newArrayList();
58 | checkExpectation(vpcCidrBlock, otherVswCidrBlocks);
59 |
60 | // Case 8 ~ 11: Multiple existing VSWs and there is still available subnet which can be created
61 | // Case 8: VPC-"172.16.0.0/12", other cidrBlocks-{"172.16.128.0/18","172.16.64.0/18","172.16.32.0/19"}
62 | vpcCidrBlock = "172.16.0.0/12";
63 | otherVswCidrBlocks = Lists.newArrayList("172.16.128.0/18", "172.16.64.0/18", "172.16.32.0/19");
64 | checkExpectation(vpcCidrBlock, otherVswCidrBlocks);
65 |
66 | // Case 9: VPC-"192.168.0.0/16", other cidrBlocks-{"192.168.0.0/18","192.168.144.0/20","192.168.64.0/18"}
67 | vpcCidrBlock = "192.168.0.0/16";
68 | otherVswCidrBlocks = Lists.newArrayList("192.168.0.0/18", "192.168.144.0/20", "192.168.64.0/18");
69 | checkExpectation(vpcCidrBlock, otherVswCidrBlocks);
70 |
71 | // Case 10: VPC-"10.0.0.0/8", other cidrBlocks-{"10.0.0.0/18","10.0.128.0/18"}
72 | vpcCidrBlock = "10.0.0.0/8";
73 | otherVswCidrBlocks = Lists.newArrayList("10.0.0.0/18", "10.0.128.0/18");
74 | checkExpectation(vpcCidrBlock, otherVswCidrBlocks);
75 |
76 | // Case 11: VPC-"10.0.0.0/8", other cidrBlocks-{"10.0.128.0/18","10.0.0.0/18","10.0.192.0/18","10.0.64.0/19"}
77 | vpcCidrBlock = "10.0.0.0/8";
78 | otherVswCidrBlocks = Lists.newArrayList("10.0.128.0/18", "10.0.0.0/18", "10.0.192.0/18", "10.0.64.0/19");
79 | checkExpectation(vpcCidrBlock, otherVswCidrBlocks);
80 |
81 | //Case 12 ~ 13: All subnet possibilities are already occupied
82 | //Case 12: VPC-"192.168.0.0/16", other cidrBlocks-{"192.168.0.0/17","192.168.128.0/17"}
83 | vpcCidrBlock = "192.168.0.0/16";
84 | otherVswCidrBlocks = Lists.newArrayList("192.168.0.0/17", "192.168.128.0/17");
85 | subnet = NetworkUtils.autoGenerateSubnet(vpcCidrBlock, otherVswCidrBlocks);
86 | Assert.assertEquals(0, subnet.length());
87 |
88 | //Case 13: VPC-"192.168.0.0/24", other cidrBlocks-{"192.168.0.0/26","192.168.0.128/25","192.168.0.64/26"}
89 | vpcCidrBlock = "192.168.0.0/24";
90 | otherVswCidrBlocks = Lists.newArrayList("192.168.0.0/26", "192.168.0.128/25", "192.168.0.64/26");
91 | subnet = NetworkUtils.autoGenerateSubnet(vpcCidrBlock, otherVswCidrBlocks);
92 | Assert.assertEquals(0, subnet.length());
93 |
94 | }
95 |
96 | private void checkExpectation(String vpcCidrBlock, List otherVswCidrBlocks) {
97 | Set otherVswCidrBlockSet = new HashSet<>(otherVswCidrBlocks);
98 | for (String vsw : otherVswCidrBlockSet) {
99 | for (String otherVsw : otherVswCidrBlockSet) {
100 | if (StringUtils.equals(vsw, otherVsw)) {
101 | continue;
102 | }
103 | Assert.assertFalse(NetworkUtils.contains(vsw, otherVsw));
104 | Assert.assertFalse(NetworkUtils.contains(otherVsw, vsw));
105 | }
106 | }
107 | String subnet = NetworkUtils.autoGenerateSubnet(vpcCidrBlock, otherVswCidrBlocks);
108 | Assert.assertTrue(NetworkUtils.contains(vpcCidrBlock, subnet));
109 | for (String vswCidrBlock : otherVswCidrBlocks) {
110 | Assert.assertFalse(NetworkUtils.contains(subnet, vswCidrBlock));
111 | }
112 | }
113 |
114 | @Test
115 | public void containTest() {
116 | // Case 1: Three subnets and they have no overlapping each other
117 | List subNetNoOverlapping = Lists.newArrayList("192.168.0.0/26", "192.168.0.128/25", "192.168.0.64/26");
118 | for (String subnetOne : subNetNoOverlapping) {
119 | for (String subnetTwo : subNetNoOverlapping) {
120 | if (StringUtils.equals(subnetOne, subnetTwo)) {
121 | continue;
122 | }
123 | Assert.assertFalse(NetworkUtils.contains(subnetOne, subnetTwo));
124 | }
125 | }
126 |
127 | // Case 2: Same subnet
128 | String subnetSame = "192.168.0.0/26";
129 | Assert.assertTrue(NetworkUtils.contains(subnetSame, subnetSame));
130 |
131 | // Case 3: Two subnets, one contains another
132 | String biggerSubnet = "10.0.0.0/8";
133 | String smallerSubnet = "10.0.0.0/18";
134 | Assert.assertTrue(NetworkUtils.contains(biggerSubnet, smallerSubnet));
135 | Assert.assertFalse(NetworkUtils.contains(smallerSubnet, biggerSubnet));
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/win/WinConnection.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs.win;
2 |
3 | import java.io.IOException;
4 | import java.io.InputStream;
5 | import java.io.OutputStream;
6 | import java.net.InetSocketAddress;
7 | import java.net.Socket;
8 | import java.util.EnumSet;
9 | import java.util.logging.Level;
10 | import java.util.logging.Logger;
11 |
12 | import javax.net.ssl.SSLException;
13 |
14 | import com.alibabacloud.jenkins.ecs.win.winrm.WinRM;
15 | import com.alibabacloud.jenkins.ecs.win.winrm.WindowsProcess;
16 | import com.hierynomus.msdtyp.AccessMask;
17 | import com.hierynomus.mssmb2.SMB2CreateDisposition;
18 | import com.hierynomus.mssmb2.SMB2ShareAccess;
19 | import com.hierynomus.protocol.transport.TransportException;
20 | import com.hierynomus.smbj.SMBClient;
21 | import com.hierynomus.smbj.auth.AuthenticationContext;
22 | import com.hierynomus.smbj.connection.Connection;
23 | import com.hierynomus.smbj.session.Session;
24 | import com.hierynomus.smbj.share.DiskShare;
25 |
26 | public class WinConnection {
27 | private static final Logger LOGGER = Logger.getLogger(WinConnection.class.getName());
28 | private static final int TIMEOUT=80000; //8 seconds
29 | private final String host;
30 | private final String username;
31 | private final String password;
32 | private final SMBClient smbclient;
33 | private final AuthenticationContext authentication;
34 | private Connection connection;
35 | private Session session;
36 | private boolean useHTTPS;
37 | private boolean allowSelfSignedCertificate;
38 |
39 | @Deprecated
40 | public WinConnection(String host, String username, String password) {
41 | this(host, username, password, true);
42 | }
43 |
44 | public WinConnection(String host, String username, String password, boolean allowSelfSignedCertificate) {
45 | this.host = host;
46 | this.username = username;
47 | this.password = password;
48 | this.smbclient = new SMBClient();
49 | this.authentication = new AuthenticationContext(username, password.toCharArray(), null);
50 | this.allowSelfSignedCertificate = allowSelfSignedCertificate;
51 | }
52 |
53 | private static String toAdministrativeShare(String path) {
54 | // administrative windows share are DRIVE$path like
55 | return path.substring(0, 1) + "$";
56 | }
57 |
58 | private static String toFilePath(String path) {
59 | //Strip drive and leading forward slash
60 | return path.substring(3);
61 | }
62 |
63 | public WinRM winrm() {
64 | WinRM winrm = new WinRM(host, username, password, allowSelfSignedCertificate);
65 | winrm.setUseHTTPS(useHTTPS);
66 | return winrm;
67 | }
68 |
69 | public WinRM winrm(int timeout) {
70 | WinRM winrm = winrm();
71 | winrm.setTimeout(timeout);
72 | return winrm;
73 | }
74 |
75 | public WindowsProcess execute(String commandLine) {
76 | return execute(commandLine, 60);
77 | }
78 |
79 | public WindowsProcess execute(String commandLine, int timeout) {
80 | return winrm(timeout).execute(commandLine);
81 | }
82 |
83 | private DiskShare getSmbShare(String path) throws IOException {
84 | if(this.connection == null) {
85 | this.connection = smbclient.connect(host);
86 | }
87 | if(this.session == null) {
88 | this.session = connection.authenticate(this.authentication);
89 | }
90 | return (DiskShare) session.connectShare(toAdministrativeShare(path));
91 | }
92 |
93 | public OutputStream putFile(String path) throws IOException {
94 | return getSmbShare(path).openFile(toFilePath(path),
95 | EnumSet.of(AccessMask.GENERIC_READ,
96 | AccessMask.GENERIC_WRITE),
97 | null,
98 | SMB2ShareAccess.ALL,
99 | SMB2CreateDisposition.FILE_OVERWRITE_IF,
100 | null).getOutputStream();
101 | }
102 |
103 | public InputStream getFile(String path) throws IOException {
104 | return getSmbShare(path).openFile(toFilePath(path),
105 | EnumSet.of(AccessMask.GENERIC_READ),
106 | null, SMB2ShareAccess.ALL,
107 | null,
108 | null).getInputStream();
109 | }
110 |
111 | public boolean exists(String path) throws IOException {
112 | return getSmbShare(path).fileExists(toFilePath(path));
113 | }
114 |
115 | // keep this method for compatibility, not used in this plugin anymore
116 | public boolean ping() {
117 | try {
118 | return pingFailingIfSSHHandShakeError();
119 | } catch (IOException ignored) {
120 | return false;
121 | }
122 | }
123 |
124 | public boolean pingFailingIfSSHHandShakeError() throws IOException {
125 | LOGGER.log(Level.FINE, () -> "checking SMB connection to " + host);
126 | try (
127 | Socket socket = new Socket();
128 | Connection connection = smbclient.connect(host);
129 | Session session = connection.authenticate(authentication);
130 | ) {
131 | socket.connect(new InetSocketAddress(host, 5985), TIMEOUT);
132 | winrm().ping();
133 | session.connectShare("IPC$");
134 | return true;
135 | } catch (Exception e) {
136 | LOGGER.log(Level.WARNING, "Failed to verify connectivity to Windows agent", e);
137 | if (e instanceof SSLException) {
138 | throw e;
139 | } else if (e instanceof TransportException) {
140 | // JENKINS-66736: unregister and try again
141 | smbclient.getServerList().unregister(host);
142 | } else if (e.getCause() instanceof SSLException) {
143 | throw (SSLException) e.getCause();
144 | }
145 | return false;
146 | }
147 | }
148 |
149 | public void close() {
150 | if(this.session != null) {
151 | try {
152 | this.session.close();
153 | } catch (Exception e) {
154 | LOGGER.log(Level.SEVERE, "Failed to close session", e);
155 | }
156 | }
157 | if(this.connection != null) {
158 | try {
159 | this.connection.close();
160 | } catch (Exception e) {
161 | LOGGER.log(Level.SEVERE, "Failed to close connection", e);
162 | }
163 | }
164 | if(this.smbclient != null) {
165 | try {
166 | this.smbclient.close();
167 | } catch (Exception e) {
168 | LOGGER.log(Level.SEVERE, "Failed to close smbClient", e);
169 | }
170 | }
171 | }
172 |
173 | public void setUseHTTPS(boolean useHTTPS) {
174 | this.useHTTPS = useHTTPS;
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/util/NetworkUtils.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs.util;
2 |
3 | import inet.ipaddr.IPAddress;
4 | import inet.ipaddr.IPAddressString;
5 | import lombok.extern.slf4j.Slf4j;
6 |
7 | import java.util.Iterator;
8 | import java.util.Set;
9 | import java.util.List;
10 | import java.util.Comparator;
11 | import java.util.stream.Collectors;
12 | import java.util.TreeSet;
13 | import java.util.HashSet;
14 | import java.util.Arrays;
15 |
16 | /**
17 | * Created by kunlun.ykl on 2020/11/20.
18 | */
19 | @Slf4j
20 | public class NetworkUtils {
21 |
22 | /**
23 | * generate a VSW cidr block which doesn't overlap with any existing VSWs' cidr blocks
24 | *
25 | * @param vpcCidrBlock vpc address in which we generate the VSW cidr block
26 | * @param otherVswCidrBlocks existing VSWs' cidr blocks list
27 | * @return available VSW cidr block address
28 | */
29 | private final static int DEFAULT_SUBNET_MASK_SHIFT = 1;
30 | private final static int DEFAULT_SUBNET_MASK_LENGTH = 17;
31 |
32 | public static String autoGenerateSubnet(String vpcCidrBlock, List otherVswCidrBlocks) {
33 | log.info("-------------------vpcCidrBlock : {}, otherVswCidrBlocks : {}", vpcCidrBlock, otherVswCidrBlocks);
34 | IPAddress result = null;
35 | IPAddress vpcIpAddress = new IPAddressString(vpcCidrBlock).getAddress();
36 | // When there is no existing VSWs, generate a default VSW whose network prefix length is bigger than the VPC's network prefix length and fits the Aliyun VSW requirement
37 | // In Aliyun, users can use 192.168.0.0/16、172.16.0.0/12、10.0.0.0/8 and theirs subnets to construct a VPC; subnet mask ranges from 16 to 24 (including 16 and 24)
38 | // For VSW, the maximum network prefix length is 29 and the minimum is 16 (VSW network prefix length should be no less than VPC network prefix length)
39 | if (otherVswCidrBlocks.size() == 0) {
40 | int vpcPrefixLength = vpcIpAddress.getNetworkPrefixLength();
41 | IPAddress availableSubNetRange;
42 | if (vpcPrefixLength >= 16) {
43 | availableSubNetRange = vpcIpAddress.setPrefixLength(vpcIpAddress.getPrefixLength() + DEFAULT_SUBNET_MASK_SHIFT, false);
44 | } else {
45 | availableSubNetRange = vpcIpAddress.setPrefixLength(DEFAULT_SUBNET_MASK_LENGTH);
46 | }
47 | Iterator extends IPAddress> iterator = availableSubNetRange.prefixBlockIterator();
48 | String availableSubnetAddress = iterator.next().toString();
49 | log.info("-------------------vpcCidrBlock : {}, otherVswCidrBlocks : {}, output available subnet : {}", vpcCidrBlock, otherVswCidrBlocks, availableSubnetAddress);
50 | return availableSubnetAddress;
51 | }
52 | List vswIpAddresses = otherVswCidrBlocks.stream().
53 | map(s -> new IPAddressString(s).getAddress()).
54 | sorted(Comparator.comparing(IPAddress::getNetworkPrefixLength)).
55 | collect(Collectors.toList());
56 | Set resultSet = new TreeSet<>();
57 | resultSet.add(vpcIpAddress);
58 | int numOfVSWs = vswIpAddresses.size();
59 | for (int i = 0; i < numOfVSWs; i++) {
60 | IPAddress vswIpAddress = vswIpAddresses.get(i);
61 | TreeSet filteredSet = new TreeSet<>();
62 | for (IPAddress candidateSubNet : resultSet) {
63 | filteredSet.addAll(subtractSubnet(candidateSubNet, vswIpAddress));
64 | }
65 | // All possible subnets are occupied
66 | if (filteredSet.size() == 0) {
67 | break;
68 | }
69 | // Judge if there is one available subnet address already
70 | IPAddress availableSubnet;
71 | if (i <= numOfVSWs - 2) {
72 | availableSubnet = getAvailableSubnet(filteredSet, vswIpAddresses.subList(i + 1, numOfVSWs));
73 | } else {
74 | availableSubnet = filteredSet.first();
75 | }
76 | // Subnet founded, exit
77 | if (availableSubnet != null) {
78 | result = availableSubnet;
79 | break;
80 | }
81 | resultSet = filteredSet;
82 | }
83 | if (result == null) {
84 | log.warn("No available subnet. vpcCidrBlock : {}, otherVswCidrBlocks : {}", vpcCidrBlock, otherVswCidrBlocks);
85 | return "";
86 | }
87 | String availableSubnetAddress = result.toString();
88 | log.info("-------------------vpcCidrBlock : {}, otherVswCidrBlocks : {}, output available subnet : {}", vpcCidrBlock, otherVswCidrBlocks, availableSubnetAddress);
89 | return availableSubnetAddress;
90 | }
91 |
92 | /**
93 | * try to find out one available subnet in the candidateSet which doesn't contain any addresses in existingIpAddresses
94 | *
95 | * @param candidateSet candidate subnets
96 | * @param existingIpAddresses ip addresses which need to be exclusive
97 | * @return any available subnet or null if there is no available subnet
98 | */
99 | private static IPAddress getAvailableSubnet(Set candidateSet, List existingIpAddresses) {
100 | for (IPAddress candidateSubNet : candidateSet) {
101 | boolean found = true;
102 | for (int i = 0; i < existingIpAddresses.size(); i++) {
103 | if (candidateSubNet.contains(existingIpAddresses.get(i))) {
104 | found = false;
105 | break;
106 | }
107 | }
108 | if (found) {
109 | return candidateSubNet;
110 | }
111 | }
112 | return null;
113 | }
114 |
115 | /**
116 | * remove vswIpAddress from the candidateSubNet and get the result set which contains the remaining subnets
117 | *
118 | * @param candidateSubNet original subnet
119 | * @param vswIpAddress ip address which need to be excluded from the original subnet
120 | * @return available subnets
121 | */
122 | private static Set subtractSubnet(IPAddress candidateSubNet, IPAddress vswIpAddress) {
123 | Set resultSet = new HashSet<>();
124 | IPAddress[] addresses = candidateSubNet.subtract(vswIpAddress);
125 | if (addresses != null) {
126 | for (IPAddress ipAddress : addresses) {
127 | resultSet.addAll(Arrays.asList(ipAddress.spanWithPrefixBlocks()));
128 | }
129 | }
130 | return resultSet;
131 | }
132 |
133 | /**
134 | * judge if the first network contains the second network; return true if there is inclusion relation, otherwise false
135 | *
136 | * @param parentNetwork
137 | * @param childNetwork
138 | * @return
139 | */
140 | public static Boolean contains(String parentNetwork, String childNetwork) {
141 | IPAddress one = new IPAddressString(parentNetwork).getAddress();
142 | IPAddress two = new IPAddressString(childNetwork).getAddress();
143 | return one.contains(two);
144 | }
145 | }
146 |
147 |
148 |
--------------------------------------------------------------------------------
/src/main/java/com/alibabacloud/jenkins/ecs/util/CloudHelper.java:
--------------------------------------------------------------------------------
1 | package com.alibabacloud.jenkins.ecs.util;
2 |
3 | import com.alibabacloud.jenkins.ecs.AlibabaCloud;
4 | import com.alibabacloud.jenkins.ecs.AlibabaEcsFollowerTemplate;
5 | import com.alibabacloud.jenkins.ecs.AlibabaEcsSpotFollower;
6 | import com.aliyuncs.ecs.model.v20140526.DescribeInstancesResponse;
7 | import com.google.common.collect.Lists;
8 | import hudson.model.Computer;
9 | import hudson.model.Hudson.CloudList;
10 | import hudson.model.Node;
11 | import hudson.slaves.Cloud;
12 | import hudson.slaves.NodeProvisioner;
13 | import jenkins.model.Jenkins;
14 |
15 | import java.io.IOException;
16 | import java.util.List;
17 | import java.util.concurrent.Callable;
18 | import java.util.logging.Level;
19 | import java.util.logging.Logger;
20 |
21 | /**
22 | * Created by kunlun.ykl on 2020/8/27.
23 | */
24 | public class CloudHelper {
25 | private static final Logger LOGGER = Logger.getLogger(CloudHelper.class.getName());
26 |
27 | public static AlibabaCloud getCloud(String cloudName) {
28 | CloudList clouds = Jenkins.get().clouds;
29 | for (Cloud cloud : clouds) {
30 | if (!(cloud instanceof AlibabaCloud)) {
31 | continue;
32 | }
33 | if (((AlibabaCloud) cloud).getCloudName().equals(cloudName)) {
34 | return (AlibabaCloud) cloud;
35 | }
36 | }
37 | return null;
38 | }
39 |
40 | public static int getCntOfNodeByCloudName(String cloudName) {
41 | int nodeCnt = 0;
42 | Jenkins jenkinsInstance = Jenkins.get();
43 | for (Node node : jenkinsInstance.getNodes()) {
44 | if (!(node instanceof AlibabaEcsSpotFollower)) {
45 | continue;
46 | }
47 | AlibabaEcsSpotFollower follower = (AlibabaEcsSpotFollower) node;
48 | if (follower.getCloud() == null) {
49 | LOGGER.log(Level.INFO, "cannot get cloud for slave: {0}", new Object[]{follower.getNodeName()});
50 | continue;
51 | }
52 | // httodo: 判断follower状态
53 | if (follower.getCloud().getCloudName().equals(cloudName)) {
54 | nodeCnt++;
55 | }
56 | }
57 | return nodeCnt;
58 | }
59 |
60 | public static void attachSlavesToJenkins(List slaves, AlibabaEcsFollowerTemplate t) throws IOException {
61 | Jenkins jenkins = Jenkins.get();
62 | for (final AlibabaEcsSpotFollower slave : slaves) {
63 | if (slave == null) {
64 | LOGGER.warning("Can't raise node for " + t);
65 | continue;
66 | }
67 | Computer c = slave.toComputer();
68 | if (c != null) {
69 | c.connect(false);
70 | }
71 | jenkins.addNode(slave);
72 | }
73 | }
74 |
75 |
76 | public static List getNodesByTmp(String templateName) {
77 | List nodes = Lists.newArrayList();
78 | /*
79 | Computer[] computers = Jenkins.get().getComputers();
80 | for (Computer computer : computers) {
81 | if (!(computer instanceof AlibabaEcsComputer)) {
82 | continue;
83 | }
84 | AlibabaEcsSpotFollower node = ((AlibabaEcsComputer) computer).getNode();
85 | if(null != node) {
86 | nodes.add(node);
87 | }
88 | }
89 | */
90 | Jenkins jenkinsInstance = Jenkins.get();
91 | for (Node node : jenkinsInstance.getNodes()) {
92 | if (!(node instanceof AlibabaEcsSpotFollower)) {
93 | continue;
94 | }
95 | AlibabaEcsSpotFollower follower = (AlibabaEcsSpotFollower) node;
96 | // httodo: 判断follower状态
97 | if (templateName.equals(follower.getTemplateName())) {
98 | nodes.add(follower);
99 | }
100 | }
101 | return nodes;
102 | }
103 |
104 | public static NodeProvisioner.PlannedNode createPlannedNode(AlibabaEcsFollowerTemplate t, AlibabaEcsSpotFollower slave) {
105 | return new NodeProvisioner.PlannedNode(t.getTemplateName(), Computer.threadPoolForRemoting.submit(new Callable() {
106 | int retryCount = 0;
107 | private static final int DESCRIBE_LIMIT = 2;
108 |
109 | public Node call() throws Exception {
110 | while (true) {
111 | String instanceId = slave.getEcsInstanceId();
112 | DescribeInstancesResponse.Instance instance = getInstanceWithRetry(slave);
113 | if (instance == null) {
114 | LOGGER.log(Level.WARNING, "{0} Cannot find instance with instance id {1} in cloud {2}. Terminate provisioning ", new Object[]{t, instanceId, slave.getCloudName()});
115 | return null;
116 | }
117 |
118 | String status = instance.getStatus();
119 | if (status.equals("Running")) {
120 | Computer c = slave.toComputer();
121 | if (c != null) {
122 | c.connect(false);
123 | }
124 | long startCostInSeconds = EcsInstanceHelper.getStartCostInSeconds(instance);
125 | LOGGER.log(Level.INFO, "{0} Node {1} moved to RUNNING state in {2} seconds and is ready to be connected by Jenkins", new Object[]{t, slave.getNodeName(), startCostInSeconds});
126 | return slave;
127 | }
128 |
129 | if (!status.equals("Starting")) {
130 | if (retryCount >= DESCRIBE_LIMIT) {
131 | LOGGER.log(Level.WARNING, "Instance {0} did not move to running after {1} attempts, terminating provisioning", new Object[]{instanceId, retryCount});
132 | return null;
133 | }
134 |
135 | LOGGER.log(Level.INFO, "Attempt {0}: {1}. Node {2} is neither pending, neither running, it''s {3}. Will try again after 5s", new Object[]{retryCount, t, slave.getNodeName(), status});
136 | retryCount++;
137 | }
138 |
139 | Thread.sleep(5000);
140 | }
141 | }
142 | }), t.getNumExecutors());
143 | }
144 |
145 | public static DescribeInstancesResponse.Instance getInstanceWithRetry(AlibabaEcsSpotFollower slave) {
146 | // Sometimes even after a successful RunInstances, DescribeInstances
147 | // returns an error for a few seconds. We do a few retries instead of
148 | // failing instantly. See [JENKINS-15319].
149 | for (int i = 0; i < 5; i++) {
150 | DescribeInstancesResponse.Instance instance = slave.describeNode();
151 | if (null != instance) {
152 | return instance;
153 | }
154 | try {
155 | Thread.sleep(5000);
156 | } catch (InterruptedException e) {
157 | e.printStackTrace();
158 | }
159 | }
160 | return null;
161 | }
162 | }
163 |
--------------------------------------------------------------------------------