Files

734 lines
24 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 实现 ClientvcnID 为空时列出全部子网。
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 实现 ClientvcnID 为空时列出全部安全列表。
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
}