diff --git a/.circleci/config.yml b/.circleci/config.yml index 7ce248d728758..b15e100c4936b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -106,7 +106,7 @@ jobs: - run: 'make check-deps' - run: name: "Install golangci-lint" - command: go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.63.4 + command: go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.64.5 - run: name: "golangci-lint/Linux" # There are only 4 vCPUs available for this executor, so use only 4 instead of the default number @@ -120,7 +120,7 @@ jobs: - check-changed-files-or-halt - run: name: "Install golangci-lint" - command: go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.63.4 + command: go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.64.5 - run: name: "golangci-lint/macOS" # There are only 4 vCPUs available for this executor, so use only 4 instead of the default number @@ -134,7 +134,7 @@ jobs: - check-changed-files-or-halt - run: name: "Install golangci-lint" - command: go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.63.4 + command: go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.64.5 - run: name: "golangci-lint/Windows" # There are only 4 vCPUs available for this executor, so use only 4 instead of the default number diff --git a/.golangci.yml b/.golangci.yml index 4715c5109f0bc..a4cbf4c251baa 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -31,13 +31,13 @@ linters: - revive - sqlclosecheck - staticcheck - - tenv - testifylint - tparallel - typecheck - unconvert - unparam - unused + - usetesting linters-settings: depguard: @@ -322,11 +322,6 @@ linters-settings: - name: var-declaration - name: var-naming - name: waitgroup-by-value - tenv: - # The option `all` will run against whole test files (`_test.go`) regardless of method/function signatures. - # Otherwise, only methods that take `*testing.T`, `*testing.B`, and `testing.TB` as arguments are checked. - # Default: false - all: true testifylint: # Disable all checkers (https://github.com/Antonboom/testifylint#checkers). # Default: false @@ -356,6 +351,14 @@ linters-settings: - suite-subtest-run - suite-thelper - useless-assert + usetesting: + # Enable/disable `os.CreateTemp("", ...)` detections. + # Default: true + os-create-temp: false + # Enable/disable `os.MkdirTemp()` detections. + # Default: true + os-mkdir-temp: false + issues: # List of regexps of issue texts to exclude. diff --git a/Makefile b/Makefile index 199b08577711c..77dd7a69da867 100644 --- a/Makefile +++ b/Makefile @@ -180,7 +180,7 @@ vet: .PHONY: lint-install lint-install: @echo "Installing golangci-lint" - go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.63.4 + go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.64.5 @echo "Installing markdownlint" npm install -g markdownlint-cli diff --git a/agent/agent.go b/agent/agent.go index 0a67d55017b19..3a02cf64377f2 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -740,8 +740,7 @@ func (a *Agent) runAggregators( log.Printf("D! [agent] Aggregator channel closed") } -func updateWindow(start time.Time, roundInterval bool, period time.Duration) (time.Time, time.Time) { - var until time.Time +func updateWindow(start time.Time, roundInterval bool, period time.Duration) (since, until time.Time) { if roundInterval { until = internal.AlignTime(start, period) if until.Equal(start) { @@ -751,7 +750,7 @@ func updateWindow(start time.Time, roundInterval bool, period time.Duration) (ti until = start.Add(period) } - since := until.Add(-period) + since = until.Add(-period) return since, until } diff --git a/plugins/common/shim/goshim_test.go b/plugins/common/shim/goshim_test.go index c25b2bb97f34d..8b1be9e942641 100644 --- a/plugins/common/shim/goshim_test.go +++ b/plugins/common/shim/goshim_test.go @@ -31,9 +31,9 @@ func TestShimSetsUpLogger(t *testing.T) { require.NoError(t, err) } -func runErroringInputPlugin(t *testing.T, interval time.Duration, stdin io.Reader, stdout, stderr io.Writer) (chan bool, chan bool) { - metricProcessed := make(chan bool, 1) - exited := make(chan bool, 1) +func runErroringInputPlugin(t *testing.T, interval time.Duration, stdin io.Reader, stdout, stderr io.Writer) (processed, exited chan bool) { + processed = make(chan bool, 1) + exited = make(chan bool, 1) inp := &erroringInput{} shim := New() @@ -47,15 +47,15 @@ func runErroringInputPlugin(t *testing.T, interval time.Duration, stdin io.Reade shim.stderr = stderr logger.RedirectLogging(stderr) } - err := shim.AddInput(inp) - require.NoError(t, err) - go func() { + + require.NoError(t, shim.AddInput(inp)) + go func(e chan bool) { if err := shim.Run(interval); err != nil { t.Error(err) } - exited <- true - }() - return metricProcessed, exited + e <- true + }(exited) + return processed, exited } type erroringInput struct { diff --git a/plugins/common/shim/input_test.go b/plugins/common/shim/input_test.go index 631271ca56949..9d7abc330e0ac 100644 --- a/plugins/common/shim/input_test.go +++ b/plugins/common/shim/input_test.go @@ -55,11 +55,11 @@ func TestInputShimStdinSignalingWorks(t *testing.T) { <-exited } -func runInputPlugin(t *testing.T, interval time.Duration, stdin io.Reader, stdout, stderr io.Writer) (chan bool, chan bool) { - metricProcessed := make(chan bool, 1) - exited := make(chan bool, 1) +func runInputPlugin(t *testing.T, interval time.Duration, stdin io.Reader, stdout, stderr io.Writer) (processed, exited chan bool) { + processed = make(chan bool, 1) + exited = make(chan bool, 1) inp := &testInput{ - metricProcessed: metricProcessed, + metricProcessed: processed, } shim := New() @@ -74,13 +74,13 @@ func runInputPlugin(t *testing.T, interval time.Duration, stdin io.Reader, stdou } err := shim.AddInput(inp) require.NoError(t, err) - go func() { + go func(e chan bool) { if err := shim.Run(interval); err != nil { t.Error(err) } - exited <- true - }() - return metricProcessed, exited + e <- true + }(exited) + return processed, exited } type testInput struct { diff --git a/plugins/inputs/apcupsd/apcupsd_test.go b/plugins/inputs/apcupsd/apcupsd_test.go index 1ce26542d2419..fe031258193f5 100644 --- a/plugins/inputs/apcupsd/apcupsd_test.go +++ b/plugins/inputs/apcupsd/apcupsd_test.go @@ -197,7 +197,7 @@ func TestApcupsdGather(t *testing.T) { // The following functionality is straight from apcupsd tests. // kvBytes is a helper to generate length and key/value byte buffers. -func kvBytes(kv string) ([]byte, []byte) { +func kvBytes(kv string) (keyValLen, keyVal []byte) { lenb := make([]byte, 2) binary.BigEndian.PutUint16(lenb, uint16(len(kv))) diff --git a/plugins/inputs/beanstalkd/beanstalkd.go b/plugins/inputs/beanstalkd/beanstalkd.go index 2b3935d8fbeb6..fb7f1e066ba5b 100644 --- a/plugins/inputs/beanstalkd/beanstalkd.go +++ b/plugins/inputs/beanstalkd/beanstalkd.go @@ -165,7 +165,6 @@ func (b *Beanstalkd) gatherTubeStats(connection *textproto.Conn, tube string, ac } func runQuery(connection *textproto.Conn, cmd string, result interface{}) error { - //nolint:govet // Keep dynamic command as the passed string is constant requestID, err := connection.Cmd(cmd) if err != nil { return err diff --git a/plugins/inputs/exec/exec_test.go b/plugins/inputs/exec/exec_test.go index b49dc8d07f4d5..8d99079ecf981 100644 --- a/plugins/inputs/exec/exec_test.go +++ b/plugins/inputs/exec/exec_test.go @@ -81,7 +81,7 @@ func newRunnerMock(out, errout []byte, err error) runner { } } -func (r runnerMock) run(_ string, _ []string, _ time.Duration) ([]byte, []byte, error) { +func (r runnerMock) run(string, []string, time.Duration) (out, errout []byte, err error) { return r.out, r.errout, r.err } diff --git a/plugins/inputs/exec/run_notwindows.go b/plugins/inputs/exec/run_notwindows.go index fa346e590bf0e..b36599e653c30 100644 --- a/plugins/inputs/exec/run_notwindows.go +++ b/plugins/inputs/exec/run_notwindows.go @@ -19,7 +19,7 @@ func (c commandRunner) run( command string, environments []string, timeout time.Duration, -) ([]byte, []byte, error) { +) (out, errout []byte, err error) { splitCmd, err := shellquote.Split(command) if err != nil || len(splitCmd) == 0 { return nil, nil, fmt.Errorf("exec: unable to parse command: %w", err) @@ -33,19 +33,19 @@ func (c commandRunner) run( } var ( - out bytes.Buffer + outbuf bytes.Buffer stderr bytes.Buffer ) - cmd.Stdout = &out + cmd.Stdout = &outbuf cmd.Stderr = &stderr runErr := internal.RunTimeout(cmd, timeout) - out = removeWindowsCarriageReturns(out) + outbuf = removeWindowsCarriageReturns(outbuf) if stderr.Len() > 0 && !c.debug { stderr = removeWindowsCarriageReturns(stderr) stderr = truncate(stderr) } - return out.Bytes(), stderr.Bytes(), runErr + return outbuf.Bytes(), stderr.Bytes(), runErr } diff --git a/plugins/inputs/exec/run_windows.go b/plugins/inputs/exec/run_windows.go index f7acc7c5fb712..8cef6e1e348e7 100644 --- a/plugins/inputs/exec/run_windows.go +++ b/plugins/inputs/exec/run_windows.go @@ -19,7 +19,7 @@ func (c commandRunner) run( command string, environments []string, timeout time.Duration, -) ([]byte, []byte, error) { +) (out, errout []byte, err error) { splitCmd, err := shellquote.Split(command) if err != nil || len(splitCmd) == 0 { return nil, nil, fmt.Errorf("exec: unable to parse command: %w", err) @@ -35,19 +35,19 @@ func (c commandRunner) run( } var ( - out bytes.Buffer + outbuf bytes.Buffer stderr bytes.Buffer ) - cmd.Stdout = &out + cmd.Stdout = &outbuf cmd.Stderr = &stderr runErr := internal.RunTimeout(cmd, timeout) - out = removeWindowsCarriageReturns(out) + outbuf = removeWindowsCarriageReturns(outbuf) if stderr.Len() > 0 && !c.debug { stderr = removeWindowsCarriageReturns(stderr) stderr = truncate(stderr) } - return out.Bytes(), stderr.Bytes(), runErr + return outbuf.Bytes(), stderr.Bytes(), runErr } diff --git a/plugins/inputs/execd/shim/shim_test.go b/plugins/inputs/execd/shim/shim_test.go index e3124ea74b353..10fc8016f53ca 100644 --- a/plugins/inputs/execd/shim/shim_test.go +++ b/plugins/inputs/execd/shim/shim_test.go @@ -53,11 +53,11 @@ func TestShimStdinSignalingWorks(t *testing.T) { <-exited } -func runInputPlugin(t *testing.T, interval time.Duration, stdin io.Reader, stdout, stderr io.Writer) (chan bool, chan bool) { - metricProcessed := make(chan bool) - exited := make(chan bool) +func runInputPlugin(t *testing.T, interval time.Duration, stdin io.Reader, stdout, stderr io.Writer) (processed, exited chan bool) { + processed = make(chan bool) + exited = make(chan bool) inp := &testInput{ - metricProcessed: metricProcessed, + metricProcessed: processed, } shim := New() @@ -72,13 +72,13 @@ func runInputPlugin(t *testing.T, interval time.Duration, stdin io.Reader, stdou } require.NoError(t, shim.AddInput(inp)) - go func() { + go func(e chan bool) { if err := shim.Run(interval); err != nil { t.Error(err) } - exited <- true - }() - return metricProcessed, exited + e <- true + }(exited) + return processed, exited } type testInput struct { diff --git a/plugins/inputs/ipmi_sensor/ipmi_sensor.go b/plugins/inputs/ipmi_sensor/ipmi_sensor.go index 403e7b3fe7a59..33faa6c94fee3 100644 --- a/plugins/inputs/ipmi_sensor/ipmi_sensor.go +++ b/plugins/inputs/ipmi_sensor/ipmi_sensor.go @@ -175,9 +175,8 @@ func (m *Ipmi) parse(acc telegraf.Accumulator, server, sensor string) error { case "sdr": if m.MetricVersion == 2 { return m.parseV2(acc, hostname, out, timestamp) - } else { - return m.parseV1(acc, hostname, out, timestamp) } + return m.parseV1(acc, hostname, out, timestamp) case "chassis_power_status": return parseChassisPowerStatus(acc, hostname, out, timestamp) case "dcmi_power_reading": diff --git a/plugins/inputs/nginx_plus_api/nginx_plus_api_metrics_test.go b/plugins/inputs/nginx_plus_api/nginx_plus_api_metrics_test.go index 0a2a9cbdd9c1c..c03e1ff9d42f3 100644 --- a/plugins/inputs/nginx_plus_api/nginx_plus_api_metrics_test.go +++ b/plugins/inputs/nginx_plus_api/nginx_plus_api_metrics_test.go @@ -1534,13 +1534,12 @@ func TestUnknownContentType(t *testing.T) { require.Error(t, acc.FirstError()) } -func prepareAddr(t *testing.T, ts *httptest.Server) (*url.URL, string, string) { +func prepareAddr(t *testing.T, ts *httptest.Server) (addr *url.URL, host, port string) { t.Helper() addr, err := url.Parse(ts.URL + "/api") require.NoError(t, err) - host, port, err := net.SplitHostPort(addr.Host) - + host, port, err = net.SplitHostPort(addr.Host) if err != nil { host = addr.Host if addr.Scheme == "http" { diff --git a/plugins/inputs/smart/smart.go b/plugins/inputs/smart/smart.go index 58452f8284d37..0abebf489a41c 100644 --- a/plugins/inputs/smart/smart.go +++ b/plugins/inputs/smart/smart.go @@ -476,7 +476,7 @@ func (m *Smart) Gather(acc telegraf.Accumulator) error { return nil } -func (m *Smart) scanAllDevices(ignoreExcludes bool) ([]string, []string, error) { +func (m *Smart) scanAllDevices(ignoreExcludes bool) (nvme, nonNvme []string, err error) { // this will return all devices (including NVMe devices) for smartctl version >= 7.0 // for older versions this will return non NVMe devices devices, err := m.scanDevices(ignoreExcludes, "--scan") diff --git a/plugins/inputs/stackdriver/stackdriver.go b/plugins/inputs/stackdriver/stackdriver.go index 8b4e66422fbc7..88f4694ae6b68 100644 --- a/plugins/inputs/stackdriver/stackdriver.go +++ b/plugins/inputs/stackdriver/stackdriver.go @@ -192,8 +192,7 @@ func (s *Stackdriver) initializeStackdriverClient(ctx context.Context) error { } // Returns the start and end time for the next collection. -func (s *Stackdriver) updateWindow(prevEnd time.Time) (time.Time, time.Time) { - var start time.Time +func (s *Stackdriver) updateWindow(prevEnd time.Time) (start, end time.Time) { if time.Duration(s.Window) != 0 { start = time.Now().Add(-time.Duration(s.Delay)).Add(-time.Duration(s.Window)) } else if prevEnd.IsZero() { @@ -201,7 +200,8 @@ func (s *Stackdriver) updateWindow(prevEnd time.Time) (time.Time, time.Time) { } else { start = prevEnd } - end := time.Now().Add(-time.Duration(s.Delay)) + end = time.Now().Add(-time.Duration(s.Delay)) + return start, end } diff --git a/plugins/inputs/tail/tail.go b/plugins/inputs/tail/tail.go index 251927054b660..34e091164a533 100644 --- a/plugins/inputs/tail/tail.go +++ b/plugins/inputs/tail/tail.go @@ -153,16 +153,14 @@ func (t *Tail) getSeekInfo(file string) (*tail.SeekInfo, error) { if offset, ok := t.offsets[file]; ok { t.Log.Debugf("Using offset %d for %q", offset, file) return &tail.SeekInfo{Whence: 0, Offset: offset}, nil - } else { - return &tail.SeekInfo{Whence: 2, Offset: 0}, nil } + return &tail.SeekInfo{Whence: 2, Offset: 0}, nil case "save-or-beginning": if offset, ok := t.offsets[file]; ok { t.Log.Debugf("Using offset %d for %q", offset, file) return &tail.SeekInfo{Whence: 0, Offset: offset}, nil - } else { - return &tail.SeekInfo{Whence: 0, Offset: 0}, nil } + return &tail.SeekInfo{Whence: 0, Offset: 0}, nil default: return nil, errors.New("invalid 'initial_read_offset' setting") } diff --git a/plugins/inputs/varnish/varnish.go b/plugins/inputs/varnish/varnish.go index e78adf16fb062..14a6987ae5289 100644 --- a/plugins/inputs/varnish/varnish.go +++ b/plugins/inputs/varnish/varnish.go @@ -164,7 +164,7 @@ func (s *Varnish) Gather(acc telegraf.Accumulator) error { } // Prepare varnish cli tools arguments -func (s *Varnish) prepareCmdArgs() ([]string, []string) { +func (s *Varnish) prepareCmdArgs() (adm, stats []string) { // default varnishadm arguments admArgs := []string{"vcl.list", "-j"} diff --git a/plugins/inputs/webhooks/artifactory/artifactory_webhook.go b/plugins/inputs/webhooks/artifactory/artifactory_webhook.go index 910f6b4ee51f8..e99b233c5a401 100644 --- a/plugins/inputs/webhooks/artifactory/artifactory_webhook.go +++ b/plugins/inputs/webhooks/artifactory/artifactory_webhook.go @@ -77,11 +77,11 @@ func (awh *Webhook) newEvent(data []byte, et, ed string) (event, error) { case "artifact": if et == "deployed" || et == "deleted" { return generateEvent(data, &artifactDeploymentOrDeletedEvent{}) - } else if et == "moved" || et == "copied" { + } + if et == "moved" || et == "copied" { return generateEvent(data, &artifactMovedOrCopiedEvent{}) - } else { - return nil, &newEventError{"Not a recognized event type"} } + return nil, &newEventError{"Not a recognized event type"} case "artifact_property": return generateEvent(data, &artifactPropertiesEvent{}) case "docker": diff --git a/plugins/inputs/zfs/zfs_linux.go b/plugins/inputs/zfs/zfs_linux.go index 2ad42951ca486..65ab8f914e936 100644 --- a/plugins/inputs/zfs/zfs_linux.go +++ b/plugins/inputs/zfs/zfs_linux.go @@ -83,13 +83,13 @@ func getTags(pools []poolInfo) map[string]string { return map[string]string{"pools": poolNames} } -func gather(lines []string, fileLines int) ([]string, []string, error) { +func gather(lines []string, fileLines int) (keys, values []string, err error) { if len(lines) < fileLines { return nil, nil, errors.New("expected lines in kstat does not match") } - keys := strings.Fields(lines[1]) - values := strings.Fields(lines[2]) + keys = strings.Fields(lines[1]) + values = strings.Fields(lines[2]) if len(keys) != len(values) { return nil, nil, fmt.Errorf("key and value count don't match Keys:%v Values:%v", keys, values) } diff --git a/plugins/inputs/zipkin/codec/codec.go b/plugins/inputs/zipkin/codec/codec.go index c7044317c1730..2d653d2cceffb 100644 --- a/plugins/inputs/zipkin/codec/codec.go +++ b/plugins/inputs/zipkin/codec/codec.go @@ -133,9 +133,9 @@ func NewBinaryAnnotations(annotations []BinaryAnnotation, endpoint Endpoint) []t return formatted } -func minMax(span Span) (time.Time, time.Time) { - low := now().UTC() - high := time.Time{}.UTC() +func minMax(span Span) (low, high time.Time) { + low = now().UTC() + high = time.Time{}.UTC() for _, annotation := range span.Annotations() { ts := annotation.Timestamp() if !ts.IsZero() && ts.Before(low) { diff --git a/plugins/outputs/stackdriver/stackdriver.go b/plugins/outputs/stackdriver/stackdriver.go index 0da5d6f37c545..767da3514e39b 100644 --- a/plugins/outputs/stackdriver/stackdriver.go +++ b/plugins/outputs/stackdriver/stackdriver.go @@ -462,7 +462,7 @@ func getStackdriverIntervalEndpoints( m telegraf.Metric, f *telegraf.Field, cc *counterCache, -) (*timestamppb.Timestamp, *timestamppb.Timestamp) { +) (start, end *timestamppb.Timestamp) { endTime := timestamppb.New(m.Time()) var startTime *timestamppb.Timestamp if kind == metricpb.MetricDescriptor_CUMULATIVE { diff --git a/plugins/parsers/json_v2/parser.go b/plugins/parsers/json_v2/parser.go index c42a8e8cdfcb3..cafe107967951 100644 --- a/plugins/parsers/json_v2/parser.go +++ b/plugins/parsers/json_v2/parser.go @@ -334,7 +334,6 @@ func (p *Parser) expandArray(result metricNode, timestamp time.Time) ([]telegraf } if result.IsArray() { - var err error if result.IncludeCollection == nil && (len(p.objectConfig.FieldPaths) > 0 || len(p.objectConfig.TagPaths) > 0) { result.IncludeCollection = p.existsInpathResults(result.Index) } @@ -380,9 +379,6 @@ func (p *Parser) expandArray(result metricNode, timestamp time.Time) ([]telegraf results = append(results, r...) return true }) - if err != nil { - return nil, err - } } else { if p.objectConfig.TimestampKey != "" && result.SetName == p.objectConfig.TimestampKey { if p.objectConfig.TimestampFormat == "" { @@ -714,11 +710,11 @@ func convertType(input gjson.Result, desiredType, name string) (interface{}, err case "bool": if inputType == 0 { return false, nil - } else if inputType == 1 { + } + if inputType == 1 { return true, nil - } else { - return nil, fmt.Errorf("unable to convert field %q to type bool", name) } + return nil, fmt.Errorf("unable to convert field %q to type bool", name) } default: return nil, fmt.Errorf("unknown format '%T' for field %q", inputType, name) diff --git a/plugins/processors/converter/converter.go b/plugins/processors/converter/converter.go index 76081424b9e57..8cb5f9ec16b04 100644 --- a/plugins/processors/converter/converter.go +++ b/plugins/processors/converter/converter.go @@ -182,12 +182,12 @@ func (p *Converter) convertTags(metric telegraf.Metric) { metric.AddField(key, v) } case p.tagConversions.Timestamp != nil && p.tagConversions.Timestamp.Match(key): - if time, err := internal.ParseTimestamp(p.Tags.TimestampFormat, value, nil); err != nil { + time, err := internal.ParseTimestamp(p.Tags.TimestampFormat, value, nil) + if err != nil { p.Log.Errorf("Converting to timestamp [%T] failed: %v", value, err) continue - } else { - metric.SetTime(time) } + metric.SetTime(time) default: continue } diff --git a/plugins/secretstores/http/key_derivation.go b/plugins/secretstores/http/key_derivation.go index dcb5b6ce3f188..752f8bc44c925 100644 --- a/plugins/secretstores/http/key_derivation.go +++ b/plugins/secretstores/http/key_derivation.go @@ -30,7 +30,7 @@ func (k *KDFConfig) NewKey(keylen int) (key, iv config.Secret, err error) { return config.Secret{}, config.Secret{}, fmt.Errorf("unknown key-derivation function %q", k.Algorithm) } -func (k *KDFConfig) generatePBKDF2HMAC(hf hashFunc, keylen int) (config.Secret, config.Secret, error) { +func (k *KDFConfig) generatePBKDF2HMAC(hf hashFunc, keylen int) (key, iv config.Secret, err error) { if k.Iterations == 0 { return config.Secret{}, config.Secret{}, errors.New("'iteration value not set") } @@ -48,6 +48,6 @@ func (k *KDFConfig) generatePBKDF2HMAC(hf hashFunc, keylen int) (config.Secret, defer salt.Destroy() rawkey := pbkdf2.Key(passwd.Bytes(), salt.Bytes(), k.Iterations, keylen, hf) - key := config.NewSecret([]byte(hex.EncodeToString(rawkey))) + key = config.NewSecret([]byte(hex.EncodeToString(rawkey))) return key, config.Secret{}, nil }