| #!/usr/bin/env python3 | |
| import argparse | |
| from collections import namedtuple | |
| from concurrent.futures import ThreadPoolExecutor, as_completed | |
| import ipaddress | |
| from itertools import islice | |
| import json | |
| import time | |
| import alibabacloud_credentials as credentials | |
| import alibabacloud_credentials.client | |
| import alibabacloud_credentials.models | |
| import alibabacloud_ecs20140526 as ecs | |
| import alibabacloud_ecs20140526.client | |
| import alibabacloud_ecs20140526.models | |
| import alibabacloud_ram20150501 as ram | |
| import alibabacloud_ram20150501.client | |
| import alibabacloud_ram20150501.models | |
| import alibabacloud_tea_openapi as openapi | |
| import alibabacloud_tea_openapi.client | |
| import alibabacloud_tea_openapi.models | |
| import alibabacloud_tea_util as util | |
| import alibabacloud_tea_util.client | |
| import alibabacloud_tea_util.models | |
| import alibabacloud_vpc20160428 as vpc | |
| import alibabacloud_vpc20160428.client | |
| import alibabacloud_vpc20160428.models | |
| ECS_ENDPOINT = 'ecs.aliyuncs.com' | |
| RAM_ENDPOINT = 'ram.aliyuncs.com' | |
| IPXE_VPC_TAG = 'ipxe-default-vpc' | |
| IPXE_VSWITCH_TAG = 'ipxe-default-vswitch' | |
| IPXE_SG_TAG = 'ipxe-default-sg' | |
| IPXE_CENSORSHIP_BYPASS_ROLE_NAME = 'iPXECensorshipBypassRole' | |
| IPXE_CENSORSHIP_BYPASS_ROLE_ASSUME_POLICY = { | |
| 'Statement': [{ | |
| 'Action': 'sts:AssumeRole', | |
| 'Effect': 'Allow', | |
| 'Principal': {'Service': ['ecs.aliyuncs.com']}, | |
| }], | |
| 'Version': '1', | |
| } | |
| Clients = namedtuple('Clients', ['region', 'ecs', 'vpc']) | |
| def all_regions(): | |
| """Get list of all regions""" | |
| cred = credentials.client.Client() | |
| conf = openapi.models.Config(credential=cred, endpoint=ECS_ENDPOINT) | |
| client = ecs.client.Client(conf) | |
| req = ecs.models.DescribeRegionsRequest() | |
| rsp = client.describe_regions(req) | |
| regions = sorted(x.region_id for x in rsp.body.regions.region) | |
| return regions | |
| def all_clients(region): | |
| """Construct all per-region clients""" | |
| cred = credentials.client.Client() | |
| conf = openapi.models.Config(credential=cred, region_id=region) | |
| clients = Clients( | |
| region=region, | |
| ecs=ecs.client.Client(conf), | |
| vpc=vpc.client.Client(conf), | |
| ) | |
| return clients | |
| def ram_client(): | |
| """Construct resource access management client""" | |
| cred = credentials.client.Client() | |
| conf = openapi.models.Config(credential=cred, endpoint=RAM_ENDPOINT) | |
| client = ram.client.Client(conf) | |
| return client | |
| def setup_censorship_bypass_role(client): | |
| """Set up censorship bypass role (required for importing images)""" | |
| role_name = IPXE_CENSORSHIP_BYPASS_ROLE_NAME | |
| assume_policy = json.dumps(IPXE_CENSORSHIP_BYPASS_ROLE_ASSUME_POLICY) | |
| req = ram.models.GetRoleRequest( | |
| role_name=role_name, | |
| ) | |
| try: | |
| rsp = client.get_role(req) | |
| arn = rsp.body.role.arn | |
| except openapi.exceptions.ClientException as exc: | |
| if exc.code != 'EntityNotExist.Role': | |
| raise | |
| req = ram.models.CreateRoleRequest( | |
| role_name=role_name, | |
| assume_role_policy_document=assume_policy, | |
| ) | |
| rsp = client.create_role(req) | |
| arn = rsp.body.role.arn | |
| req = ram.models.UpdateRoleRequest( | |
| role_name=role_name, | |
| new_assume_role_policy_document=assume_policy, | |
| new_description="iPXE role to help bypass OSS censorship restrictions", | |
| ) | |
| rsp = client.update_role(req) | |
| req = ram.models.AttachPolicyToRoleRequest( | |
| role_name=role_name, | |
| policy_type='System', | |
| policy_name='AliyunOSSFullAccess', | |
| ) | |
| try: | |
| rsp = client.attach_policy_to_role(req) | |
| except openapi.exceptions.ClientException as exc: | |
| if exc.code != 'EntityAlreadyExists.Role.Policy': | |
| raise | |
| return arn | |
| def setup_vpc(clients): | |
| """Set up VPC""" | |
| tag = vpc.models.DescribeVpcsRequestTag( | |
| key=IPXE_VPC_TAG, | |
| value=IPXE_VPC_TAG, | |
| ) | |
| req = vpc.models.DescribeVpcsRequest( | |
| region_id=clients.region, | |
| tag=[tag], | |
| ) | |
| rsp = clients.vpc.describe_vpcs(req) | |
| vpcs = rsp.body.vpcs.vpc or [] | |
| if vpcs: | |
| assert len(vpcs) == 1 | |
| vpc_id = vpcs[0].vpc_id | |
| if not vpcs: | |
| tag = vpc.models.CreateVpcRequestTag( | |
| key=IPXE_VPC_TAG, | |
| value=IPXE_VPC_TAG, | |
| ) | |
| req = vpc.models.CreateVpcRequest( | |
| region_id=clients.region, | |
| tag=[tag], | |
| ) | |
| rsp = clients.vpc.create_vpc(req) | |
| vpc_id = rsp.body.vpc_id | |
| while True: | |
| time.sleep(1) | |
| req = vpc.models.DescribeVpcsRequest( | |
| region_id=clients.region, | |
| vpc_id=vpc_id, | |
| ) | |
| rsp = clients.vpc.describe_vpcs(req) | |
| status = rsp.body.vpcs.vpc[0].status | |
| if status != 'Pending': | |
| break | |
| if status != 'Available': | |
| raise RuntimeError(status) | |
| req = vpc.models.ModifyVpcAttributeRequest( | |
| region_id=clients.region, | |
| vpc_id=vpc_id, | |
| vpc_name=("%s-%s" % (IPXE_VPC_TAG, clients.region)), | |
| description="Default VPC for iPXE development and testing", | |
| ) | |
| rsp = clients.vpc.modify_vpc_attribute(req) | |
| req = vpc.models.ModifyVpcAttributeRequest( | |
| region_id=clients.region, | |
| vpc_id=vpc_id, | |
| enable_ipv_6=True, | |
| ) | |
| try: | |
| rsp = clients.vpc.modify_vpc_attribute(req) | |
| except openapi.exceptions.ClientException as exc: | |
| # AliCloud provides no other way to detect regions without IPv6 support | |
| if exc.code != 'OperationUnsupported.Ipv6Feature': | |
| raise | |
| return vpc_id | |
| def setup_vswitch(clients, vpc_id, zone_id, index): | |
| """Set up vSwitch""" | |
| tag = vpc.models.DescribeVSwitchesRequestTag( | |
| key=IPXE_VSWITCH_TAG, | |
| value=IPXE_VSWITCH_TAG, | |
| ) | |
| req = vpc.models.DescribeVSwitchesRequest( | |
| region_id=clients.region, | |
| zone_id=zone_id, | |
| tag=[tag], | |
| ) | |
| rsp = clients.vpc.describe_vswitches(req) | |
| vswitches = rsp.body.v_switches.v_switch or [] | |
| if vswitches: | |
| assert len(vswitches) == 1 | |
| assert vswitches[0].vpc_id == vpc_id | |
| vswitch_id = vswitches[0].v_switch_id | |
| else: | |
| req = vpc.models.DescribeVpcsRequest( | |
| region_id=clients.region, | |
| vpc_id=vpc_id, | |
| ) | |
| rsp = clients.vpc.describe_vpcs(req) | |
| ipv6_cidr_block = index if rsp.body.vpcs.vpc[0].enabled_ipv_6 else None | |
| ipv4net = ipaddress.ip_network(rsp.body.vpcs.vpc[0].cidr_block) | |
| ipv4subnet = next(islice(ipv4net.subnets(new_prefix=24), index, None)) | |
| cidr_block = str(ipv4subnet) | |
| tag = vpc.models.CreateVSwitchRequestTag( | |
| key=IPXE_VSWITCH_TAG, | |
| value=IPXE_VSWITCH_TAG, | |
| ) | |
| req = vpc.models.CreateVSwitchRequest( | |
| region_id=clients.region, | |
| vpc_id=vpc_id, | |
| zone_id=zone_id, | |
| tag=[tag], | |
| cidr_block=cidr_block, | |
| ipv_6cidr_block=ipv6_cidr_block, | |
| ) | |
| try: | |
| rsp = clients.vpc.create_vswitch(req) | |
| vswitch_id = rsp.body.v_switch_id | |
| except openapi.exceptions.ClientException as exc: | |
| # AliCloud provides no other way to detect disabled zones | |
| if exc.code != 'OperationDenied.ZoneIsDisabled': | |
| raise | |
| vswitch_id = None | |
| if vswitch_id: | |
| while True: | |
| time.sleep(1) | |
| req = vpc.models.DescribeVSwitchesRequest( | |
| region_id=clients.region, | |
| v_switch_id=vswitch_id, | |
| ) | |
| rsp = clients.vpc.describe_vswitches(req) | |
| status = rsp.body.v_switches.v_switch[0].status | |
| if status != 'Pending': | |
| break | |
| if status != 'Available': | |
| raise RuntimeError(status) | |
| req = vpc.models.ModifyVSwitchAttributeRequest( | |
| region_id=clients.region, | |
| v_switch_id=vswitch_id, | |
| v_switch_name=('%s-%s' % (IPXE_VSWITCH_TAG, zone_id)), | |
| description="Default vSwitch for iPXE development and testing", | |
| ) | |
| rsp = clients.vpc.modify_vswitch_attribute(req) | |
| return vswitch_id | |
| def setup_vswitches(clients, vpc_id): | |
| """Set up vSwitches""" | |
| req = vpc.models.DescribeZonesRequest(region_id=clients.region) | |
| rsp = clients.vpc.describe_zones(req) | |
| vswitch_ids = [setup_vswitch(clients, vpc_id, zone.zone_id, index) | |
| for index, zone in enumerate(rsp.body.zones.zone or [])] | |
| return sorted(filter(None, vswitch_ids)) | |
| def setup_sg(clients, vpc_id): | |
| """Set up security group""" | |
| tag = ecs.models.DescribeSecurityGroupsRequestTag( | |
| key=IPXE_SG_TAG, | |
| value=IPXE_SG_TAG, | |
| ) | |
| req = ecs.models.DescribeSecurityGroupsRequest( | |
| region_id=clients.region, | |
| vpc_id=vpc_id, | |
| tag=[tag], | |
| ) | |
| rsp = clients.ecs.describe_security_groups(req) | |
| sgs = rsp.body.security_groups.security_group or [] | |
| if sgs: | |
| assert len(sgs) == 1 | |
| assert sgs[0].vpc_id == vpc_id | |
| sg_id = sgs[0].security_group_id | |
| else: | |
| tag = ecs.models.CreateSecurityGroupRequestTag( | |
| key=IPXE_SG_TAG, | |
| value=IPXE_SG_TAG, | |
| ) | |
| req = ecs.models.CreateSecurityGroupRequest( | |
| region_id=clients.region, | |
| vpc_id=vpc_id, | |
| tag=[tag], | |
| ) | |
| rsp = clients.ecs.create_security_group(req) | |
| sg_id = rsp.body.security_group_id | |
| req = ecs.models.ModifySecurityGroupAttributeRequest( | |
| region_id=clients.region, | |
| security_group_id=sg_id, | |
| security_group_name=IPXE_SG_TAG, | |
| description="Default security group for iPXE development and testing", | |
| ) | |
| rsp = clients.ecs.modify_security_group_attribute(req) | |
| perm4 = ecs.models.AuthorizeSecurityGroupEgressRequestPermissions( | |
| policy='accept', | |
| dest_cidr_ip='0.0.0.0/0', | |
| ip_protocol='ALL', | |
| port_range='-1/-1', | |
| ) | |
| perm6 = ecs.models.AuthorizeSecurityGroupEgressRequestPermissions( | |
| policy='accept', | |
| ipv_6dest_cidr_ip='::/0', | |
| ip_protocol='ALL', | |
| port_range='-1/-1', | |
| ) | |
| req = ecs.models.AuthorizeSecurityGroupEgressRequest( | |
| region_id=clients.region, | |
| security_group_id=sg_id, | |
| permissions=[perm4, perm6], | |
| ) | |
| rsp = clients.ecs.authorize_security_group_egress(req) | |
| return sg_id | |
| def setup_region(clients): | |
| """Set up region""" | |
| vpc_id = setup_vpc(clients) | |
| vswitch_ids = setup_vswitches(clients, vpc_id) | |
| sg_id = setup_sg(clients, vpc_id) | |
| return (sg_id, vpc_id, vswitch_ids) | |
| # Parse command-line arguments | |
| parser = argparse.ArgumentParser(description="Set up Alibaba Cloud defaults") | |
| parser.add_argument('--region', '-r', action='append', | |
| help="AliCloud region(s)") | |
| parser.add_argument('--create-role', action=argparse.BooleanOptionalAction, | |
| default=True, help="Create censorship bypass role") | |
| args = parser.parse_args() | |
| # Set up censorship bypass role | |
| if args.create_role: | |
| arn = setup_censorship_bypass_role(ram_client()) | |
| # Use all regions if none specified | |
| if not args.region: | |
| args.region = all_regions() | |
| # Construct per-region clients | |
| clients = {region: all_clients(region) for region in args.region} | |
| # Set up each region | |
| with ThreadPoolExecutor(max_workers=len(args.region)) as executor: | |
| futures = {executor.submit(setup_region, | |
| clients=clients[region]): region | |
| for region in args.region} | |
| results = {futures[x]: x.result() for x in as_completed(futures)} | |
| # Show created resources | |
| if args.create_role: | |
| print("%s" % arn) | |
| for region in args.region: | |
| (sg_id, vpc_id, vswitch_ids) = results[region] | |
| print("%s %s %s %s" % (region, sg_id, vpc_id, " ".join(vswitch_ids))) |