pkg/skoop/infra/aliyun/cloud_manager.go (517 lines of code) (raw):
package aliyun
import (
"fmt"
"net"
"strings"
"sync"
openapi "github.com/alibabacloud-go/darabonba-openapi/client"
openapiv2 "github.com/alibabacloud-go/darabonba-openapi/v2/client"
ecs "github.com/alibabacloud-go/ecs-20140526/v2/client"
slb "github.com/alibabacloud-go/slb-20140515/v4/client"
"github.com/alibabacloud-go/tea/tea"
vpc "github.com/alibabacloud-go/vpc-20160428/v2/client"
)
type CloudManager struct {
vpcID string
region string
vpcCIDRs []*net.IPNet
vpc *vpc.Client
ecs *ecs.Client
slb *slb.Client
ecsInfoCache map[string]*ECSInfo
}
type CloudManagerOptions struct {
Region string
AccessKeyID string
AccessKeySecret string
SecurityToken string
VPCID string
InstanceOfCluster string
}
type SecurityGroupType string
type SecurityGroupPolicy string
const (
SecurityGroupTypeEnterprise SecurityGroupType = "enterprise"
SecurityGroupTypeNormal SecurityGroupType = "normal"
SecurityGroupPolicyAccept SecurityGroupPolicy = "accept"
SecurityGroupPolicyDrop SecurityGroupPolicy = "drop"
)
type SecurityGroupRules struct {
Allows []*ecs.DescribeSecurityGroupAttributeResponseBodyPermissionsPermission
Drops []*ecs.DescribeSecurityGroupAttributeResponseBodyPermissionsPermission
}
type SecurityGroupRule struct {
ID string
Type SecurityGroupType
InRule SecurityGroupRules
OutRule SecurityGroupRules
}
type ENIInfo struct {
NetworkInterfaceSet *ecs.DescribeNetworkInterfacesResponseBodyNetworkInterfaceSetsNetworkInterfaceSet
RouteTableEntries []*vpc.DescribeRouteEntryListResponseBodyRouteEntrysRouteEntry
SecurityGroups map[string]SecurityGroupRule
}
type ECSNetwork struct {
IP []string
VSwitchID string
VpcID string
SecurityGroups map[string]SecurityGroupRule
RouteTableEntries []*vpc.DescribeRouteEntryListResponseBodyRouteEntrysRouteEntry
VpcDefaultRouteTableEntries []*vpc.DescribeRouteEntryListResponseBodyRouteEntrysRouteEntry
NetworkInterfaces []*ENIInfo
EIPAddress string
}
type ECSInfo struct {
ID string
Status string
Network ECSNetwork
}
type Listener struct {
TCP *slb.DescribeLoadBalancerTCPListenerAttributeResponseBody
UDP *slb.DescribeLoadBalancerUDPListenerAttributeResponseBody
}
func NewCloudManager(options *CloudManagerOptions) (*CloudManager, error) {
//TODO user-agent
cfg := &openapiv2.Config{
AccessKeyId: tea.String(options.AccessKeyID),
AccessKeySecret: tea.String(options.AccessKeySecret),
SecurityToken: tea.String(options.SecurityToken),
RegionId: tea.String(options.Region),
}
vpcClient, err := vpc.NewClient(cfg)
if err != nil {
return nil, fmt.Errorf("create vpc client: %s", err)
}
ecsClient, err := ecs.NewClient(&openapi.Config{
AccessKeyId: tea.String(options.AccessKeyID),
AccessKeySecret: tea.String(options.AccessKeySecret),
SecurityToken: tea.String(options.SecurityToken),
RegionId: tea.String(options.Region),
})
if err != nil {
return nil, fmt.Errorf("create ecs client: %s", err)
}
slbClient, err := slb.NewClient(cfg)
if err != nil {
return nil, fmt.Errorf("create slb client: %s", err)
}
vpcID := ""
if options.VPCID != "" {
vpcID = options.VPCID
} else if options.InstanceOfCluster != "" {
request := &ecs.DescribeInstancesRequest{}
request.SetRegionId(options.Region).
SetInstanceIds(fmt.Sprintf("[\"%s\"]", options.InstanceOfCluster))
response, err := ecsClient.DescribeInstances(request)
if err != nil {
return nil, err
}
if len(response.Body.Instances.Instance) == 0 {
return nil, fmt.Errorf("cannot find ecs instance info from id %s", options.InstanceOfCluster)
}
info := response.Body.Instances.Instance[0]
vpcID = *info.VpcAttributes.VpcId
} else {
return nil, fmt.Errorf("VPCID or InstanceOfCluster must be provided to get VPC ID")
}
cm := &CloudManager{
vpc: vpcClient,
ecs: ecsClient,
slb: slbClient,
region: options.Region,
vpcID: vpcID,
ecsInfoCache: map[string]*ECSInfo{},
}
cm.vpcCIDRs, err = cm.GetCIDRsFromVPC(vpcID)
if err != nil {
return nil, err
}
return cm, nil
}
func (m *CloudManager) VPC() string {
return m.vpcID
}
func (m *CloudManager) GetENIInfoFromID(networkInterfaceID string) (*ENIInfo, error) {
ids := []*string{&networkInterfaceID}
request := &ecs.DescribeNetworkInterfacesRequest{}
request.SetRegionId(m.region).SetNetworkInterfaceId(ids)
response, err := m.ecs.DescribeNetworkInterfaces(request)
if err != nil {
return nil, err
}
if len(response.Body.NetworkInterfaceSets.NetworkInterfaceSet) == 0 {
return nil, fmt.Errorf("eni '%s' no found", networkInterfaceID)
}
networkInterface := response.Body.NetworkInterfaceSets.NetworkInterfaceSet[0]
info := &ENIInfo{
NetworkInterfaceSet: networkInterface,
SecurityGroups: map[string]SecurityGroupRule{},
}
for _, sgID := range networkInterface.SecurityGroupIds.SecurityGroupId {
sgInfo, err := m.GetSecurityGroupRule(*sgID)
if err != nil {
return nil, err
}
info.SecurityGroups[sgInfo.ID] = sgInfo
}
info.RouteTableEntries, err = m.GetRouteEntryFromVswitch(*networkInterface.VSwitchId)
if err != nil {
return nil, err
}
return info, nil
}
func (m *CloudManager) GetVSwitchFromID(id string) (*vpc.DescribeVSwitchesResponseBodyVSwitchesVSwitch, error) {
request := &vpc.DescribeVSwitchesRequest{}
request.SetRegionId(m.region).SetVSwitchId(id)
response, err := m.vpc.DescribeVSwitches(request)
if err != nil {
return nil, err
}
if len(response.Body.VSwitches.VSwitch) == 0 {
return nil, fmt.Errorf("vswitch '%s' not found", id)
}
return response.Body.VSwitches.VSwitch[0], nil
}
func (m *CloudManager) GetVPCDefaultRouteTableID(id string) (string, error) {
request := &vpc.DescribeVpcsRequest{}
request.SetRegionId(m.region).SetVpcId(id)
response, err := m.vpc.DescribeVpcs(request)
if err != nil {
return "", err
}
if len(response.Body.Vpcs.Vpc) == 0 {
return "", fmt.Errorf("vpc '%s' not found", id)
}
vRouterID := response.Body.Vpcs.Vpc[0].VRouterId
routeRequest := &vpc.DescribeRouteTablesRequest{}
routeRequest.SetRegionId(m.region).SetVRouterId(*vRouterID)
routeResponse, err := m.vpc.DescribeRouteTables(routeRequest)
if err != nil {
return "", err
}
var defaultTable *vpc.DescribeRouteTablesResponseBodyRouteTablesRouteTable
for _, t := range routeResponse.Body.RouteTables.RouteTable {
if *t.RouteTableType == "System" {
defaultTable = t
break
}
}
if defaultTable == nil {
return "", fmt.Errorf("default route table for vpc '%s' not found", id)
}
return *defaultTable.RouteTableId, nil
}
func (m *CloudManager) GetSecurityGroupRule(id string) (SecurityGroupRule, error) {
sgRequest := &ecs.DescribeSecurityGroupsRequest{}
sgRequest.SetRegionId(m.region).SetSecurityGroupId(id)
sgResponse, err := m.ecs.DescribeSecurityGroups(sgRequest)
if err != nil {
return SecurityGroupRule{}, err
}
if len(sgResponse.Body.SecurityGroups.SecurityGroup) == 0 {
return SecurityGroupRule{}, fmt.Errorf("security group '%s' not found", id)
}
sg := sgResponse.Body.SecurityGroups.SecurityGroup[0]
securityGroup := SecurityGroupRule{
ID: id,
Type: SecurityGroupType(*sg.SecurityGroupType),
InRule: SecurityGroupRules{
Allows: nil,
Drops: nil,
},
OutRule: SecurityGroupRules{
Allows: nil,
Drops: nil,
},
}
sgAttrRequest := &ecs.DescribeSecurityGroupAttributeRequest{}
sgAttrRequest.SetRegionId(m.region).SetSecurityGroupId(id)
sgAttrResponse, err := m.ecs.DescribeSecurityGroupAttribute(sgAttrRequest)
if err != nil {
return securityGroup, err
}
polices := sgAttrResponse.Body.Permissions.Permission
for _, policy := range polices {
// when direction is 'ingress', use InRule
rule := &securityGroup.InRule
if *policy.Direction == "egress" {
rule = &securityGroup.OutRule
}
if *policy.Policy == "Accept" {
rule.Allows = append(rule.Allows, policy)
} else {
// policy is 'Drop'
rule.Drops = append(rule.Drops, policy)
}
}
return securityGroup, nil
}
var cachedRouteTableEntries = sync.Map{}
func (m *CloudManager) GetRouteTableEntries(routeTableID string) ([]*vpc.DescribeRouteEntryListResponseBodyRouteEntrysRouteEntry, error) {
if entries, ok := cachedRouteTableEntries.Load(routeTableID); ok {
return entries.([]*vpc.DescribeRouteEntryListResponseBodyRouteEntrysRouteEntry), nil
}
request := &vpc.DescribeRouteEntryListRequest{}
request.SetRegionId(m.region).SetRouteTableId(routeTableID).SetNextToken("").SetMaxResult(100)
var entries []*vpc.DescribeRouteEntryListResponseBodyRouteEntrysRouteEntry
for {
response, err := m.vpc.DescribeRouteEntryList(request)
if err != nil {
return nil, err
}
entries = append(entries, response.Body.RouteEntrys.RouteEntry...)
if *response.Body.NextToken == "" {
break
}
request.SetNextToken(*response.Body.NextToken)
}
cachedRouteTableEntries.Store(routeTableID, entries)
return entries, nil
}
func (m *CloudManager) GetECSInfo(id string) (*ECSInfo, error) {
if info, ok := m.ecsInfoCache[id]; ok {
return info, nil
}
request := &ecs.DescribeInstancesRequest{}
request.SetRegionId(m.region).SetInstanceIds(fmt.Sprintf("[%q]", id))
response, err := m.ecs.DescribeInstances(request)
if err != nil {
return nil, err
}
if len(response.Body.Instances.Instance) == 0 {
return nil, fmt.Errorf("instance '%s' not found", id)
}
instance := response.Body.Instances.Instance[0]
ecsInfo := &ECSInfo{
ID: *instance.InstanceId,
Status: *instance.Status,
Network: ECSNetwork{
SecurityGroups: map[string]SecurityGroupRule{},
VSwitchID: *instance.VpcAttributes.VSwitchId,
VpcID: *instance.VpcAttributes.VpcId,
},
}
for _, ip := range instance.VpcAttributes.PrivateIpAddress.IpAddress {
ecsInfo.Network.IP = append(ecsInfo.Network.IP, *ip)
}
for _, sg := range instance.SecurityGroupIds.SecurityGroupId {
sgInfo, err := m.GetSecurityGroupRule(*sg)
if err != nil {
return ecsInfo, err
}
ecsInfo.Network.SecurityGroups[sgInfo.ID] = sgInfo
}
ecsInfo.Network.RouteTableEntries, err = m.GetRouteEntryFromVswitch(*instance.VpcAttributes.VSwitchId)
if err != nil {
return nil, err
}
ecsInfo.Network.VpcDefaultRouteTableEntries, err = m.GetVPCDefaultRouteEntry(*instance.VpcAttributes.VpcId)
if err != nil {
return ecsInfo, err
}
// get eni
for _, networkInterface := range instance.NetworkInterfaces.NetworkInterface {
info, err := m.GetENIInfoFromID(*networkInterface.NetworkInterfaceId)
if err != nil {
return ecsInfo, err
}
ecsInfo.Network.NetworkInterfaces = append(ecsInfo.Network.NetworkInterfaces, info)
}
if instance.EipAddress != nil && instance.EipAddress.IpAddress != nil {
ecsInfo.Network.EIPAddress = *instance.EipAddress.IpAddress
}
m.ecsInfoCache[id] = ecsInfo
return ecsInfo, nil
}
func (m *CloudManager) GetRouteEntryFromVswitch(id string) ([]*vpc.DescribeRouteEntryListResponseBodyRouteEntrysRouteEntry, error) {
vswitch, err := m.GetVSwitchFromID(id)
if err != nil {
return nil, err
}
return m.GetRouteTableEntries(*vswitch.RouteTable.RouteTableId)
}
func (m *CloudManager) GetVPCDefaultRouteEntry(vpcID string) ([]*vpc.DescribeRouteEntryListResponseBodyRouteEntrysRouteEntry, error) {
routeTableID, err := m.GetVPCDefaultRouteTableID(vpcID)
if err != nil {
return nil, err
}
return m.GetRouteTableEntries(routeTableID)
}
func (m *CloudManager) GetSLBFromPublicIP(ip string) (*slb.DescribeLoadBalancersResponseBodyLoadBalancersLoadBalancer, error) {
request := &slb.DescribeLoadBalancersRequest{}
request.SetRegionId(m.region).SetAddress(ip).SetAddressType("internet")
response, err := m.slb.DescribeLoadBalancers(request)
if err != nil {
return nil, err
}
if *response.Body.TotalCount == 0 {
return nil, nil
}
return response.Body.LoadBalancers.LoadBalancer[0], nil
}
func (m *CloudManager) GetSLBFromPrivateIP(ip string) (*slb.DescribeLoadBalancersResponseBodyLoadBalancersLoadBalancer, error) {
request := &slb.DescribeLoadBalancersRequest{}
request.SetRegionId(m.region).SetAddress(ip).SetAddressType("intranet")
response, err := m.slb.DescribeLoadBalancers(request)
if err != nil {
return nil, err
}
if *response.Body.TotalCount == 0 {
return nil, nil
}
return response.Body.LoadBalancers.LoadBalancer[0], nil
}
func (m *CloudManager) GetSLBFromID(id string) (*slb.DescribeLoadBalancersResponseBodyLoadBalancersLoadBalancer, error) {
request := &slb.DescribeLoadBalancersRequest{}
request.SetRegionId(m.region).SetLoadBalancerId(id)
response, err := m.slb.DescribeLoadBalancers(request)
if err != nil {
return nil, err
}
if *response.Body.TotalCount == 0 {
return nil, nil
}
return response.Body.LoadBalancers.LoadBalancer[0], nil
}
func (m *CloudManager) GetSLBListener(id string, port int32, protocol string) (*Listener, error) {
ret := &Listener{}
if strings.EqualFold(protocol, "udp") {
request := &slb.DescribeLoadBalancerUDPListenerAttributeRequest{}
request.SetRegionId(m.region).SetListenerPort(port).SetLoadBalancerId(id)
response, err := m.slb.DescribeLoadBalancerUDPListenerAttribute(request)
if err != nil {
if strings.Contains(err.Error(), "The specified resource does not exist") {
return nil, nil
}
return nil, err
}
ret.UDP = response.Body
} else {
request := &slb.DescribeLoadBalancerTCPListenerAttributeRequest{}
request.SetRegionId(m.region).SetListenerPort(port).SetLoadBalancerId(id)
response, err := m.slb.DescribeLoadBalancerTCPListenerAttribute(request)
if err != nil {
if strings.Contains(err.Error(), "The specified resource does not exist") {
return nil, nil
}
return nil, err
}
ret.TCP = response.Body
}
return ret, nil
}
func (m *CloudManager) GetSLBVserverGroup(id string) (*slb.DescribeVServerGroupAttributeResponseBody, error) {
request := &slb.DescribeVServerGroupAttributeRequest{}
request.SetRegionId(m.region).SetVServerGroupId(id)
response, err := m.slb.DescribeVServerGroupAttribute(request)
if err != nil {
if strings.Contains(err.Error(), "The specified VServerGroupId does not exist") {
return nil, nil
}
return nil, err
}
return response.Body, nil
}
func (m *CloudManager) GetSLBHealthStatus(id string, port int32, protocol string) ([]*slb.DescribeHealthStatusResponseBodyBackendServersBackendServer, error) {
request := &slb.DescribeHealthStatusRequest{}
request.SetRegionId(m.region).SetLoadBalancerId(id)
if port != 0 {
request.SetListenerPort(port)
}
if protocol != "" {
request.SetListenerProtocol(protocol)
}
response, err := m.slb.DescribeHealthStatus(request)
if err != nil {
if strings.Contains(err.Error(), "ListenerNotFound") {
return nil, nil
}
return nil, err
}
return response.Body.BackendServers.BackendServer, nil
}
func (m *CloudManager) GetENIInfoFromVPCAndPrivateIP(vpcID, privateIP string) (*ecs.DescribeNetworkInterfacesResponseBodyNetworkInterfaceSetsNetworkInterfaceSet, error) {
request := &ecs.DescribeNetworkInterfacesRequest{}
request.SetRegionId(m.region).SetVpcId(vpcID).SetPrivateIpAddress([]*string{&privateIP})
response, err := m.ecs.DescribeNetworkInterfaces(request)
if err != nil {
return nil, err
}
if *response.Body.TotalCount == 0 {
return nil, fmt.Errorf("cannot find eni with vpc %s and private ip %s", vpcID, privateIP)
}
return response.Body.NetworkInterfaceSets.NetworkInterfaceSet[0], nil
}
func (m *CloudManager) GetNatGatewayInfo(id string) (*vpc.DescribeNatGatewaysResponseBodyNatGatewaysNatGateway, error) {
request := &vpc.DescribeNatGatewaysRequest{}
request.SetRegionId(m.region).SetNatGatewayId(id)
response, err := m.vpc.DescribeNatGateways(request)
if err != nil {
return nil, err
}
if *response.Body.TotalCount == 0 {
return nil, fmt.Errorf("cannot find nat gateway %s", id)
}
return response.Body.NatGateways.NatGateway[0], nil
}
func (m *CloudManager) GetSNATEntriesBySegment(snatTableID string, segment string) ([]*vpc.DescribeSnatTableEntriesResponseBodySnatTableEntriesSnatTableEntry, error) {
request := &vpc.DescribeSnatTableEntriesRequest{}
request.SetRegionId(m.region).SetSnatTableId(snatTableID).SetSourceCIDR(segment)
response, err := m.vpc.DescribeSnatTableEntries(request)
if err != nil {
return nil, err
}
return response.Body.SnatTableEntries.SnatTableEntry, nil
}
func (m *CloudManager) GetVSwitch(id string) (*vpc.DescribeVSwitchesResponseBodyVSwitchesVSwitch, error) {
request := &vpc.DescribeVSwitchesRequest{}
request.SetRegionId(m.region).SetVSwitchId(id)
response, err := m.vpc.DescribeVSwitches(request)
if err != nil {
return nil, err
}
if *response.Body.TotalCount == 0 {
return nil, fmt.Errorf("cannot find vswitch %s", id)
}
return response.Body.VSwitches.VSwitch[0], nil
}
func (m *CloudManager) VPCCIDRs() []*net.IPNet {
return m.vpcCIDRs
}
func (m *CloudManager) GetCIDRsFromVPC(id string) ([]*net.IPNet, error) {
request := &vpc.DescribeVpcsRequest{}
request.SetRegionId(m.region).SetVpcId(id)
response, err := m.vpc.DescribeVpcs(request)
if err != nil {
return nil, err
}
if *response.Body.TotalCount == 0 {
return nil, fmt.Errorf("cannot find vpc %s", id)
}
v := *response.Body.Vpcs.Vpc[0]
var ipNets []*net.IPNet
_, n, err := net.ParseCIDR(*v.CidrBlock)
if err != nil {
return nil, err
}
ipNets = append(ipNets, n)
if v.UserCidrs == nil {
return ipNets, nil
}
for _, cidr := range v.UserCidrs.UserCidr {
_, n, err := net.ParseCIDR(*cidr)
if err != nil {
return nil, err
}
ipNets = append(ipNets, n)
}
return ipNets, nil
}
func (m *CloudManager) GetACLFromID(id string) (*slb.DescribeAccessControlListAttributeResponseBody, error) {
request := &slb.DescribeAccessControlListAttributeRequest{}
request.SetRegionId(m.region).SetAclId(id)
response, err := m.slb.DescribeAccessControlListAttribute(request)
if err != nil {
if strings.Contains(err.Error(), "Acl does not exist") {
return nil, nil
}
return nil, err
}
return response.Body, nil
}