@@ -6,6 +6,7 @@ package imds
66import (
77 "context"
88 "encoding/json"
9+ "net"
910 "net/http"
1011 "net/url"
1112
@@ -44,15 +45,17 @@ func RetryAttempts(attempts uint) ClientOption {
4445}
4546
4647const (
47- vmUniqueIDProperty = "vmId"
48- imdsComputePath = "/metadata/instance/compute"
49- imdsNetworkPath = "/metadata/instance/network"
50- imdsAPIVersion = "api-version=2025-07-24"
51- imdsFormatJSON = "format=json"
52- metadataHeaderKey = "Metadata"
53- metadataHeaderValue = "true"
54- defaultRetryAttempts = 3
55- defaultIMDSEndpoint = "http://169.254.169.254"
48+ vmUniqueIDProperty = "vmId"
49+ imdsComputePath = "/metadata/instance/compute"
50+ imdsNetworkPath = "/metadata/instance/network"
51+ imdsVersionsPath = "/metadata/versions"
52+ imdsDefaultAPIVersion = "api-version=2021-01-01"
53+ imdsNCDetailsVersion = "api-version=2025-07-24"
54+ imdsFormatJSON = "format=json"
55+ metadataHeaderKey = "Metadata"
56+ metadataHeaderValue = "true"
57+ defaultRetryAttempts = 3
58+ defaultIMDSEndpoint = "http://169.254.169.254"
5659)
5760
5861var (
@@ -80,7 +83,7 @@ func NewClient(opts ...ClientOption) *Client {
8083func (c * Client ) GetVMUniqueID (ctx context.Context ) (string , error ) {
8184 var vmUniqueID string
8285 err := retry .Do (func () error {
83- computeDoc , err := c .getInstanceMetadata (ctx , imdsComputePath )
86+ computeDoc , err := c .getInstanceMetadata (ctx , imdsComputePath , imdsDefaultAPIVersion )
8487 if err != nil {
8588 return errors .Wrap (err , "error getting IMDS compute metadata" )
8689 }
@@ -106,7 +109,7 @@ func (c *Client) GetVMUniqueID(ctx context.Context) (string, error) {
106109func (c * Client ) GetNetworkInterfaces (ctx context.Context ) ([]NetworkInterface , error ) {
107110 var networkData NetworkInterfaces
108111 err := retry .Do (func () error {
109- networkInterfaces , err := c .getInstanceMetadata (ctx , imdsNetworkPath )
112+ networkInterfaces , err := c .getInstanceMetadata (ctx , imdsNetworkPath , imdsNCDetailsVersion )
110113 if err != nil {
111114 return errors .Wrap (err , "error getting IMDS network metadata" )
112115 }
@@ -130,7 +133,7 @@ func (c *Client) GetNetworkInterfaces(ctx context.Context) ([]NetworkInterface,
130133 return networkData .Interface , nil
131134}
132135
133- func (c * Client ) getInstanceMetadata (ctx context.Context , imdsMetadataPath string ) (map [string ]any , error ) {
136+ func (c * Client ) getInstanceMetadata (ctx context.Context , imdsMetadataPath string , imdsAPIVersion string ) (map [string ]any , error ) {
134137 imdsRequestURL , err := url .JoinPath (c .config .endpoint , imdsMetadataPath )
135138 if err != nil {
136139 return nil , errors .Wrap (err , "unable to build path to IMDS metadata for path" + imdsMetadataPath )
@@ -162,14 +165,83 @@ func (c *Client) getInstanceMetadata(ctx context.Context, imdsMetadataPath strin
162165 return m , nil
163166}
164167
168+ func (c * Client ) GetIMDSVersions (ctx context.Context ) (* APIVersionsResponse , error ) {
169+ var versionsResp APIVersionsResponse
170+ err := retry .Do (func () error {
171+ // Build the URL for the versions endpoint
172+ imdsRequestURL , err := url .JoinPath (c .config .endpoint , imdsVersionsPath )
173+ if err != nil {
174+ return errors .Wrap (err , "unable to build path to IMDS versions endpoint" )
175+ }
176+
177+ req , err := http .NewRequestWithContext (ctx , http .MethodGet , imdsRequestURL , http .NoBody )
178+ if err != nil {
179+ return errors .Wrap (err , "error building IMDS versions http request" )
180+ }
181+
182+ req .Header .Add (metadataHeaderKey , metadataHeaderValue )
183+ resp , err := c .cli .Do (req )
184+ if err != nil {
185+ return errors .Wrap (err , "error querying IMDS versions API" )
186+ }
187+ defer resp .Body .Close ()
188+
189+ if resp .StatusCode != http .StatusOK {
190+ return errors .Wrapf (ErrUnexpectedStatusCode , "unexpected status code %d" , resp .StatusCode )
191+ }
192+
193+ if err := json .NewDecoder (resp .Body ).Decode (& versionsResp ); err != nil {
194+ return errors .Wrap (err , "error decoding IMDS versions response as json" )
195+ }
196+
197+ return nil
198+ }, retry .Context (ctx ), retry .Attempts (c .config .retryAttempts ), retry .DelayType (retry .BackOffDelay ))
199+
200+ if err != nil {
201+ return nil , errors .Wrap (err , "exhausted retries querying IMDS versions" )
202+ }
203+
204+ return & versionsResp , nil
205+ }
206+
207+ // Required for marshaling/unmarshaling of mac address
208+ type HardwareAddr net.HardwareAddr
209+
210+ func (h HardwareAddr ) MarshalJSON () ([]byte , error ) {
211+ return json .Marshal (net .HardwareAddr (h ).String ())
212+ }
213+
214+ func (h * HardwareAddr ) UnmarshalJSON (data []byte ) error {
215+ var s string
216+ if err := json .Unmarshal (data , & s ); err != nil {
217+ return err
218+ }
219+ mac , err := net .ParseMAC (s )
220+ if err != nil {
221+ return err
222+ }
223+ * h = HardwareAddr (mac )
224+ return nil
225+ }
226+
227+ func (h HardwareAddr ) String () string {
228+ return net .HardwareAddr (h ).String ()
229+ }
230+
165231// NetworkInterface represents a network interface from IMDS
166232type NetworkInterface struct {
167233 // IMDS returns compartment fields - these are mapped to NC ID and NC version
168- InterfaceCompartmentID string `json:"interfaceCompartmentID,omitempty "`
169- InterfaceCompartmentVersion string `json:"interfaceCompartmentVersion ,omitempty"`
234+ MacAddress HardwareAddr `json:"macAddress "`
235+ InterfaceCompartmentID string `json:"interfaceCompartmentID ,omitempty"`
170236}
171237
172238// NetworkInterfaces represents the network interfaces from IMDS
173239type NetworkInterfaces struct {
174240 Interface []NetworkInterface `json:"interface"`
175241}
242+
243+
244+ // APIVersionsResponse represents versions form IMDS
245+ type APIVersionsResponse struct {
246+ APIVersions []string `json:"apiVersions"`
247+ }
0 commit comments