package oci import ( "context" "fmt" "time" "github.com/oracle/oci-go-sdk/v65/common" "github.com/oracle/oci-go-sdk/v65/core" ) // ReservedIP 是保留公网 IP 摘要;绑定目标尽力反查,失败时对应字段为空。 type ReservedIP struct { ID string `json:"id"` DisplayName string `json:"displayName"` IPAddress string `json:"ipAddress"` LifecycleState string `json:"lifecycleState"` AssignedInstanceID string `json:"assignedInstanceId"` AssignedInstanceName string `json:"assignedInstanceName"` TimeCreated *time.Time `json:"timeCreated"` } // ListReservedIPs 实现 Client:列出区间内全部保留公网 IP(REGION 作用域); // compartmentID 为空时用生效 compartment。 func (c *RealClient) ListReservedIPs(ctx context.Context, cred Credentials, region, compartmentID string) ([]ReservedIP, error) { vn, err := c.vcnClient(cred, region) if err != nil { return nil, err } cc, err := c.computeClient(cred, region) if err != nil { return nil, err } out := make([]ReservedIP, 0, 4) var page *string for { resp, err := vn.ListPublicIps(ctx, core.ListPublicIpsRequest{ Scope: core.ListPublicIpsScopeRegion, Lifetime: core.ListPublicIpsLifetimeReserved, CompartmentId: hostCompartment(cred, compartmentID), Page: page, }) if err != nil { return nil, fmt.Errorf("list reserved ips: %w", err) } for i := range resp.Items { out = append(out, c.toReservedIP(ctx, vn, cc, resp.Items[i])) } if resp.OpcNextPage == nil { break } page = resp.OpcNextPage } return out, nil } // toReservedIP 转换 SDK 对象并尽力反查绑定的实例。 func (c *RealClient) toReservedIP(ctx context.Context, vn core.VirtualNetworkClient, cc core.ComputeClient, p core.PublicIp) ReservedIP { var created *time.Time if p.TimeCreated != nil { created = &p.TimeCreated.Time } r := ReservedIP{ ID: deref(p.Id), DisplayName: deref(p.DisplayName), IPAddress: deref(p.IpAddress), LifecycleState: string(p.LifecycleState), TimeCreated: created, } if p.AssignedEntityType == core.PublicIpAssignedEntityTypePrivateIp && p.AssignedEntityId != nil { r.AssignedInstanceID, r.AssignedInstanceName = resolveIPAssignee(ctx, vn, cc, *p.AssignedEntityId) } return r } // resolveIPAssignee 由私有 IP 反查所属实例;任一步失败即放弃,不影响列表主体。 func resolveIPAssignee(ctx context.Context, vn core.VirtualNetworkClient, cc core.ComputeClient, privateIPID string) (string, string) { priv, err := vn.GetPrivateIp(ctx, core.GetPrivateIpRequest{PrivateIpId: &privateIPID}) if err != nil || priv.VnicId == nil { return "", "" } atts, err := cc.ListVnicAttachments(ctx, core.ListVnicAttachmentsRequest{ CompartmentId: priv.CompartmentId, VnicId: priv.VnicId, }) if err != nil || len(atts.Items) == 0 || atts.Items[0].InstanceId == nil { return "", "" } instanceID := *atts.Items[0].InstanceId inst, err := cc.GetInstance(ctx, core.GetInstanceRequest{InstanceId: &instanceID}) if err != nil { return instanceID, "" } return instanceID, deref(inst.DisplayName) } // CreateReservedIP 实现 Client:在指定 compartment 创建保留公网 IP(空为生效 compartment)。 func (c *RealClient) CreateReservedIP(ctx context.Context, cred Credentials, region, compartmentID, displayName string) (ReservedIP, error) { vn, err := c.vcnClient(cred, region) if err != nil { return ReservedIP{}, err } details := core.CreatePublicIpDetails{ CompartmentId: hostCompartment(cred, compartmentID), Lifetime: core.CreatePublicIpDetailsLifetimeReserved, } if displayName != "" { details.DisplayName = &displayName } resp, err := vn.CreatePublicIp(ctx, core.CreatePublicIpRequest{CreatePublicIpDetails: details}) if err != nil { return ReservedIP{}, fmt.Errorf("create reserved ip: %w", err) } cc, err := c.computeClient(cred, region) if err != nil { return ReservedIP{}, err } return c.toReservedIP(ctx, vn, cc, resp.PublicIp), nil } // AssignReservedIP 实现 Client:绑定保留 IP 到实例主私有 IP;instanceID 为空表示解绑。 // 目标私有 IP 已有公网 IP 时自动释放(临时删除、其他保留解绑),地址会变更,调用方需提示用户。 func (c *RealClient) AssignReservedIP(ctx context.Context, cred Credentials, region, publicIPID, instanceID string) error { vn, err := c.vcnClient(cred, region) if err != nil { return err } target := common.String("") if instanceID != "" { priv, err := c.primaryPrivateIP(ctx, cred, region, instanceID) if err != nil { return err } if err := detachExistingPublicIP(ctx, vn, priv.Id); err != nil { return err } target = priv.Id } _, err = vn.UpdatePublicIp(ctx, core.UpdatePublicIpRequest{ PublicIpId: &publicIPID, UpdatePublicIpDetails: core.UpdatePublicIpDetails{PrivateIpId: target}, }) if err != nil { return fmt.Errorf("assign reserved ip: %w", err) } return nil } // AssignReservedIPToVnic 实现 Client:绑定保留 IP 到指定 VNIC 的主私有 IP(次要网卡场景); // 该私有 IP 已有公网 IP 时自动释放(临时删除、其他保留解绑)。 func (c *RealClient) AssignReservedIPToVnic(ctx context.Context, cred Credentials, region, publicIPID, vnicID string) error { vn, err := c.vcnClient(cred, region) if err != nil { return err } priv, err := vnicPrimaryPrivateIP(ctx, vn, vnicID) if err != nil { return err } if err := detachExistingPublicIP(ctx, vn, priv.Id); err != nil { return err } _, err = vn.UpdatePublicIp(ctx, core.UpdatePublicIpRequest{ PublicIpId: &publicIPID, UpdatePublicIpDetails: core.UpdatePublicIpDetails{PrivateIpId: priv.Id}, }) if err != nil { return fmt.Errorf("assign reserved ip to vnic: %w", err) } return nil } // vnicPrimaryPrivateIP 取 VNIC 的主私有 IP。 func vnicPrimaryPrivateIP(ctx context.Context, vn core.VirtualNetworkClient, vnicID string) (core.PrivateIp, error) { resp, err := vn.ListPrivateIps(ctx, core.ListPrivateIpsRequest{VnicId: &vnicID}) if err != nil { return core.PrivateIp{}, fmt.Errorf("list private ips: %w", err) } for _, p := range resp.Items { if p.IsPrimary != nil && *p.IsPrimary { return p, nil } } return core.PrivateIp{}, fmt.Errorf("assign reserved ip: vnic has no primary private ip") } // DeleteReservedIP 实现 Client:删除保留公网 IP(已绑定的会先被 OCI 拒绝)。 func (c *RealClient) DeleteReservedIP(ctx context.Context, cred Credentials, region, publicIPID string) error { vn, err := c.vcnClient(cred, region) if err != nil { return err } if _, err := vn.DeletePublicIp(ctx, core.DeletePublicIpRequest{PublicIpId: &publicIPID}); err != nil { return fmt.Errorf("delete reserved ip: %w", err) } return nil }