nebius/SWE-rebench-V2-PRs huggingface dataset:
Repository: vmware/govmomi
Pull request #1929 (2020-04-13 00:03:45)

vcsim: apply ExtraConfig after devices
In order to set a VM guest net.config ipAddress, a VM ethernet card device must be present.
The original code worked fine for existing VMs, but we need to apply ExtraConfig after devices
for SET to work properly when creating new VMs.

Issue #1928


Patch:
diff --git a/simulator/virtual_machine.go b/simulator/virtual_machine.go
index 9434c034..b7885537 100644
--- a/simulator/virtual_machine.go
+++ b/simulator/virtual_machine.go
@@ -306,10 +306,25 @@ func (vm *VirtualMachine) apply(spec *types.VirtualMachineConfigSpec) {
 		vm.Guest.GuestFamily = guestFamily(spec.GuestId)
 	}
 
+	vm.Config.Modified = time.Now()
+}
+
+var extraConfigAlias = map[string]string{
+	"ip0": "SET.guest.ipAddress",
+}
+
+func extraConfigKey(key string) string {
+	if k, ok := extraConfigAlias[key]; ok {
+		return k
+	}
+	return key
+}
+
+func (vm *VirtualMachine) applyExtraConfig(spec *types.VirtualMachineConfigSpec) {
 	var changes []types.PropertyChange
 	for _, c := range spec.ExtraConfig {
 		val := c.GetOptionValue()
-		key := strings.TrimPrefix(val.Key, "SET.")
+		key := strings.TrimPrefix(extraConfigKey(val.Key), "SET.")
 		if key == val.Key {
 			vm.Config.ExtraConfig = append(vm.Config.ExtraConfig, c)
 			continue
@@ -335,8 +350,6 @@ func (vm *VirtualMachine) apply(spec *types.VirtualMachineConfigSpec) {
 	if len(changes) != 0 {
 		Map.Update(vm, changes)
 	}
-
-	vm.Config.Modified = time.Now()
 }
 
 func validateGuestID(id string) types.BaseMethodFault {
@@ -1264,6 +1277,8 @@ func (vm *VirtualMachine) configureDevices(spec *types.VirtualMachineConfigSpec)
 
 	vm.updateDiskLayouts()
 
+	vm.applyExtraConfig(spec) // Do this after device config, as some may apply to the devices themselves (e.g. ethernet -> guest.net)
+
 	return nil
 }
 


Test patch:
diff --git a/govc/test/vcsim.bats b/govc/test/vcsim.bats
index 4a6ae0dd..a801ec64 100755
--- a/govc/test/vcsim.bats
+++ b/govc/test/vcsim.bats
@@ -126,6 +126,41 @@ load test_helper
 
   run govc object.collect -s $vm config.uuid
   assert_success "$uuid"
+
+  govc import.ovf -options - "$GOVC_IMAGES/$TTYLINUX_NAME.ovf" <<EOF
+{
+  "PropertyMapping": [
+    {
+      "Key": "SET.guest.ipAddress",
+      "Value": "10.0.0.42"
+    }
+  ],
+  "PowerOn": true,
+  "WaitForIP": true
+}
+EOF
+
+  run govc vm.ip "$TTYLINUX_NAME"
+  assert_success "10.0.0.42"
+
+  run govc vm.destroy "$TTYLINUX_NAME"
+  assert_success
+
+  govc import.ovf -options - "$GOVC_IMAGES/$TTYLINUX_NAME.ovf" <<EOF
+{
+  "PropertyMapping": [
+    {
+      "Key": "ip0",
+      "Value": "10.0.0.43"
+    }
+  ],
+  "PowerOn": true,
+  "WaitForIP": true
+}
+EOF
+
+  run govc vm.ip "$TTYLINUX_NAME"
+  assert_success "10.0.0.43"
 }
 
 @test "vcsim vm.create" {
Repository: vmware/govmomi
Pull request #1992 (2020-05-26 07:25:09)

vcsim: CreateSnapshotTask now returns moref in result
simulator CreateSnapshotTask should return the created snapshot mo-ref on success,
similar to vSphere behavior
issue #1991


Patch:
diff --git a/simulator/virtual_machine.go b/simulator/virtual_machine.go
index 2089acde..9068d25e 100644
--- a/simulator/virtual_machine.go
+++ b/simulator/virtual_machine.go
@@ -1894,7 +1894,7 @@ func (vm *VirtualMachine) CreateSnapshotTask(req *types.CreateSnapshot_Task) soa
 		changes = append(changes, types.PropertyChange{Name: "snapshot.currentSnapshot", Val: snapshot.Self})
 		Map.Update(vm, changes)
 
-		return nil, nil
+		return snapshot.Self, nil
 	})
 
 	return &methods.CreateSnapshot_TaskBody{


Test patch:
diff --git a/simulator/virtual_machine_test.go b/simulator/virtual_machine_test.go
index 90c07990..bb79f6d9 100644
--- a/simulator/virtual_machine_test.go
+++ b/simulator/virtual_machine_test.go
@@ -773,11 +773,21 @@ func TestVmSnapshot(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	err = task.Wait(ctx)
+	info, err := task.WaitForResult(ctx)
 	if err != nil {
 		t.Fatal(err)
 	}
 
+	snapRef, ok := info.Result.(types.ManagedObjectReference)
+	if !ok {
+		t.Fatal("expected ManagedObjectRefrence result for CreateSnapshot")
+	}
+
+	_, err = vm.FindSnapshot(ctx, snapRef.Value)
+	if err != nil {
+		t.Fatal(err, "snapshot should be found by result reference")
+	}
+
 	_, err = fieldValue(reflect.ValueOf(simVm), "snapshot")
 	if err == errEmptyField {
 		t.Fatal("snapshot property should not be 'nil' if there are snapshots")
Repository: vmware/govmomi
Pull request #2104 (2020-09-03 19:43:26)

vcsim: fix ListView.Modify
PR #2069 attempted to fixed a race, but introduced a panic - missed because race_test.go did
not check the return error.


Patch:
diff --git a/simulator/registry.go b/simulator/registry.go
index 22d419e6..ceac9511 100644
--- a/simulator/registry.go
+++ b/simulator/registry.go
@@ -520,6 +520,8 @@ func (r *Registry) locker(obj mo.Reference) sync.Locker {
 	case *types.ManagedObjectReference:
 		ref = *x
 		obj = r.Get(ref) // to check for sync.Locker
+	case *ListView: // otherwise race_test.go fails in the default case
+		ref = x.Self
 	default:
 		ref = obj.Reference()
 	}
diff --git a/simulator/view_manager.go b/simulator/view_manager.go
index 0a75301d..001e2f9a 100644
--- a/simulator/view_manager.go
+++ b/simulator/view_manager.go
@@ -209,7 +209,7 @@ type ListView struct {
 }
 
 func (v *ListView) update() {
-	Map.Update(v.Self, []types.PropertyChange{{Name: "view", Val: v.View}})
+	Map.Update(v, []types.PropertyChange{{Name: "view", Val: v.View}})
 }
 
 func (v *ListView) add(refs []types.ManagedObjectReference) *types.ManagedObjectNotFound {


Test patch:
diff --git a/simulator/race_test.go b/simulator/race_test.go
index 688fff99..1596cd58 100644
--- a/simulator/race_test.go
+++ b/simulator/race_test.go
@@ -153,7 +153,10 @@ func TestRace(t *testing.T) {
 					if terr != nil {
 						t.Error(terr)
 					}
-					_ = lv.Add(ctx, []types.ManagedObjectReference{r.Result.(types.ManagedObjectReference)})
+					terr = lv.Add(ctx, []types.ManagedObjectReference{r.Result.(types.ManagedObjectReference)})
+					if terr != nil {
+						t.Error(terr)
+					}
 				}()
 			}
Repository: vmware/govmomi
Pull request #3292 (2023-11-21 23:59:09)

vcsim: handle HostNotConnected when saving inventory


Patch:
diff --git a/govc/object/save.go b/govc/object/save.go
index e6c19bd4..796b5490 100644
--- a/govc/object/save.go
+++ b/govc/object/save.go
@@ -31,6 +31,7 @@ import (
 	"github.com/vmware/govmomi/vim25"
 	"github.com/vmware/govmomi/vim25/methods"
 	"github.com/vmware/govmomi/vim25/mo"
+	"github.com/vmware/govmomi/vim25/soap"
 	"github.com/vmware/govmomi/vim25/types"
 	"github.com/vmware/govmomi/vim25/xml"
 )
@@ -153,6 +154,16 @@ var saveObjects = map[string]func(context.Context, *vim25.Client, types.ManagedO
 	"HostNetworkSystem":              saveHostNetworkSystem,
 }
 
+func isNotConnected(err error) bool {
+	if soap.IsSoapFault(err) {
+		switch soap.ToSoapFault(err).VimFault().(type) {
+		case types.HostNotConnected:
+			return true
+		}
+	}
+	return false
+}
+
 func (cmd *save) save(content []types.ObjectContent) error {
 	for _, x := range content {
 		x.MissingSet = nil // drop any NoPermission faults
@@ -174,6 +185,9 @@ func (cmd *save) save(content []types.ObjectContent) error {
 		if method, ok := saveObjects[x.Obj.Type]; ok {
 			objs, err := method(context.Background(), c, x.Obj)
 			if err != nil {
+				if isNotConnected(err) {
+					continue
+				}
 				return err
 			}
 			dir := filepath.Join(cmd.dir, ref)
diff --git a/simulator/host_network_system.go b/simulator/host_network_system.go
index 4afdda0a..351604f8 100644
--- a/simulator/host_network_system.go
+++ b/simulator/host_network_system.go
@@ -198,6 +198,12 @@ func (s *HostNetworkSystem) UpdateNetworkConfig(req *types.UpdateNetworkConfig)
 }
 
 func (s *HostNetworkSystem) QueryNetworkHint(req *types.QueryNetworkHint) soap.HasFault {
+	if s.Host.Runtime.ConnectionState != types.HostSystemConnectionStateConnected {
+		return &methods.QueryNetworkHintBody{
+			Fault_: Fault("", &types.HostNotConnected{}),
+		}
+	}
+
 	return &methods.QueryNetworkHintBody{
 		Res: &types.QueryNetworkHintResponse{
 			Returnval: s.QueryNetworkHintResponse.Returnval,


Test patch:
diff --git a/govc/test/host.bats b/govc/test/host.bats
index 83fbb5e4..1aac4712 100755
--- a/govc/test/host.bats
+++ b/govc/test/host.bats
@@ -114,6 +114,20 @@ load test_helper
   govc host.vnic.info -json | jq .
 }
 
+@test "host.vnic.hint" {
+  vcsim_env
+
+  run govc host.vnic.hint -xml -host DC0_C0_H0
+  assert_success
+
+  run govc host.disconnect DC0_C0_H0
+  assert_success
+
+  run govc host.vnic.hint -xml -host DC0_C0_H0
+  assert_failure
+  assert_matches HostNotConnected "$output"
+}
+
 @test "host.vswitch.info" {
   vcsim_env -esx
Repository: vmware/govmomi
Pull request #3310 (2023-12-11 19:40:37)

fix: expose util for rest status return
Closes: #3309

## Description

During the implementation of https://github.com/kubernetes-sigs/cluster-api-provider-vsphere/pull/2535 we have realized it is hard to check what was the error returned from the rest/http layer of vAPI.

This PR creates a util function that can be used to check if a returned error from rest contains a specific http Status code.

Closes: #3309

## Type of change

Please mark options that are relevant:

- [ ] Bug fix (non-breaking change which fixes an issue)
- [X] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to
  not work as expected)
- [ ] This change requires a documentation update
- [ ] Build related change

## How Has This Been Tested?

Extended current rest Session test to also do an invalid internal call and check the returned error

## Checklist:

- [ ] My code follows the `CONTRIBUTION` [guidelines] of this project
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged

[guidelines]: https://github.com/vmware/govmomi/blob/main/CONTRIBUTING.md



Patch:
diff --git a/vapi/rest/client.go b/vapi/rest/client.go
index 46fb4e3a..9cd59e5a 100644
--- a/vapi/rest/client.go
+++ b/vapi/rest/client.go
@@ -155,6 +155,14 @@ func (e *statusError) Error() string {
 	return fmt.Sprintf("%s %s: %s", e.res.Request.Method, e.res.Request.URL, e.res.Status)
 }
 
+func IsStatusError(err error, code int) bool {
+	statusErr, ok := err.(*statusError)
+	if !ok || statusErr == nil || statusErr.res == nil {
+		return false
+	}
+	return statusErr.res.StatusCode == code
+}
+
 // RawResponse may be used with the Do method as the resBody argument in order
 // to capture the raw response data.
 type RawResponse struct {


Test patch:
diff --git a/vapi/rest/client_test.go b/vapi/rest/client_test.go
index a24bfddd..458bfbcf 100644
--- a/vapi/rest/client_test.go
+++ b/vapi/rest/client_test.go
@@ -27,9 +27,8 @@ import (
 	"github.com/vmware/govmomi/simulator"
 	"github.com/vmware/govmomi/vapi/internal"
 	"github.com/vmware/govmomi/vapi/rest"
-	"github.com/vmware/govmomi/vim25"
-
 	_ "github.com/vmware/govmomi/vapi/simulator"
+	"github.com/vmware/govmomi/vim25"
 )
 
 func TestSession(t *testing.T) {
@@ -58,6 +57,12 @@ func TestSession(t *testing.T) {
 		if session == nil {
 			t.Fatal("expected non-nil session")
 		}
+
+		path := c.Resource("/xpto/bla")
+		err = c.Do(ctx, path.Request(http.MethodGet), nil)
+		if !rest.IsStatusError(err, http.StatusNotFound) {
+			t.Fatal("expecting error to be 'StatusNotFound'", err)
+		}
 	})
 }
Repository: vmware/govmomi
Pull request #3449 (2024-05-21 17:46:31)

vcsim: fix: ignore unknown refs in Datacenter.PowerOnMultiVM


Patch:
diff --git a/simulator/datacenter.go b/simulator/datacenter.go
index 41fc5696..7680b602 100644
--- a/simulator/datacenter.go
+++ b/simulator/datacenter.go
@@ -162,8 +162,10 @@ func (dc *Datacenter) PowerOnMultiVMTask(ctx *Context, req *types.PowerOnMultiVM
 		res.Attempted = []types.ClusterAttemptedVmInfo{}
 
 		for _, ref := range req.Vm {
-			vm := ctx.Map.Get(ref).(*VirtualMachine)
-
+			vm, ok := ctx.Map.Get(ref).(*VirtualMachine)
+			if !ok {
+				continue
+			}
 			// This task creates multiple subtasks which violates the assumption
 			// of 1:1 Context:Task, which results in data races in objects
 			// like the Simulator.Event manager. This is the minimum context


Test patch:
diff --git a/simulator/datacenter_test.go b/simulator/datacenter_test.go
index c6899548..334b6c1b 100644
--- a/simulator/datacenter_test.go
+++ b/simulator/datacenter_test.go
@@ -142,7 +142,10 @@ func TestDatacenterPowerOnMultiVMs(t *testing.T) {
 		}
 	}
 
-	dcTask, err := dc.PowerOnVM(ctx, testVMs)
+	// real VC ignores unknown VM refs
+	unknown := types.ManagedObjectReference{Type: "VirtualMachine", Value: "unknown"}
+
+	dcTask, err := dc.PowerOnVM(ctx, append(testVMs, unknown))
 	if err != nil {
 		t.Fatal(err)
 	}
Repository: vmware/govmomi
Pull request #3594 (2024-10-15 17:35:02)

bug: Do not panic on fault.In for valid errs


## Description

This patch prevents fault.In from panic'ing if a valid error is sent into the function.

Closes: `NA`

## How Has This Been Tested?

```shell
go test -v -count 1 -cover ./fault
...
PASS
coverage: 100.0% of statements
ok  	github.com/vmware/govmomi/fault	0.284s	coverage: 100.0% of statements
```

## Guidelines

Please read and follow the `CONTRIBUTION` [guidelines] of this project.

[guidelines]: https://github.com/vmware/govmomi/blob/main/CONTRIBUTING.md



Patch:
diff --git a/fault/fault.go b/fault/fault.go
index e16478ee..40f2bd1e 100644
--- a/fault/fault.go
+++ b/fault/fault.go
@@ -255,6 +255,8 @@ func In(err any, onFaultFn OnFaultFn) {
 			}
 			In(uErr, onFaultFn)
 		}
+	case error:
+		// No-op
 	default:
 		panic("fault: err must implement error, types.BaseMethodFault, or " +
 			"types.HasLocalizedMethodFault")


Test patch:
diff --git a/fault/fault_test.go b/fault/fault_test.go
index 0191b276..f8309a67 100644
--- a/fault/fault_test.go
+++ b/fault/fault_test.go
@@ -413,6 +413,12 @@ func TestIn(t *testing.T) {
 			expectedNumCalls: 0,
 			expectedPanic:    unsupported,
 		},
+		{
+			name:             "err is unsupported but still an err",
+			err:              errors.New("error"),
+			callback:         &testHarness{},
+			expectedNumCalls: 0,
+		},
 		{
 			name:             "error is task.Error",
 			err:              task.Error{},
Repository: vmware/govmomi
Pull request #3601 (2024-10-23 17:31:19)

Fix error assertion in IsCertificateUntrusted


Closes: #3600

## How Has This Been Tested?

Adds unit tests and tested in CAPV.

## Guidelines

Please read and follow the `CONTRIBUTION` [guidelines] of this project.

[guidelines]: https://github.com/vmware/govmomi/blob/main/CONTRIBUTING.md



Patch:
diff --git a/vim25/soap/error.go b/vim25/soap/error.go
index b98da7bb..b82e508b 100644
--- a/vim25/soap/error.go
+++ b/vim25/soap/error.go
@@ -121,11 +121,11 @@ func IsCertificateUntrusted(err error) bool {
 	// golang 1.20 introduce a new type to wrap 509 errors. So instead of
 	// casting the type, now we check the error chain contains the
 	// x509 error or not.
-	if errors.Is(err, &x509.UnknownAuthorityError{}) {
+	if errors.As(err, &x509.UnknownAuthorityError{}) {
 		return true
 	}
 
-	if errors.Is(err, &x509.HostnameError{}) {
+	if errors.As(err, &x509.HostnameError{}) {
 		return true
 	}
 


Test patch:
diff --git a/vim25/soap/error_test.go b/vim25/soap/error_test.go
new file mode 100644
index 00000000..eede94d7
--- /dev/null
+++ b/vim25/soap/error_test.go
@@ -0,0 +1,62 @@
+/*
+Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package soap
+
+import (
+	"crypto/tls"
+	"crypto/x509"
+	"testing"
+)
+
+func TestIsCertificateUntrusted(t *testing.T) {
+	type args struct {
+	}
+	tests := []struct {
+		name string
+		err  error
+		want bool
+	}{
+		{
+			name: "tls.CertificateVerificationError",
+			err: x509.HostnameError{
+				Certificate: &x509.Certificate{},
+				Host:        "1.2.3.4",
+			},
+			want: true,
+		},
+		{
+			name: "tls.CertificateVerificationError",
+			err: &tls.CertificateVerificationError{
+				UnverifiedCertificates: []*x509.Certificate{
+					&x509.Certificate{},
+				},
+				Err: x509.HostnameError{
+					Certificate: &x509.Certificate{},
+					Host:        "5.6.7.8",
+				},
+			},
+			want: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := IsCertificateUntrusted(tt.err); got != tt.want {
+				t.Errorf("IsCertificateUntrusted() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
Repository: vmware/govmomi
Pull request #3607 (2024-10-31 17:20:41)

fix: CreateSATAController to return error when no bus numbers
## Description

This PR updates the `CreateSATAController()` to return an error when there is no available bus number.

## How Has This Been Tested?

Added `TestCreateSATAController`.

## Guidelines

Please read and follow the `CONTRIBUTION` [guidelines] of this project.

[guidelines]: https://github.com/vmware/govmomi/blob/main/CONTRIBUTING.md



Patch:
diff --git a/object/virtual_device_list.go b/object/virtual_device_list.go
index 4d4335ea..32b738a2 100644
--- a/object/virtual_device_list.go
+++ b/object/virtual_device_list.go
@@ -402,6 +402,10 @@ func (l VirtualDeviceList) FindSATAController(name string) (types.BaseVirtualCon
 func (l VirtualDeviceList) CreateSATAController() (types.BaseVirtualDevice, error) {
 	sata := &types.VirtualAHCIController{}
 	sata.BusNumber = l.newSATABusNumber()
+	if sata.BusNumber == -1 {
+		return nil, errors.New("no bus numbers available")
+	}
+
 	sata.Key = l.NewKey()
 
 	return sata, nil


Test patch:
diff --git a/object/virtual_device_list_test.go b/object/virtual_device_list_test.go
index 9cfc3273..0421a8ec 100644
--- a/object/virtual_device_list_test.go
+++ b/object/virtual_device_list_test.go
@@ -768,6 +768,24 @@ func TestCreateSCSIController(t *testing.T) {
 	}
 }
 
+func TestCreateSATAController(t *testing.T) {
+	l := VirtualDeviceList{}
+	for _, i := range sataBusNumbers {
+		c, err := l.CreateSATAController()
+		if err != nil {
+			t.Error(err)
+		}
+		if j := c.(types.BaseVirtualController).GetVirtualController().BusNumber; j != int32(i) {
+			t.Errorf("expected bus number: %d, got: %d", i, j)
+		}
+		l = append(l, c)
+	}
+
+	if _, err := l.CreateSATAController(); err == nil {
+		t.Error("should fail")
+	}
+}
+
 func TestCreateEthernetCard(t *testing.T) {
 	_, err := EthernetCardTypes().CreateEthernetCard("enoent", nil)
 	if err == nil {
Repository: googleforgames/open-saves
Pull request #269 (2021-07-13 23:00:11)

refactor: Define ErrIteratorNil in blobref

**What type of PR is this?**

/kind refactor


**What this PR does / Why we need it**:
Define ErrIteratorNil for blobref.BlobRefCursor to have a clear error definition.

**Which issue(s) this PR fixes**:
<!--
*Automatically closes linked issue when PR is merged.
Usage: `Closes #<issue number>`, or `Closes (paste link of issue)`.
-->
Closes #

**Special notes for your reviewer**:



Patch:
diff --git a/internal/pkg/metadb/blobref/cursor.go b/internal/pkg/metadb/blobref/cursor.go
index cbb5e6b..ff96778 100644
--- a/internal/pkg/metadb/blobref/cursor.go
+++ b/internal/pkg/metadb/blobref/cursor.go
@@ -20,6 +20,10 @@ import (
 	ds "cloud.google.com/go/datastore"
 )
 
+var (
+	ErrIteratorNil = errors.New("iterator is nil")
+)
+
 // BlobRefCursor is a database cursor for BlobRef.
 type BlobRefCursor struct {
 	iter *ds.Iterator
@@ -30,13 +34,14 @@ func NewCursor(i *ds.Iterator) *BlobRefCursor {
 }
 
 // Next advances the iterator and returns the next value.
-// Returns nil and an iterator.Done at the end of the iterator.
+// Returns nil and and iterator.Done at the end of the iterator.
+// Returns ErrIteratorNil if the iterator is nil.
 func (i *BlobRefCursor) Next() (*BlobRef, error) {
 	if i == nil {
-		return nil, errors.New("BlobRefIterator.Next was called on nil")
+		return nil, ErrIteratorNil
 	}
 	if i.iter == nil {
-		return nil, errors.New("iterator is nil")
+		return nil, ErrIteratorNil
 	}
 	var blob BlobRef
 	_, err := i.iter.Next(&blob)


Test patch:
diff --git a/internal/pkg/metadb/blobref/cursor_test.go b/internal/pkg/metadb/blobref/cursor_test.go
index a773a4a..1c12edf 100644
--- a/internal/pkg/metadb/blobref/cursor_test.go
+++ b/internal/pkg/metadb/blobref/cursor_test.go
@@ -24,5 +24,5 @@ func TestCursor_NextOnNil(t *testing.T) {
 	var c BlobRefCursor
 	b, err := c.Next()
 	assert.Nil(t, b)
-	assert.Error(t, err)
+	assert.ErrorIs(t, ErrIteratorNil, err)
 }
Repository: xanzy/go-gitlab
Pull request #1412 (2022-03-13 14:03:37)

Implement GET API for SSH Key of given user
I've recently implemented this API in GitLab and will be available in
GitLab 14.9 and is already deployed on gitlab.com


Patch:
diff --git a/users.go b/users.go
index 5c7e2b0..4e04e3a 100644
--- a/users.go
+++ b/users.go
@@ -468,6 +468,26 @@ func (s *UsersService) GetSSHKey(key int, options ...RequestOptionFunc) (*SSHKey
 	return k, resp, err
 }
 
+// GetSSHKeyForUser gets a single key for a given user.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/users.html#single-ssh-key-for-given-user
+func (s *UsersService) GetSSHKeyForUser(user int, key int, options ...RequestOptionFunc) (*SSHKey, *Response, error) {
+	u := fmt.Sprintf("users/%d/keys/%d", user, key)
+
+	req, err := s.client.NewRequest(http.MethodGet, u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	k := new(SSHKey)
+	resp, err := s.client.Do(req, k)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return k, resp, err
+}
+
 // AddSSHKeyOptions represents the available AddSSHKey() options.
 //
 // GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#add-ssh-key


Test patch:
diff --git a/users_test.go b/users_test.go
index ddb9f7e..edfc400 100644
--- a/users_test.go
+++ b/users_test.go
@@ -21,6 +21,7 @@ import (
 	"fmt"
 	"net"
 	"net/http"
+	"reflect"
 	"testing"
 	"time"
 
@@ -612,3 +613,38 @@ func TestGetMemberships(t *testing.T) {
 	want := []*UserMembership{{SourceID: 1, SourceName: "Project one", SourceType: "Project", AccessLevel: 20}, {SourceID: 3, SourceName: "Group three", SourceType: "Namespace", AccessLevel: 20}}
 	assert.Equal(t, want, memberships)
 }
+
+func TestGetSingleSSHKeyForUser(t *testing.T) {
+	mux, server, client := setup(t)
+	defer teardown(server)
+
+	mux.HandleFunc("/api/v4/users/1/keys/1", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, http.MethodGet)
+		fmt.Fprint(w, `
+		{
+			"id": 1,
+			"title": "Public key",
+			"key": "ssh-rsa AAAA...",
+			"created_at": "2014-08-01T14:47:39.080Z"
+		  }
+`)
+	})
+
+	sshKey, _, err := client.Users.GetSSHKeyForUser(1, 1)
+	if err != nil {
+		t.Errorf("Users.GetSSHKeyForUser returned an error: %v", err)
+	}
+
+	wantCreatedAt := time.Date(2014, 8, 1, 14, 47, 39, 80000000, time.UTC)
+
+	want := &SSHKey{
+		ID:        1,
+		Title:     "Public key",
+		Key:       "ssh-rsa AAAA...",
+		CreatedAt: &wantCreatedAt,
+	}
+
+	if !reflect.DeepEqual(want, sshKey) {
+		t.Errorf("Users.GetSSHKeyForUser returned %+v, want %+v", sshKey, want)
+	}
+}