Chore: Missed deprecations due to overly broad lint exclusion (#59732)

This commit is contained in:
Kyle Brandt
2022-12-14 06:32:45 -05:00
committed by GitHub
parent cea5e6589d
commit 55d2d872ec
16 changed files with 71 additions and 51 deletions

View File

@@ -84,7 +84,11 @@ text = "ST1001"
# Use golang.org/x/text/cases instead. # Use golang.org/x/text/cases instead.
[[issues.exclude-rules]] [[issues.exclude-rules]]
linters = ["staticcheck"] linters = ["staticcheck"]
text = "SA1019" text = "SA1019: strings.Title"
[[issues.exclude-rules]]
linters = ["staticcheck"]
text = "use fake service and real access control evaluator instead"
[[issues.exclude-rules]] [[issues.exclude-rules]]
linters = ["gosec"] linters = ["gosec"]

2
go.mod
View File

@@ -41,7 +41,7 @@ require (
github.com/gchaincl/sqlhooks v1.3.0 github.com/gchaincl/sqlhooks v1.3.0
github.com/getsentry/sentry-go v0.13.0 github.com/getsentry/sentry-go v0.13.0
github.com/go-git/go-git/v5 v5.4.2 github.com/go-git/go-git/v5 v5.4.2
github.com/go-kit/kit v0.12.0 github.com/go-kit/kit v0.12.0 // indirect
github.com/go-openapi/strfmt v0.21.3 github.com/go-openapi/strfmt v0.21.3
github.com/go-redis/redis/v8 v8.11.4 github.com/go-redis/redis/v8 v8.11.4
github.com/go-sourcemap/sourcemap v2.1.3+incompatible github.com/go-sourcemap/sourcemap v2.1.3+incompatible

View File

@@ -6,10 +6,11 @@ import (
"os/exec" "os/exec"
"strings" "strings"
"github.com/urfave/cli/v2"
"github.com/grafana/grafana/pkg/build/config" "github.com/grafana/grafana/pkg/build/config"
"github.com/grafana/grafana/pkg/build/docker" "github.com/grafana/grafana/pkg/build/docker"
"github.com/grafana/grafana/pkg/build/gcloud" "github.com/grafana/grafana/pkg/build/gcloud"
"github.com/urfave/cli/v2"
) )
const ( const (
@@ -20,9 +21,9 @@ const (
func FetchImages(c *cli.Context) error { func FetchImages(c *cli.Context) error {
if c.NArg() > 0 { if c.NArg() > 0 {
if err := cli.ShowSubcommandHelp(c); err != nil { if err := cli.ShowSubcommandHelp(c); err != nil {
return cli.NewExitError(err.Error(), 1) return cli.Exit(err.Error(), 1)
} }
return cli.NewExitError("", 1) return cli.Exit("", 1)
} }
metadata, err := GenerateMetadata(c) metadata, err := GenerateMetadata(c)

View File

@@ -15,11 +15,12 @@ import (
"strings" "strings"
"time" "time"
"github.com/urfave/cli/v2"
"github.com/grafana/grafana/pkg/build/config" "github.com/grafana/grafana/pkg/build/config"
"github.com/grafana/grafana/pkg/build/gcloud" "github.com/grafana/grafana/pkg/build/gcloud"
"github.com/grafana/grafana/pkg/build/gcloud/storage" "github.com/grafana/grafana/pkg/build/gcloud/storage"
"github.com/grafana/grafana/pkg/build/packaging" "github.com/grafana/grafana/pkg/build/packaging"
"github.com/urfave/cli/v2"
) )
const grafanaAPI = "https://grafana.com/api" const grafanaAPI = "https://grafana.com/api"
@@ -66,11 +67,11 @@ func GrafanaCom(c *cli.Context) error {
grafanaAPIKey := strings.TrimSpace(os.Getenv("GRAFANA_COM_API_KEY")) grafanaAPIKey := strings.TrimSpace(os.Getenv("GRAFANA_COM_API_KEY"))
if grafanaAPIKey == "" { if grafanaAPIKey == "" {
return cli.NewExitError("the environment variable GRAFANA_COM_API_KEY must be set", 1) return cli.Exit("the environment variable GRAFANA_COM_API_KEY must be set", 1)
} }
whatsNewURL, releaseNotesURL, err := getReleaseURLs() whatsNewURL, releaseNotesURL, err := getReleaseURLs()
if err != nil { if err != nil {
return cli.NewExitError(err.Error(), 1) return cli.Exit(err.Error(), 1)
} }
// TODO: Verify config values // TODO: Verify config values
@@ -89,7 +90,7 @@ func GrafanaCom(c *cli.Context) error {
} }
if err := publishPackages(cfg); err != nil { if err := publishPackages(cfg); err != nil {
return cli.NewExitError(err.Error(), 1) return cli.Exit(err.Error(), 1)
} }
log.Println("Successfully published packages to grafana.com!") log.Println("Successfully published packages to grafana.com!")

View File

@@ -5,11 +5,12 @@ import (
"log" "log"
"strings" "strings"
"github.com/urfave/cli/v2"
"github.com/grafana/grafana/pkg/build/config" "github.com/grafana/grafana/pkg/build/config"
"github.com/grafana/grafana/pkg/build/gpg" "github.com/grafana/grafana/pkg/build/gpg"
"github.com/grafana/grafana/pkg/build/packaging" "github.com/grafana/grafana/pkg/build/packaging"
"github.com/grafana/grafana/pkg/build/syncutil" "github.com/grafana/grafana/pkg/build/syncutil"
"github.com/urfave/cli/v2"
) )
func Package(c *cli.Context) error { func Package(c *cli.Context) error {
@@ -22,12 +23,12 @@ func Package(c *cli.Context) error {
releaseMode, err := metadata.GetReleaseMode() releaseMode, err := metadata.GetReleaseMode()
if err != nil { if err != nil {
return cli.NewExitError(err.Error(), 1) return cli.Exit(err.Error(), 1)
} }
releaseModeConfig, err := config.GetBuildConfig(metadata.ReleaseMode.Mode) releaseModeConfig, err := config.GetBuildConfig(metadata.ReleaseMode.Mode)
if err != nil { if err != nil {
return cli.NewExitError(err.Error(), 1) return cli.Exit(err.Error(), 1)
} }
cfg := config.Config{ cfg := config.Config{

View File

@@ -6,18 +6,19 @@ import (
"os" "os"
"os/exec" "os/exec"
"github.com/urfave/cli/v2"
"github.com/grafana/grafana/pkg/build/config" "github.com/grafana/grafana/pkg/build/config"
"github.com/grafana/grafana/pkg/build/docker" "github.com/grafana/grafana/pkg/build/docker"
"github.com/grafana/grafana/pkg/build/gcloud" "github.com/grafana/grafana/pkg/build/gcloud"
"github.com/urfave/cli/v2"
) )
func Enterprise2(c *cli.Context) error { func Enterprise2(c *cli.Context) error {
if c.NArg() > 0 { if c.NArg() > 0 {
if err := cli.ShowSubcommandHelp(c); err != nil { if err := cli.ShowSubcommandHelp(c); err != nil {
return cli.NewExitError(err.Error(), 1) return cli.Exit(err.Error(), 1)
} }
return cli.NewExitError("", 1) return cli.Exit("", 1)
} }
if err := gcloud.ActivateServiceAccount(); err != nil { if err := gcloud.ActivateServiceAccount(); err != nil {

View File

@@ -8,8 +8,9 @@ import (
"os" "os"
"regexp" "regexp"
"github.com/grafana/grafana/pkg/build/metrics"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"github.com/grafana/grafana/pkg/build/metrics"
) )
func PublishMetrics(c *cli.Context) error { func PublishMetrics(c *cli.Context) error {
@@ -17,24 +18,24 @@ func PublishMetrics(c *cli.Context) error {
input, err := io.ReadAll(os.Stdin) input, err := io.ReadAll(os.Stdin)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Sprintf("Reading from stdin failed: %s", err), 1) return cli.Exit(fmt.Sprintf("Reading from stdin failed: %s", err), 1)
} }
reMetrics := regexp.MustCompile(`(?ms)^Metrics: (\{.+\})`) reMetrics := regexp.MustCompile(`(?ms)^Metrics: (\{.+\})`)
ms := reMetrics.FindSubmatch(input) ms := reMetrics.FindSubmatch(input)
if len(ms) == 0 { if len(ms) == 0 {
return cli.NewExitError(fmt.Sprintf("Input on wrong format: %q", string(input)), 1) return cli.Exit(fmt.Sprintf("Input on wrong format: %q", string(input)), 1)
} }
m := map[string]string{} m := map[string]string{}
if err := json.Unmarshal(ms[1], &m); err != nil { if err := json.Unmarshal(ms[1], &m); err != nil {
return cli.NewExitError(fmt.Sprintf("decoding metrics failed: %s", err), 1) return cli.Exit(fmt.Sprintf("decoding metrics failed: %s", err), 1)
} }
log.Printf("Received metrics %+v", m) log.Printf("Received metrics %+v", m)
if err := metrics.Publish(m, apiKey); err != nil { if err := metrics.Publish(m, apiKey); err != nil {
return cli.NewExitError(fmt.Sprintf("publishing metrics failed: %s", err), 1) return cli.Exit(fmt.Sprintf("publishing metrics failed: %s", err), 1)
} }
return nil return nil

View File

@@ -6,18 +6,19 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"github.com/urfave/cli/v2"
"github.com/grafana/grafana/pkg/build/config" "github.com/grafana/grafana/pkg/build/config"
"github.com/grafana/grafana/pkg/build/gcloud/storage" "github.com/grafana/grafana/pkg/build/gcloud/storage"
"github.com/urfave/cli/v2"
) )
// UploadCDN implements the sub-command "upload-cdn". // UploadCDN implements the sub-command "upload-cdn".
func UploadCDN(c *cli.Context) error { func UploadCDN(c *cli.Context) error {
if c.NArg() > 0 { if c.NArg() > 0 {
if err := cli.ShowSubcommandHelp(c); err != nil { if err := cli.ShowSubcommandHelp(c); err != nil {
return cli.NewExitError(err.Error(), 1) return cli.Exit(err.Error(), 1)
} }
return cli.NewExitError("", 1) return cli.Exit("", 1)
} }
metadata, err := GenerateMetadata(c) metadata, err := GenerateMetadata(c)
@@ -27,7 +28,7 @@ func UploadCDN(c *cli.Context) error {
version := metadata.GrafanaVersion version := metadata.GrafanaVersion
if err != nil { if err != nil {
return cli.NewExitError(err.Error(), 1) return cli.Exit(err.Error(), 1)
} }
buildConfig, err := config.GetBuildConfig(metadata.ReleaseMode.Mode) buildConfig, err := config.GetBuildConfig(metadata.ReleaseMode.Mode)

View File

@@ -9,10 +9,11 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/urfave/cli/v2"
"github.com/grafana/grafana/pkg/build/config" "github.com/grafana/grafana/pkg/build/config"
"github.com/grafana/grafana/pkg/build/gcloud" "github.com/grafana/grafana/pkg/build/gcloud"
"github.com/grafana/grafana/pkg/build/packaging" "github.com/grafana/grafana/pkg/build/packaging"
"github.com/urfave/cli/v2"
) )
const releaseFolder = "release" const releaseFolder = "release"
@@ -32,24 +33,24 @@ type uploadConfig struct {
func UploadPackages(c *cli.Context) error { func UploadPackages(c *cli.Context) error {
if c.NArg() > 0 { if c.NArg() > 0 {
if err := cli.ShowSubcommandHelp(c); err != nil { if err := cli.ShowSubcommandHelp(c); err != nil {
return cli.NewExitError(err.Error(), 1) return cli.Exit(err.Error(), 1)
} }
return cli.NewExitError("", 1) return cli.Exit("", 1)
} }
gcpKeyB64 := strings.TrimSpace(os.Getenv("GCP_KEY")) gcpKeyB64 := strings.TrimSpace(os.Getenv("GCP_KEY"))
if gcpKeyB64 == "" { if gcpKeyB64 == "" {
return cli.NewExitError("the environment variable GCP_KEY must be set", 1) return cli.Exit("the environment variable GCP_KEY must be set", 1)
} }
gcpKeyB, err := base64.StdEncoding.DecodeString(gcpKeyB64) gcpKeyB, err := base64.StdEncoding.DecodeString(gcpKeyB64)
if err != nil { if err != nil {
return cli.NewExitError("failed to base64 decode $GCP_KEY", 1) return cli.Exit("failed to base64 decode $GCP_KEY", 1)
} }
gcpKey := string(gcpKeyB) gcpKey := string(gcpKeyB)
distDir, err := filepath.Abs("dist") distDir, err := filepath.Abs("dist")
if err != nil { if err != nil {
return cli.NewExitError(err.Error(), 1) return cli.Exit(err.Error(), 1)
} }
metadata, err := GenerateMetadata(c) metadata, err := GenerateMetadata(c)
@@ -61,12 +62,12 @@ func UploadPackages(c *cli.Context) error {
releaseMode, err := metadata.GetReleaseMode() releaseMode, err := metadata.GetReleaseMode()
if err != nil { if err != nil {
return cli.NewExitError(err.Error(), 1) return cli.Exit(err.Error(), 1)
} }
releaseModeConfig, err := config.GetBuildConfig(releaseMode.Mode) releaseModeConfig, err := config.GetBuildConfig(releaseMode.Mode)
if err != nil { if err != nil {
return cli.NewExitError(err.Error(), 1) return cli.Exit(err.Error(), 1)
} }
var edition config.Edition var edition config.Edition
@@ -103,7 +104,7 @@ func UploadPackages(c *cli.Context) error {
} }
if err := uploadPackages(cfg); err != nil { if err := uploadPackages(cfg); err != nil {
return cli.NewExitError(err.Error(), 1) return cli.Exit(err.Error(), 1)
} }
log.Println("Successfully uploaded packages!") log.Println("Successfully uploaded packages!")

View File

@@ -13,10 +13,11 @@ import (
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"github.com/grafana/grafana/pkg/build/fsutil"
cliv1 "github.com/urfave/cli" cliv1 "github.com/urfave/cli"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"github.com/grafana/grafana/pkg/build/fsutil"
) )
func VerifyDrone(c *cli.Context) error { func VerifyDrone(c *cli.Context) error {
@@ -24,7 +25,7 @@ func VerifyDrone(c *cli.Context) error {
const backup = ".drone.yml.bak" const backup = ".drone.yml.bak"
if err := fsutil.CopyFile(yml, backup); err != nil { if err := fsutil.CopyFile(yml, backup); err != nil {
return cli.NewExitError(fmt.Sprintf("failed to copy %s to %s: %s", yml, backup, err), 1) return cli.Exit(fmt.Sprintf("failed to copy %s to %s: %s", yml, backup, err), 1)
} }
defer func() { defer func() {
if err := os.Remove(yml); err != nil { if err := os.Remove(yml); err != nil {
@@ -73,7 +74,7 @@ func readConfig(fpath string) ([]map[string]interface{}, error) {
//nolint:gosec //nolint:gosec
f, err := os.Open(fpath) f, err := os.Open(fpath)
if err != nil { if err != nil {
return nil, cli.NewExitError(fmt.Sprintf("failed to read %s: %s", fpath, err), 1) return nil, cli.Exit(fmt.Sprintf("failed to read %s: %s", fpath, err), 1)
} }
defer func() { defer func() {
if err := f.Close(); err != nil { if err := f.Close(); err != nil {
@@ -90,7 +91,7 @@ func readConfig(fpath string) ([]map[string]interface{}, error) {
if errors.Is(err, io.EOF) { if errors.Is(err, io.EOF) {
break break
} }
return nil, cli.NewExitError(fmt.Sprintf("Failed to decode %s: %s", fpath, err), 1) return nil, cli.Exit(fmt.Sprintf("Failed to decode %s: %s", fpath, err), 1)
} }
if m["kind"] == "signature" { if m["kind"] == "signature" {
@@ -118,7 +119,7 @@ func verifyYAML(yml, backup string) error {
} }
if !cmp.Equal(c1, c2) { if !cmp.Equal(c1, c2) {
return cli.NewExitError(fmt.Sprintf("%s is out of sync with .drone.star - regenerate it with drone starlark convert", return cli.Exit(fmt.Sprintf("%s is out of sync with .drone.star - regenerate it with drone starlark convert",
yml), 1) yml), 1)
} }

View File

@@ -6,8 +6,9 @@ import (
"log" "log"
"path/filepath" "path/filepath"
"github.com/grafana/grafana/pkg/infra/fs"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"github.com/grafana/grafana/pkg/infra/fs"
) )
// VerifyStorybook Action implements the sub-command "verify-storybook". // VerifyStorybook Action implements the sub-command "verify-storybook".
@@ -20,7 +21,7 @@ func VerifyStorybook(c *cli.Context) error {
for _, p := range paths { for _, p := range paths {
exists, err := fs.Exists(filepath.Join(grafanaDir, p)) exists, err := fs.Exists(filepath.Join(grafanaDir, p))
if err != nil { if err != nil {
return cli.NewExitError(fmt.Sprintf("failed to verify Storybook build: %s", err), 1) return cli.Exit(fmt.Sprintf("failed to verify Storybook build: %s", err), 1)
} }
if !exists { if !exists {
return fmt.Errorf("failed to verify Storybook build, missing %q", p) return fmt.Errorf("failed to verify Storybook build, missing %q", p)

View File

@@ -8,10 +8,11 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/urfave/cli/v2"
"github.com/grafana/grafana/pkg/build/config" "github.com/grafana/grafana/pkg/build/config"
"github.com/grafana/grafana/pkg/build/fsutil" "github.com/grafana/grafana/pkg/build/fsutil"
"github.com/grafana/grafana/pkg/infra/fs" "github.com/grafana/grafana/pkg/infra/fs"
"github.com/urfave/cli/v2"
) )
func writeAptlyConf(dbDir, repoDir string) error { func writeAptlyConf(dbDir, repoDir string) error {
@@ -166,7 +167,7 @@ func UpdateDebRepo(cfg PublishConfig, workDir string) error {
cmd := exec.Command("aptly", "publish", "update", "-batch", passArg, "-force-overwrite", tp, cmd := exec.Command("aptly", "publish", "update", "-batch", passArg, "-force-overwrite", tp,
"filesystem:repo:grafana") "filesystem:repo:grafana")
if output, err := cmd.CombinedOutput(); err != nil { if output, err := cmd.CombinedOutput(); err != nil {
return cli.NewExitError(fmt.Sprintf("failed to update Debian %q repository: %s", tp, output), 1) return cli.Exit(fmt.Sprintf("failed to update Debian %q repository: %s", tp, output), 1)
} }
} }
@@ -179,7 +180,7 @@ func UpdateDebRepo(cfg PublishConfig, workDir string) error {
//nolint:gosec //nolint:gosec
cmd = exec.Command("gsutil", "-m", "rsync", "-r", "-d", dbDir, u) cmd = exec.Command("gsutil", "-m", "rsync", "-r", "-d", dbDir, u)
if output, err := cmd.CombinedOutput(); err != nil { if output, err := cmd.CombinedOutput(); err != nil {
return cli.NewExitError(fmt.Sprintf("failed to upload Debian repo database to GCS: %s", output), 1) return cli.Exit(fmt.Sprintf("failed to upload Debian repo database to GCS: %s", output), 1)
} }
} }
@@ -193,14 +194,14 @@ func UpdateDebRepo(cfg PublishConfig, workDir string) error {
//nolint:gosec //nolint:gosec
cmd = exec.Command("gsutil", "-m", "rsync", "-r", "-d", grafDir, u) cmd = exec.Command("gsutil", "-m", "rsync", "-r", "-d", grafDir, u)
if output, err := cmd.CombinedOutput(); err != nil { if output, err := cmd.CombinedOutput(); err != nil {
return cli.NewExitError(fmt.Sprintf("failed to upload Debian repo resources to GCS: %s", output), 1) return cli.Exit(fmt.Sprintf("failed to upload Debian repo resources to GCS: %s", output), 1)
} }
allRepoResources := fmt.Sprintf("%s/**/*", u) allRepoResources := fmt.Sprintf("%s/**/*", u)
log.Printf("Setting cache ttl for Debian repo resources on GCS (%s)...\n", allRepoResources) log.Printf("Setting cache ttl for Debian repo resources on GCS (%s)...\n", allRepoResources)
//nolint:gosec //nolint:gosec
cmd = exec.Command("gsutil", "-m", "setmeta", "-h", CacheSettings+cfg.TTL, allRepoResources) cmd = exec.Command("gsutil", "-m", "setmeta", "-h", CacheSettings+cfg.TTL, allRepoResources)
if output, err := cmd.CombinedOutput(); err != nil { if output, err := cmd.CombinedOutput(); err != nil {
return cli.NewExitError(fmt.Sprintf("failed to set cache ttl for Debian repo resources on GCS: %s", output), 1) return cli.Exit(fmt.Sprintf("failed to set cache ttl for Debian repo resources on GCS: %s", output), 1)
} }
} }
@@ -238,7 +239,7 @@ func addPkgsToRepo(cfg PublishConfig, workDir, tmpDir, repoName string) error {
//nolint:gosec //nolint:gosec
cmd := exec.Command("aptly", "repo", "add", "-force-replace", repoName, tmpDir) cmd := exec.Command("aptly", "repo", "add", "-force-replace", repoName, tmpDir)
if output, err := cmd.CombinedOutput(); err != nil { if output, err := cmd.CombinedOutput(); err != nil {
return cli.NewExitError(fmt.Sprintf("failed to add packages to local Debian repository: %s", output), 1) return cli.Exit(fmt.Sprintf("failed to add packages to local Debian repository: %s", output), 1)
} }
return nil return nil

View File

@@ -10,12 +10,15 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
// Consider switching this over to a community fork unless there is
// an option to move us away from OpenPGP.
"golang.org/x/crypto/openpgp" //nolint:staticcheck
"golang.org/x/crypto/openpgp/armor" //nolint:staticcheck
"golang.org/x/crypto/openpgp/packet" //nolint:staticcheck
"github.com/grafana/grafana/pkg/build/config" "github.com/grafana/grafana/pkg/build/config"
"github.com/grafana/grafana/pkg/build/fsutil" "github.com/grafana/grafana/pkg/build/fsutil"
"github.com/grafana/grafana/pkg/infra/fs" "github.com/grafana/grafana/pkg/infra/fs"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/armor"
"golang.org/x/crypto/openpgp/packet"
) )
// UpdateRPMRepo updates the RPM repository with the new release. // UpdateRPMRepo updates the RPM repository with the new release.

View File

@@ -6,7 +6,7 @@ import (
"io" "io"
"reflect" "reflect"
gokitlog "github.com/go-kit/kit/log" gokitlog "github.com/go-kit/log"
) )
type textLogger struct { type textLogger struct {

View File

@@ -364,6 +364,9 @@ func TestService_GetHttpTransport(t *testing.T) {
tr := configuredTransport tr := configuredTransport
require.False(t, tr.TLSClientConfig.InsecureSkipVerify) require.False(t, tr.TLSClientConfig.InsecureSkipVerify)
// Ignoring deprecation, the system will not include the root CA
// used in this scenario.
//nolint:staticcheck
require.Len(t, tr.TLSClientConfig.RootCAs.Subjects(), 1) require.Len(t, tr.TLSClientConfig.RootCAs.Subjects(), 1)
require.Equal(t, "server-name", tr.TLSClientConfig.ServerName) require.Equal(t, "server-name", tr.TLSClientConfig.ServerName)
}) })

View File

@@ -251,7 +251,7 @@ func (mg *Migrator) InTransaction(callback dbTransactionFunc) error {
} }
func casRestoreOnErr(lock *atomic.Bool, o, n bool, casErr error, f func(LockCfg) error, lockCfg LockCfg) error { func casRestoreOnErr(lock *atomic.Bool, o, n bool, casErr error, f func(LockCfg) error, lockCfg LockCfg) error {
if !lock.CAS(o, n) { if !lock.CompareAndSwap(o, n) {
return casErr return casErr
} }
if err := f(lockCfg); err != nil { if err := f(lockCfg); err != nil {