package oci import ( "context" "fmt" "time" "github.com/oracle/oci-go-sdk/v65/core" ) // VCN 是一个虚拟云网络。 type VCN struct { ID string `json:"id"` DisplayName string `json:"displayName"` LifecycleState string `json:"lifecycleState"` CompartmentID string `json:"compartmentId"` CidrBlocks []string `json:"cidrBlocks"` Ipv6CidrBlocks []string `json:"ipv6CidrBlocks"` DnsLabel string `json:"dnsLabel"` DefaultRouteTableID string `json:"defaultRouteTableId"` DefaultSecurityListID string `json:"defaultSecurityListId"` TimeCreated *time.Time `json:"timeCreated"` } // CreateVCNInput 是创建 VCN 的输入。 type CreateVCNInput struct { Region string CompartmentID string // 目标 compartment,空为租户根 DisplayName string CidrBlock string DnsLabel string EnableIPv6 bool // 创建时同时分配 Oracle GUA IPv6 /56 } // Subnet 是 VCN 下的一个子网。 type Subnet struct { ID string `json:"id"` VcnID string `json:"vcnId"` DisplayName string `json:"displayName"` LifecycleState string `json:"lifecycleState"` CidrBlock string `json:"cidrBlock"` Ipv6CidrBlock string `json:"ipv6CidrBlock"` DnsLabel string `json:"dnsLabel"` ProhibitPublicIP bool `json:"prohibitPublicIp"` AvailabilityDomain string `json:"availabilityDomain"` RouteTableID string `json:"routeTableId"` TimeCreated *time.Time `json:"timeCreated"` } // CreateSubnetInput 是创建子网的输入。 type CreateSubnetInput struct { Region string VcnID string DisplayName string CidrBlock string DnsLabel string Ipv6CidrBlock string ProhibitPublicIP bool } // SecurityRule 是安全列表中的一条规则;TCP/UDP 只支持目标端口范围。 type SecurityRule struct { Protocol string `json:"protocol"` // all、6(TCP)、17(UDP)、1(ICMP)、58(ICMPv6) Source string `json:"source,omitempty"` Destination string `json:"destination,omitempty"` IsStateless bool `json:"isStateless"` PortMin *int `json:"portMin,omitempty"` PortMax *int `json:"portMax,omitempty"` IcmpType *int `json:"icmpType,omitempty"` IcmpCode *int `json:"icmpCode,omitempty"` Description string `json:"description,omitempty"` } // SecurityList 是一个安全列表及其规则。 type SecurityList struct { ID string `json:"id"` VcnID string `json:"vcnId"` DisplayName string `json:"displayName"` LifecycleState string `json:"lifecycleState"` IngressRules []SecurityRule `json:"ingressRules"` EgressRules []SecurityRule `json:"egressRules"` TimeCreated *time.Time `json:"timeCreated"` } // CreateSecurityListInput 是创建安全列表的输入。 type CreateSecurityListInput struct { Region string VcnID string DisplayName string IngressRules []SecurityRule EgressRules []SecurityRule } // UpdateSecurityListInput 是更新安全列表的输入;nil 字段保持不变, // 规则字段一经提供即整体替换。 type UpdateSecurityListInput struct { Region string DisplayName string IngressRules *[]SecurityRule EgressRules *[]SecurityRule } // 自动建网使用的固定名称,便于复用而非每次新建 VCN。 const ( autoVcnName = "oci-portal-auto-vcn" autoSubnetName = "oci-portal-auto-subnet" autoVcnCidr = "10.0.0.0/16" autoSubnetCidr = "10.0.0.0/24" ) // ensureDefaultSubnet 在 subnetId 为空时准备一个可上公网的子网: // 复用已有的 oci-portal-auto-vcn,否则新建 VCN + IGW + 默认路由 + 子网。 func (c *RealClient) ensureDefaultSubnet(ctx context.Context, cred Credentials, region string) (string, error) { vcns, err := c.ListVCNs(ctx, cred, region) if err != nil { return "", err } vn, err := c.vcnClient(cred, region) if err != nil { return "", err } for _, v := range vcns { if v.DisplayName != autoVcnName { continue } // 列表项自带 VCN 所在 compartment,直接用它列子网,免去重复 GetVCN subnets, err := listSubnets(ctx, vn, *hostCompartment(cred, v.CompartmentID), v.ID) if err == nil && len(subnets) > 0 { return subnets[0].ID, nil } } return c.createDefaultNetwork(ctx, cred, region) } func (c *RealClient) createDefaultNetwork(ctx context.Context, cred Credentials, region string) (string, error) { vcn, err := c.CreateVCN(ctx, cred, CreateVCNInput{ Region: region, DisplayName: autoVcnName, CidrBlock: autoVcnCidr, DnsLabel: "ociportalauto", EnableIPv6: true, }) if err != nil { return "", err } vn, err := c.vcnClient(cred, region) if err != nil { return "", err } // IGW 与子网都建在新 VCN 自己的 compartment,与其余关联资源保持同域 vcnCompartment := *hostCompartment(cred, vcn.CompartmentID) igwID, err := ensureInternetGateway(ctx, vn, vcnCompartment, vcn.ID) if err != nil { return "", err } if err := addInternetRoute(ctx, vn, vcn.DefaultRouteTableID, igwID, "0.0.0.0/0"); err != nil { return "", err } subnet, err := c.createSubnetIn(ctx, cred, vcnCompartment, CreateSubnetInput{ Region: region, VcnID: vcn.ID, DisplayName: autoSubnetName, CidrBlock: autoSubnetCidr, DnsLabel: "ociauto", Ipv6CidrBlock: autoSubnetIPv6Cidr(ctx, vn, vcn.ID, vcn.DefaultRouteTableID, igwID), }) if err != nil { return "", err } return subnet.ID, nil } // autoSubnetIPv6Cidr 等待自动 VCN 的 IPv6 /56 分配就绪并补 ::/0 路由, // 返回子网可用的 /64;IPv6 尽力而为,失败返回空串降级纯 IPv4 不阻塞建网。 func autoSubnetIPv6Cidr(ctx context.Context, vn core.VirtualNetworkClient, vcnID, routeTableID, igwID string) string { vcnCidr, err := waitVCNIPv6Cidr(ctx, vn, vcnID) if err != nil { return "" } if err := addInternetRoute(ctx, vn, routeTableID, igwID, ipv6AnyCIDR); err != nil { return "" } cidr, err := nextSubnetIPv6CIDR(vcnCidr, nil) if err != nil { return "" } return cidr } // addInternetRoute 在路由表补 dest → IGW,已存在则跳过。 func addInternetRoute(ctx context.Context, vn core.VirtualNetworkClient, routeTableID, igwID, dest string) error { resp, err := vn.GetRouteTable(ctx, core.GetRouteTableRequest{RtId: &routeTableID}) if err != nil { return fmt.Errorf("get route table: %w", err) } for _, rule := range resp.RouteRules { if deref(rule.Destination) == dest { return nil } } rules := append(resp.RouteRules, core.RouteRule{ Destination: &dest, DestinationType: core.RouteRuleDestinationTypeCidrBlock, NetworkEntityId: &igwID, }) _, err = vn.UpdateRouteTable(ctx, core.UpdateRouteTableRequest{ RtId: &routeTableID, UpdateRouteTableDetails: core.UpdateRouteTableDetails{RouteRules: rules}, }) if err != nil { return fmt.Errorf("add internet route %s: %w", dest, err) } return nil } func (c *RealClient) vcnClient(cred Credentials, region string) (core.VirtualNetworkClient, error) { vn, err := core.NewVirtualNetworkClientWithConfigurationProvider(provider(cred)) if err != nil { return vn, fmt.Errorf("new virtual network client: %w", err) } applyProxy(&vn.BaseClient, cred) if region != "" { vn.SetRegion(normalizeRegion(region)) } return vn, nil } // ListVCNs 实现 Client。 func (c *RealClient) ListVCNs(ctx context.Context, cred Credentials, region string) ([]VCN, error) { vn, err := c.vcnClient(cred, region) if err != nil { return nil, err } var vcns []VCN var page *string for { resp, err := vn.ListVcns(ctx, core.ListVcnsRequest{CompartmentId: cred.EffectiveCompartment(), Page: page}) if err != nil { return nil, fmt.Errorf("list vcns: %w", err) } for _, item := range resp.Items { vcns = append(vcns, toVCN(item)) } if resp.OpcNextPage == nil { return orEmpty(vcns), nil } page = resp.OpcNextPage } } // CreateVCN 实现 Client;创建后将默认安全列表放开为出入方向全放行。 func (c *RealClient) CreateVCN(ctx context.Context, cred Credentials, in CreateVCNInput) (VCN, error) { vn, err := c.vcnClient(cred, in.Region) if err != nil { return VCN{}, err } details := core.CreateVcnDetails{ CompartmentId: cred.EffectiveCompartment(), CidrBlocks: []string{in.CidrBlock}, DisplayName: &in.DisplayName, } if in.DnsLabel != "" { details.DnsLabel = &in.DnsLabel } if in.EnableIPv6 { details.IsIpv6Enabled = &in.EnableIPv6 } resp, err := vn.CreateVcn(ctx, core.CreateVcnRequest{CreateVcnDetails: details}) if err != nil { return VCN{}, fmt.Errorf("create vcn: %w", err) } vcn := toVCN(resp.Vcn) // VCN 初始化会异步写入默认安全列表的初始规则,未就绪时替换会被覆盖 if err := waitVCNAvailable(ctx, vn, vcn.ID); err != nil { return vcn, err } if err := openDefaultSecurityList(ctx, vn, vcn.DefaultSecurityListID, in.EnableIPv6); err != nil { return vcn, err } return vcn, nil } // waitVCNAvailable 轮询等待 VCN 进入 AVAILABLE,最长约 60 秒。 func waitVCNAvailable(ctx context.Context, vn core.VirtualNetworkClient, vcnID string) error { for i := 0; i < 30; i++ { resp, err := vn.GetVcn(ctx, core.GetVcnRequest{VcnId: &vcnID}) if err != nil { return fmt.Errorf("poll vcn state: %w", err) } if resp.LifecycleState == core.VcnLifecycleStateAvailable { return nil } select { case <-ctx.Done(): return fmt.Errorf("poll vcn state: %w", ctx.Err()) case <-time.After(2 * time.Second): } } return fmt.Errorf("poll vcn state: timed out waiting for AVAILABLE") } // openDefaultSecurityList 将安全列表规则替换为出入方向全放行; // includeIPv6 时附带 ::/0 规则(OCI 拒绝在未启用 IPv6 的 VCN 上写入)。 func openDefaultSecurityList(ctx context.Context, vn core.VirtualNetworkClient, securityListID string, includeIPv6 bool) error { protocol := "all" v4, v6 := "0.0.0.0/0", ipv6AnyCIDR ingress := []core.IngressSecurityRule{{Protocol: &protocol, Source: &v4}} egress := []core.EgressSecurityRule{{Protocol: &protocol, Destination: &v4}} if includeIPv6 { ingress = append(ingress, core.IngressSecurityRule{Protocol: &protocol, Source: &v6}) egress = append(egress, core.EgressSecurityRule{Protocol: &protocol, Destination: &v6}) } _, err := vn.UpdateSecurityList(ctx, core.UpdateSecurityListRequest{ SecurityListId: &securityListID, UpdateSecurityListDetails: core.UpdateSecurityListDetails{ IngressSecurityRules: ingress, EgressSecurityRules: egress, }, }) if err != nil { return fmt.Errorf("open default security list %s: %w", securityListID, err) } return nil } // GetVCN 实现 Client。 func (c *RealClient) GetVCN(ctx context.Context, cred Credentials, region, vcnID string) (VCN, error) { vn, err := c.vcnClient(cred, region) if err != nil { return VCN{}, err } resp, err := vn.GetVcn(ctx, core.GetVcnRequest{VcnId: &vcnID}) if err != nil { return VCN{}, fmt.Errorf("get vcn %s: %w", vcnID, err) } return toVCN(resp.Vcn), nil } // UpdateVCN 实现 Client:当前仅支持改名。 func (c *RealClient) UpdateVCN(ctx context.Context, cred Credentials, region, vcnID, displayName string) (VCN, error) { vn, err := c.vcnClient(cred, region) if err != nil { return VCN{}, err } resp, err := vn.UpdateVcn(ctx, core.UpdateVcnRequest{ VcnId: &vcnID, UpdateVcnDetails: core.UpdateVcnDetails{DisplayName: &displayName}, }) if err != nil { return VCN{}, fmt.Errorf("update vcn %s: %w", vcnID, err) } return toVCN(resp.Vcn), nil } // DeleteVCN 实现 Client:级联清理子网、网关、网络安全组与非默认路由表 / // 安全列表 / DHCP 选项后删除 VCN(见 vcndelete.go);子网被实例占用时 OCI 返回 409。 func (c *RealClient) DeleteVCN(ctx context.Context, cred Credentials, region, vcnID string) error { vn, err := c.vcnClient(cred, region) if err != nil { return err } return deleteVcnCascade(ctx, vn, vcnID) } // vcnScopedCompartment 返回按 VCN 定位的查询与创建应使用的 compartment: // OCI 要求 compartmentId 与资源实际所在 compartment 一致,vcnID 非空时取 // VCN 自身所在 compartment,为空(全 compartment 列表)时用生效 compartment。 func (c *RealClient) vcnScopedCompartment(ctx context.Context, cred Credentials, region, vcnID string) (string, error) { if vcnID == "" { return *cred.EffectiveCompartment(), nil } vcn, err := c.GetVCN(ctx, cred, region, vcnID) if err != nil { return "", err } return *hostCompartment(cred, vcn.CompartmentID), nil } // ListSubnets 实现 Client;vcnID 为空时列出全部子网。 func (c *RealClient) ListSubnets(ctx context.Context, cred Credentials, region, vcnID string) ([]Subnet, error) { vn, err := c.vcnClient(cred, region) if err != nil { return nil, err } compartmentID, err := c.vcnScopedCompartment(ctx, cred, region, vcnID) if err != nil { return nil, err } return listSubnets(ctx, vn, compartmentID, vcnID) } func listSubnets(ctx context.Context, vn core.VirtualNetworkClient, compartmentID, vcnID string) ([]Subnet, error) { var subnets []Subnet var page *string for { req := core.ListSubnetsRequest{CompartmentId: &compartmentID, Page: page} if vcnID != "" { req.VcnId = &vcnID } resp, err := vn.ListSubnets(ctx, req) if err != nil { return nil, fmt.Errorf("list subnets: %w", err) } for _, item := range resp.Items { subnets = append(subnets, toSubnet(item)) } if resp.OpcNextPage == nil { return orEmpty(subnets), nil } page = resp.OpcNextPage } } // CreateSubnet 实现 Client;不传 AD 时创建 regional 子网。 // 子网跟随其 VCN 所在 compartment 创建,避免跨 compartment 悬挂资源。 func (c *RealClient) CreateSubnet(ctx context.Context, cred Credentials, in CreateSubnetInput) (Subnet, error) { compartmentID, err := c.vcnScopedCompartment(ctx, cred, in.Region, in.VcnID) if err != nil { return Subnet{}, err } return c.createSubnetIn(ctx, cred, compartmentID, in) } // createSubnetIn 在指定 compartment 创建子网,供已知 VCN compartment // 的调用方(如自动建网)复用,免去一次重复的 GetVCN。 func (c *RealClient) createSubnetIn(ctx context.Context, cred Credentials, compartmentID string, in CreateSubnetInput) (Subnet, error) { vn, err := c.vcnClient(cred, in.Region) if err != nil { return Subnet{}, err } resp, err := vn.CreateSubnet(ctx, core.CreateSubnetRequest{CreateSubnetDetails: buildSubnetDetails(compartmentID, in)}) if err != nil { return Subnet{}, fmt.Errorf("create subnet: %w", err) } return toSubnet(resp.Subnet), nil } // buildSubnetDetails 组装创建子网请求体;可选字段仅在非零值时下发。 func buildSubnetDetails(compartmentID string, in CreateSubnetInput) core.CreateSubnetDetails { details := core.CreateSubnetDetails{ CompartmentId: &compartmentID, VcnId: &in.VcnID, CidrBlock: &in.CidrBlock, DisplayName: &in.DisplayName, } if in.DnsLabel != "" { details.DnsLabel = &in.DnsLabel } if in.Ipv6CidrBlock != "" { details.Ipv6CidrBlock = &in.Ipv6CidrBlock } if in.ProhibitPublicIP { details.ProhibitPublicIpOnVnic = &in.ProhibitPublicIP } return details } // GetSubnet 实现 Client。 func (c *RealClient) GetSubnet(ctx context.Context, cred Credentials, region, subnetID string) (Subnet, error) { vn, err := c.vcnClient(cred, region) if err != nil { return Subnet{}, err } resp, err := vn.GetSubnet(ctx, core.GetSubnetRequest{SubnetId: &subnetID}) if err != nil { return Subnet{}, fmt.Errorf("get subnet %s: %w", subnetID, err) } return toSubnet(resp.Subnet), nil } // UpdateSubnet 实现 Client:当前仅支持改名。 func (c *RealClient) UpdateSubnet(ctx context.Context, cred Credentials, region, subnetID, displayName string) (Subnet, error) { vn, err := c.vcnClient(cred, region) if err != nil { return Subnet{}, err } resp, err := vn.UpdateSubnet(ctx, core.UpdateSubnetRequest{ SubnetId: &subnetID, UpdateSubnetDetails: core.UpdateSubnetDetails{DisplayName: &displayName}, }) if err != nil { return Subnet{}, fmt.Errorf("update subnet %s: %w", subnetID, err) } return toSubnet(resp.Subnet), nil } // DeleteSubnet 实现 Client。 func (c *RealClient) DeleteSubnet(ctx context.Context, cred Credentials, region, subnetID string) error { vn, err := c.vcnClient(cred, region) if err != nil { return err } if _, err := vn.DeleteSubnet(ctx, core.DeleteSubnetRequest{SubnetId: &subnetID}); err != nil { return fmt.Errorf("delete subnet %s: %w", subnetID, err) } return nil } // ListSecurityLists 实现 Client;vcnID 为空时列出全部安全列表。 func (c *RealClient) ListSecurityLists(ctx context.Context, cred Credentials, region, vcnID string) ([]SecurityList, error) { vn, err := c.vcnClient(cred, region) if err != nil { return nil, err } compartmentID, err := c.vcnScopedCompartment(ctx, cred, region, vcnID) if err != nil { return nil, err } var lists []SecurityList var page *string for { req := core.ListSecurityListsRequest{CompartmentId: &compartmentID, Page: page} if vcnID != "" { req.VcnId = &vcnID } resp, err := vn.ListSecurityLists(ctx, req) if err != nil { return nil, fmt.Errorf("list security lists: %w", err) } for _, item := range resp.Items { lists = append(lists, toSecurityList(item)) } if resp.OpcNextPage == nil { return orEmpty(lists), nil } page = resp.OpcNextPage } } // CreateSecurityList 实现 Client。 // 安全列表跟随其 VCN 所在 compartment 创建,避免跨 compartment 悬挂资源。 func (c *RealClient) CreateSecurityList(ctx context.Context, cred Credentials, in CreateSecurityListInput) (SecurityList, error) { vn, err := c.vcnClient(cred, in.Region) if err != nil { return SecurityList{}, err } compartmentID, err := c.vcnScopedCompartment(ctx, cred, in.Region, in.VcnID) if err != nil { return SecurityList{}, err } resp, err := vn.CreateSecurityList(ctx, core.CreateSecurityListRequest{ CreateSecurityListDetails: core.CreateSecurityListDetails{ CompartmentId: &compartmentID, VcnId: &in.VcnID, DisplayName: &in.DisplayName, IngressSecurityRules: toIngressRules(in.IngressRules), EgressSecurityRules: toEgressRules(in.EgressRules), }, }) if err != nil { return SecurityList{}, fmt.Errorf("create security list: %w", err) } return toSecurityList(resp.SecurityList), nil } // GetSecurityList 实现 Client。 func (c *RealClient) GetSecurityList(ctx context.Context, cred Credentials, region, securityListID string) (SecurityList, error) { vn, err := c.vcnClient(cred, region) if err != nil { return SecurityList{}, err } resp, err := vn.GetSecurityList(ctx, core.GetSecurityListRequest{SecurityListId: &securityListID}) if err != nil { return SecurityList{}, fmt.Errorf("get security list %s: %w", securityListID, err) } return toSecurityList(resp.SecurityList), nil } // UpdateSecurityList 实现 Client。 func (c *RealClient) UpdateSecurityList(ctx context.Context, cred Credentials, securityListID string, in UpdateSecurityListInput) (SecurityList, error) { vn, err := c.vcnClient(cred, in.Region) if err != nil { return SecurityList{}, err } details := core.UpdateSecurityListDetails{} if in.DisplayName != "" { details.DisplayName = &in.DisplayName } if in.IngressRules != nil { details.IngressSecurityRules = toIngressRules(*in.IngressRules) } if in.EgressRules != nil { details.EgressSecurityRules = toEgressRules(*in.EgressRules) } resp, err := vn.UpdateSecurityList(ctx, core.UpdateSecurityListRequest{ SecurityListId: &securityListID, UpdateSecurityListDetails: details, }) if err != nil { return SecurityList{}, fmt.Errorf("update security list %s: %w", securityListID, err) } return toSecurityList(resp.SecurityList), nil } // DeleteSecurityList 实现 Client。 func (c *RealClient) DeleteSecurityList(ctx context.Context, cred Credentials, region, securityListID string) error { vn, err := c.vcnClient(cred, region) if err != nil { return err } if _, err := vn.DeleteSecurityList(ctx, core.DeleteSecurityListRequest{SecurityListId: &securityListID}); err != nil { return fmt.Errorf("delete security list %s: %w", securityListID, err) } return nil } func toVCN(v core.Vcn) VCN { return VCN{ ID: deref(v.Id), DisplayName: deref(v.DisplayName), LifecycleState: string(v.LifecycleState), CompartmentID: deref(v.CompartmentId), CidrBlocks: orEmpty(v.CidrBlocks), Ipv6CidrBlocks: orEmpty(v.Ipv6CidrBlocks), DnsLabel: deref(v.DnsLabel), DefaultRouteTableID: deref(v.DefaultRouteTableId), DefaultSecurityListID: deref(v.DefaultSecurityListId), TimeCreated: sdkTime(v.TimeCreated), } } func toSubnet(s core.Subnet) Subnet { return Subnet{ ID: deref(s.Id), VcnID: deref(s.VcnId), DisplayName: deref(s.DisplayName), LifecycleState: string(s.LifecycleState), CidrBlock: deref(s.CidrBlock), Ipv6CidrBlock: deref(s.Ipv6CidrBlock), DnsLabel: deref(s.DnsLabel), ProhibitPublicIP: s.ProhibitPublicIpOnVnic != nil && *s.ProhibitPublicIpOnVnic, AvailabilityDomain: deref(s.AvailabilityDomain), RouteTableID: deref(s.RouteTableId), TimeCreated: sdkTime(s.TimeCreated), } } func toSecurityList(sl core.SecurityList) SecurityList { out := SecurityList{ ID: deref(sl.Id), VcnID: deref(sl.VcnId), DisplayName: deref(sl.DisplayName), LifecycleState: string(sl.LifecycleState), TimeCreated: sdkTime(sl.TimeCreated), IngressRules: make([]SecurityRule, 0, len(sl.IngressSecurityRules)), EgressRules: make([]SecurityRule, 0, len(sl.EgressSecurityRules)), } for _, r := range sl.IngressSecurityRules { rule := SecurityRule{ Protocol: deref(r.Protocol), Source: deref(r.Source), IsStateless: r.IsStateless != nil && *r.IsStateless, Description: deref(r.Description), } fillRuleOptions(&rule, r.TcpOptions, r.UdpOptions, r.IcmpOptions) out.IngressRules = append(out.IngressRules, rule) } for _, r := range sl.EgressSecurityRules { rule := SecurityRule{ Protocol: deref(r.Protocol), Destination: deref(r.Destination), IsStateless: r.IsStateless != nil && *r.IsStateless, Description: deref(r.Description), } fillRuleOptions(&rule, r.TcpOptions, r.UdpOptions, r.IcmpOptions) out.EgressRules = append(out.EgressRules, rule) } return out } func fillRuleOptions(rule *SecurityRule, tcp *core.TcpOptions, udp *core.UdpOptions, icmp *core.IcmpOptions) { switch { case tcp != nil && tcp.DestinationPortRange != nil: rule.PortMin = tcp.DestinationPortRange.Min rule.PortMax = tcp.DestinationPortRange.Max case udp != nil && udp.DestinationPortRange != nil: rule.PortMin = udp.DestinationPortRange.Min rule.PortMax = udp.DestinationPortRange.Max case icmp != nil: rule.IcmpType = icmp.Type rule.IcmpCode = icmp.Code } } func toIngressRules(rules []SecurityRule) []core.IngressSecurityRule { out := make([]core.IngressSecurityRule, 0, len(rules)) for i := range rules { r := rules[i] sdk := core.IngressSecurityRule{ Protocol: &r.Protocol, Source: &r.Source, IsStateless: &rules[i].IsStateless, } if r.Description != "" { sdk.Description = &rules[i].Description } sdk.TcpOptions, sdk.UdpOptions, sdk.IcmpOptions = buildRuleOptions(r) out = append(out, sdk) } return out } func toEgressRules(rules []SecurityRule) []core.EgressSecurityRule { out := make([]core.EgressSecurityRule, 0, len(rules)) for i := range rules { r := rules[i] sdk := core.EgressSecurityRule{ Protocol: &r.Protocol, Destination: &r.Destination, IsStateless: &rules[i].IsStateless, } if r.Description != "" { sdk.Description = &rules[i].Description } sdk.TcpOptions, sdk.UdpOptions, sdk.IcmpOptions = buildRuleOptions(r) out = append(out, sdk) } return out } // buildRuleOptions 按协议号构造端口或 ICMP 选项:6=TCP、17=UDP、1/58=ICMP(v6)。 func buildRuleOptions(r SecurityRule) (*core.TcpOptions, *core.UdpOptions, *core.IcmpOptions) { if r.PortMin != nil { portMax := r.PortMax if portMax == nil { portMax = r.PortMin } pr := &core.PortRange{Min: r.PortMin, Max: portMax} switch r.Protocol { case "6": return &core.TcpOptions{DestinationPortRange: pr}, nil, nil case "17": return nil, &core.UdpOptions{DestinationPortRange: pr}, nil } } if r.IcmpType != nil && (r.Protocol == "1" || r.Protocol == "58") { return nil, nil, &core.IcmpOptions{Type: r.IcmpType, Code: r.IcmpCode} } return nil, nil, nil }