#!/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)))