Skip to content

Commit

Permalink
Rollback changes, bump govmomi to test moref support
Browse files Browse the repository at this point in the history
  • Loading branch information
rikatz committed Nov 6, 2024
1 parent 1865865 commit 4ec1841
Show file tree
Hide file tree
Showing 13 changed files with 112 additions and 136 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ require (
// The version of vm-operator should be kept in sync with the manifests at: config/deployments/integration-tests
github.com/vmware-tanzu/vm-operator/api v1.8.6
github.com/vmware-tanzu/vm-operator/external/ncp v0.0.0-20240404200847-de75746a9505
github.com/vmware/govmomi v0.45.1
github.com/vmware/govmomi v0.31.1-0.20241105193514-63f11e929d13
)

require (
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,8 @@ github.com/vmware-tanzu/vm-operator/api v1.8.6 h1:NIndORjcnSmIlQsCMIewpIwg/ocRVD
github.com/vmware-tanzu/vm-operator/api v1.8.6/go.mod h1:HHA2SNI9B5Yqtyp5t+Gt9WTWBi/fIkM6+MukDDSf11A=
github.com/vmware-tanzu/vm-operator/external/ncp v0.0.0-20240404200847-de75746a9505 h1:y4wXx1FUFqqSgJ/xUOEM1DLS2Uu0KaeLADWpzpioGTU=
github.com/vmware-tanzu/vm-operator/external/ncp v0.0.0-20240404200847-de75746a9505/go.mod h1:5rqRJ9zGR+KnKbkGx373WgN8xJpvAj99kHnfoDYRO5I=
github.com/vmware/govmomi v0.45.1 h1:pmMmSUNIw/kePaCRFaUOpDh7IxDfhDi9M4Qh+DRlBV4=
github.com/vmware/govmomi v0.45.1/go.mod h1:uoLVU9zlXC4p4GmLVG+ZJmBC0Gn3Q7mytOJvi39OhxA=
github.com/vmware/govmomi v0.31.1-0.20241105193514-63f11e929d13 h1:7tok/TLQSr/liKdCHPY235M3TnIZ9spmE9GsDNpI/38=
github.com/vmware/govmomi v0.31.1-0.20241105193514-63f11e929d13/go.mod h1:uoLVU9zlXC4p4GmLVG+ZJmBC0Gn3Q7mytOJvi39OhxA=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
Expand Down
4 changes: 2 additions & 2 deletions packaging/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,8 @@ github.com/vmware-tanzu/vm-operator/api v1.8.6 h1:NIndORjcnSmIlQsCMIewpIwg/ocRVD
github.com/vmware-tanzu/vm-operator/api v1.8.6/go.mod h1:HHA2SNI9B5Yqtyp5t+Gt9WTWBi/fIkM6+MukDDSf11A=
github.com/vmware-tanzu/vm-operator/external/ncp v0.0.0-20240404200847-de75746a9505 h1:y4wXx1FUFqqSgJ/xUOEM1DLS2Uu0KaeLADWpzpioGTU=
github.com/vmware-tanzu/vm-operator/external/ncp v0.0.0-20240404200847-de75746a9505/go.mod h1:5rqRJ9zGR+KnKbkGx373WgN8xJpvAj99kHnfoDYRO5I=
github.com/vmware/govmomi v0.45.1 h1:pmMmSUNIw/kePaCRFaUOpDh7IxDfhDi9M4Qh+DRlBV4=
github.com/vmware/govmomi v0.45.1/go.mod h1:uoLVU9zlXC4p4GmLVG+ZJmBC0Gn3Q7mytOJvi39OhxA=
github.com/vmware/govmomi v0.31.1-0.20241105193514-63f11e929d13 h1:7tok/TLQSr/liKdCHPY235M3TnIZ9spmE9GsDNpI/38=
github.com/vmware/govmomi v0.31.1-0.20241105193514-63f11e929d13/go.mod h1:uoLVU9zlXC4p4GmLVG+ZJmBC0Gn3Q7mytOJvi39OhxA=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ=
Expand Down
14 changes: 3 additions & 11 deletions pkg/clustermodule/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import (

"github.com/pkg/errors"
"github.com/vmware/govmomi/find"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
Expand Down Expand Up @@ -120,16 +119,9 @@ func (s *service) Remove(ctx context.Context, clusterCtx *capvcontext.ClusterCon
}

func getComputeClusterResource(ctx context.Context, s *session.Session, resourcePool string) (types.ManagedObjectReference, error) {
var err error
var rp *object.ResourcePool
rpRef := object.ReferenceFromString(resourcePool)
if rpRef != nil {
rp = object.NewResourcePool(s.Client.Client, *rpRef)
} else {
rp, err = s.Finder.ResourcePoolOrDefault(ctx, resourcePool)
if err != nil {
return types.ManagedObjectReference{}, err
}
rp, err := s.Finder.ResourcePoolOrDefault(ctx, resourcePool)
if err != nil {
return types.ManagedObjectReference{}, err
}

cc, err := rp.Owner(ctx)
Expand Down
111 changes: 86 additions & 25 deletions pkg/services/govmomi/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"context"
"testing"

"github.com/vmware/govmomi/find"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/simulator"
"github.com/vmware/govmomi/vim25"
Expand Down Expand Up @@ -48,7 +49,7 @@ func TestCreate(t *testing.T) {
t.Fatal("failed to get reference to an existing VM on the vcsim instance")
}

executeTest := func(vmname, dc string, replaceFunc func(vmContext *vmcontext.VMContext)) {
executeTest := func(vmname string, replaceFunc func(vmContext *vmcontext.VMContext, vimClient *vim25.Client)) {
vmContext := fake.NewVMContext(ctx, fake.NewControllerManagerContext())
vmContext.VSphereVM.Spec.Server = simr.ServerURL().Host
vmContext.VSphereVM.SetName(vmname)
Expand All @@ -58,7 +59,7 @@ func TestCreate(t *testing.T) {
session.NewParams().
WithServer(vmContext.VSphereVM.Spec.Server).
WithUserInfo(simr.Username(), simr.Password()).
WithDatacenter(dc))
WithDatacenter("*"))
if err != nil {
t.Fatal(err)
}
Expand All @@ -69,8 +70,13 @@ func TestCreate(t *testing.T) {
disk := object.VirtualDeviceList(vm.Config.Hardware.Device).SelectByType((*types.VirtualDisk)(nil))[0].(*types.VirtualDisk)
disk.CapacityInKB = int64(vmContext.VSphereVM.Spec.DiskGiB) * 1024 * 1024

vimClient, err := vim25.NewClient(ctx, vmContext.Session.RoundTripper)
if err != nil {
t.Fatal("could not make vim25 client.")
}

if replaceFunc != nil {
replaceFunc(vmContext)
replaceFunc(vmContext, vimClient)
}

if err := createVM(ctx, vmContext, []byte(""), ""); err != nil {
Expand All @@ -81,10 +87,7 @@ func TestCreate(t *testing.T) {
Type: morefTypeTask,
Value: vmContext.VSphereVM.Status.TaskRef,
}
vimClient, err := vim25.NewClient(ctx, vmContext.Session.RoundTripper)
if err != nil {
t.Fatal("could not make vim25 client.")
}

task := object.NewTask(vimClient, taskRef)
err = task.Wait(ctx)
if err != nil {
Expand All @@ -93,43 +96,101 @@ func TestCreate(t *testing.T) {
}

t.Log("executing with canonical name")
executeTest("canonical", "*", nil)
executeTest("canonical", nil)
if model.Machine+1 != model.Count().Machine {
t.Error("failed to clone vm")
}

t.Log("executing with moid")
replaceFuncMOID := func(vmContext *vmcontext.VMContext) {
vmContext.VSphereVM.Spec.Datacenter = simulator.Map.Any("Datacenter").Reference().String()
vmContext.VSphereVM.Spec.Datastore = simulator.Map.Any("Datastore").Reference().String()
vmContext.VSphereVM.Spec.Folder = simulator.Map.Any("Folder").Reference().String()
vmContext.VSphereVM.Spec.ResourcePool = simulator.Map.Any("ResourcePool").Reference().String()
replaceFuncMOID := func(vmContext *vmcontext.VMContext, vimClient *vim25.Client) {
finderClient := find.NewFinder(vimClient)
dc, err := finderClient.DatacenterOrDefault(ctx, "")
if err != nil {
t.Fatal(err)
}
folder, err := finderClient.FolderOrDefault(ctx, "")
if err != nil {
t.Fatal(err)
}

rp, err := finderClient.ResourcePoolOrDefault(ctx, "")
if err != nil {
t.Fatal(err)
}

ds, err := finderClient.DatastoreOrDefault(ctx, "")
if err != nil {
t.Fatal(err)
}

t.Logf("creating using MOID. Datacenter: %s, Datastore: %s, Folder: %s, ResourcePool: %s",
netw, err := finderClient.NetworkOrDefault(ctx, "VM Network")
if err != nil {
t.Fatal(err)
}

vmContext.VSphereVM.Spec.Datacenter = dc.Reference().String()
vmContext.VSphereVM.Spec.Folder = folder.Reference().String()
vmContext.VSphereVM.Spec.ResourcePool = rp.Reference().String()
vmContext.VSphereVM.Spec.Datastore = ds.Reference().String()
vmContext.VSphereVM.Spec.Network.Devices[0].NetworkName = netw.Reference().String()
vmContext.VSphereVM.Spec.Template = vmRef.Reference().String()

t.Logf("creating using MOID. Datacenter: %s, Datastore: %s, Folder: %s, ResourcePool: %s, Network %s, Template %s",
vmContext.VSphereVM.Spec.Datacenter,
vmContext.VSphereVM.Spec.Datastore,
vmContext.VSphereVM.Spec.Folder,
vmContext.VSphereVM.Spec.ResourcePool)
vmContext.VSphereVM.Spec.ResourcePool,
vmContext.VSphereVM.Spec.Network.Devices[0].NetworkName,
vmContext.VSphereVM.Spec.Template)
}
executeTest("moid", simulator.Map.Any("Datacenter").Reference().String(), replaceFuncMOID)

executeTest("moid", replaceFuncMOID)
if model.Machine+2 != model.Count().Machine {
t.Error("failed to clone vm")
}

t.Log("executing with mixed")
replaceFuncMixed := func(vmContext *vmcontext.VMContext) {
vmContext.VSphereVM.Spec.Datacenter = simulator.Map.Any("Datacenter").Reference().String()
vmContext.VSphereVM.Spec.Datastore = simulator.Map.Any("Datastore").Entity().Name
vmContext.VSphereVM.Spec.Folder = simulator.Map.Any("Folder").Reference().String()
vmContext.VSphereVM.Spec.ResourcePool = simulator.Map.Any("ResourcePool").Entity().Name
t.Log("executing with mixed, moref, etc")
replaceFuncMixed := func(vmContext *vmcontext.VMContext, vimClient *vim25.Client) {
finderClient := find.NewFinder(vimClient)
dc, err := finderClient.DatacenterOrDefault(ctx, "")
if err != nil {
t.Fatal(err)
}
folder, err := finderClient.FolderOrDefault(ctx, "")
if err != nil {
t.Fatal(err)
}

rp, err := finderClient.ResourcePoolOrDefault(ctx, "")
if err != nil {
t.Fatal(err)
}

ds, err := finderClient.DatastoreOrDefault(ctx, "")
if err != nil {
t.Fatal(err)
}

netw, err := finderClient.NetworkOrDefault(ctx, "VM Network")
if err != nil {
t.Fatal(err)
}

vmContext.VSphereVM.Spec.Datacenter = dc.Reference().Value // moref
vmContext.VSphereVM.Spec.Datastore = ds.Reference().String() // moid
vmContext.VSphereVM.Spec.Folder = folder.InventoryPath // inventory path
vmContext.VSphereVM.Spec.ResourcePool = rp.Name()
vmContext.VSphereVM.Spec.Network.Devices[0].NetworkName = netw.Reference().String()

t.Logf("creating using mixed entries. Datacenter: %s, Datastore: %s, Folder: %s, ResourcePool: %s",
t.Logf("creating using mixed entries. Datacenter: %s, Datastore: %s, Folder: %s, ResourcePool: %s, Network %s, Template %s",
vmContext.VSphereVM.Spec.Datacenter,
vmContext.VSphereVM.Spec.Datastore,
vmContext.VSphereVM.Spec.Folder,
vmContext.VSphereVM.Spec.ResourcePool)
vmContext.VSphereVM.Spec.ResourcePool,
vmContext.VSphereVM.Spec.Network.Devices[0].NetworkName,
vmContext.VSphereVM.Spec.Template)
}
executeTest("mixed", simulator.Map.Any("Datacenter").Reference().String(), replaceFuncMixed)
executeTest("mixed", replaceFuncMixed)
if model.Machine+3 != model.Count().Machine {
t.Error("failed to clone vm")
}
Expand Down
13 changes: 0 additions & 13 deletions pkg/services/govmomi/template/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,19 +59,6 @@ func findTemplateByInstanceUUID(ctx context.Context, session *session.Session, t

func findTemplateByName(ctx context.Context, session *session.Session, templateID string) (*object.VirtualMachine, error) {
log := ctrl.LoggerFrom(ctx)

vmRef := object.ReferenceFromString(templateID)
if vmRef != nil {
log.V(5).Info("Finding template by moid", "moid", templateID)
tpl := object.NewVirtualMachine(session.Client.Client, *vmRef)
name, err := tpl.ObjectName(ctx)
if err != nil {
return nil, errors.Wrapf(err, "unable to find template by moid %q", templateID)
}
log.V(5).Info("Found template by moid", "moid", templateID, "name", name)
return tpl, nil
}

log.V(5).Info("Find template by name", "name", templateID)
tpl, err := session.Finder.VirtualMachine(ctx, templateID)
if err != nil {
Expand Down
27 changes: 0 additions & 27 deletions pkg/services/govmomi/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import (
"github.com/pkg/errors"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/property"
"github.com/vmware/govmomi/view"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -84,32 +83,6 @@ func findVM(ctx context.Context, vmCtx *capvcontext.VMContext) (types.ManagedObj
return types.ManagedObjectReference{}, err
}
if objRef == nil {
folderRef := object.ReferenceFromString(vmCtx.VSphereVM.Spec.Folder)
if folderRef != nil {
m := view.NewManager(vmCtx.Session.Client.Client)
kind := []string{"VirtualMachine"}

v, err := m.CreateContainerView(ctx, *folderRef, kind, true)
if err != nil {
return types.ManagedObjectReference{}, err
}

var vms []mo.VirtualMachine

err = v.Retrieve(ctx, kind, []string{"name"}, &vms)
if err != nil {
return types.ManagedObjectReference{}, err
}

for _, vm := range vms {
if vm.Name == vmCtx.VSphereVM.Name {
log.Info("VM found by MOID folder", "vmRef", vm.Reference(), "vm", vm.Name)
return vm.Reference(), nil
}
}
log.Info("VM not found by moid folder", "folder", vmCtx.VSphereVM.Spec.Folder, "name", vmCtx.VSphereVM.Name)
return types.ManagedObjectReference{}, errNotFound{byInventoryPath: folderRef.Reference().String()}
}
// fallback to use inventory paths
folder, err := vmCtx.Session.Finder.FolderOrDefault(ctx, vmCtx.VSphereVM.Spec.Folder)
if err != nil {
Expand Down
37 changes: 10 additions & 27 deletions pkg/services/govmomi/vcenter/clone.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,27 +118,14 @@ func Clone(ctx context.Context, vmCtx *capvcontext.VMContext, bootstrapData []by
diskMoveType = linkCloneDiskMoveType
}

var folder *object.Folder

folderRef := object.ReferenceFromString(vmCtx.VSphereVM.Spec.Folder)
if folderRef != nil {
folder = object.NewFolder(vmCtx.Session.Client.Client, *folderRef)
} else {
folder, err = vmCtx.Session.Finder.FolderOrDefault(ctx, vmCtx.VSphereVM.Spec.Folder)
if err != nil {
return errors.Wrapf(err, "unable to get folder %q for %s", vmCtx.VSphereVM.Spec.Folder, vmCtx)
}
folder, err := vmCtx.Session.Finder.FolderOrDefault(ctx, vmCtx.VSphereVM.Spec.Folder)
if err != nil {
return errors.Wrapf(err, "unable to get folder for %q", vmCtx)
}

var pool *object.ResourcePool
poolRef := object.ReferenceFromString(vmCtx.VSphereVM.Spec.ResourcePool)
if poolRef != nil {
pool = object.NewResourcePool(vmCtx.Session.Client.Client, *poolRef)
} else {
pool, err = vmCtx.Session.Finder.ResourcePoolOrDefault(ctx, vmCtx.VSphereVM.Spec.ResourcePool)
if err != nil {
return errors.Wrapf(err, "unable to get resource pool %q for %q", vmCtx.VSphereVM.Spec.ResourcePool, vmCtx)
}
pool, err := vmCtx.Session.Finder.ResourcePoolOrDefault(ctx, vmCtx.VSphereVM.Spec.ResourcePool)
if err != nil {
return errors.Wrapf(err, "unable to get resource pool for %q", vmCtx)
}

devices, err := tpl.Device(ctx)
Expand Down Expand Up @@ -218,15 +205,11 @@ func Clone(ctx context.Context, vmCtx *capvcontext.VMContext, bootstrapData []by

var datastoreRef *types.ManagedObjectReference
if vmCtx.VSphereVM.Spec.Datastore != "" {
datastoreRef = object.ReferenceFromString(vmCtx.VSphereVM.Spec.Datastore)
// Fallback to use the name in case Datastore is not a valid ref
if datastoreRef == nil {
datastore, err := vmCtx.Session.Finder.Datastore(ctx, vmCtx.VSphereVM.Spec.Datastore)
if err != nil {
return errors.Wrapf(err, "unable to get datastore %s for %q", vmCtx.VSphereVM.Spec.Datastore, vmCtx)
}
datastoreRef = types.NewReference(datastore.Reference())
datastore, err := vmCtx.Session.Finder.Datastore(ctx, vmCtx.VSphereVM.Spec.Datastore)
if err != nil {
return errors.Wrapf(err, "unable to get datastore %s for %q", vmCtx.VSphereVM.Spec.Datastore, vmCtx)
}
datastoreRef = types.NewReference(datastore.Reference())
spec.Location.Datastore = datastoreRef
}

Expand Down
14 changes: 1 addition & 13 deletions pkg/session/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,19 +206,7 @@ func GetOrCreate(ctx context.Context, params *Params) (*Session, error) {

// Assign the datacenter if one was specified.
if params.datacenter != "" {
var dc *object.Datacenter
dcRef := object.ReferenceFromString(params.datacenter)
if dcRef != nil {
var dcName string
dc = object.NewDatacenter(client.Client, *dcRef)
dcName, err = dc.ObjectName(ctx)
log.Info("retrieving datacenter name via MOID", "datacenter", dcName)
} else {
dc, err = session.Finder.Datacenter(ctx, params.datacenter)
if dc != nil {
log.Info("retrieved datacenter name via name", "datacenter", dc.Name())
}
}
dc, err := session.Finder.Datacenter(ctx, params.datacenter)
if err != nil {
log.Error(err, "Failed to get datacenter, will logout")
// Logout of previously logged session to not leak
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/quick_start_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ var _ = Describe("Cluster Creation using Cluster API quick-start test [vcsim] [s
})

var _ = Describe("Cluster Creation using Cluster API quick-start test and MOID [vcsim]", func() {
const specName = "quick-start" // copied from CAPI
const specName = "quick-start-moid"
Setup(specName, func(testSpecificSettingsGetter func() testSettings) {
capi_e2e.QuickStartSpec(ctx, func() capi_e2e.QuickStartSpecInput {
return capi_e2e.QuickStartSpecInput{
Expand Down
2 changes: 1 addition & 1 deletion test/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ require (
github.com/vmware-tanzu/net-operator-api v0.0.0-20240326163340-1f32d6bf7f9d
// The version of vm-operator should be kept in sync with the manifests at: config/deployments/integration-tests
github.com/vmware-tanzu/vm-operator/api v1.8.6
github.com/vmware/govmomi v0.45.1
github.com/vmware/govmomi v0.31.1-0.20241105193514-63f11e929d13
)

require (
Expand Down
4 changes: 2 additions & 2 deletions test/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -323,8 +323,8 @@ github.com/vmware-tanzu/vm-operator/api v1.8.6 h1:NIndORjcnSmIlQsCMIewpIwg/ocRVD
github.com/vmware-tanzu/vm-operator/api v1.8.6/go.mod h1:HHA2SNI9B5Yqtyp5t+Gt9WTWBi/fIkM6+MukDDSf11A=
github.com/vmware-tanzu/vm-operator/external/ncp v0.0.0-20240404200847-de75746a9505 h1:y4wXx1FUFqqSgJ/xUOEM1DLS2Uu0KaeLADWpzpioGTU=
github.com/vmware-tanzu/vm-operator/external/ncp v0.0.0-20240404200847-de75746a9505/go.mod h1:5rqRJ9zGR+KnKbkGx373WgN8xJpvAj99kHnfoDYRO5I=
github.com/vmware/govmomi v0.45.1 h1:pmMmSUNIw/kePaCRFaUOpDh7IxDfhDi9M4Qh+DRlBV4=
github.com/vmware/govmomi v0.45.1/go.mod h1:uoLVU9zlXC4p4GmLVG+ZJmBC0Gn3Q7mytOJvi39OhxA=
github.com/vmware/govmomi v0.31.1-0.20241105193514-63f11e929d13 h1:7tok/TLQSr/liKdCHPY235M3TnIZ9spmE9GsDNpI/38=
github.com/vmware/govmomi v0.31.1-0.20241105193514-63f11e929d13/go.mod h1:uoLVU9zlXC4p4GmLVG+ZJmBC0Gn3Q7mytOJvi39OhxA=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
Expand Down
Loading

0 comments on commit 4ec1841

Please sign in to comment.