diff --git a/.golangci.yml b/.golangci.yml index fd4007b61..3768ca0bd 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -12,20 +12,26 @@ run: linters: enable: - errcheck - - govet - - ineffassign - - golint - - goconst + - structcheck + - varcheck + - staticcheck + - unconvert + - gofmt - goimports + - ineffassign + - vet - unused - - varcheck - - deadcode - misspell - - nolintlint - - gocritic + - gosec + - exportloopref # Checks for pointers to enclosing loop variables + - tenv # Detects using os.Setenv instead of t.Setenv since Go 1.17 +linters-settings: + gosec: + excludes: + - G304 issues: exclude-use-default: false exclude: # EXC0001 errcheck: Almost all programs ignore errors on these functions and in most cases it's ok - - Error return value of .((os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*print(f|ln)?|os\.(Un)?Setenv). is not checked + - Error return value of .((os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*print(f|ln)?|os\.(Un)?Setenv). (is not checked|Errors unhandled) diff --git a/cmd/virtual-kubelet/internal/commands/root/flag.go b/cmd/virtual-kubelet/internal/commands/root/flag.go index 5a6ba5ed0..3bb48e01f 100644 --- a/cmd/virtual-kubelet/internal/commands/root/flag.go +++ b/cmd/virtual-kubelet/internal/commands/root/flag.go @@ -22,7 +22,7 @@ import ( "github.com/pkg/errors" "github.com/spf13/pflag" - "k8s.io/klog/v2" + klog "k8s.io/klog/v2" ) type mapVar map[string]string @@ -61,8 +61,10 @@ func installFlags(flags *pflag.FlagSet, c *Opts) { flags.StringVar(&c.KubeConfigPath, "kubeconfig", c.KubeConfigPath, "kube config file to use for connecting to the Kubernetes API server") flags.StringVar(&c.KubeNamespace, "namespace", c.KubeNamespace, "kubernetes namespace (default is 'all')") + /* #nosec */ flags.MarkDeprecated("namespace", "Nodes must watch for pods in all namespaces. This option is now ignored.") //nolint:errcheck - flags.MarkHidden("namespace") //nolint:errcheck + /* #nosec */ + flags.MarkHidden("namespace") //nolint:errcheck flags.StringVar(&c.KubeClusterDomain, "cluster-domain", c.KubeClusterDomain, "kubernetes cluster-domain (default is 'cluster.local')") flags.StringVar(&c.NodeName, "nodename", c.NodeName, "kubernetes node name") @@ -74,13 +76,16 @@ func installFlags(flags *pflag.FlagSet, c *Opts) { flags.StringVar(&c.TaintKey, "taint", c.TaintKey, "Set node taint key") flags.BoolVar(&c.DisableTaint, "disable-taint", c.DisableTaint, "disable the virtual-kubelet node taint") + /* #nosec */ flags.MarkDeprecated("taint", "Taint key should now be configured using the VK_TAINT_KEY environment variable") //nolint:errcheck flags.IntVar(&c.PodSyncWorkers, "pod-sync-workers", c.PodSyncWorkers, `set the number of pod synchronization workers`) flags.BoolVar(&c.EnableNodeLease, "enable-node-lease", c.EnableNodeLease, `use node leases (1.13) for node heartbeats`) + /* #nosec */ flags.MarkDeprecated("enable-node-lease", "leases are always enabled") //nolint:errcheck - flags.MarkHidden("enable-node-lease") //nolint:errcheck + /* #nosec */ + flags.MarkHidden("enable-node-lease") //nolint:errcheck flags.StringSliceVar(&c.TraceExporters, "trace-exporter", c.TraceExporters, fmt.Sprintf("sets the tracing exporter to use, available exporters: %s", AvailableTraceExporters())) flags.StringVar(&c.TraceConfig.ServiceName, "trace-service-name", c.TraceConfig.ServiceName, "sets the name of the service used to register with the trace exporter") diff --git a/cmd/virtual-kubelet/internal/commands/root/node.go b/cmd/virtual-kubelet/internal/commands/root/node.go index a1b35900b..1b1f8acae 100644 --- a/cmd/virtual-kubelet/internal/commands/root/node.go +++ b/cmd/virtual-kubelet/internal/commands/root/node.go @@ -36,7 +36,7 @@ func getTaint(c Opts) (*corev1.Taint, error) { key = getEnv("VKUBELET_TAINT_KEY", key) value = getEnv("VKUBELET_TAINT_VALUE", value) - effectEnv := getEnv("VKUBELET_TAINT_EFFECT", string(c.TaintEffect)) + effectEnv := getEnv("VKUBELET_TAINT_EFFECT", c.TaintEffect) var effect corev1.TaintEffect switch effectEnv { diff --git a/cmd/virtual-kubelet/internal/commands/root/opts.go b/cmd/virtual-kubelet/internal/commands/root/opts.go index 0af8ee72c..db464eca0 100644 --- a/cmd/virtual-kubelet/internal/commands/root/opts.go +++ b/cmd/virtual-kubelet/internal/commands/root/opts.go @@ -15,6 +15,7 @@ package root import ( + "fmt" "os" "path/filepath" "strconv" @@ -95,6 +96,8 @@ type Opts struct { Version string } +const maxInt32 = 1<<31 - 1 + // SetDefaultOpts sets default options for unset values on the passed in option struct. // Fields tht are already set will not be modified. func SetDefaultOpts(c *Opts) error { @@ -128,6 +131,10 @@ func SetDefaultOpts(c *Opts) error { if err != nil { return errors.Wrap(err, "error parsing KUBELET_PORT environment variable") } + if p > maxInt32 { + return fmt.Errorf("KUBELET_PORT environment variable is too large") + } + /* #nosec */ c.ListenPort = int32(p) } else { c.ListenPort = DefaultListenPort diff --git a/cmd/virtual-kubelet/internal/commands/root/tracing.go b/cmd/virtual-kubelet/internal/commands/root/tracing.go index e3dc619f7..257f6e6f9 100644 --- a/cmd/virtual-kubelet/internal/commands/root/tracing.go +++ b/cmd/virtual-kubelet/internal/commands/root/tracing.go @@ -21,6 +21,7 @@ import ( "os" "strconv" "strings" + "time" "github.com/pkg/errors" "github.com/virtual-kubelet/virtual-kubelet/errdefs" @@ -105,7 +106,8 @@ func setupZpages(ctx context.Context) { zpages.Handle(mux, "/debug") go func() { // This should never terminate, if it does, it will always terminate with an error - e := http.Serve(listener, mux) + srv := &http.Server{Handler: mux, ReadHeaderTimeout: 30 * time.Second} + e := srv.Serve(listener) if e == http.ErrServerClosed { return } diff --git a/cmd/virtual-kubelet/internal/commands/root/tracing_register.go b/cmd/virtual-kubelet/internal/commands/root/tracing_register.go index 1303456e2..b60100393 100644 --- a/cmd/virtual-kubelet/internal/commands/root/tracing_register.go +++ b/cmd/virtual-kubelet/internal/commands/root/tracing_register.go @@ -19,7 +19,8 @@ import ( "go.opencensus.io/trace" ) -type TracingExporterOptions struct { // nolint: golint +// TracingExporterOptions is the options passed to the tracing exporter init function. +type TracingExporterOptions struct { //nolint: golint Tags map[string]string ServiceName string } diff --git a/cmd/virtual-kubelet/internal/commands/root/tracing_register_jaeger.go b/cmd/virtual-kubelet/internal/commands/root/tracing_register_jaeger.go index 73c9e2e86..2dc9209c0 100644 --- a/cmd/virtual-kubelet/internal/commands/root/tracing_register_jaeger.go +++ b/cmd/virtual-kubelet/internal/commands/root/tracing_register_jaeger.go @@ -12,13 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build !no_jaeger_exporter // +build !no_jaeger_exporter package root import ( "errors" - "fmt" "os" "contrib.go.opencensus.io/exporter/jaeger" @@ -32,7 +32,6 @@ func init() { // NewJaegerExporter creates a new opencensus tracing exporter. func NewJaegerExporter(opts TracingExporterOptions) (trace.Exporter, error) { jOpts := jaeger.Options{ - Endpoint: os.Getenv("JAEGER_ENDPOINT"), // deprecated CollectorEndpoint: os.Getenv("JAEGER_COLLECTOR_ENDPOINT"), AgentEndpoint: os.Getenv("JAEGER_AGENT_ENDPOINT"), Username: os.Getenv("JAEGER_USER"), @@ -42,11 +41,8 @@ func NewJaegerExporter(opts TracingExporterOptions) (trace.Exporter, error) { }, } - if jOpts.Endpoint != "" && jOpts.CollectorEndpoint == "" { // nolintlint:staticcheck - jOpts.CollectorEndpoint = fmt.Sprintf("%s/api/traces", jOpts.Endpoint) // nolintlint:staticcheck - } if jOpts.CollectorEndpoint == "" && jOpts.AgentEndpoint == "" { // nolintlint:staticcheck - return nil, errors.New("Must specify either JAEGER_COLLECTOR_ENDPOINT or JAEGER_AGENT_ENDPOINT") + return nil, errors.New("must specify either JAEGER_COLLECTOR_ENDPOINT or JAEGER_AGENT_ENDPOINT") } for k, v := range opts.Tags { diff --git a/cmd/virtual-kubelet/internal/commands/root/tracing_register_ocagent.go b/cmd/virtual-kubelet/internal/commands/root/tracing_register_ocagent.go index 551bea0dd..92678935c 100644 --- a/cmd/virtual-kubelet/internal/commands/root/tracing_register_ocagent.go +++ b/cmd/virtual-kubelet/internal/commands/root/tracing_register_ocagent.go @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build !no_ocagent_exporter // +build !no_ocagent_exporter package root diff --git a/cmd/virtual-kubelet/internal/provider/mock/mock.go b/cmd/virtual-kubelet/internal/provider/mock/mock.go index 4284060f6..987d08752 100644 --- a/cmd/virtual-kubelet/internal/provider/mock/mock.go +++ b/cmd/virtual-kubelet/internal/provider/mock/mock.go @@ -42,7 +42,7 @@ var ( */ // MockProvider implements the virtual-kubelet provider interface and stores pods in memory. -type MockProvider struct { // nolint:golint +type MockProvider struct { //nolint:golint nodeName string operatingSystem string internalIP string @@ -54,7 +54,7 @@ type MockProvider struct { // nolint:golint } // MockConfig contains a mock virtual-kubelet's configurable parameters. -type MockConfig struct { // nolint:golint +type MockConfig struct { //nolint:golint CPU string `json:"cpu,omitempty"` Memory string `json:"memory,omitempty"` Pods string `json:"pods,omitempty"` @@ -328,8 +328,8 @@ func (p *MockProvider) GetPods(ctx context.Context) ([]*v1.Pod, error) { return pods, nil } -func (p *MockProvider) ConfigureNode(ctx context.Context, n *v1.Node) { // nolint:golint - ctx, span := trace.StartSpan(ctx, "mock.ConfigureNode") // nolint:staticcheck,ineffassign +func (p *MockProvider) ConfigureNode(ctx context.Context, n *v1.Node) { //nolint:golint + ctx, span := trace.StartSpan(ctx, "mock.ConfigureNode") //nolint:staticcheck,ineffassign defer span.End() n.Status.Capacity = p.capacity() @@ -467,10 +467,14 @@ func (p *MockProvider) GetStatsSummary(ctx context.Context) (*stats.Summary, err for _, container := range pod.Spec.Containers { // Grab a dummy value to be used as the total CPU usage. // The value should fit a uint32 in order to avoid overflows later on when computing pod stats. + + /* #nosec */ dummyUsageNanoCores := uint64(rand.Uint32()) totalUsageNanoCores += dummyUsageNanoCores // Create a dummy value to be used as the total RAM usage. // The value should fit a uint32 in order to avoid overflows later on when computing pod stats. + + /* #nosec */ dummyUsageBytes := uint64(rand.Uint32()) totalUsageBytes += dummyUsageBytes // Append a ContainerStats object containing the dummy stats to the PodStats object. diff --git a/cmd/virtual-kubelet/internal/provider/store.go b/cmd/virtual-kubelet/internal/provider/store.go index 2601d6a66..e5a74f9a1 100644 --- a/cmd/virtual-kubelet/internal/provider/store.go +++ b/cmd/virtual-kubelet/internal/provider/store.go @@ -13,7 +13,7 @@ type Store struct { ls map[string]InitFunc } -func NewStore() *Store { // nolint:golint +func NewStore() *Store { //nolint:golint return &Store{ ls: make(map[string]InitFunc), } @@ -71,4 +71,4 @@ type InitConfig struct { ResourceManager *manager.ResourceManager } -type InitFunc func(InitConfig) (Provider, error) // nolint:golint +type InitFunc func(InitConfig) (Provider, error) //nolint:golint diff --git a/cmd/virtual-kubelet/internal/provider/types.go b/cmd/virtual-kubelet/internal/provider/types.go index 5ad323410..7ef9eb07b 100644 --- a/cmd/virtual-kubelet/internal/provider/types.go +++ b/cmd/virtual-kubelet/internal/provider/types.go @@ -7,7 +7,7 @@ const ( OperatingSystemWindows = "windows" ) -type OperatingSystems map[string]bool // nolint:golint +type OperatingSystems map[string]bool //nolint:golint var ( // ValidOperatingSystems defines the group of operating systems @@ -18,7 +18,7 @@ var ( } ) -func (o OperatingSystems) Names() []string { // nolint:golint +func (o OperatingSystems) Names() []string { //nolint:golint keys := make([]string, 0, len(o)) for k := range o { keys = append(keys, k) diff --git a/cmd/virtual-kubelet/register.go b/cmd/virtual-kubelet/register.go index f315338a3..588fb3b5b 100644 --- a/cmd/virtual-kubelet/register.go +++ b/cmd/virtual-kubelet/register.go @@ -6,6 +6,7 @@ import ( ) func registerMock(s *provider.Store) { + /* #nosec */ s.Register("mock", func(cfg provider.InitConfig) (provider.Provider, error) { //nolint:errcheck return mock.NewMockProvider( cfg.ConfigPath, diff --git a/internal/manager/resource_test.go b/internal/manager/resource_test.go index c28581674..68c60e3d4 100644 --- a/internal/manager/resource_test.go +++ b/internal/manager/resource_test.go @@ -123,7 +123,7 @@ func TestGetConfigMap(t *testing.T) { } value := configMap.Data["key-0"] if value != "val-0" { - t.Fatal("got unexpected value", string(value)) + t.Fatal("got unexpected value", value) } // Try to get a configmap that does not exist, and make sure we've got a "not found" error as a response. diff --git a/log/klogv2/klogv2_test.go b/log/klogv2/klogv2_test.go index 625dbe034..324674225 100644 --- a/log/klogv2/klogv2_test.go +++ b/log/klogv2/klogv2_test.go @@ -4,7 +4,7 @@ // 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 +// 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, diff --git a/node/api/helpers.go b/node/api/helpers.go index 2051d9cb0..5e3c49d9a 100644 --- a/node/api/helpers.go +++ b/node/api/helpers.go @@ -33,7 +33,9 @@ func handleError(f handlerFunc) http.HandlerFunc { code := httpStatusCode(err) w.WriteHeader(code) - io.WriteString(w, err.Error()) //nolint:errcheck + if _, err := io.WriteString(w, err.Error()); err != nil { + log.G(req.Context()).WithError(err).Error("error writing error response") + } logger := log.G(req.Context()).WithError(err).WithField("httpStatusCode", code) if code >= 500 { diff --git a/node/api/pods.go b/node/api/pods.go index 6142c17b9..3e44c6f42 100644 --- a/node/api/pods.go +++ b/node/api/pods.go @@ -24,14 +24,15 @@ import ( "k8s.io/apimachinery/pkg/runtime/serializer" ) -type PodListerFunc func(context.Context) ([]*v1.Pod, error) // nolint:golint +type PodListerFunc func(context.Context) ([]*v1.Pod, error) //nolint:golint -func HandleRunningPods(getPods PodListerFunc) http.HandlerFunc { // nolint:golint +func HandleRunningPods(getPods PodListerFunc) http.HandlerFunc { //nolint:golint if getPods == nil { return NotImplemented } scheme := runtime.NewScheme() + /* #nosec */ v1.SchemeBuilder.AddToScheme(scheme) //nolint:errcheck codecs := serializer.NewCodecFactory(scheme) diff --git a/node/api/server.go b/node/api/server.go index a2da257ec..cba81e7d9 100644 --- a/node/api/server.go +++ b/node/api/server.go @@ -33,7 +33,7 @@ type ServeMux interface { Handle(path string, h http.Handler) } -type PodHandlerConfig struct { // nolint:golint +type PodHandlerConfig struct { //nolint:golint RunInContainer ContainerExecHandlerFunc GetContainerLogs ContainerLogsHandlerFunc // GetPods is meant to enumerate the pods that the provider knows about @@ -79,7 +79,7 @@ func PodHandler(p PodHandlerConfig, debug bool) http.Handler { // PodStatsSummaryHandler creates an http handler for serving pod metrics. // // If the passed in handler func is nil this will create handlers which only -// serves http.StatusNotImplemented +// serves http.StatusNotImplemented func PodStatsSummaryHandler(f PodStatsSummaryHandlerFunc) http.Handler { if f == nil { return http.HandlerFunc(NotImplemented) diff --git a/node/lease_controller_v1.go b/node/lease_controller_v1.go index 3d57e8b27..343b16cc4 100644 --- a/node/lease_controller_v1.go +++ b/node/lease_controller_v1.go @@ -333,7 +333,7 @@ func (e *nodeNotReadyError) Is(target error) bool { return ok } -func (e *nodeNotReadyError) As(target error) bool { +func (e *nodeNotReadyError) As(target interface{}) bool { val, ok := target.(*nodeNotReadyError) if ok { *val = *e diff --git a/node/lifecycle_test.go b/node/lifecycle_test.go index 506eb016a..00e7e15d2 100644 --- a/node/lifecycle_test.go +++ b/node/lifecycle_test.go @@ -38,7 +38,7 @@ var ( const ( // There might be a constant we can already leverage here testNamespace = "default" - informerResyncPeriod = time.Duration(1 * time.Second) + informerResyncPeriod = 1 * time.Second testNodeName = "testnode" podSyncWorkers = 3 ) @@ -232,7 +232,7 @@ type system struct { } func (s *system) start(ctx context.Context) error { - go s.pc.Run(ctx, podSyncWorkers) // nolint:errcheck + go s.pc.Run(ctx, podSyncWorkers) //nolint:errcheck select { case <-s.pc.Ready(): case <-s.pc.Done(): diff --git a/node/node.go b/node/node.go index 232601d7b..1adc21903 100644 --- a/node/node.go +++ b/node/node.go @@ -54,7 +54,7 @@ var ( // // Note: Implementers can choose to manage a node themselves, in which case // it is not needed to provide an implementation for this interface. -type NodeProvider interface { // nolint:golint +type NodeProvider interface { //nolint:revive // Ping checks if the node is still active. // This is intended to be lightweight as it will be called periodically as a // heartbeat to keep the node marked as ready in Kubernetes. @@ -105,7 +105,7 @@ func NewNodeController(p NodeProvider, node *corev1.Node, nodes v1.NodeInterface } // NodeControllerOpt are the functional options used for configuring a node -type NodeControllerOpt func(*NodeController) error // nolint:golint +type NodeControllerOpt func(*NodeController) error //nolint:revive // WithNodeEnableLeaseV1 enables support for v1 leases. // V1 Leases share all the same properties as v1beta1 leases, except they do not fallback like @@ -208,7 +208,7 @@ type ErrorHandler func(context.Context, error) error // NodeController deals with creating and managing a node object in Kubernetes. // It can register a node with Kubernetes and periodically update its status. // NodeController manages a single node entity. -type NodeController struct { // nolint:golint +type NodeController struct { //nolint:revive p NodeProvider // serverNode must be updated each time it is updated in API Server diff --git a/node/node_test.go b/node/node_test.go index e4d97375a..922626e33 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -71,7 +71,7 @@ func testNodeRun(t *testing.T, enableLease bool) { assert.NilError(t, node.Err()) }() - go node.Run(ctx) // nolint:errcheck + go node.Run(ctx) //nolint:errcheck nw := makeWatch(ctx, t, nodes, testNodeCopy.Name) defer nw.Stop() @@ -189,7 +189,7 @@ func TestNodeCustomUpdateStatusErrorHandler(t *testing.T) { ) assert.NilError(t, err) - go node.Run(ctx) // nolint:errcheck + go node.Run(ctx) //nolint:errcheck timer := time.NewTimer(10 * time.Second) defer timer.Stop() @@ -295,7 +295,7 @@ func TestPingAfterStatusUpdate(t *testing.T) { node, err := NewNodeController(testP, testNode, nodes, opts...) assert.NilError(t, err) - go node.Run(ctx) // nolint:errcheck + go node.Run(ctx) //nolint:errcheck defer func() { cancel() <-node.Done() @@ -363,7 +363,7 @@ func TestBeforeAnnotationsPreserved(t *testing.T) { assert.NilError(t, node.Err()) }() - go node.Run(ctx) // nolint:errcheck + go node.Run(ctx) //nolint:errcheck nw := makeWatch(ctx, t, nodes, testNodeCopy.Name) defer nw.Stop() @@ -427,7 +427,7 @@ func TestManualConditionsPreserved(t *testing.T) { assert.NilError(t, node.Err()) }() - go node.Run(ctx) // nolint:errcheck + go node.Run(ctx) //nolint:errcheck nw := makeWatch(ctx, t, nodes, testNodeCopy.Name) defer nw.Stop() diff --git a/node/nodeutil/auth.go b/node/nodeutil/auth.go index ef5e52cac..f13568ac2 100644 --- a/node/nodeutil/auth.go +++ b/node/nodeutil/auth.go @@ -33,7 +33,7 @@ type authWrapper struct { // InstrumentAuth wraps the provided Auth in a new instrumented Auth // // Note: You would only need this if you rolled your own auth. -// The Auth implementations defined in this package are already instrumented. +// The Auth implementations defined in this package are already instrumented. func InstrumentAuth(auth Auth) Auth { if _, ok := auth.(*authWrapper); ok { // This is already instrumented diff --git a/node/nodeutil/controller.go b/node/nodeutil/controller.go index edb8c3c72..09b3b7cc1 100644 --- a/node/nodeutil/controller.go +++ b/node/nodeutil/controller.go @@ -78,12 +78,14 @@ func (n *Node) runHTTP(ctx context.Context) (func(), error) { log.G(ctx).Debug("Started TLS listener") - srv := &http.Server{Handler: n.h, TLSConfig: n.tlsConfig} + srv := &http.Server{Handler: n.h, TLSConfig: n.tlsConfig, ReadHeaderTimeout: 30 * time.Second} go srv.Serve(l) //nolint:errcheck log.G(ctx).Debug("HTTP server running") return func() { + /* #nosec */ srv.Close() + /* #nosec */ l.Close() }, nil } @@ -275,7 +277,6 @@ func WithClient(c kubernetes.Interface) NodeOpt { // Some basic values are set for node status, you'll almost certainly want to modify it. // // If client is nil, this will construct a client using ClientsetFromEnv -// // It is up to the caller to configure auth on the HTTP handler. func NewNode(name string, newProvider NewProviderFunc, opts ...NodeOpt) (*Node, error) { cfg := NodeConfig{ diff --git a/test/e2e/basic.go b/test/e2e/basic.go index acf39b12b..445fe3ec4 100644 --- a/test/e2e/basic.go +++ b/test/e2e/basic.go @@ -403,7 +403,7 @@ func (ts *EndToEndTestSuite) TestCreatePodWithMandatoryInexistentConfigMap(t *te // It returns an error if the specified pod is not found. func findPodInPodStats(summary *stats.Summary, pod *v1.Pod) (int, error) { for i, p := range summary.Pods { - if p.PodRef.Namespace == pod.Namespace && p.PodRef.Name == pod.Name && string(p.PodRef.UID) == string(pod.UID) { + if p.PodRef.Namespace == pod.Namespace && p.PodRef.Name == pod.Name && p.PodRef.UID == string(pod.UID) { return i, nil } }