初始提交:OCI 面板后端(含 GenAI 网关一期)

This commit is contained in:
Wang Defa
2026-07-09 15:31:04 +08:00
commit b9a3e97e84
168 changed files with 31794 additions and 0 deletions
+733
View File
@@ -0,0 +1,733 @@
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
}