Files
oci-portal/internal/oci/publicip.go
T

133 lines
4.8 KiB
Go
Raw 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"
"github.com/oracle/oci-go-sdk/v65/core"
)
// instanceVnics 列出实例的全部 VNIC,并一并返回实例所在 compartment
// 供调用方复用为后续按实例定位查询的 compartmentId,避免重复 GetInstance。
func (c *RealClient) instanceVnics(ctx context.Context, cred Credentials, region, instanceID string) ([]core.Vnic, string, error) {
cc, err := c.computeClient(cred, region)
if err != nil {
return nil, "", err
}
vn, err := c.vcnClient(cred, region)
if err != nil {
return nil, "", err
}
// VNIC 挂载记录在实例所在 compartment,先取实例定位,传租户根会漏查
instResp, err := cc.GetInstance(ctx, core.GetInstanceRequest{InstanceId: &instanceID})
if err != nil {
return nil, "", fmt.Errorf("get instance %s: %w", instanceID, err)
}
compartmentID := hostCompartment(cred, deref(instResp.CompartmentId))
resp, err := cc.ListVnicAttachments(ctx, core.ListVnicAttachmentsRequest{
CompartmentId: compartmentID,
InstanceId: &instanceID,
})
if err != nil {
return nil, "", fmt.Errorf("list vnic attachments: %w", err)
}
return attachedVnics(ctx, vn, resp.Items), *compartmentID, nil
}
// attachedVnics 逐挂载记录查询 VNIC 详情,跳过查询失败的记录。
func attachedVnics(ctx context.Context, vn core.VirtualNetworkClient, atts []core.VnicAttachment) []core.Vnic {
vnics := make([]core.Vnic, 0, len(atts))
for _, att := range atts {
if att.VnicId == nil {
continue
}
vnicResp, err := vn.GetVnic(ctx, core.GetVnicRequest{VnicId: att.VnicId})
if err != nil {
continue
}
vnics = append(vnics, vnicResp.Vnic)
}
return vnics
}
// lookupPublicIP 查询私有 IP 绑定的公网 IP,未绑定返回 nil。
func lookupPublicIP(ctx context.Context, vn core.VirtualNetworkClient, privateIPID *string) *core.PublicIp {
resp, err := vn.GetPublicIpByPrivateIpId(ctx, core.GetPublicIpByPrivateIpIdRequest{
GetPublicIpByPrivateIpIdDetails: core.GetPublicIpByPrivateIpIdDetails{PrivateIpId: privateIPID},
})
if err != nil || resp.Id == nil {
return nil
}
return &resp.PublicIp
}
// ChangeInstancePublicIP 实现 Client:为实例主 VNIC 主私有 IP 更换临时公网 IP。
// 删除旧的临时公网 IP 再分配新的;若旧地址是保留 IP(RESERVED),拒绝操作。
func (c *RealClient) ChangeInstancePublicIP(ctx context.Context, cred Credentials, region, instanceID string) (string, error) {
vn, err := c.vcnClient(cred, region)
if err != nil {
return "", err
}
priv, err := c.primaryPrivateIP(ctx, cred, region, instanceID)
if err != nil {
return "", err
}
if existing := lookupPublicIP(ctx, vn, priv.Id); existing != nil {
if existing.Lifetime == core.PublicIpLifetimeReserved {
return "", fmt.Errorf("change public ip: private ip has a reserved public ip, release it manually")
}
if _, err := vn.DeletePublicIp(ctx, core.DeletePublicIpRequest{PublicIpId: existing.Id}); err != nil {
return "", fmt.Errorf("delete old public ip: %w", err)
}
}
// OCI 要求临时公网 IP 与其私有 IP 同 compartment,故不能用生效 compartment
resp, err := vn.CreatePublicIp(ctx, core.CreatePublicIpRequest{
CreatePublicIpDetails: core.CreatePublicIpDetails{
CompartmentId: hostCompartment(cred, deref(priv.CompartmentId)),
Lifetime: core.CreatePublicIpDetailsLifetimeEphemeral,
PrivateIpId: priv.Id,
},
})
if err != nil {
return "", fmt.Errorf("create public ip: %w", err)
}
return deref(resp.IpAddress), nil
}
// primaryVnic 返回实例的主 VNIC。
func (c *RealClient) primaryVnic(ctx context.Context, cred Credentials, region, instanceID string) (core.Vnic, error) {
vnics, _, err := c.instanceVnics(ctx, cred, region, instanceID)
if err != nil {
return core.Vnic{}, err
}
for i := range vnics {
if vnics[i].IsPrimary != nil && *vnics[i].IsPrimary {
return vnics[i], nil
}
}
return core.Vnic{}, fmt.Errorf("instance %s has no primary vnic", instanceID)
}
// primaryPrivateIP 返回实例主 VNIC 上的主私有 IP 对象;
// 保留完整对象是为了让调用方能拿到私有 IP 所在的 compartment。
func (c *RealClient) primaryPrivateIP(ctx context.Context, cred Credentials, region, instanceID string) (core.PrivateIp, error) {
vn, err := c.vcnClient(cred, region)
if err != nil {
return core.PrivateIp{}, err
}
vnic, err := c.primaryVnic(ctx, cred, region, instanceID)
if err != nil {
return core.PrivateIp{}, err
}
privResp, err := vn.ListPrivateIps(ctx, core.ListPrivateIpsRequest{VnicId: vnic.Id})
if err != nil {
return core.PrivateIp{}, fmt.Errorf("list private ips: %w", err)
}
for _, priv := range privResp.Items {
if priv.IsPrimary != nil && *priv.IsPrimary {
return priv, nil
}
}
return core.PrivateIp{}, fmt.Errorf("change public ip: primary vnic has no primary private ip")
}