├── LICENSE ├── README.md ├── aws-instance-scheduler ├── instance-scheduler-2.3.2.0.zip ├── instance-scheduler-helper.zip └── scheduler-cli.zip ├── images ├── lambda-structure.png ├── resource-tagging.png └── whole-architecture.png ├── instance-scheduler-cn.template └── instance-scheduler-remote-cn.template /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Amazon Software License 3 | 4 | 1. Definitions 5 | 6 | “Licensor” means any person or entity that distributes its Work. 7 | 8 | “Software” means the original work of authorship made available under this License. 9 | 10 | “Work” means the Software and any additions to or derivative works of the Software that are made available under this License. 11 | 12 | The terms “reproduce,” “reproduction,” “derivative works,” and “distribution” have the meaning as provided under U.S. copyright law; provided, however, that for the purposes of this License, derivative works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work. 13 | 14 | Works, including the Software, are “made available” under this License by including in or with the Work either (a) a copyright notice referencing the applicability of this License to the Work, or (b) a copy of this License. 15 | 16 | 2. License Grants 17 | 18 | 2.1 Copyright Grant. Subject to the terms and conditions of this License, each Licensor grants to you a perpetual, worldwide, non-exclusive, royalty-free, copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense and distribute its Work and any resulting derivative works in any form. 19 | 20 | 2.2 Patent Grant. Subject to the terms and conditions of this License, each Licensor grants to you a perpetual, worldwide, non-exclusive, royalty-free patent license to make, have made, use, sell, offer for sale, import, and otherwise transfer its Work, in whole or in part. The foregoing license applies only to the patent claims licensable by Licensor that would be infringed by Licensor’s Work (or portion thereof) individually and excluding any combinations with any other materials or technology. 21 | 22 | 3. Limitations 23 | 24 | 3.1 Redistribution. You may reproduce or distribute the Work only if (a) you do so under this License, (b) you include a complete copy of this License with your distribution, and (c) you retain without modification any copyright, patent, trademark, or attribution notices that are present in the Work. 25 | 26 | 3.2 Derivative Works. You may specify that additional or different terms apply to the use, reproduction, and distribution of your derivative works of the Work (“Your Terms”) only if (a) Your Terms provide that the use limitation in Section 3.3 applies to your derivative works, and (b) you identify the specific derivative works that are subject to Your Terms. Notwithstanding Your Terms, this License (including the redistribution requirements in Section 3.1) will continue to apply to the Work itself. 27 | 28 | 3.3 Use Limitation. The Work and any derivative works thereof only may be used or intended for use with the web services, computing platforms or applications provided by Amazon.com, Inc. or its affiliates, including Amazon Web Services, Inc. 29 | 30 | 3.4 Patent Claims. If you bring or threaten to bring a patent claim against any Licensor (including any claim, cross-claim or counterclaim in a lawsuit) to enforce any patents that you allege are infringed by any Work, then your rights under this License from such Licensor (including the grants in Sections 2.1 and 2.2) will terminate immediately. 31 | 32 | 3.5 Trademarks. This License does not grant any rights to use any Licensor’s or its affiliates’ names, logos, or trademarks, except as necessary to reproduce the notices described in this License. 33 | 34 | 3.6 Termination. If you violate any term of this License, then your rights under this License (including the grants in Sections 2.1 and 2.2) will terminate immediately. 35 | 36 | 4. Disclaimer of Warranty. 37 | 38 | THE WORK IS PROVIDED “AS IS” WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WARRANTIES OR CONDITIONS OF M ERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE OR NON-INFRINGEMENT. YOU BEAR THE RISK OF UNDERTAKING ANY ACTIVITIES UNDER THIS LICENSE. SOME STATES’ CONSUMER LAWS DO NOT ALLOW EXCLUSION OF AN IMPLIED WARRANTY, SO THIS DISCLAIMER MAY NOT APPLY TO YOU. 39 | 40 | 5. Limitation of Liability. 41 | 42 | EXCEPT AS PROHIBITED BY APPLICABLE LAW, IN NO EVENT AND UNDER NO LEGAL THEORY, WHETHER IN TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE SHALL ANY LICENSOR BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR RELATED TO THIS LICENSE, THE USE OR INABILITY TO USE THE WORK (INCLUDING BUT NOT LIMITED TO LOSS OF GOODWILL, BUSINESS INTERRUPTION, LOST PROFITS OR DATA, COMPUTER FAILURE OR MALFUNCTION, OR ANY OTHER COMM ERCIAL DAMAGES OR LOSSES), EVEN IF THE LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 43 | 44 | Effective Date – April 18, 2008 © 2008 Amazon.com, Inc. or its affiliates. All rights reserved. 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AWS Instance Scheduler In China regions 2 | 3 | 在AWS中国区域,通过AWS Lambda实现实例的自动化调度,以优化资源使用成本和简化管理。 4 | 5 | ## 1,为什么要实现实例自动化调度? 6 | 7 | AWS按需提供基础架构,以便客户可以控制其资源容量并仅为其使用的资源付费。降低成本的一种简单方法是停止未使用的资源,然后在需要时再次启动这些资源。但是当客户存在大量这类资源,或者需要在多个区域(如:北京和宁夏区域)甚至跨多个帐户进行统一管理这些资源时,对管理员来说将是一个非常繁琐的工作。 8 | 9 | [AWS Instance Scheduler](https://aws.amazon.com/cn/answers/infrastructure-management/instance-scheduler/)是一种自动启动和停止Amazon Elastic Compute Cloud(Amazon EC2)和Amazon Relational Database Service(Amazon RDS)实例的解决方案。本项目以AWS Instance Scheduler方案为核心,针对中国区域的实际情况,进行了补充和修改,以便在北京和宁夏区域可以直接部署和使用。 10 | 11 | 实例调度程序将利用AWS资源标记和AWS Lambda在客户定义的调度计划中跨多个AWS区域和帐户自动停止和重新启动实例。 (请注意,停止Amazon EC2实例与终止Amazon EC2实例不同。默认情况下,Amazon EC2实例配置为在关闭时停止,而不是终止,但客户可以修改此行为。在使用此解决方案之前,请验证实例已合适设置为停止或终止。)该解决方案易于部署,有助于降低运营成本,简化运维工作量。例如,组织可以使用生产环境中的Instance Scheduler在工作时间之外每天自动停止实例。对于使所有实例完全运行的客户而言,此解决方案可以为那些仅在正常工作时间内需要的实例(每周利用率从168小时减少到50小时)节省高达70%的成本。 12 | 请注意,由于该实例调度程序使用了AWS Lambda和Amazon DynamoDB,你需要负责运行实例调度程序时使用的AWS服务的成本。 13 | 14 | ## 2,方案架构 15 | ![整体架构](images/whole-architecture.png)。 16 | 17 | 通过以客户定义的时间间隔设置Amazon CloudWatch事件。此事件调用Instance Scheduler的AWS Lambda函数。在配置期间,用户定义AWS区域和帐户,以及Instance Scheduler将用于将调度计划与适用的Amazon EC2和Amazon RDS实例相关联的自定义标签。这些值存储在Amazon DynamoDB中,Lambda函数每次运行时都会检索它们。然后,客户将自定义标签应用于适用的实例。 18 | 19 | 在Instance Scheduler的初始配置期间,将定义用于标识适用的Amazon EC2和Amazon RDS实例的标签键。创建计划时,您指定的名称将用作标签值,用于标识要应用于标签资源的调度计划。例如,用户可以使用解决方案的默认标签键Schedule并创建一个名为uk-office-hours的调度计划。要识别将使用英国办公时间计划的实例,用户会将Schedule标签键添加uk-office-hours值。如图所示:![资源标记](images/resource-tagging.png) 20 | 21 | 每次实例调度程序的Lambda函数运行时,它会根据相关计划中的目标状态(由实例标签中的计划中的一个或多个句点定义)检查每个适当标记的实例的当前状态,然后在必要时执行适当的启动或停止活动。 22 | 23 | 例如,如果在周五上午9点调用Lambda函数,并且它使用Schedule = cn-office-hours标签标识已停止的Amazon EC2或Amazon RDS实例,则它将检查Amazon DynamoDB的办公时间计划配置的细节。 如果办公时间计划包含一个周期规则,指示该实例应在周一至周五从上午9点到下午6点运行,则Lambda函数将启动该实例。 24 | 25 | 本方案使用AWS Systems Manager的参数仓库(Parameter Store)存放Lambda函数运行所需的参数,如Lambda函数运行时访问DynamoDB的表名称。需要说明一下,从2019年11月,Lambda环境变量特性在中国宁夏和北京区域已经可以使用。 26 | 27 | AWS Systems Manager 提供一个集中式存储来管理配置数据,支持数据库字符串等纯文本数据或密码等保密数据。这让您能够将保密数据和配置数据与代码分开。您可以标记参数并将其整理成不同的层级,这有助于您更轻松地管理参数。例如,您可以将同一参数名称 (“db-string”) 与不同的层级路径 (“dev/db-string”或“prod/db-string”) 结合使用,用于存储不同的值。此外,您还可以使用 AWS Identity and Access Management (IAM) 控制用户和资源对参数的访问权限。参数可以通过 Amazon Elastic Container Service、AWS Lambda 和 AWS CloudFormation 等其他 AWS 服务引用。 28 | 29 | ## 3,实现细节 30 | [**定义IAM角色和策略**] 31 | 32 | 这里要为Lambda和CloudWatch Events授予相应的访问权限,例如Lambda函数执行中需要读DynamoDB调度计划配置表、写DynamoDB状态表,读取资源标记,启动、停止实例等。详细权限信息可参考[部署模版](instance-scheduler-cn.template)中权限部分的定义。 33 | 34 | [**构建Lambda函数及相关服务配置**] 35 | 36 | 给出Lambda函数的核心结构:![Lambda函数结构](images/lambda-structure.png)。 37 | 38 | 其中,handle_request部分为核心处理逻辑,这里根据处理类型的不同,封装了几个Handler类,分别处理来自CloudWatch Event、调度器初始化设置、管理CLI的计划配置请求和实例调度请求,实例调度请求将按照账户、区域和调度种类(EC2/RDS)进行分层处理。 39 | 40 | 这部分涉及到的DynamoDB配置表和状态表、SNS Topic/Subscription、CloudWatch Log Group及Parameter Store参数的创建相对比较简单,这里就不再赘述。更多信息可参考[部署模版](instance-scheduler-cn.template)中相应部分的定义。 41 | 42 | 一个复杂的程序经常需要在很多模块文件中读取和使用不同的参数,因此,为了避免在不同地方重复编写雷同的代码,有必要将Lambda函数中对其它服务的API访问进行封装。这里参照面向对象的设计思想,以对Parameter Store API的访问为例,根据访问逻辑对参数集对象进行封装并定义相应的类,以方便其它地方复用。 43 | 44 | 封装的Paramter Store类定义如下: 45 | 46 | ```python 47 | class SSMParamStore(object): 48 | """ 49 | Provide a dictionary-like interface to access AWS SSM Parameter Store 50 | """ 51 | def __init__(self, prefix=None, ssm_client=None, ttl=None): 52 | self._prefix = (prefix or "").rstrip("/") + "/" 53 | self._client = ssm_client or boto3.client('ssm') 54 | self._ttl = ttl 55 | ... 56 | 57 | def get(self, name, default=None): 58 | ... 59 | abs_key = "%s%s" % (self._prefix, name) 60 | if name not in self._keys: 61 | self.errormsg = "get error: Key not exist!" 62 | return default 63 | ... 64 | else: 65 | return self._get_value(name, abs_key) 66 | 67 | def put(self, key, value): 68 | assert key, 'Key can not be empty!' 69 | abs_key = "%s%s" % (self._prefix, key) 70 | try: 71 | response = self._client.put_parameter( 72 | Name=abs_key, 73 | Value=value, 74 | Overwrite=True, 75 | Type='String' 76 | ) 77 | ... 78 | 79 | def put_secure(self, key, value, kmsKeyId=None): 80 | assert key, 'Key can not be empty!' 81 | abs_key = "%s%s" % (self._prefix, key) 82 | try: 83 | if kmsKeyId is None: 84 | response = self._client.put_parameter( 85 | Name=abs_key, 86 | Value=value, 87 | Overwrite=True, 88 | Type='SecureString' 89 | ) 90 | ... 91 | 92 | def delete(self, name): 93 | ... 94 | abs_key = "%s%s" % (self._prefix, name) 95 | ... 96 | else: 97 | resp = self._client.delete_parameter(Name=abs_key) 98 | self._keys.pop(name,None) 99 | return resp 100 | 101 | def refresh(self): 102 | ... 103 | while not end_of_rec: 104 | if not nextToken: 105 | responses = self._client.describe_parameters( 106 | ParameterFilters=[ 107 | dict(Key="Path", Option="Recursive", Values=[self._prefix]) 108 | ] 109 | ) 110 | else: 111 | responses = self._client.describe_parameters( 112 | NextToken=nextToken, 113 | ParameterFilters=[ 114 | dict(Key="Path", Option="Recursive", Values=[self._prefix]) 115 | ] 116 | ) 117 | ... 118 | 119 | def _get_value(self, name, abs_key): 120 | ... 121 | if 'value' not in entry: 122 | parameter = self._client.get_parameter(Name=abs_key, WithDecryption=True)['Parameter'] 123 | ... 124 | return entry['value'] 125 | ``` 126 | 这样,在Lambda函数中当需要访问Parameter Store中的参数时,通过以下调用就可以方便获取所需的参数: 127 | 128 | ```python 129 | import ssm_paramstore 130 | from ssm_paramstore import SSMParamStore 131 | 132 | #set parameterstore path prefix and get the handler to 133 | ssmPrefix = "/lambdaEnvConfig/InstanceSchedulerMain" 134 | ssm_paramstore.conf = SSMParamStore(prefix=ssmPrefix) 135 | 136 | ... 137 | debug = ssm_paramstore.conf.get(configuration.ENV_TRACE,'False') 138 | ... 139 | ``` 140 | 141 | [**创建CloudWatch的Event规则**] 142 | 143 | 完成前面步骤后,这一步就非常简单了,只需要在CloudWatch控制台中创建Event Rule,并指定自定义时间间隔,然后将上面创建的Lambda函数加到Target中就好了。 144 | 145 | ## 4,部署模版 146 | 为了简化实例调度程序的部署,这里给出了可以在中国区域使用的AWS CloudFormation json格式模版,你可以直接通过模版快速部署上述实例自动调度方案。 147 | 共包括两个模版: 148 | - [**在管理账户中执行的模版**](instance-scheduler-cn.template) 149 | 150 | - [**在被管账户中执行的模版**](instance-scheduler-remote-cn.template) 151 | 152 | 同时,请将aws-instance-scheduler目录复制到相应的S3 Bucket下面。 153 | 154 | 注意:如果使用跨账户管理实例调度,请将被管账户模版执行结果role ARN填写到管理账户模版执行的输入参数cross account role ARN中,多个ARN以逗号分隔。 155 | 156 | ## 5,使用说明 157 | 两个简单步骤: 158 | - 在DynamoDB的xxx-ConfigTable-xxx中参照缺省计划定义你的period和schedule。其中,period是用于定义时间段或周期信息,schedule定义调度计划信息。 这里也提供了CLI接口程序包,你可以将schedule-cli.zip下载到本地安装,通过cli接口配置和定义调度计划,具体命令格式可执行scheduler-cli -h查看 159 | - 为需要管理的实例打标签,格式如Schedule = cn-office-hours,其中cn-office-hours就是你定义的schedule的名称 160 | 161 | 162 | **schedule定义支持的属性** 163 | 164 | | 属性名称 | 说明 | 165 | |:--------------|----------------------------------------------------------------------------------| 166 | |name|Unique Name,用做tag value,标识作用于被标记资源的schedule| 167 | |type|标识此item为schedule| 168 | |timezone|Time Zone。如果不指定,采用config中的缺省时区| 169 | |periods|一个或多个period,定义实例的运行时间。当有多个时,至少一个满足条件| 170 | |enforced|强制保持schedule中定义的状态| 171 | |retain_running|保持运行状态,即如果在period之前启动了实例,那么在period结束时间点不自动停止| 172 | |override_status|临时重载启停活动,即如果设置为running,将不会自动停止实例;如果设置为stopped,将不会自动启动实例。如果同时指定了enforced属性,enforced优先| 173 | |\@\|可选,只针对EC2实例。当针对一个period指定实例类型时,将自动按照指定实例类型启动被标记实例。如果运行中实例的类型不同于period指定的类型,将停止当前实例并按照指定实例类型重新启动(Resizing要求指定的实例类型与当前类型兼容)| 174 | |use_maintenance_window|只针对RDS实例。选择是否将RDS维护窗口作为一个running period加到schedule中| 175 | |use_metrics|选择是否在schedule级别enable CloudWatch metrics,它会重载config级别的设置| 176 | 177 | 178 | **period定义支持的属性** 179 | 180 | |属性名称|说明| 181 | |:--------------|----------------------------------------------------------------------------------| 182 | |type|标识此item为 period| 183 | |begintime|启动实例的时间点(HH:MM)。如果未指定endtime,但指定了weekdays,将通过weekdays确定停止时间点。例如weekdays指定mon-fri,则实例将在周五的11:59 pm被停止| 184 | |endtime|停止实例的时间点(HH:MM)。如果begintime和endtime都未指定,将使用weekdays, monthdays, months来确定启停时间点| 185 | |weekdays|指定一星期中在哪些天实例将运行。可以是列表(mon,fri),范围(mon-fri),一个月中第n个(mon#1),一个月中最后一个(friL)| 186 | |monthdays|指定一个月中在哪些天实例将运行。可以是列表(1-7,10,20),范围(1-30),每n天(1/5,1-15/2),最后一天(L),离指定日期最近的工作日(25W)| 187 | |months|指定那几个月实例将运行。可以是列表(1,6,9,12),范围(1-6),每n个月(Jan/3, Jan-Jul/2)| 188 | 189 | 注意: 190 | - 如果一个schedule包含多个period,建议同时指定begintime和endtime,否则将根据其它period中指定的时间来决定启停时间点。 191 | - 必须至少指定以下几个属性中的一个:begintime, endtime, weekdays, monthdays, months 192 | - 当一个period中包含多个条件时,所有条件必须为true 193 | 194 | 195 | 196 | 197 | 198 | -------------------------------------------------------------------------------- /aws-instance-scheduler/instance-scheduler-2.3.2.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jet1350/AWSInstanceScheduler/b0533edc871b353caca578773ac5c3aabff39c52/aws-instance-scheduler/instance-scheduler-2.3.2.0.zip -------------------------------------------------------------------------------- /aws-instance-scheduler/instance-scheduler-helper.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jet1350/AWSInstanceScheduler/b0533edc871b353caca578773ac5c3aabff39c52/aws-instance-scheduler/instance-scheduler-helper.zip -------------------------------------------------------------------------------- /aws-instance-scheduler/scheduler-cli.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jet1350/AWSInstanceScheduler/b0533edc871b353caca578773ac5c3aabff39c52/aws-instance-scheduler/scheduler-cli.zip -------------------------------------------------------------------------------- /images/lambda-structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jet1350/AWSInstanceScheduler/b0533edc871b353caca578773ac5c3aabff39c52/images/lambda-structure.png -------------------------------------------------------------------------------- /images/resource-tagging.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jet1350/AWSInstanceScheduler/b0533edc871b353caca578773ac5c3aabff39c52/images/resource-tagging.png -------------------------------------------------------------------------------- /images/whole-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jet1350/AWSInstanceScheduler/b0533edc871b353caca578773ac5c3aabff39c52/images/whole-architecture.png -------------------------------------------------------------------------------- /instance-scheduler-cn.template: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "Instance scheduler, version 2.3.2.0", 4 | "Parameters": { 5 | "SchedulingActive": { 6 | "Type": "String", 7 | "AllowedValues": [ 8 | "Yes", 9 | "No" 10 | ], 11 | "Default": "Yes", 12 | "Description": "Activate or deactivate scheduling." 13 | }, 14 | "ScheduledServices": { 15 | "Type": "String", 16 | "AllowedValues": [ 17 | "EC2", 18 | "RDS", 19 | "Both" 20 | ], 21 | "Default": "EC2", 22 | "Description": "Scheduled Services" 23 | }, 24 | "MemorySize": { 25 | "Type": "Number", 26 | "AllowedValues": [ 27 | 128, 28 | 384, 29 | 512, 30 | 640, 31 | 768, 32 | 896, 33 | 1024, 34 | 1152, 35 | 1280, 36 | 1408, 37 | 1536 38 | ], 39 | "Default": 128, 40 | "Description": "Size of the Lambda function running the scheduler, increase size when processing large numbers of instances" 41 | }, 42 | "UseCloudWatchMetrics": { 43 | "Type": "String", 44 | "AllowedValues": [ 45 | "Yes", 46 | "No" 47 | ], 48 | "Default": "No", 49 | "Description": "Collect instance scheduling data using CloudWatch metrics." 50 | }, 51 | "LogRetentionDays": { 52 | "Type": "Number", 53 | "Default": 30, 54 | "AllowedValues": [ 55 | 1, 56 | 3, 57 | 5, 58 | 7, 59 | 14, 60 | 30, 61 | 60, 62 | 90, 63 | 120, 64 | 150, 65 | 180, 66 | 365, 67 | 400, 68 | 545, 69 | 731, 70 | 1827, 71 | 3653 72 | ], 73 | "Description": "Retention days for scheduler logs." 74 | }, 75 | "Trace": { 76 | "Type": "String", 77 | "AllowedValues": [ 78 | "Yes", 79 | "No" 80 | ], 81 | "Default": "No", 82 | "Description": "Enable logging of detailed informtion in CloudWatch logs." 83 | }, 84 | "TagName": { 85 | "Type": "String", 86 | "Default": "Schedule", 87 | "MinLength": 1, 88 | "MaxLength": 127, 89 | "Description": "Name of tag to use for associating instance schedule schemas with service instances." 90 | }, 91 | "DefaultTimezone": { 92 | "Type": "String", 93 | "Default": "UTC", 94 | "AllowedValues": [ 95 | "Africa/Abidjan", 96 | "Africa/Accra", 97 | "Africa/Addis_Ababa", 98 | "Africa/Algiers", 99 | "Africa/Asmara", 100 | "Africa/Bamako", 101 | "Africa/Bangui", 102 | "Africa/Banjul", 103 | "Africa/Bissau", 104 | "Africa/Blantyre", 105 | "Africa/Brazzaville", 106 | "Africa/Bujumbura", 107 | "Africa/Cairo", 108 | "Africa/Casablanca", 109 | "Africa/Ceuta", 110 | "Africa/Conakry", 111 | "Africa/Dakar", 112 | "Africa/Dar_es_Salaam", 113 | "Africa/Djibouti", 114 | "Africa/Douala", 115 | "Africa/El_Aaiun", 116 | "Africa/Freetown", 117 | "Africa/Gaborone", 118 | "Africa/Harare", 119 | "Africa/Johannesburg", 120 | "Africa/Juba", 121 | "Africa/Kampala", 122 | "Africa/Khartoum", 123 | "Africa/Kigali", 124 | "Africa/Kinshasa", 125 | "Africa/Lagos", 126 | "Africa/Libreville", 127 | "Africa/Lome", 128 | "Africa/Luanda", 129 | "Africa/Lubumbashi", 130 | "Africa/Lusaka", 131 | "Africa/Malabo", 132 | "Africa/Maputo", 133 | "Africa/Maseru", 134 | "Africa/Mbabane", 135 | "Africa/Mogadishu", 136 | "Africa/Monrovia", 137 | "Africa/Nairobi", 138 | "Africa/Ndjamena", 139 | "Africa/Niamey", 140 | "Africa/Nouakchott", 141 | "Africa/Ouagadougou", 142 | "Africa/Porto-Novo", 143 | "Africa/Sao_Tome", 144 | "Africa/Tripoli", 145 | "Africa/Tunis", 146 | "Africa/Windhoek", 147 | "America/Adak", 148 | "America/Anchorage", 149 | "America/Anguilla", 150 | "America/Antigua", 151 | "America/Araguaina", 152 | "America/Argentina/Buenos_Aires", 153 | "America/Argentina/Catamarca", 154 | "America/Argentina/Cordoba", 155 | "America/Argentina/Jujuy", 156 | "America/Argentina/La_Rioja", 157 | "America/Argentina/Mendoza", 158 | "America/Argentina/Rio_Gallegos", 159 | "America/Argentina/Salta", 160 | "America/Argentina/San_Juan", 161 | "America/Argentina/San_Luis", 162 | "America/Argentina/Tucuman", 163 | "America/Argentina/Ushuaia", 164 | "America/Aruba", 165 | "America/Asuncion", 166 | "America/Atikokan", 167 | "America/Bahia", 168 | "America/Bahia_Banderas", 169 | "America/Barbados", 170 | "America/Belem", 171 | "America/Belize", 172 | "America/Blanc-Sablon", 173 | "America/Boa_Vista", 174 | "America/Bogota", 175 | "America/Boise", 176 | "America/Cambridge_Bay", 177 | "America/Campo_Grande", 178 | "America/Cancun", 179 | "America/Caracas", 180 | "America/Cayenne", 181 | "America/Cayman", 182 | "America/Chicago", 183 | "America/Chihuahua", 184 | "America/Costa_Rica", 185 | "America/Creston", 186 | "America/Cuiaba", 187 | "America/Curacao", 188 | "America/Danmarkshavn", 189 | "America/Dawson", 190 | "America/Dawson_Creek", 191 | "America/Denver", 192 | "America/Detroit", 193 | "America/Dominica", 194 | "America/Edmonton", 195 | "America/Eirunepe", 196 | "America/El_Salvador", 197 | "America/Fortaleza", 198 | "America/Glace_Bay", 199 | "America/Godthab", 200 | "America/Goose_Bay", 201 | "America/Grand_Turk", 202 | "America/Grenada", 203 | "America/Guadeloupe", 204 | "America/Guatemala", 205 | "America/Guayaquil", 206 | "America/Guyana", 207 | "America/Halifax", 208 | "America/Havana", 209 | "America/Hermosillo", 210 | "America/Indiana/Indianapolis", 211 | "America/Indiana/Knox", 212 | "America/Indiana/Marengo", 213 | "America/Indiana/Petersburg", 214 | "America/Indiana/Tell_City", 215 | "America/Indiana/Vevay", 216 | "America/Indiana/Vincennes", 217 | "America/Indiana/Winamac", 218 | "America/Inuvik", 219 | "America/Iqaluit", 220 | "America/Jamaica", 221 | "America/Juneau", 222 | "America/Kentucky/Louisville", 223 | "America/Kentucky/Monticello", 224 | "America/Kralendijk", 225 | "America/La_Paz", 226 | "America/Lima", 227 | "America/Los_Angeles", 228 | "America/Lower_Princes", 229 | "America/Maceio", 230 | "America/Managua", 231 | "America/Manaus", 232 | "America/Marigot", 233 | "America/Martinique", 234 | "America/Matamoros", 235 | "America/Mazatlan", 236 | "America/Menominee", 237 | "America/Merida", 238 | "America/Metlakatla", 239 | "America/Mexico_City", 240 | "America/Miquelon", 241 | "America/Moncton", 242 | "America/Monterrey", 243 | "America/Montevideo", 244 | "America/Montreal", 245 | "America/Montserrat", 246 | "America/Nassau", 247 | "America/New_York", 248 | "America/Nipigon", 249 | "America/Nome", 250 | "America/Noronha", 251 | "America/North_Dakota/Beulah", 252 | "America/North_Dakota/Center", 253 | "America/North_Dakota/New_Salem", 254 | "America/Ojinaga", 255 | "America/Panama", 256 | "America/Pangnirtung", 257 | "America/Paramaribo", 258 | "America/Phoenix", 259 | "America/Port-au-Prince", 260 | "America/Port_of_Spain", 261 | "America/Porto_Velho", 262 | "America/Puerto_Rico", 263 | "America/Rainy_River", 264 | "America/Rankin_Inlet", 265 | "America/Recife", 266 | "America/Regina", 267 | "America/Resolute", 268 | "America/Rio_Branco", 269 | "America/Santa_Isabel", 270 | "America/Santarem", 271 | "America/Santiago", 272 | "America/Santo_Domingo", 273 | "America/Sao_Paulo", 274 | "America/Scoresbysund", 275 | "America/Sitka", 276 | "America/St_Barthelemy", 277 | "America/St_Johns", 278 | "America/St_Kitts", 279 | "America/St_Lucia", 280 | "America/St_Thomas", 281 | "America/St_Vincent", 282 | "America/Swift_Current", 283 | "America/Tegucigalpa", 284 | "America/Thule", 285 | "America/Thunder_Bay", 286 | "America/Tijuana", 287 | "America/Toronto", 288 | "America/Tortola", 289 | "America/Vancouver", 290 | "America/Whitehorse", 291 | "America/Winnipeg", 292 | "America/Yakutat", 293 | "America/Yellowknife", 294 | "Antarctica/Casey", 295 | "Antarctica/Davis", 296 | "Antarctica/DumontDUrville", 297 | "Antarctica/Macquarie", 298 | "Antarctica/Mawson", 299 | "Antarctica/McMurdo", 300 | "Antarctica/Palmer", 301 | "Antarctica/Rothera", 302 | "Antarctica/Syowa", 303 | "Antarctica/Vostok", 304 | "Arctic/Longyearbyen", 305 | "Asia/Aden", 306 | "Asia/Almaty", 307 | "Asia/Amman", 308 | "Asia/Anadyr", 309 | "Asia/Aqtau", 310 | "Asia/Aqtobe", 311 | "Asia/Ashgabat", 312 | "Asia/Baghdad", 313 | "Asia/Bahrain", 314 | "Asia/Baku", 315 | "Asia/Bangkok", 316 | "Asia/Beirut", 317 | "Asia/Bishkek", 318 | "Asia/Brunei", 319 | "Asia/Choibalsan", 320 | "Asia/Chongqing", 321 | "Asia/Colombo", 322 | "Asia/Damascus", 323 | "Asia/Dhaka", 324 | "Asia/Dili", 325 | "Asia/Dubai", 326 | "Asia/Dushanbe", 327 | "Asia/Gaza", 328 | "Asia/Harbin", 329 | "Asia/Hebron", 330 | "Asia/Ho_Chi_Minh", 331 | "Asia/Hong_Kong", 332 | "Asia/Hovd", 333 | "Asia/Irkutsk", 334 | "Asia/Jakarta", 335 | "Asia/Jayapura", 336 | "Asia/Jerusalem", 337 | "Asia/Kabul", 338 | "Asia/Kamchatka", 339 | "Asia/Karachi", 340 | "Asia/Kashgar", 341 | "Asia/Kathmandu", 342 | "Asia/Khandyga", 343 | "Asia/Kolkata", 344 | "Asia/Krasnoyarsk", 345 | "Asia/Kuala_Lumpur", 346 | "Asia/Kuching", 347 | "Asia/Kuwait", 348 | "Asia/Macau", 349 | "Asia/Magadan", 350 | "Asia/Makassar", 351 | "Asia/Manila", 352 | "Asia/Muscat", 353 | "Asia/Nicosia", 354 | "Asia/Novokuznetsk", 355 | "Asia/Novosibirsk", 356 | "Asia/Omsk", 357 | "Asia/Oral", 358 | "Asia/Phnom_Penh", 359 | "Asia/Pontianak", 360 | "Asia/Pyongyang", 361 | "Asia/Qatar", 362 | "Asia/Qyzylorda", 363 | "Asia/Rangoon", 364 | "Asia/Riyadh", 365 | "Asia/Sakhalin", 366 | "Asia/Samarkand", 367 | "Asia/Seoul", 368 | "Asia/Shanghai", 369 | "Asia/Singapore", 370 | "Asia/Taipei", 371 | "Asia/Tashkent", 372 | "Asia/Tbilisi", 373 | "Asia/Tehran", 374 | "Asia/Thimphu", 375 | "Asia/Tokyo", 376 | "Asia/Ulaanbaatar", 377 | "Asia/Urumqi", 378 | "Asia/Ust-Nera", 379 | "Asia/Vientiane", 380 | "Asia/Vladivostok", 381 | "Asia/Yakutsk", 382 | "Asia/Yekaterinburg", 383 | "Asia/Yerevan", 384 | "Atlantic/Azores", 385 | "Atlantic/Bermuda", 386 | "Atlantic/Canary", 387 | "Atlantic/Cape_Verde", 388 | "Atlantic/Faroe", 389 | "Atlantic/Madeira", 390 | "Atlantic/Reykjavik", 391 | "Atlantic/South_Georgia", 392 | "Atlantic/St_Helena", 393 | "Atlantic/Stanley", 394 | "Australia/Adelaide", 395 | "Australia/Brisbane", 396 | "Australia/Broken_Hill", 397 | "Australia/Currie", 398 | "Australia/Darwin", 399 | "Australia/Eucla", 400 | "Australia/Hobart", 401 | "Australia/Lindeman", 402 | "Australia/Lord_Howe", 403 | "Australia/Melbourne", 404 | "Australia/Perth", 405 | "Australia/Sydney", 406 | "Canada/Atlantic", 407 | "Canada/Central", 408 | "Canada/Eastern", 409 | "Canada/Mountain", 410 | "Canada/Newfoundland", 411 | "Canada/Pacific", 412 | "Europe/Amsterdam", 413 | "Europe/Andorra", 414 | "Europe/Athens", 415 | "Europe/Belgrade", 416 | "Europe/Berlin", 417 | "Europe/Bratislava", 418 | "Europe/Brussels", 419 | "Europe/Bucharest", 420 | "Europe/Budapest", 421 | "Europe/Busingen", 422 | "Europe/Chisinau", 423 | "Europe/Copenhagen", 424 | "Europe/Dublin", 425 | "Europe/Gibraltar", 426 | "Europe/Guernsey", 427 | "Europe/Helsinki", 428 | "Europe/Isle_of_Man", 429 | "Europe/Istanbul", 430 | "Europe/Jersey", 431 | "Europe/Kaliningrad", 432 | "Europe/Kiev", 433 | "Europe/Lisbon", 434 | "Europe/Ljubljana", 435 | "Europe/London", 436 | "Europe/Luxembourg", 437 | "Europe/Madrid", 438 | "Europe/Malta", 439 | "Europe/Mariehamn", 440 | "Europe/Minsk", 441 | "Europe/Monaco", 442 | "Europe/Moscow", 443 | "Europe/Oslo", 444 | "Europe/Paris", 445 | "Europe/Podgorica", 446 | "Europe/Prague", 447 | "Europe/Riga", 448 | "Europe/Rome", 449 | "Europe/Samara", 450 | "Europe/San_Marino", 451 | "Europe/Sarajevo", 452 | "Europe/Simferopol", 453 | "Europe/Skopje", 454 | "Europe/Sofia", 455 | "Europe/Stockholm", 456 | "Europe/Tallinn", 457 | "Europe/Tirane", 458 | "Europe/Uzhgorod", 459 | "Europe/Vaduz", 460 | "Europe/Vatican", 461 | "Europe/Vienna", 462 | "Europe/Vilnius", 463 | "Europe/Volgograd", 464 | "Europe/Warsaw", 465 | "Europe/Zagreb", 466 | "Europe/Zaporozhye", 467 | "Europe/Zurich", 468 | "GMT", 469 | "Indian/Antananarivo", 470 | "Indian/Chagos", 471 | "Indian/Christmas", 472 | "Indian/Cocos", 473 | "Indian/Comoro", 474 | "Indian/Kerguelen", 475 | "Indian/Mahe", 476 | "Indian/Maldives", 477 | "Indian/Mauritius", 478 | "Indian/Mayotte", 479 | "Indian/Reunion", 480 | "Pacific/Apia", 481 | "Pacific/Auckland", 482 | "Pacific/Chatham", 483 | "Pacific/Chuuk", 484 | "Pacific/Easter", 485 | "Pacific/Efate", 486 | "Pacific/Enderbury", 487 | "Pacific/Fakaofo", 488 | "Pacific/Fiji", 489 | "Pacific/Funafuti", 490 | "Pacific/Galapagos", 491 | "Pacific/Gambier", 492 | "Pacific/Guadalcanal", 493 | "Pacific/Guam", 494 | "Pacific/Honolulu", 495 | "Pacific/Johnston", 496 | "Pacific/Kiritimati", 497 | "Pacific/Kosrae", 498 | "Pacific/Kwajalein", 499 | "Pacific/Majuro", 500 | "Pacific/Marquesas", 501 | "Pacific/Midway", 502 | "Pacific/Nauru", 503 | "Pacific/Niue", 504 | "Pacific/Norfolk", 505 | "Pacific/Noumea", 506 | "Pacific/Pago_Pago", 507 | "Pacific/Palau", 508 | "Pacific/Pitcairn", 509 | "Pacific/Pohnpei", 510 | "Pacific/Port_Moresby", 511 | "Pacific/Rarotonga", 512 | "Pacific/Saipan", 513 | "Pacific/Tahiti", 514 | "Pacific/Tarawa", 515 | "Pacific/Tongatapu", 516 | "Pacific/Wake", 517 | "Pacific/Wallis", 518 | "US/Alaska", 519 | "US/Arizona", 520 | "US/Central", 521 | "US/Eastern", 522 | "US/Hawaii", 523 | "US/Mountain", 524 | "US/Pacific", 525 | "UTC" 526 | ], 527 | "Description": "Choose the default Time Zone. Default is 'UTC'" 528 | }, 529 | "Regions": { 530 | "Type": "CommaDelimitedList", 531 | "Description": "List of regions in which instances are scheduled, leave blank for current region only." 532 | }, 533 | "CrossAccountRoles": { 534 | "Type": "CommaDelimitedList", 535 | "Description": "Comma separated list of ARN's for cross account access roles. These roles must be created in all checked accounts the scheduler to start and stop instances." 536 | }, 537 | "StartedTags": { 538 | "Type": "String", 539 | "Description": "Comma separated list of tagname and values on the formt name=value,name=value,.. that are set on started instances" 540 | }, 541 | "StoppedTags": { 542 | "Type": "String", 543 | "Description": "Comma separated list of tagname and values on the formt name=value,name=value,.. that are set on stopped instances" 544 | }, 545 | "SchedulerFrequency": { 546 | "Type": "String", 547 | "AllowedValues": [ 548 | "1", 549 | "2", 550 | "5", 551 | "10", 552 | "15", 553 | "30", 554 | "60" 555 | ], 556 | "Default": "5", 557 | "Description": "Scheduler running frequency in minutes." 558 | }, 559 | "ScheduleLambdaAccount": { 560 | "Type": "String", 561 | "AllowedValues": [ 562 | "Yes", 563 | "No" 564 | ], 565 | "Default": "Yes", 566 | "Description": "Schedule instances in this account." 567 | }, 568 | "SendAnonymousData": { 569 | "Type": "String", 570 | "AllowedValues": [ 571 | "Yes", 572 | "No" 573 | ], 574 | "Default": "No", 575 | "Description": "Send Anonymous Metrics Data." 576 | } 577 | }, 578 | "Metadata": { 579 | "AWS::CloudFormation::Interface": { 580 | "ParameterGroups": [ 581 | { 582 | "Label": { 583 | "default": "Scheduler (version 2.3.2.0)" 584 | }, 585 | "Parameters": [ 586 | "TagName", 587 | "ScheduledServices", 588 | "SchedulingActive", 589 | "Regions", 590 | "DefaultTimezone", 591 | "CrossAccountRoles", 592 | "ScheduleLambdaAccount", 593 | "SchedulerFrequency", 594 | "MemorySize" 595 | ] 596 | }, 597 | { 598 | "Label": { 599 | "default": "Options" 600 | }, 601 | "Parameters": [ 602 | "UseCloudWatchMetrics", 603 | "SendAnonymousData", 604 | "Trace" 605 | ] 606 | }, 607 | { 608 | "Label": { 609 | "default": "Other parameters" 610 | }, 611 | "Parameters": [ 612 | "LogRetentionDays", 613 | "StartedTags", 614 | "StoppedTags" 615 | ] 616 | } 617 | ], 618 | "ParameterLabels": { 619 | "LogRetentionDays": { 620 | "default": "Log retention days" 621 | }, 622 | "StartedTags": { 623 | "default": "Started tags" 624 | }, 625 | "StoppedTags": { 626 | "default": "Stopped tags" 627 | }, 628 | "SchedulingActive": { 629 | "default": "Scheduling enabled" 630 | }, 631 | "CrossAccountRoles": { 632 | "default": "Cross-account roles" 633 | }, 634 | "ScheduleLambdaAccount": { 635 | "default": "This account" 636 | }, 637 | "UseCloudWatchMetrics": { 638 | "default": "Enable CloudWatch Metrics" 639 | }, 640 | "Trace": { 641 | "default": "Enable CloudWatch Logs" 642 | }, 643 | "TagName": { 644 | "default": "Instance Scheduler tag name" 645 | }, 646 | "ScheduledServices": { 647 | "default": "Service(s) to schedule" 648 | }, 649 | "DefaultTimezone": { 650 | "default": "Default time zone" 651 | }, 652 | "SchedulerFrequency": { 653 | "default": "Frequency" 654 | }, 655 | "Regions": { 656 | "default": "Region(s)" 657 | }, 658 | "MemorySize": { 659 | "default": "Memory size" 660 | }, 661 | "SendAnonymousData": { 662 | "default": "Send anonymous usage data" 663 | } 664 | } 665 | } 666 | }, 667 | "Mappings": { 668 | "Code": { 669 | "General": { 670 | "S3Bucket": "solutions-jet", 671 | "KeyPrefix": "aws-instance-scheduler", 672 | "SSMPathPrefix": "/lambdaEnvConfig/InstanceSchedulerMain" 673 | } 674 | }, 675 | "TrueFalse": { 676 | "Yes": { 677 | "Value": "True" 678 | }, 679 | "No": { 680 | "Value": "False" 681 | } 682 | }, 683 | "EnabledDisabled": { 684 | "Yes": { 685 | "Value": "ENABLED" 686 | }, 687 | "No": { 688 | "Value": "DISABLED" 689 | } 690 | }, 691 | "Services": { 692 | "EC2": { 693 | "Value": "ec2" 694 | }, 695 | "RDS": { 696 | "Value": "rds" 697 | }, 698 | "Both": { 699 | "Value": "ec2,rds" 700 | } 701 | }, 702 | "Timeouts": { 703 | "1": { 704 | "Value": "cron(0/1 * * * ? *)" 705 | }, 706 | "2": { 707 | "Value": "cron(0/2 * * * ? *)" 708 | }, 709 | "5": { 710 | "Value": "cron(0/5 * * * ? *)" 711 | }, 712 | "10": { 713 | "Value": "cron(0/10 * * * ? *)" 714 | }, 715 | "15": { 716 | "Value": "cron(0/15 * * * ? *)" 717 | }, 718 | "30": { 719 | "Value": "cron(0/30 * * * ? *)" 720 | }, 721 | "60": { 722 | "Value": "cron(0 0/1 * * ? *)" 723 | } 724 | }, 725 | "Settings": { 726 | "Metrics": { 727 | "Url": "https://metrics.awssolutionsbuilder.com/generic", 728 | "SolutionId": "S00030" 729 | }, 730 | "DynamoDbTableCapacities": { 731 | "ConfigTableMinRead": 1, 732 | "ConfigTableMaxRead": 10, 733 | "ConfigTableMinWrite": 1, 734 | "ConfigTableMaxWrite": 5, 735 | "StateTableMinRead": 2, 736 | "StateTableMaxRead": 50, 737 | "StateTableMinWrite": 2, 738 | "StateTableMaxWrite": 50, 739 | "RdsTagCacheTableMinRead": 2, 740 | "RdsTagCacheTableMaxRead": 50, 741 | "RdsTagCacheTableMinWrite": 2, 742 | "RdsTagCacheTableMaxWrite": 50 743 | }, 744 | "RdsTagCaching": { 745 | "Enabled": "True" 746 | } 747 | } 748 | }, 749 | "Conditions": { 750 | "NotUsingCrossAccountRoles": { 751 | "Fn::Equals": [ 752 | { 753 | "Fn::Join": [ 754 | "", 755 | { 756 | "Ref": "CrossAccountRoles" 757 | } 758 | ] 759 | }, 760 | "" 761 | ] 762 | } 763 | }, 764 | "Resources": { 765 | "HelperPolicy": { 766 | "Type": "AWS::IAM::Policy", 767 | "Properties": { 768 | "PolicyName": "HelperPolicy", 769 | "Roles": [ 770 | { 771 | "Ref": "HelperRole" 772 | } 773 | ], 774 | "PolicyDocument": { 775 | "Version": "2012-10-17", 776 | "Statement": [ 777 | { 778 | "Effect": "Allow", 779 | "Action": [ 780 | "logs:CreateLogGroup", 781 | "logs:CreateLogStream", 782 | "logs:PutLogEvents" 783 | ], 784 | "Resource": "*" 785 | }, 786 | { 787 | "Effect": "Allow", 788 | "Action": "ssm:DescribeParameters", 789 | "Resource": "*" 790 | }, 791 | { 792 | "Effect": "Allow", 793 | "Action": [ 794 | "ssm:PutParameter", 795 | "ssm:DeleteParameter", 796 | "ssm:GetParameter" 797 | ], 798 | "Resource": { 799 | "Fn::Join": [ 800 | ":", 801 | [ 802 | "arn:aws-cn:ssm", 803 | { 804 | "Ref": "AWS::Region" 805 | }, 806 | { 807 | "Ref": "AWS::AccountId" 808 | }, 809 | { 810 | "Fn::Join": [ 811 | "", 812 | [ 813 | "parameter", 814 | { 815 | "Fn::FindInMap": [ 816 | "Code", 817 | "General", 818 | "SSMPathPrefix" 819 | ] 820 | }, 821 | "*" 822 | ] 823 | ] 824 | } 825 | ] 826 | ] 827 | } 828 | } 829 | ] 830 | } 831 | } 832 | }, 833 | "HelperRole": { 834 | "Type": "AWS::IAM::Role", 835 | "Properties": { 836 | "AssumeRolePolicyDocument": { 837 | "Version": "2012-10-17", 838 | "Statement": [ 839 | { 840 | "Effect": "Allow", 841 | "Principal": { 842 | "Service": "lambda.amazonaws.com" 843 | }, 844 | "Action": "sts:AssumeRole" 845 | } 846 | ] 847 | }, 848 | "Path": "/" 849 | } 850 | }, 851 | "SchedulerPolicy": { 852 | "Type": "AWS::IAM::Policy", 853 | "Properties": { 854 | "PolicyName": "SchedulerPolicy", 855 | "Roles": [ 856 | { 857 | "Ref": "SchedulerRole" 858 | } 859 | ], 860 | "PolicyDocument": { 861 | "Version": "2012-10-17", 862 | "Statement": [ 863 | { 864 | "Effect": "Allow", 865 | "Action": [ 866 | "logs:CreateLogGroup", 867 | "logs:CreateLogStream", 868 | "logs:PutLogEvents" 869 | ], 870 | "Resource": "*" 871 | }, 872 | { 873 | "Effect": "Allow", 874 | "Action": "ssm:DescribeParameters", 875 | "Resource": "*" 876 | }, 877 | { 878 | "Effect": "Allow", 879 | "Action": "ssm:GetParameter", 880 | "Resource": { 881 | "Fn::Join": [ 882 | ":", 883 | [ 884 | "arn:aws-cn:ssm", 885 | { 886 | "Ref": "AWS::Region" 887 | }, 888 | { 889 | "Ref": "AWS::AccountId" 890 | }, 891 | { 892 | "Fn::Join": [ 893 | "", 894 | [ 895 | "parameter", 896 | { 897 | "Fn::FindInMap": [ 898 | "Code", 899 | "General", 900 | "SSMPathPrefix" 901 | ] 902 | }, 903 | "*" 904 | ] 905 | ] 906 | } 907 | ] 908 | ] 909 | } 910 | }, 911 | { 912 | "Effect": "Allow", 913 | "Action": [ 914 | "ec2:CreateTags", 915 | "ec2:DeleteTags", 916 | "ec2:DescribeInstances", 917 | "ec2:DescribeRegions", 918 | "ec2:ModifyInstanceAttribute", 919 | "ec2:StartInstances", 920 | "ec2:StopInstances" 921 | ], 922 | "Resource": [ 923 | "*" 924 | ] 925 | }, 926 | { 927 | "Effect": "Allow", 928 | "Action": [ 929 | "rds:AddTagsToResource", 930 | "rds:DeleteDBSnapshot", 931 | "rds:DescribeDBInstances", 932 | "rds:DescribeDBSnapshots", 933 | "rds:RemoveTagsFromResource", 934 | "rds:StartDBInstance", 935 | "rds:StopDBInstance" 936 | ], 937 | "Resource": [ 938 | "*" 939 | ] 940 | }, 941 | { 942 | "Effect": "Allow", 943 | "Action": [ 944 | "dynamodb:DeleteItem", 945 | "dynamodb:GetItem", 946 | "dynamodb:PutItem", 947 | "dynamodb:Query", 948 | "dynamodb:Scan", 949 | "dynamodb:BatchWriteItem" 950 | ], 951 | "Resource": [ 952 | { 953 | "Fn::Join": [ 954 | "", 955 | [ 956 | { 957 | "Fn::Join": [ 958 | ":", 959 | [ 960 | "arn:aws-cn:dynamodb", 961 | { 962 | "Ref": "AWS::Region" 963 | }, 964 | { 965 | "Ref": "AWS::AccountId" 966 | }, 967 | "table/" 968 | ] 969 | ] 970 | }, 971 | { 972 | "Ref": "StateTable" 973 | } 974 | ] 975 | ] 976 | }, 977 | { 978 | "Fn::Join": [ 979 | "", 980 | [ 981 | { 982 | "Fn::Join": [ 983 | ":", 984 | [ 985 | "arn:aws-cn:dynamodb", 986 | { 987 | "Ref": "AWS::Region" 988 | }, 989 | { 990 | "Ref": "AWS::AccountId" 991 | }, 992 | "table/" 993 | ] 994 | ] 995 | }, 996 | { 997 | "Ref": "ConfigTable" 998 | } 999 | ] 1000 | ] 1001 | } 1002 | ] 1003 | }, 1004 | { 1005 | "Effect": "Allow", 1006 | "Action": [ 1007 | "logs:DescribeLogStreams", 1008 | "logs:PutRetentionPolicy" 1009 | ], 1010 | "Resource": [ 1011 | "*" 1012 | ] 1013 | }, 1014 | { 1015 | "Effect": "Allow", 1016 | "Action": "sns:Publish", 1017 | "Resource": [ 1018 | { 1019 | "Ref": "InstanceSchedulerSnsTopic" 1020 | } 1021 | ] 1022 | }, 1023 | { 1024 | "Effect": "Allow", 1025 | "Action": [ 1026 | "lambda:InvokeFunction" 1027 | ], 1028 | "Resource": [ 1029 | { 1030 | "Fn::Join": [ 1031 | ":", 1032 | [ 1033 | "arn:aws-cn:lambda", 1034 | { 1035 | "Ref": "AWS::Region" 1036 | }, 1037 | { 1038 | "Ref": "AWS::AccountId" 1039 | }, 1040 | "function", 1041 | { 1042 | "Fn::Join": [ 1043 | "-", 1044 | [ 1045 | { 1046 | "Ref": "AWS::StackName" 1047 | }, 1048 | "InstanceSchedulerMain" 1049 | ] 1050 | ] 1051 | } 1052 | ] 1053 | ] 1054 | } 1055 | ] 1056 | }, 1057 | { 1058 | "Effect": "Allow", 1059 | "Action": [ 1060 | "tag:GetResources" 1061 | ], 1062 | "Resource": [ 1063 | "*" 1064 | ] 1065 | }, 1066 | { 1067 | "Effect": "Allow", 1068 | "Action": [ 1069 | "cloudwatch:PutMetricData" 1070 | ], 1071 | "Resource": [ 1072 | "*" 1073 | ] 1074 | }, 1075 | { 1076 | "Effect": "Allow", 1077 | "Action": [ 1078 | "sts:AssumeRole" 1079 | ], 1080 | "Resource": { 1081 | "Fn::If": [ 1082 | "NotUsingCrossAccountRoles", 1083 | { 1084 | "Fn::Sub": "arn:aws-cn:iam::${AWS::AccountId}:role/None" 1085 | }, 1086 | { 1087 | "Ref": "CrossAccountRoles" 1088 | } 1089 | ] 1090 | } 1091 | } 1092 | ] 1093 | } 1094 | } 1095 | }, 1096 | "SchedulerRole": { 1097 | "Type": "AWS::IAM::Role", 1098 | "Properties": { 1099 | "AssumeRolePolicyDocument": { 1100 | "Version": "2012-10-17", 1101 | "Statement": [ 1102 | { 1103 | "Effect": "Allow", 1104 | "Principal": { 1105 | "Service": "lambda.amazonaws.com" 1106 | }, 1107 | "Action": "sts:AssumeRole" 1108 | }, 1109 | { 1110 | "Effect": "Allow", 1111 | "Principal": { 1112 | "Service": "events.amazonaws.com" 1113 | }, 1114 | "Action": "sts:AssumeRole" 1115 | } 1116 | ] 1117 | }, 1118 | "Path": "/" 1119 | } 1120 | }, 1121 | "SchedulerDynamoDBScalingRole": { 1122 | "Type": "AWS::IAM::Role", 1123 | "Properties": { 1124 | "AssumeRolePolicyDocument": { 1125 | "Version": "2012-10-17", 1126 | "Statement": [ 1127 | { 1128 | "Effect": "Allow", 1129 | "Principal": { 1130 | "Service": "application-autoscaling.amazonaws.com", 1131 | "AWS": { 1132 | "Fn::GetAtt": [ 1133 | "SchedulerRole", 1134 | "Arn" 1135 | ] 1136 | } 1137 | }, 1138 | "Action": "sts:AssumeRole" 1139 | } 1140 | ] 1141 | }, 1142 | "Policies": [ 1143 | { 1144 | "PolicyName": "OpsAutomatorScalingRolePolicy", 1145 | "PolicyDocument": { 1146 | "Version": "2012-10-17", 1147 | "Statement": [ 1148 | { 1149 | "Effect": "Allow", 1150 | "Action": [ 1151 | "dynamodb:DescribeTable", 1152 | "dynamodb:UpdateTable", 1153 | "cloudwatch:PutMetricAlarm", 1154 | "cloudwatch:DescribeAlarms", 1155 | "cloudwatch:GetMetricStatistics", 1156 | "cloudwatch:SetAlarmState", 1157 | "cloudwatch:DeleteAlarms" 1158 | ], 1159 | "Resource": "*" 1160 | } 1161 | ] 1162 | } 1163 | } 1164 | ], 1165 | "ManagedPolicyArns": [ 1166 | "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaDynamoDBExecutionRole", 1167 | "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" 1168 | ], 1169 | "Path": "/" 1170 | }, 1171 | "DependsOn": [ 1172 | "SchedulerRole" 1173 | ] 1174 | }, 1175 | "StateTable": { 1176 | "Type": "AWS::DynamoDB::Table", 1177 | "Properties": { 1178 | "AttributeDefinitions": [ 1179 | { 1180 | "AttributeName": "service", 1181 | "AttributeType": "S" 1182 | }, 1183 | { 1184 | "AttributeName": "account-region", 1185 | "AttributeType": "S" 1186 | } 1187 | ], 1188 | "KeySchema": [ 1189 | { 1190 | "AttributeName": "service", 1191 | "KeyType": "HASH" 1192 | }, 1193 | { 1194 | "AttributeName": "account-region", 1195 | "KeyType": "RANGE" 1196 | } 1197 | ], 1198 | "ProvisionedThroughput": { 1199 | "ReadCapacityUnits": { 1200 | "Fn::FindInMap": [ 1201 | "Settings", 1202 | "DynamoDbTableCapacities", 1203 | "StateTableMinRead" 1204 | ] 1205 | }, 1206 | "WriteCapacityUnits": { 1207 | "Fn::FindInMap": [ 1208 | "Settings", 1209 | "DynamoDbTableCapacities", 1210 | "StateTableMinWrite" 1211 | ] 1212 | } 1213 | } 1214 | } 1215 | }, 1216 | "StateTableAutoScalingReadTarget": { 1217 | "Type": "AWS::ApplicationAutoScaling::ScalableTarget", 1218 | "Properties": { 1219 | "MaxCapacity": { 1220 | "Fn::FindInMap": [ 1221 | "Settings", 1222 | "DynamoDbTableCapacities", 1223 | "StateTableMaxRead" 1224 | ] 1225 | }, 1226 | "MinCapacity": { 1227 | "Fn::FindInMap": [ 1228 | "Settings", 1229 | "DynamoDbTableCapacities", 1230 | "StateTableMinRead" 1231 | ] 1232 | }, 1233 | "ResourceId": { 1234 | "Fn::Join": [ 1235 | "/", 1236 | [ 1237 | "table", 1238 | { 1239 | "Ref": "StateTable" 1240 | } 1241 | ] 1242 | ] 1243 | }, 1244 | "RoleARN": { 1245 | "Fn::GetAtt": [ 1246 | "SchedulerDynamoDBScalingRole", 1247 | "Arn" 1248 | ] 1249 | }, 1250 | "ScalableDimension": "dynamodb:table:ReadCapacityUnits", 1251 | "ServiceNamespace": "dynamodb" 1252 | } 1253 | }, 1254 | "StateTableAutoScalingReadPolicy": { 1255 | "Type": "AWS::ApplicationAutoScaling::ScalingPolicy", 1256 | "Properties": { 1257 | "PolicyName": { 1258 | "Fn::Join": [ 1259 | "-", 1260 | [ 1261 | { 1262 | "Ref": "AWS::StackName" 1263 | }, 1264 | "StateTableAutoScalingReadPolicy" 1265 | ] 1266 | ] 1267 | }, 1268 | "PolicyType": "TargetTrackingScaling", 1269 | "ScalingTargetId": { 1270 | "Ref": "StateTableAutoScalingReadTarget" 1271 | }, 1272 | "TargetTrackingScalingPolicyConfiguration": { 1273 | "TargetValue": 75.0, 1274 | "ScaleInCooldown": 60, 1275 | "ScaleOutCooldown": 60, 1276 | "PredefinedMetricSpecification": { 1277 | "PredefinedMetricType": "DynamoDBReadCapacityUtilization" 1278 | } 1279 | } 1280 | } 1281 | }, 1282 | "StateTableAutoScalingWriteTarget": { 1283 | "Type": "AWS::ApplicationAutoScaling::ScalableTarget", 1284 | "Properties": { 1285 | "MaxCapacity": { 1286 | "Fn::FindInMap": [ 1287 | "Settings", 1288 | "DynamoDbTableCapacities", 1289 | "StateTableMaxWrite" 1290 | ] 1291 | }, 1292 | "MinCapacity": { 1293 | "Fn::FindInMap": [ 1294 | "Settings", 1295 | "DynamoDbTableCapacities", 1296 | "StateTableMinWrite" 1297 | ] 1298 | }, 1299 | "ResourceId": { 1300 | "Fn::Join": [ 1301 | "/", 1302 | [ 1303 | "table", 1304 | { 1305 | "Ref": "StateTable" 1306 | } 1307 | ] 1308 | ] 1309 | }, 1310 | "RoleARN": { 1311 | "Fn::GetAtt": [ 1312 | "SchedulerDynamoDBScalingRole", 1313 | "Arn" 1314 | ] 1315 | }, 1316 | "ScalableDimension": "dynamodb:table:WriteCapacityUnits", 1317 | "ServiceNamespace": "dynamodb" 1318 | } 1319 | }, 1320 | "StateTableAutoScalingWritePolicy": { 1321 | "Type": "AWS::ApplicationAutoScaling::ScalingPolicy", 1322 | "Properties": { 1323 | "PolicyName": { 1324 | "Fn::Join": [ 1325 | "-", 1326 | [ 1327 | { 1328 | "Ref": "AWS::StackName" 1329 | }, 1330 | "StateTableAutoScalingWritePolicy" 1331 | ] 1332 | ] 1333 | }, 1334 | "PolicyType": "TargetTrackingScaling", 1335 | "ScalingTargetId": { 1336 | "Ref": "StateTableAutoScalingWriteTarget" 1337 | }, 1338 | "TargetTrackingScalingPolicyConfiguration": { 1339 | "TargetValue": 75.0, 1340 | "ScaleInCooldown": 60, 1341 | "ScaleOutCooldown": 60, 1342 | "PredefinedMetricSpecification": { 1343 | "PredefinedMetricType": "DynamoDBWriteCapacityUtilization" 1344 | } 1345 | } 1346 | }, 1347 | "DependsOn": [ 1348 | "StateTableAutoScalingReadPolicy" 1349 | ] 1350 | }, 1351 | "ConfigTable": { 1352 | "Type": "AWS::DynamoDB::Table", 1353 | "Properties": { 1354 | "AttributeDefinitions": [ 1355 | { 1356 | "AttributeName": "type", 1357 | "AttributeType": "S" 1358 | }, 1359 | { 1360 | "AttributeName": "name", 1361 | "AttributeType": "S" 1362 | } 1363 | ], 1364 | "KeySchema": [ 1365 | { 1366 | "AttributeName": "type", 1367 | "KeyType": "HASH" 1368 | }, 1369 | { 1370 | "AttributeName": "name", 1371 | "KeyType": "RANGE" 1372 | } 1373 | ], 1374 | "ProvisionedThroughput": { 1375 | "ReadCapacityUnits": 2, 1376 | "WriteCapacityUnits": 2 1377 | } 1378 | } 1379 | }, 1380 | "ConfigTableAutoScalingReadTarget": { 1381 | "Type": "AWS::ApplicationAutoScaling::ScalableTarget", 1382 | "Properties": { 1383 | "MaxCapacity": { 1384 | "Fn::FindInMap": [ 1385 | "Settings", 1386 | "DynamoDbTableCapacities", 1387 | "ConfigTableMaxRead" 1388 | ] 1389 | }, 1390 | "MinCapacity": { 1391 | "Fn::FindInMap": [ 1392 | "Settings", 1393 | "DynamoDbTableCapacities", 1394 | "ConfigTableMinRead" 1395 | ] 1396 | }, 1397 | "ResourceId": { 1398 | "Fn::Join": [ 1399 | "/", 1400 | [ 1401 | "table", 1402 | { 1403 | "Ref": "ConfigTable" 1404 | } 1405 | ] 1406 | ] 1407 | }, 1408 | "RoleARN": { 1409 | "Fn::GetAtt": [ 1410 | "SchedulerDynamoDBScalingRole", 1411 | "Arn" 1412 | ] 1413 | }, 1414 | "ScalableDimension": "dynamodb:table:ReadCapacityUnits", 1415 | "ServiceNamespace": "dynamodb" 1416 | } 1417 | }, 1418 | "ConfigurationTableAutoScalingReadPolicy": { 1419 | "Type": "AWS::ApplicationAutoScaling::ScalingPolicy", 1420 | "Properties": { 1421 | "PolicyName": { 1422 | "Fn::Join": [ 1423 | "-", 1424 | [ 1425 | { 1426 | "Ref": "AWS::StackName" 1427 | }, 1428 | "ConfigTableAutoScalingReadPolicy" 1429 | ] 1430 | ] 1431 | }, 1432 | "PolicyType": "TargetTrackingScaling", 1433 | "ScalingTargetId": { 1434 | "Ref": "ConfigTableAutoScalingReadTarget" 1435 | }, 1436 | "TargetTrackingScalingPolicyConfiguration": { 1437 | "TargetValue": 75.0, 1438 | "ScaleInCooldown": 60, 1439 | "ScaleOutCooldown": 60, 1440 | "PredefinedMetricSpecification": { 1441 | "PredefinedMetricType": "DynamoDBReadCapacityUtilization" 1442 | } 1443 | } 1444 | }, 1445 | "DependsOn": "StateTableAutoScalingWritePolicy" 1446 | }, 1447 | "ConfigTableAutoScalingWriteTarget": { 1448 | "Type": "AWS::ApplicationAutoScaling::ScalableTarget", 1449 | "Properties": { 1450 | "MaxCapacity": { 1451 | "Fn::FindInMap": [ 1452 | "Settings", 1453 | "DynamoDbTableCapacities", 1454 | "ConfigTableMaxWrite" 1455 | ] 1456 | }, 1457 | "MinCapacity": { 1458 | "Fn::FindInMap": [ 1459 | "Settings", 1460 | "DynamoDbTableCapacities", 1461 | "ConfigTableMinWrite" 1462 | ] 1463 | }, 1464 | "ResourceId": { 1465 | "Fn::Join": [ 1466 | "/", 1467 | [ 1468 | "table", 1469 | { 1470 | "Ref": "ConfigTable" 1471 | } 1472 | ] 1473 | ] 1474 | }, 1475 | "RoleARN": { 1476 | "Fn::GetAtt": [ 1477 | "SchedulerDynamoDBScalingRole", 1478 | "Arn" 1479 | ] 1480 | }, 1481 | "ScalableDimension": "dynamodb:table:WriteCapacityUnits", 1482 | "ServiceNamespace": "dynamodb" 1483 | } 1484 | }, 1485 | "ConfigTableAutoScalingWritePolicy": { 1486 | "Type": "AWS::ApplicationAutoScaling::ScalingPolicy", 1487 | "Properties": { 1488 | "PolicyName": { 1489 | "Fn::Join": [ 1490 | "-", 1491 | [ 1492 | { 1493 | "Ref": "AWS::StackName" 1494 | }, 1495 | "ConfigTableAutoScalingWritePolicy" 1496 | ] 1497 | ] 1498 | }, 1499 | "PolicyType": "TargetTrackingScaling", 1500 | "ScalingTargetId": { 1501 | "Ref": "ConfigTableAutoScalingWriteTarget" 1502 | }, 1503 | "TargetTrackingScalingPolicyConfiguration": { 1504 | "TargetValue": 75.0, 1505 | "ScaleInCooldown": 60, 1506 | "ScaleOutCooldown": 60, 1507 | "PredefinedMetricSpecification": { 1508 | "PredefinedMetricType": "DynamoDBWriteCapacityUtilization" 1509 | } 1510 | } 1511 | }, 1512 | "DependsOn": [ 1513 | "ConfigurationTableAutoScalingReadPolicy" 1514 | ] 1515 | }, 1516 | "SchedulerLogGroup": { 1517 | "Type": "AWS::Logs::LogGroup", 1518 | "Properties": { 1519 | "LogGroupName": { 1520 | "Fn::Join": [ 1521 | "-", 1522 | [ 1523 | { 1524 | "Ref": "AWS::StackName" 1525 | }, 1526 | "logs" 1527 | ] 1528 | ] 1529 | }, 1530 | "RetentionInDays": { 1531 | "Ref": "LogRetentionDays" 1532 | } 1533 | } 1534 | }, 1535 | "InstanceSchedulerSnsTopic": { 1536 | "Type": "AWS::SNS::Topic", 1537 | "Properties": { 1538 | "DisplayName": { 1539 | "Ref": "AWS::StackName" 1540 | } 1541 | } 1542 | }, 1543 | "LambdaEnvConfig": { 1544 | "Type": "Custom::LoadLambda", 1545 | "Properties": { 1546 | "ServiceToken": { 1547 | "Fn::GetAtt": [ 1548 | "InstanceScheulerHelper", 1549 | "Arn" 1550 | ] 1551 | }, 1552 | "Region": { 1553 | "Ref": "AWS::Region" 1554 | }, 1555 | "destPathPrefix": { 1556 | "Fn::FindInMap": [ 1557 | "Code", 1558 | "General", 1559 | "SSMPathPrefix" 1560 | ] 1561 | }, 1562 | "configItem": { 1563 | "CONFIG_TABLE": { 1564 | "Ref": "ConfigTable" 1565 | }, 1566 | "TAG_NAME": { 1567 | "Ref": "TagName" 1568 | }, 1569 | "STATE_TABLE": { 1570 | "Ref": "StateTable" 1571 | }, 1572 | "LOG_GROUP": { 1573 | "Ref": "SchedulerLogGroup" 1574 | }, 1575 | "ACCOUNT": { 1576 | "Ref": "AWS::AccountId" 1577 | }, 1578 | "ISSUES_TOPIC_ARN": { 1579 | "Ref": "InstanceSchedulerSnsTopic" 1580 | }, 1581 | "STACK_NAME": { 1582 | "Ref": "AWS::StackName" 1583 | }, 1584 | "BOTO_RETRY": "5,10,30,0.25", 1585 | "BOTO_RETRY_LOGGING": "False", 1586 | "SEND_METRICS": { 1587 | "Fn::FindInMap": [ 1588 | "TrueFalse", 1589 | { 1590 | "Ref": "SendAnonymousData" 1591 | }, 1592 | "Value" 1593 | ] 1594 | }, 1595 | "SOLUTION_ID": { 1596 | "Fn::FindInMap": [ 1597 | "Settings", 1598 | "Metrics", 1599 | "SolutionId" 1600 | ] 1601 | }, 1602 | "TRACE": { 1603 | "Fn::FindInMap": [ 1604 | "TrueFalse", 1605 | { 1606 | "Ref": "Trace" 1607 | }, 1608 | "Value" 1609 | ] 1610 | }, 1611 | "USER_AGENT": { 1612 | "Fn::Join": [ 1613 | "-", 1614 | [ 1615 | "InstanceScheduler", 1616 | { 1617 | "Ref": "AWS::StackName" 1618 | }, 1619 | "2.3.2.0" 1620 | ] 1621 | ] 1622 | }, 1623 | "METRICS_URL": { 1624 | "Fn::FindInMap": [ 1625 | "Settings", 1626 | "Metrics", 1627 | "Url" 1628 | ] 1629 | } 1630 | }, 1631 | "customAction": "putEnvConfig" 1632 | }, 1633 | "DependsOn": "InstanceScheulerHelper" 1634 | }, 1635 | "InstanceScheulerHelper": { 1636 | "Type": "AWS::Lambda::Function", 1637 | "Properties": { 1638 | "FunctionName": "instance-scheduler-helper", 1639 | "Description": "Instance Scheduler deployment helper", 1640 | "Code": { 1641 | "S3Bucket": { 1642 | "Fn::Join": [ 1643 | "-", 1644 | [ 1645 | { 1646 | "Fn::FindInMap": [ 1647 | "Code", 1648 | "General", 1649 | "S3Bucket" 1650 | ], 1651 | }, 1652 | { 1653 | "Ref": "AWS::Region" 1654 | } 1655 | ] 1656 | ] 1657 | }, 1658 | "S3Key": { 1659 | "Fn::Join": [ 1660 | "/", 1661 | [ 1662 | { 1663 | "Fn::FindInMap": [ 1664 | "Code", 1665 | "General", 1666 | "KeyPrefix" 1667 | ] 1668 | }, 1669 | "instance-scheduler-helper.zip" 1670 | ] 1671 | ] 1672 | } 1673 | }, 1674 | "Handler": "index.lambda_handler", 1675 | "Runtime": "python2.7", 1676 | "Role": { 1677 | "Fn::GetAtt": [ 1678 | "HelperRole", 1679 | "Arn" 1680 | ] 1681 | }, 1682 | "Timeout": 300, 1683 | "MemorySize": 128, 1684 | "Description": "Lambda function configuration helper" 1685 | }, 1686 | "DependsOn": [ 1687 | "HelperPolicy", 1688 | "HelperRole" 1689 | ] 1690 | }, 1691 | "Main": { 1692 | "Type": "AWS::Lambda::Function", 1693 | "Properties": { 1694 | "FunctionName": { 1695 | "Fn::Join": [ 1696 | "-", 1697 | [ 1698 | { 1699 | "Ref": "AWS::StackName" 1700 | }, 1701 | "InstanceSchedulerMain" 1702 | ] 1703 | ] 1704 | }, 1705 | "Code": { 1706 | "S3Bucket": { 1707 | "Fn::Join": [ 1708 | "-", 1709 | [ 1710 | { 1711 | "Fn::FindInMap": [ 1712 | "Code", 1713 | "General", 1714 | "S3Bucket" 1715 | ], 1716 | }, 1717 | { 1718 | "Ref": "AWS::Region" 1719 | } 1720 | ] 1721 | ] 1722 | }, 1723 | "S3Key": { 1724 | "Fn::Join": [ 1725 | "/", 1726 | [ 1727 | { 1728 | "Fn::FindInMap": [ 1729 | "Code", 1730 | "General", 1731 | "KeyPrefix" 1732 | ] 1733 | }, 1734 | "instance-scheduler-2.3.2.0.zip" 1735 | ] 1736 | ] 1737 | } 1738 | }, 1739 | "Handler": "main.lambda_handler", 1740 | "Runtime": "python2.7", 1741 | "Role": { 1742 | "Fn::GetAtt": [ 1743 | "SchedulerRole", 1744 | "Arn" 1745 | ] 1746 | }, 1747 | "MemorySize": { 1748 | "Ref": "MemorySize" 1749 | }, 1750 | "Timeout": 300, 1751 | "Description": "ECS and RDS instance scheduler, version 2.3.2.0" 1752 | }, 1753 | "DependsOn": [ 1754 | "SchedulerPolicy", 1755 | "SchedulerRole" 1756 | ] 1757 | }, 1758 | "SchedulerInvokePermission": { 1759 | "Type": "AWS::Lambda::Permission", 1760 | "Properties": { 1761 | "FunctionName": { 1762 | "Fn::Join": [ 1763 | ":", 1764 | [ 1765 | "arn:aws-cn:lambda", 1766 | { 1767 | "Ref": "AWS::Region" 1768 | }, 1769 | { 1770 | "Ref": "AWS::AccountId" 1771 | }, 1772 | "function", 1773 | { 1774 | "Fn::Join": [ 1775 | "-", 1776 | [ 1777 | { 1778 | "Ref": "AWS::StackName" 1779 | }, 1780 | "InstanceSchedulerMain" 1781 | ] 1782 | ] 1783 | } 1784 | ] 1785 | ] 1786 | }, 1787 | "Action": "lambda:InvokeFunction", 1788 | "Principal": "events.amazonaws.com", 1789 | "SourceArn": { 1790 | "Fn::GetAtt": [ 1791 | "SchedulerRule", 1792 | "Arn" 1793 | ] 1794 | } 1795 | }, 1796 | "DependsOn": "Main" 1797 | }, 1798 | "SchedulerRule": { 1799 | "Type": "AWS::Events::Rule", 1800 | "Properties": { 1801 | "Description": "Instance Scheduler - Rule to trigger instance for scheduler function version 2.3.2.0", 1802 | "ScheduleExpression": { 1803 | "Fn::FindInMap": [ 1804 | "Timeouts", 1805 | { 1806 | "Ref": "SchedulerFrequency" 1807 | }, 1808 | "Value" 1809 | ] 1810 | }, 1811 | "State": { 1812 | "Fn::FindInMap": [ 1813 | "EnabledDisabled", 1814 | { 1815 | "Ref": "SchedulingActive" 1816 | }, 1817 | "Value" 1818 | ] 1819 | }, 1820 | "Targets": [ 1821 | { 1822 | "Id": "Instance-Scheduler-Main", 1823 | "Arn": { 1824 | "Fn::GetAtt": [ 1825 | "Main", 1826 | "Arn" 1827 | ] 1828 | } 1829 | } 1830 | ] 1831 | } 1832 | }, 1833 | "SchedulerConfigHelper": { 1834 | "Type": "Custom::ServiceSetup", 1835 | "Properties": { 1836 | "ServiceToken": { 1837 | "Fn::GetAtt": [ 1838 | "Main", 1839 | "Arn" 1840 | ] 1841 | }, 1842 | "timeout": "120", 1843 | "config_table": { 1844 | "Ref": "ConfigTable" 1845 | }, 1846 | "tagname": { 1847 | "Ref": "TagName" 1848 | }, 1849 | "default_timezone": { 1850 | "Ref": "DefaultTimezone" 1851 | }, 1852 | "use_metrics": { 1853 | "Fn::FindInMap": [ 1854 | "TrueFalse", 1855 | { 1856 | "Ref": "UseCloudWatchMetrics" 1857 | }, 1858 | "Value" 1859 | ] 1860 | }, 1861 | "scheduled_services": { 1862 | "Fn::Split": [ 1863 | ",", 1864 | { 1865 | "Fn::FindInMap": [ 1866 | "Services", 1867 | { 1868 | "Ref": "ScheduledServices" 1869 | }, 1870 | "Value" 1871 | ] 1872 | } 1873 | ] 1874 | }, 1875 | "regions": { 1876 | "Ref": "Regions" 1877 | }, 1878 | "cross_account_roles": { 1879 | "Ref": "CrossAccountRoles" 1880 | }, 1881 | "schedule_lambda_account": { 1882 | "Fn::FindInMap": [ 1883 | "TrueFalse", 1884 | { 1885 | "Ref": "ScheduleLambdaAccount" 1886 | }, 1887 | "Value" 1888 | ] 1889 | }, 1890 | "trace": { 1891 | "Fn::FindInMap": [ 1892 | "TrueFalse", 1893 | { 1894 | "Ref": "Trace" 1895 | }, 1896 | "Value" 1897 | ] 1898 | }, 1899 | "log_retention_days": { 1900 | "Ref": "LogRetentionDays" 1901 | }, 1902 | "started_tags": { 1903 | "Ref": "StartedTags" 1904 | }, 1905 | "stopped_tags": { 1906 | "Ref": "StoppedTags" 1907 | }, 1908 | "stack_version": "2.3.2.0" 1909 | }, 1910 | "DependsOn": [ 1911 | "Main", 1912 | "ConfigTable", 1913 | "SchedulerLogGroup" 1914 | ] 1915 | } 1916 | }, 1917 | "Outputs": { 1918 | "AccountId": { 1919 | "Value": { 1920 | "Ref": "AWS::AccountId" 1921 | }, 1922 | "Description": "Account to give access to when creating cross-account access role fro cross account scenario " 1923 | }, 1924 | "ConfigurationTable": { 1925 | "Value": { 1926 | "Ref": "ConfigTable" 1927 | }, 1928 | "Description": "Name of the DynomoDB configuration table" 1929 | }, 1930 | "SSMParameterStorePrefix": { 1931 | "Value": { 1932 | "Fn::FindInMap": [ 1933 | "Code", 1934 | "General", 1935 | "SSMPathPrefix" 1936 | ] 1937 | }, 1938 | "Description": "Path prefix for configuration parameter of lambda function" 1939 | }, 1940 | "IssueSnsTopicArn": { 1941 | "Value": { 1942 | "Ref": "InstanceSchedulerSnsTopic" 1943 | }, 1944 | "Description": "Topic to subscribe to for notifications of errors and warnings" 1945 | }, 1946 | "ServiceInstanceScheduleServiceToken": { 1947 | "Value": { 1948 | "Fn::GetAtt": [ 1949 | "Main", 1950 | "Arn" 1951 | ] 1952 | }, 1953 | "Description": "Arn to use as ServiceToken property for custom resource type Custom::ServiceInstanceSchedule" 1954 | } 1955 | } 1956 | } 1957 | -------------------------------------------------------------------------------- /instance-scheduler-remote-cn.template: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "(SO0030) - EC2 and RDS scheduler cross account role, version 2.3.2.0", 4 | "Parameters": { 5 | "InstanceSchedulerAccount": { 6 | "Type": "String", 7 | "AllowedPattern": "(^[0-9]{12}$)", 8 | "ConstraintDescription": "Account number is a 12 digit number", 9 | "Description": "Account number of Instance Scheduler account to give access to manage EC2 and RDS Instances in this account." 10 | } 11 | }, 12 | "Metadata": { 13 | "AWS::CloudFormation::Interface": { 14 | "ParameterGroups": [{ 15 | "Label": { 16 | "default": "Account" 17 | }, 18 | "Parameters": [ 19 | "InstanceSchedulerAccount" 20 | ] 21 | }], 22 | "ParameterLabels": { 23 | "InstanceSchedulerAccount": { 24 | "default": "Account number of Insttance Scheduler account" 25 | } 26 | } 27 | } 28 | }, 29 | "Resources": { 30 | "EC2SchedulerCrossAccountRole": { 31 | "Type": "AWS::IAM::Role", 32 | "Properties": { 33 | "AssumeRolePolicyDocument": { 34 | "Version": "2012-10-17", 35 | "Statement": [{ 36 | "Effect": "Allow", 37 | "Principal": { 38 | "AWS": { 39 | "Fn::Join": [ 40 | "", [ 41 | "arn:aws-cn:iam::", { 42 | "Ref": "InstanceSchedulerAccount" 43 | }, 44 | ":root" 45 | ] 46 | ] 47 | }, 48 | "Service": "lambda.amazonaws.com" 49 | }, 50 | "Action": [ 51 | "sts:AssumeRole" 52 | ] 53 | }] 54 | }, 55 | "Path": "/", 56 | "Policies": [{ 57 | "PolicyName": "EC2InstanceSchedulerRemote", 58 | "PolicyDocument": { 59 | "Version": "2012-10-17", 60 | "Statement": [{ 61 | "Effect": "Allow", 62 | "Action": [ 63 | "ec2:DescribeInstances", 64 | "ec2:StartInstances", 65 | "ec2:StopInstances", 66 | "ec2:ModifyInstanceAttribute", 67 | "ec2:CreateTags", 68 | "ec2:DeleteTags" 69 | ], 70 | "Resource": [ 71 | "*" 72 | ] 73 | }, { 74 | "Effect": "Allow", 75 | "Action": [ 76 | "rds:DescribeDBInstances", 77 | "rds:DescribeDBSnapshots", 78 | "rds:StartDBInstance", 79 | "rds:StopDBInstance", 80 | "rds:AddTagsToResource", 81 | "rds:RemoveTagsFromResource", 82 | "rds:DeleteDBSnapshot" 83 | ], 84 | "Resource": [ 85 | "*" 86 | ] 87 | }, { 88 | "Effect": "Allow", 89 | "Action": [ 90 | "tag:GetResources" 91 | ], 92 | "Resource": [ 93 | "*" 94 | ] 95 | }] 96 | } 97 | }] 98 | } 99 | } 100 | }, 101 | "Outputs": { 102 | "CrossAccountRole": { 103 | "Value": { 104 | "Fn::GetAtt": [ 105 | "EC2SchedulerCrossAccountRole", 106 | "Arn" 107 | ] 108 | }, 109 | "Description": "Arn for cross account role for Instance scheduler, add this arn to the list of crossaccount roles (CrossAccountRoles) parameter of the Instance Scheduler template." 110 | } 111 | } 112 | } 113 | --------------------------------------------------------------------------------