package oci import ( "context" "fmt" "time" "github.com/oracle/oci-go-sdk/v65/core" ) // Vnic 是实例已附加的虚拟网卡(附加关系与地址信息的合并视图)。 type Vnic struct { AttachmentID string `json:"attachmentId"` VnicID string `json:"vnicId"` DisplayName string `json:"displayName"` IsPrimary bool `json:"isPrimary"` PrivateIP string `json:"privateIp"` PublicIP string `json:"publicIp"` Ipv6Addresses []string `json:"ipv6Addresses"` SubnetID string `json:"subnetId"` SubnetName string `json:"subnetName,omitempty"` LifecycleState string `json:"lifecycleState"` TimeCreated *time.Time `json:"timeCreated"` } // AttachVnicInput 是附加新 VNIC 的输入;PrivateIP 留空自动分配。 type AttachVnicInput struct { SubnetID string `json:"subnetId" binding:"required"` DisplayName string `json:"displayName"` PrivateIP string `json:"privateIp"` AssignPublicIP bool `json:"assignPublicIp"` // AssignIpv6 为 true 时同时自动分配一个 IPv6(要求子网已启用 IPv6)。 AssignIpv6 bool `json:"assignIpv6"` } // ListInstanceVnics 实现 Client:列出实例全部 VNIC(含附加中/分离中的关系)。 func (c *RealClient) ListInstanceVnics(ctx context.Context, cred Credentials, region, instanceID string) ([]Vnic, error) { cc, err := c.computeClient(cred, region) if err != nil { return nil, err } instResp, err := cc.GetInstance(ctx, core.GetInstanceRequest{InstanceId: &instanceID}) if err != nil { return nil, fmt.Errorf("get instance %s: %w", instanceID, err) } // 附加关系在实例所在 compartment,传租户根会漏查 attResp, err := cc.ListVnicAttachments(ctx, core.ListVnicAttachmentsRequest{ CompartmentId: hostCompartment(cred, deref(instResp.CompartmentId)), InstanceId: &instanceID, }) if err != nil { return nil, fmt.Errorf("list vnic attachments: %w", err) } vn, err := c.vcnClient(cred, region) if err != nil { return nil, err } out := make([]Vnic, 0, len(attResp.Items)) subnetNames := map[string]string{} for _, att := range attResp.Items { out = append(out, buildVnic(ctx, vn, att, subnetNames)) } return out, nil } // buildVnic 合并附加关系与 VNIC 详情;VNIC 未就绪(附加中)时只有关系字段。 func buildVnic(ctx context.Context, vn core.VirtualNetworkClient, att core.VnicAttachment, subnetNames map[string]string) Vnic { v := Vnic{ AttachmentID: deref(att.Id), VnicID: deref(att.VnicId), DisplayName: deref(att.DisplayName), SubnetID: deref(att.SubnetId), LifecycleState: string(att.LifecycleState), TimeCreated: sdkTime(att.TimeCreated), } if att.VnicId != nil { if resp, err := vn.GetVnic(ctx, core.GetVnicRequest{VnicId: att.VnicId}); err == nil { v.DisplayName = deref(resp.DisplayName) v.PrivateIP = deref(resp.PrivateIp) v.PublicIP = deref(resp.PublicIp) v.Ipv6Addresses = resp.Ipv6Addresses if resp.IsPrimary != nil { v.IsPrimary = *resp.IsPrimary } if deref(resp.SubnetId) != "" { v.SubnetID = deref(resp.SubnetId) } } } v.SubnetName = subnetNameOf(ctx, vn, v.SubnetID, subnetNames) return v } // subnetNameOf 查子网显示名,map 去重;失败留空不影响列表。 func subnetNameOf(ctx context.Context, vn core.VirtualNetworkClient, id string, cache map[string]string) string { if id == "" { return "" } if name, ok := cache[id]; ok { return name } name := "" if resp, err := vn.GetSubnet(ctx, core.GetSubnetRequest{SubnetId: &id}); err == nil { name = deref(resp.DisplayName) } cache[id] = name return name } // AttachVnic 实现 Client:为实例附加次要 VNIC;实例须运行中且 shape 有空余 VNIC 配额。 func (c *RealClient) AttachVnic(ctx context.Context, cred Credentials, region, instanceID string, in AttachVnicInput) (Vnic, error) { cc, err := c.computeClient(cred, region) if err != nil { return Vnic{}, err } details := &core.CreateVnicDetails{ SubnetId: &in.SubnetID, AssignPublicIp: &in.AssignPublicIP, } if in.AssignIpv6 { details.AssignIpv6Ip = &in.AssignIpv6 } if in.DisplayName != "" { details.DisplayName = &in.DisplayName } if in.PrivateIP != "" { details.PrivateIp = &in.PrivateIP } resp, err := cc.AttachVnic(ctx, core.AttachVnicRequest{ AttachVnicDetails: core.AttachVnicDetails{InstanceId: &instanceID, CreateVnicDetails: details}, }) if err != nil { return Vnic{}, fmt.Errorf("attach vnic: %w", err) } att := resp.VnicAttachment return Vnic{ AttachmentID: deref(att.Id), VnicID: deref(att.VnicId), DisplayName: deref(att.DisplayName), SubnetID: deref(att.SubnetId), LifecycleState: string(att.LifecycleState), TimeCreated: sdkTime(att.TimeCreated), }, nil } // DetachVnic 实现 Client:分离次要 VNIC(主 VNIC 由 OCI 拒绝)。 func (c *RealClient) DetachVnic(ctx context.Context, cred Credentials, region, attachmentID string) error { cc, err := c.computeClient(cred, region) if err != nil { return err } if _, err := cc.DetachVnic(ctx, core.DetachVnicRequest{VnicAttachmentId: &attachmentID}); err != nil { return fmt.Errorf("detach vnic %s: %w", attachmentID, err) } return nil }