fix(errors): wrap db errors, improve error handling (#8859)

* use error check func, wrap db object not found

* add errorlint and fix all the linting errors

* add exportloopref linter and fix errors

* fix incorrect error details returned on an api

* fix new errors

* increase linter timeout

* increase timeout to 10minutes

* increase timeout to 10minutes

* rebase and fix new lint errors

* make CE match EE

* fix govet issue
pull/8906/head
Matt Hook 2023-05-05 12:19:47 +12:00 committed by GitHub
parent 550e235d59
commit 334eee0c8c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 158 additions and 115 deletions

View File

@ -21,13 +21,12 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/setup-node@v2 - uses: actions/setup-node@v2
with: with:
node-version: '14' node-version: '18'
cache: 'yarn' cache: 'yarn'
- uses: actions/setup-go@v3 - uses: actions/setup-go@v4
with: with:
go-version: 1.19.4 go-version: 1.19.5
- run: yarn --frozen-lockfile - run: yarn --frozen-lockfile
- name: Run linters - name: Run linters
uses: wearerequired/lint-action@v1 uses: wearerequired/lint-action@v1
with: with:
@ -44,4 +43,4 @@ jobs:
with: with:
version: latest version: latest
working-directory: api working-directory: api
args: -c .golangci.yaml args: --timeout=10m -c .golangci.yaml

View File

@ -1,8 +1,13 @@
linters: linters:
# Disable all linters. # Disable all linters, the defaults don't pass on our code yet
disable-all: true disable-all: true
# Enable these for now
enable: enable:
- depguard - depguard
- govet
- errorlint
- exportloopref
linters-settings: linters-settings:
depguard: depguard:
list-type: denylist list-type: denylist
@ -13,14 +18,12 @@ linters-settings:
packages-with-error-message: packages-with-error-message:
- github.com/sirupsen/logrus: 'logging is allowed only by github.com/rs/zerolog' - github.com/sirupsen/logrus: 'logging is allowed only by github.com/rs/zerolog'
ignore-file-rules: ignore-file-rules:
- "**/*_test.go" - '**/*_test.go'
# Create additional guards that follow the same configuration pattern.
# Results from all guards are aggregated together. # errorlint is causing a typecheck error for some reason. The go compiler will report these
# additional-guards: # anyway, so ignore them from the linter
# - list-type: allowlist issues:
# include-go-root: false exclude-rules:
# packages: - path: ./
# - github.com/sirupsen/logrus linters:
# # Specify rules by which the linter ignores certain files for consideration. - typecheck
# ignore-file-rules:
# - "!**/*_test.go"

View File

@ -3,6 +3,7 @@ package archive
import ( import (
"archive/tar" "archive/tar"
"compress/gzip" "compress/gzip"
"errors"
"fmt" "fmt"
"io" "io"
"os" "os"
@ -84,7 +85,7 @@ func ExtractTarGz(r io.Reader, outputDirPath string) error {
for { for {
header, err := tarReader.Next() header, err := tarReader.Next()
if err == io.EOF { if errors.Is(err, io.EOF) {
break break
} }
@ -109,7 +110,7 @@ func ExtractTarGz(r io.Reader, outputDirPath string) error {
} }
outFile.Close() outFile.Close()
default: default:
return fmt.Errorf("Tar: uknown type: %v in %s", return fmt.Errorf("tar: unknown type: %v in %s",
header.Typeflag, header.Typeflag,
header.Name) header.Name)
} }

View File

@ -5,6 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"math"
"os" "os"
"path" "path"
"time" "time"
@ -182,7 +183,7 @@ func (connection *DbConnection) BackupTo(w io.Writer) error {
func (connection *DbConnection) ExportRaw(filename string) error { func (connection *DbConnection) ExportRaw(filename string) error {
databasePath := connection.GetDatabaseFilePath() databasePath := connection.GetDatabaseFilePath()
if _, err := os.Stat(databasePath); err != nil { if _, err := os.Stat(databasePath); err != nil {
return fmt.Errorf("stat on %s failed: %s", databasePath, err) return fmt.Errorf("stat on %s failed, error: %w", databasePath, err)
} }
b, err := connection.ExportJSON(databasePath, true) b, err := connection.ExportJSON(databasePath, true)
@ -201,6 +202,20 @@ func (connection *DbConnection) ConvertToKey(v int) []byte {
return b return b
} }
// keyToString Converts a key to a string value suitable for logging
func keyToString(b []byte) string {
if len(b) != 8 {
return string(b)
}
v := binary.BigEndian.Uint64(b)
if v <= math.MaxInt32 {
return fmt.Sprintf("%d", v)
}
return string(b)
}
// CreateBucket is a generic function used to create a bucket inside a database. // CreateBucket is a generic function used to create a bucket inside a database.
func (connection *DbConnection) SetServiceName(bucketName string) error { func (connection *DbConnection) SetServiceName(bucketName string) error {
return connection.UpdateTx(func(tx portainer.Transaction) error { return connection.UpdateTx(func(tx portainer.Transaction) error {
@ -237,7 +252,7 @@ func (connection *DbConnection) UpdateObjectFunc(bucketName string, key []byte,
data := bucket.Get(key) data := bucket.Get(key)
if data == nil { if data == nil {
return dserrors.ErrObjectNotFound return fmt.Errorf("%w (bucket=%s, key=%s)", dserrors.ErrObjectNotFound, bucketName, keyToString(key))
} }
err := connection.UnmarshalObjectWithJsoniter(data, object) err := connection.UnmarshalObjectWithJsoniter(data, object)

View File

@ -2,6 +2,7 @@ package boltdb
import ( import (
"bytes" "bytes"
"fmt"
dserrors "github.com/portainer/portainer/api/dataservices/errors" dserrors "github.com/portainer/portainer/api/dataservices/errors"
@ -24,7 +25,7 @@ func (tx *DbTransaction) GetObject(bucketName string, key []byte, object interfa
value := bucket.Get(key) value := bucket.Get(key)
if value == nil { if value == nil {
return dserrors.ErrObjectNotFound return fmt.Errorf("%w (bucket=%s, key=%s)", dserrors.ErrObjectNotFound, bucketName, keyToString(key))
} }
data := make([]byte, len(value)) data := make([]byte, len(value))
@ -74,7 +75,6 @@ func (tx *DbTransaction) GetNextIdentifier(bucketName string) int {
id, err := bucket.NextSequence() id, err := bucket.NextSequence()
if err != nil { if err != nil {
log.Error().Err(err).Str("bucket", bucketName).Msg("failed to get the next identifer") log.Error().Err(err).Str("bucket", bucketName).Msg("failed to get the next identifer")
return 0 return 0
} }

View File

@ -5,7 +5,7 @@ import (
"testing" "testing"
portainer "github.com/portainer/portainer/api" portainer "github.com/portainer/portainer/api"
dserrors "github.com/portainer/portainer/api/dataservices/errors" "github.com/portainer/portainer/api/dataservices"
) )
const testBucketName = "test-bucket" const testBucketName = "test-bucket"
@ -97,7 +97,7 @@ func TestTxs(t *testing.T) {
err = conn.ViewTx(func(tx portainer.Transaction) error { err = conn.ViewTx(func(tx portainer.Transaction) error {
return tx.GetObject(testBucketName, conn.ConvertToKey(testId), &obj) return tx.GetObject(testBucketName, conn.ConvertToKey(testId), &obj)
}) })
if err != dserrors.ErrObjectNotFound { if !dataservices.IsErrObjectNotFound(err) {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -2,10 +2,11 @@ package apikeyrepository
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
portainer "github.com/portainer/portainer/api" portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices/errors" dserrors "github.com/portainer/portainer/api/dataservices/errors"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@ -78,12 +79,12 @@ func (service *Service) GetAPIKeyByDigest(digest []byte) (*portainer.APIKey, err
return &portainer.APIKey{}, nil return &portainer.APIKey{}, nil
}) })
if err == stop { if errors.Is(err, stop) {
return k, nil return k, nil
} }
if err == nil { if err == nil {
return nil, errors.ErrObjectNotFound return nil, dserrors.ErrObjectNotFound
} }
return nil, err return nil, err

View File

@ -1,9 +1,10 @@
package errors package errors
import "errors" import (
"errors"
)
var ( var (
// TODO: i'm pretty sure this needs wrapping at several levels
ErrObjectNotFound = errors.New("object not found inside the database") ErrObjectNotFound = errors.New("object not found inside the database")
ErrWrongDBEdition = errors.New("the Portainer database is set for Portainer Business Edition, please follow the instructions in our documentation to downgrade it: https://documentation.portainer.io/v2.0-be/downgrade/be-to-ce/") ErrWrongDBEdition = errors.New("the Portainer database is set for Portainer Business Edition, please follow the instructions in our documentation to downgrade it: https://documentation.portainer.io/v2.0-be/downgrade/be-to-ce/")
ErrDBImportFailed = errors.New("importing backup failed") ErrDBImportFailed = errors.New("importing backup failed")

View File

@ -1,15 +1,13 @@
package dataservices package dataservices
// "github.com/portainer/portainer/api/dataservices"
import ( import (
"errors"
"io" "io"
"time" "time"
"github.com/portainer/portainer/api/database/models"
"github.com/portainer/portainer/api/dataservices/errors"
portainer "github.com/portainer/portainer/api" portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/database/models"
dserrors "github.com/portainer/portainer/api/dataservices/errors"
) )
type ( type (
@ -325,5 +323,5 @@ type (
) )
func IsErrObjectNotFound(e error) bool { func IsErrObjectNotFound(e error) bool {
return e == errors.ErrObjectNotFound return errors.Is(e, dserrors.ErrObjectNotFound)
} }

View File

@ -1,6 +1,7 @@
package resourcecontrol package resourcecontrol
import ( import (
"errors"
"fmt" "fmt"
portainer "github.com/portainer/portainer/api" portainer "github.com/portainer/portainer/api"
@ -82,7 +83,7 @@ func (service *Service) ResourceControlByResourceIDAndType(resourceID string, re
return &portainer.ResourceControl{}, nil return &portainer.ResourceControl{}, nil
}) })
if err == stop { if errors.Is(err, stop) {
return resourceControl, nil return resourceControl, nil
} }

View File

@ -1,6 +1,7 @@
package resourcecontrol package resourcecontrol
import ( import (
"errors"
"fmt" "fmt"
portainer "github.com/portainer/portainer/api" portainer "github.com/portainer/portainer/api"
@ -60,7 +61,7 @@ func (service ServiceTx) ResourceControlByResourceIDAndType(resourceID string, r
return &portainer.ResourceControl{}, nil return &portainer.ResourceControl{}, nil
}) })
if err == stop { if errors.Is(err, stop) {
return resourceControl, nil return resourceControl, nil
} }

View File

@ -1,11 +1,12 @@
package stack package stack
import ( import (
"errors"
"fmt" "fmt"
"strings" "strings"
portainer "github.com/portainer/portainer/api" portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices/errors" dserrors "github.com/portainer/portainer/api/dataservices/errors"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@ -71,11 +72,11 @@ func (service *Service) StackByName(name string) (*portainer.Stack, error) {
return &portainer.Stack{}, nil return &portainer.Stack{}, nil
}) })
if err == stop { if errors.Is(err, stop) {
return s, nil return s, nil
} }
if err == nil { if err == nil {
return nil, errors.ErrObjectNotFound return nil, dserrors.ErrObjectNotFound
} }
return nil, err return nil, err
@ -92,7 +93,7 @@ func (service *Service) StacksByName(name string) ([]portainer.Stack, error) {
stack, ok := obj.(portainer.Stack) stack, ok := obj.(portainer.Stack)
if !ok { if !ok {
log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Stack object") log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Stack object")
return nil, fmt.Errorf("Failed to convert to Stack object: %s", obj) return nil, fmt.Errorf("failed to convert to Stack object: %s", obj)
} }
if stack.Name == name { if stack.Name == name {
@ -173,11 +174,11 @@ func (service *Service) StackByWebhookID(id string) (*portainer.Stack, error) {
return &portainer.Stack{}, nil return &portainer.Stack{}, nil
}) })
if err == stop { if errors.Is(err, stop) {
return s, nil return s, nil
} }
if err == nil { if err == nil {
return nil, errors.ErrObjectNotFound return nil, dserrors.ErrObjectNotFound
} }
return nil, err return nil, err

View File

@ -59,7 +59,7 @@ func (b *stackBuilder) createNewStack(webhookID string) portainer.Stack {
Type: portainer.DockerComposeStack, Type: portainer.DockerComposeStack,
EndpointID: 2, EndpointID: 2,
EntryPoint: filesystem.ComposeFileDefaultName, EntryPoint: filesystem.ComposeFileDefaultName,
Env: []portainer.Pair{{"Name1", "Value1"}}, Env: []portainer.Pair{{Name: "Name1", Value: "Value1"}},
Status: portainer.StackStatusActive, Status: portainer.StackStatusActive,
CreationDate: time.Now().Unix(), CreationDate: time.Now().Unix(),
ProjectPath: "/tmp/project", ProjectPath: "/tmp/project",

View File

@ -1,11 +1,12 @@
package team package team
import ( import (
"errors"
"fmt" "fmt"
"strings" "strings"
portainer "github.com/portainer/portainer/api" portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices/errors" dserrors "github.com/portainer/portainer/api/dataservices/errors"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@ -71,11 +72,11 @@ func (service *Service) TeamByName(name string) (*portainer.Team, error) {
return &portainer.Team{}, nil return &portainer.Team{}, nil
}) })
if err == stop { if errors.Is(err, stop) {
return t, nil return t, nil
} }
if err == nil { if err == nil {
return nil, errors.ErrObjectNotFound return nil, dserrors.ErrObjectNotFound
} }
return nil, err return nil, err

View File

@ -1,11 +1,12 @@
package user package user
import ( import (
"errors"
"fmt" "fmt"
"strings" "strings"
portainer "github.com/portainer/portainer/api" portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices/errors" dserrors "github.com/portainer/portainer/api/dataservices/errors"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@ -72,12 +73,12 @@ func (service *Service) UserByUsername(username string) (*portainer.User, error)
return &portainer.User{}, nil return &portainer.User{}, nil
}) })
if err == stop { if errors.Is(err, stop) {
return u, nil return u, nil
} }
if err == nil { if err == nil {
return nil, errors.ErrObjectNotFound return nil, dserrors.ErrObjectNotFound
} }
return nil, err return nil, err

View File

@ -1,10 +1,11 @@
package webhook package webhook
import ( import (
"errors"
"fmt" "fmt"
portainer "github.com/portainer/portainer/api" portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices/errors" dserrors "github.com/portainer/portainer/api/dataservices/errors"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@ -93,12 +94,12 @@ func (service *Service) WebhookByResourceID(ID string) (*portainer.Webhook, erro
return &portainer.Webhook{}, nil return &portainer.Webhook{}, nil
}) })
if err == stop { if errors.Is(err, stop) {
return w, nil return w, nil
} }
if err == nil { if err == nil {
return nil, errors.ErrObjectNotFound return nil, dserrors.ErrObjectNotFound
} }
return nil, err return nil, err
@ -127,12 +128,12 @@ func (service *Service) WebhookByToken(token string) (*portainer.Webhook, error)
return &portainer.Webhook{}, nil return &portainer.Webhook{}, nil
}) })
if err == stop { if errors.Is(err, stop) {
return w, nil return w, nil
} }
if err == nil { if err == nil {
return nil, errors.ErrObjectNotFound return nil, dserrors.ErrObjectNotFound
} }
return nil, err return nil, err

View File

@ -115,7 +115,7 @@ func (store *Store) backupWithOptions(options *BackupOptions) (string, error) {
if err := store.Close(); err != nil { if err := store.Close(); err != nil {
return options.BackupPath, fmt.Errorf( return options.BackupPath, fmt.Errorf(
"error closing datastore before creating backup: %v", "error closing datastore before creating backup: %w",
err, err,
) )
} }
@ -126,7 +126,7 @@ func (store *Store) backupWithOptions(options *BackupOptions) (string, error) {
if _, err := store.Open(); err != nil { if _, err := store.Open(); err != nil {
return options.BackupPath, fmt.Errorf( return options.BackupPath, fmt.Errorf(
"error opening datastore after creating backup: %v", "error opening datastore after creating backup: %w",
err, err,
) )
} }

View File

@ -1,6 +1,7 @@
package datastore package datastore
import ( import (
"errors"
"fmt" "fmt"
"io" "io"
"os" "os"
@ -104,7 +105,7 @@ func (store *Store) edition() portainer.SoftwareEdition {
// TODO: move the use of this to dataservices.IsErrObjectNotFound()? // TODO: move the use of this to dataservices.IsErrObjectNotFound()?
func (store *Store) IsErrObjectNotFound(e error) bool { func (store *Store) IsErrObjectNotFound(e error) bool {
return e == portainerErrors.ErrObjectNotFound return errors.Is(e, portainerErrors.ErrObjectNotFound)
} }
func (store *Store) Connection() portainer.Connection { func (store *Store) Connection() portainer.Connection {

View File

@ -288,7 +288,7 @@ func migrateDBTestHelper(t *testing.T, srcPath, wantPath string, overrideInstanc
// Convert database back to json. // Convert database back to json.
databasePath := con.GetDatabaseFilePath() databasePath := con.GetDatabaseFilePath()
if _, err := os.Stat(databasePath); err != nil { if _, err := os.Stat(databasePath); err != nil {
return fmt.Errorf("stat on %s failed: %s", databasePath, err) return fmt.Errorf("stat on %s failed: %w", databasePath, err)
} }
gotJSON, err := con.ExportJSON(databasePath, false) gotJSON, err := con.ExportJSON(databasePath, false)

View File

@ -2,7 +2,7 @@ package migrator
import ( import (
portainer "github.com/portainer/portainer/api" portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices/errors" "github.com/portainer/portainer/api/dataservices"
"github.com/portainer/portainer/api/stacks/stackutils" "github.com/portainer/portainer/api/stacks/stackutils"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
@ -25,7 +25,7 @@ func (m *Migrator) updateStackResourceControlToDB27() error {
stack, err := m.stackService.StackByName(stackName) stack, err := m.stackService.StackByName(stackName)
if err != nil { if err != nil {
if err == errors.ErrObjectNotFound { if dataservices.IsErrObjectNotFound(err) {
continue continue
} }

View File

@ -4,7 +4,7 @@ import (
"fmt" "fmt"
portainer "github.com/portainer/portainer/api" portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices/errors" "github.com/portainer/portainer/api/dataservices"
"github.com/portainer/portainer/api/internal/endpointutils" "github.com/portainer/portainer/api/internal/endpointutils"
snapshotutils "github.com/portainer/portainer/api/internal/snapshot" snapshotutils "github.com/portainer/portainer/api/internal/snapshot"
@ -86,7 +86,7 @@ func (m *Migrator) updateDockerhubToDB32() error {
log.Info().Msg("updating dockerhub") log.Info().Msg("updating dockerhub")
dockerhub, err := m.dockerhubService.DockerHub() dockerhub, err := m.dockerhubService.DockerHub()
if err == errors.ErrObjectNotFound { if dataservices.IsErrObjectNotFound(err) {
return nil return nil
} else if err != nil { } else if err != nil {
return err return err

View File

@ -1,7 +1,7 @@
package migrator package migrator
import ( import (
"github.com/portainer/portainer/api/dataservices/errors" "github.com/portainer/portainer/api/dataservices"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@ -19,7 +19,7 @@ func (m *Migrator) migrateDBVersionToDB71() error {
if err == nil { if err == nil {
log.Debug().Int("endpoint_id", int(s.EndpointID)).Msg("keeping snapshot") log.Debug().Int("endpoint_id", int(s.EndpointID)).Msg("keeping snapshot")
continue continue
} else if err != errors.ErrObjectNotFound { } else if !dataservices.IsErrObjectNotFound(err) {
log.Debug().Int("endpoint_id", int(s.EndpointID)).Err(err).Msg("database error") log.Debug().Int("endpoint_id", int(s.EndpointID)).Err(err).Msg("database error")
return err return err
} }

View File

@ -4,7 +4,7 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
portainer "github.com/portainer/portainer/api" portainer "github.com/portainer/portainer/api"
portainerDsErrors "github.com/portainer/portainer/api/dataservices/errors" "github.com/portainer/portainer/api/dataservices"
) )
func (m *Migrator) migrateDBVersionToDB90() error { func (m *Migrator) migrateDBVersionToDB90() error {
@ -30,7 +30,7 @@ func (m *Migrator) updateEdgeStackStatusForDB90() error {
for _, edgeJob := range edgeJobs { for _, edgeJob := range edgeJobs {
for endpointId := range edgeJob.Endpoints { for endpointId := range edgeJob.Endpoints {
_, err := m.endpointService.Endpoint(endpointId) _, err := m.endpointService.Endpoint(endpointId)
if err == portainerDsErrors.ErrObjectNotFound { if dataservices.IsErrObjectNotFound(err) {
delete(edgeJob.Endpoints, endpointId) delete(edgeJob.Endpoints, endpointId)
err = m.edgeJobService.UpdateEdgeJob(edgeJob.ID, &edgeJob) err = m.edgeJobService.UpdateEdgeJob(edgeJob.ID, &edgeJob)

View File

@ -178,6 +178,7 @@ func snapshotContainers(snapshot *portainer.DockerSnapshot, cli *client.Client)
} else { } else {
var gpuOptions *_container.DeviceRequest = nil var gpuOptions *_container.DeviceRequest = nil
for _, deviceRequest := range response.HostConfig.Resources.DeviceRequests { for _, deviceRequest := range response.HostConfig.Resources.DeviceRequests {
deviceRequest := deviceRequest
if deviceRequest.Driver == "nvidia" || deviceRequest.Capabilities[0][0] == "gpu" { if deviceRequest.Driver == "nvidia" || deviceRequest.Capabilities[0][0] == "gpu" {
gpuOptions = &deviceRequest gpuOptions = &deviceRequest
} }

View File

@ -53,7 +53,7 @@ func CloneWithBackup(gitService portainer.GitService, fileService portainer.File
log.Warn().Err(restoreError).Msg("failed restoring backup folder") log.Warn().Err(restoreError).Msg("failed restoring backup folder")
} }
if err == gittypes.ErrAuthenticationFailure { if errors.Is(err, gittypes.ErrAuthenticationFailure) {
return cleanFn, errors.WithMessage(err, ErrInvalidGitCredential.Error()) return cleanFn, errors.WithMessage(err, ErrInvalidGitCredential.Error())
} }

View File

@ -1,6 +1,7 @@
package edgegroups package edgegroups
import ( import (
"errors"
"net/http" "net/http"
httperror "github.com/portainer/libhttp/error" httperror "github.com/portainer/libhttp/error"
@ -39,8 +40,9 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
func txResponse(w http.ResponseWriter, r any, err error) *httperror.HandlerError { func txResponse(w http.ResponseWriter, r any, err error) *httperror.HandlerError {
if err != nil { if err != nil {
if httpErr, ok := err.(*httperror.HandlerError); ok { var handlerError *httperror.HandlerError
return httpErr if errors.As(err, &handlerError) {
return handlerError
} }
return httperror.InternalServerError("Unexpected error", err) return httperror.InternalServerError("Unexpected error", err)

View File

@ -1,6 +1,7 @@
package edgejobs package edgejobs
import ( import (
"errors"
"net/http" "net/http"
"strconv" "strconv"
@ -42,8 +43,9 @@ func (handler *Handler) edgeJobDelete(w http.ResponseWriter, r *http.Request) *h
} }
if err != nil { if err != nil {
if httpErr, ok := err.(*httperror.HandlerError); ok { var handlerError *httperror.HandlerError
return httpErr if errors.As(err, &handlerError) {
return handlerError
} }
return httperror.InternalServerError("Unexpected error", err) return httperror.InternalServerError("Unexpected error", err)

View File

@ -1,6 +1,7 @@
package edgejobs package edgejobs
import ( import (
"errors"
"net/http" "net/http"
"strconv" "strconv"
@ -75,8 +76,9 @@ func (handler *Handler) edgeJobTasksClear(w http.ResponseWriter, r *http.Request
} }
if err != nil { if err != nil {
if httpErr, ok := err.(*httperror.HandlerError); ok { var handlerError *httperror.HandlerError
return httpErr if errors.As(err, &handlerError) {
return handlerError
} }
return httperror.InternalServerError("Unexpected error", err) return httperror.InternalServerError("Unexpected error", err)

View File

@ -1,6 +1,7 @@
package edgejobs package edgejobs
import ( import (
"errors"
"net/http" "net/http"
httperror "github.com/portainer/libhttp/error" httperror "github.com/portainer/libhttp/error"
@ -83,8 +84,9 @@ func (handler *Handler) edgeJobTasksCollect(w http.ResponseWriter, r *http.Reque
}) })
if err != nil { if err != nil {
if httpErr, ok := err.(*httperror.HandlerError); ok { var handlerError *httperror.HandlerError
return httpErr if errors.As(err, &handlerError) {
return handlerError
} }
return httperror.InternalServerError("Unexpected error", err) return httperror.InternalServerError("Unexpected error", err)

View File

@ -1,6 +1,7 @@
package edgejobs package edgejobs
import ( import (
"errors"
"net/http" "net/http"
httperror "github.com/portainer/libhttp/error" httperror "github.com/portainer/libhttp/error"
@ -62,8 +63,9 @@ func convertEndpointsToMetaObject(endpoints []portainer.EndpointID) map[portaine
func txResponse(w http.ResponseWriter, r any, err error) *httperror.HandlerError { func txResponse(w http.ResponseWriter, r any, err error) *httperror.HandlerError {
if err != nil { if err != nil {
if httpErr, ok := err.(*httperror.HandlerError); ok { var handlerError *httperror.HandlerError
return httpErr if errors.As(err, &handlerError) {
return handlerError
} }
return httperror.InternalServerError("Unexpected error", err) return httperror.InternalServerError("Unexpected error", err)

View File

@ -153,8 +153,8 @@ func registryAccessPoliciesContainsNamespace(registryAccess portainer.RegistryAc
func (handler *Handler) filterKubernetesRegistriesByUserRole(r *http.Request, registries []portainer.Registry, endpoint *portainer.Endpoint, user *portainer.User) ([]portainer.Registry, *httperror.HandlerError) { func (handler *Handler) filterKubernetesRegistriesByUserRole(r *http.Request, registries []portainer.Registry, endpoint *portainer.Endpoint, user *portainer.User) ([]portainer.Registry, *httperror.HandlerError) {
err := handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint) err := handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint)
if err == security.ErrAuthorizationRequired { if errors.Is(err, security.ErrAuthorizationRequired) {
return nil, httperror.Forbidden("User is not authorized", errors.New("missing namespace query parameter")) return nil, httperror.Forbidden("User is not authorized", err)
} }
if err != nil { if err != nil {
return nil, httperror.InternalServerError("Unable to retrieve info from request context", err) return nil, httperror.InternalServerError("Unable to retrieve info from request context", err)

View File

@ -29,7 +29,7 @@ type repositoryFilePreviewPayload struct {
func (payload *repositoryFilePreviewPayload) Validate(r *http.Request) error { func (payload *repositoryFilePreviewPayload) Validate(r *http.Request) error {
if govalidator.IsNull(payload.Repository) || !govalidator.IsURL(payload.Repository) { if govalidator.IsNull(payload.Repository) || !govalidator.IsURL(payload.Repository) {
return errors.New("Invalid repository URL. Must correspond to a valid URL format") return errors.New("invalid repository URL. Must correspond to a valid URL format")
} }
if govalidator.IsNull(payload.Reference) { if govalidator.IsNull(payload.Reference) {
@ -37,7 +37,7 @@ func (payload *repositoryFilePreviewPayload) Validate(r *http.Request) error {
} }
if govalidator.IsNull(payload.TargetFile) { if govalidator.IsNull(payload.TargetFile) {
return errors.New("Invalid target filename.") return errors.New("invalid target filename")
} }
return nil return nil
@ -70,11 +70,11 @@ func (handler *Handler) gitOperationRepoFilePreview(w http.ResponseWriter, r *ht
err = handler.gitService.CloneRepository(projectPath, payload.Repository, payload.Reference, payload.Username, payload.Password, payload.TLSSkipVerify) err = handler.gitService.CloneRepository(projectPath, payload.Repository, payload.Reference, payload.Username, payload.Password, payload.TLSSkipVerify)
if err != nil { if err != nil {
if err == gittypes.ErrAuthenticationFailure { if errors.Is(err, gittypes.ErrAuthenticationFailure) {
return httperror.BadRequest("Invalid git credential", err) return httperror.BadRequest("Invalid git credential", err)
} }
newErr := fmt.Errorf("unable to clone git repository: %w", err) newErr := fmt.Errorf("unable to clone git repository, error: %w", err)
return httperror.InternalServerError(newErr.Error(), newErr) return httperror.InternalServerError(newErr.Error(), newErr)
} }

View File

@ -9,7 +9,6 @@ import (
"github.com/portainer/libhttp/request" "github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response" "github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api" portainer "github.com/portainer/portainer/api"
bolterrors "github.com/portainer/portainer/api/dataservices/errors"
"github.com/portainer/portainer/api/internal/endpointutils" "github.com/portainer/portainer/api/internal/endpointutils"
) )
@ -33,7 +32,7 @@ func (handler *Handler) openAMTActivate(w http.ResponseWriter, r *http.Request)
} }
endpoint, err := handler.DataStore.Endpoint().Endpoint(portainer.EndpointID(endpointID)) endpoint, err := handler.DataStore.Endpoint().Endpoint(portainer.EndpointID(endpointID))
if err == bolterrors.ErrObjectNotFound { if handler.DataStore.IsErrObjectNotFound(err) {
return httperror.NotFound("Unable to find an endpoint with the specified identifier inside the database", err) return httperror.NotFound("Unable to find an endpoint with the specified identifier inside the database", err)
} else if err != nil { } else if err != nil {
return httperror.InternalServerError("Unable to find an endpoint with the specified identifier inside the database", err) return httperror.InternalServerError("Unable to find an endpoint with the specified identifier inside the database", err)

View File

@ -8,7 +8,6 @@ import (
"github.com/portainer/libhttp/request" "github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response" "github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api" portainer "github.com/portainer/portainer/api"
bolterrors "github.com/portainer/portainer/api/dataservices/errors"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@ -33,7 +32,7 @@ func (handler *Handler) openAMTDevices(w http.ResponseWriter, r *http.Request) *
} }
endpoint, err := handler.DataStore.Endpoint().Endpoint(portainer.EndpointID(endpointID)) endpoint, err := handler.DataStore.Endpoint().Endpoint(portainer.EndpointID(endpointID))
if err == bolterrors.ErrObjectNotFound { if handler.DataStore.IsErrObjectNotFound(err) {
return httperror.NotFound("Unable to find an endpoint with the specified identifier inside the database", err) return httperror.NotFound("Unable to find an endpoint with the specified identifier inside the database", err)
} else if err != nil { } else if err != nil {
return httperror.InternalServerError("Unable to find an endpoint with the specified identifier inside the database", err) return httperror.InternalServerError("Unable to find an endpoint with the specified identifier inside the database", err)

View File

@ -12,7 +12,6 @@ import (
"github.com/portainer/libhttp/request" "github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response" "github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api" portainer "github.com/portainer/portainer/api"
bolterrors "github.com/portainer/portainer/api/dataservices/errors"
"github.com/portainer/portainer/api/hostmanagement/openamt" "github.com/portainer/portainer/api/hostmanagement/openamt"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
@ -63,7 +62,7 @@ func (handler *Handler) openAMTHostInfo(w http.ResponseWriter, r *http.Request)
log.Info().Int("endpointID", endpointID).Msg("OpenAMTHostInfo") log.Info().Int("endpointID", endpointID).Msg("OpenAMTHostInfo")
endpoint, err := handler.DataStore.Endpoint().Endpoint(portainer.EndpointID(endpointID)) endpoint, err := handler.DataStore.Endpoint().Endpoint(portainer.EndpointID(endpointID))
if err == bolterrors.ErrObjectNotFound { if handler.DataStore.IsErrObjectNotFound(err) {
return httperror.NotFound("Unable to find an endpoint with the specified identifier inside the database", err) return httperror.NotFound("Unable to find an endpoint with the specified identifier inside the database", err)
} else if err != nil { } else if err != nil {
return httperror.InternalServerError("Unable to find an endpoint with the specified identifier inside the database", err) return httperror.InternalServerError("Unable to find an endpoint with the specified identifier inside the database", err)

View File

@ -7,7 +7,6 @@ import (
"strconv" "strconv"
portainer "github.com/portainer/portainer/api" portainer "github.com/portainer/portainer/api"
portainerDsErrors "github.com/portainer/portainer/api/dataservices/errors"
"github.com/portainer/portainer/api/kubernetes" "github.com/portainer/portainer/api/kubernetes"
"github.com/gorilla/mux" "github.com/gorilla/mux"
@ -121,7 +120,7 @@ func (handler *Handler) kubeClient(next http.Handler) http.Handler {
} }
endpoint, err := handler.DataStore.Endpoint().Endpoint(portainer.EndpointID(endpointID)) endpoint, err := handler.DataStore.Endpoint().Endpoint(portainer.EndpointID(endpointID))
if err == portainerDsErrors.ErrObjectNotFound { if handler.DataStore.IsErrObjectNotFound(err) {
httperror.WriteError( httperror.WriteError(
w, w,
http.StatusNotFound, http.StatusNotFound,

View File

@ -8,7 +8,6 @@ import (
"github.com/portainer/libhttp/request" "github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response" "github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api" portainer "github.com/portainer/portainer/api"
portainerDsErrors "github.com/portainer/portainer/api/dataservices/errors"
models "github.com/portainer/portainer/api/http/models/kubernetes" models "github.com/portainer/portainer/api/http/models/kubernetes"
"github.com/portainer/portainer/api/http/security" "github.com/portainer/portainer/api/http/security"
) )
@ -23,7 +22,7 @@ func (handler *Handler) getKubernetesIngressControllers(w http.ResponseWriter, r
} }
endpoint, err := handler.DataStore.Endpoint().Endpoint(portainer.EndpointID(endpointID)) endpoint, err := handler.DataStore.Endpoint().Endpoint(portainer.EndpointID(endpointID))
if err == portainerDsErrors.ErrObjectNotFound { if handler.DataStore.IsErrObjectNotFound(err) {
return httperror.NotFound( return httperror.NotFound(
"Unable to find an environment with the specified identifier inside the database", "Unable to find an environment with the specified identifier inside the database",
err, err,

View File

@ -1,6 +1,7 @@
package stacks package stacks
import ( import (
"errors"
"net/http" "net/http"
httperror "github.com/portainer/libhttp/error" httperror "github.com/portainer/libhttp/error"
@ -38,7 +39,8 @@ func (handler *Handler) webhookInvoke(w http.ResponseWriter, r *http.Request) *h
} }
if err = deployments.RedeployWhenChanged(stack.ID, handler.StackDeployer, handler.DataStore, handler.GitService); err != nil { if err = deployments.RedeployWhenChanged(stack.ID, handler.StackDeployer, handler.DataStore, handler.GitService); err != nil {
if _, ok := err.(*deployments.StackAuthorMissingErr); ok { var StackAuthorMissingErr *deployments.StackAuthorMissingErr
if errors.As(err, &StackAuthorMissingErr) {
return &httperror.HandlerError{StatusCode: http.StatusConflict, Message: "Autoupdate for the stack isn't available", Err: err} return &httperror.HandlerError{StatusCode: http.StatusConflict, Message: "Autoupdate for the stack isn't available", Err: err}
} }

View File

@ -1,6 +1,7 @@
package tags package tags
import ( import (
"errors"
"net/http" "net/http"
httperror "github.com/portainer/libhttp/error" httperror "github.com/portainer/libhttp/error"
@ -34,8 +35,9 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
func txResponse(w http.ResponseWriter, r any, err error) *httperror.HandlerError { func txResponse(w http.ResponseWriter, r any, err error) *httperror.HandlerError {
if err != nil { if err != nil {
if httpErr, ok := err.(*httperror.HandlerError); ok { var handlerError *httperror.HandlerError
return httpErr if errors.As(err, &handlerError) {
return handlerError
} }
return httperror.InternalServerError("Unexpected error", err) return httperror.InternalServerError("Unexpected error", err)

View File

@ -1,6 +1,7 @@
package tags package tags
import ( import (
"errors"
"net/http" "net/http"
httperror "github.com/portainer/libhttp/error" httperror "github.com/portainer/libhttp/error"
@ -41,8 +42,9 @@ func (handler *Handler) tagDelete(w http.ResponseWriter, r *http.Request) *httpe
} }
if err != nil { if err != nil {
if httpErr, ok := err.(*httperror.HandlerError); ok { var handlerError *httperror.HandlerError
return httpErr if errors.As(err, &handlerError) {
return handlerError
} }
return httperror.InternalServerError("Unexpected error", err) return httperror.InternalServerError("Unexpected error", err)

View File

@ -1,6 +1,7 @@
package users package users
import ( import (
"errors"
"net/http" "net/http"
httperror "github.com/portainer/libhttp/error" httperror "github.com/portainer/libhttp/error"
@ -66,7 +67,7 @@ func (handler *Handler) userRemoveAccessToken(w http.ResponseWriter, r *http.Req
err = handler.apiKeyService.DeleteAPIKey(portainer.APIKeyID(apiKeyID)) err = handler.apiKeyService.DeleteAPIKey(portainer.APIKeyID(apiKeyID))
if err != nil { if err != nil {
if err == apikey.ErrInvalidAPIKey { if errors.Is(err, apikey.ErrInvalidAPIKey) {
return httperror.NotFound("Unable to find an api-key with the specified identifier inside the database", err) return httperror.NotFound("Unable to find an api-key with the specified identifier inside the database", err)
} }
return httperror.InternalServerError("Unable to remove the api-key from the user", err) return httperror.InternalServerError("Unable to remove the api-key from the user", err)

View File

@ -1,6 +1,7 @@
package websocket package websocket
import ( import (
"errors"
"fmt" "fmt"
"net/http" "net/http"
"net/http/httputil" "net/http/httputil"
@ -11,7 +12,7 @@ import (
func hijackRequest(websocketConn *websocket.Conn, httpConn *httputil.ClientConn, request *http.Request) error { func hijackRequest(websocketConn *websocket.Conn, httpConn *httputil.ClientConn, request *http.Request) error {
// Server hijacks the connection, error 'connection closed' expected // Server hijacks the connection, error 'connection closed' expected
resp, err := httpConn.Do(request) resp, err := httpConn.Do(request)
if err != httputil.ErrPersistEOF { if !errors.Is(err, httputil.ErrPersistEOF) {
if err != nil { if err != nil {
return err return err
} }

View File

@ -8,7 +8,7 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
httperror "github.com/portainer/libhttp/error" httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request" "github.com/portainer/libhttp/request"
bolterrors "github.com/portainer/portainer/api/dataservices/errors" "github.com/portainer/portainer/api/dataservices"
) )
type ItemContextKey string type ItemContextKey string
@ -31,7 +31,7 @@ func WithItem[TId ~int, TObject any](getter ItemGetter[TId, TObject], idParam st
item, err := getter(TId(itemId)) item, err := getter(TId(itemId))
if err != nil { if err != nil {
statusCode := http.StatusInternalServerError statusCode := http.StatusInternalServerError
if err == bolterrors.ErrObjectNotFound { if dataservices.IsErrObjectNotFound(err) {
statusCode = http.StatusNotFound statusCode = http.StatusNotFound
} }
httperror.WriteError(rw, statusCode, "Unable to find a object with the specified identifier inside the database", err) httperror.WriteError(rw, statusCode, "Unable to find a object with the specified identifier inside the database", err)

View File

@ -37,6 +37,7 @@ func createRegistryAuthenticationHeader(
} else { // any "custom" registry } else { // any "custom" registry
var matchingRegistry *portainer.Registry var matchingRegistry *portainer.Registry
for _, registry := range accessContext.registries { for _, registry := range accessContext.registries {
registry := registry
if registry.ID == registryId && if registry.ID == registryId &&
(accessContext.isAdmin || (accessContext.isAdmin ||
security.AuthorizedRegistryAccess(&registry, accessContext.user, accessContext.teamMemberships, accessContext.endpointID)) { security.AuthorizedRegistryAccess(&registry, accessContext.user, accessContext.teamMemberships, accessContext.endpointID)) {

View File

@ -15,7 +15,6 @@ import (
portainer "github.com/portainer/portainer/api" portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices" "github.com/portainer/portainer/api/dataservices"
dataerrors "github.com/portainer/portainer/api/dataservices/errors"
"github.com/portainer/portainer/api/docker" "github.com/portainer/portainer/api/docker"
"github.com/portainer/portainer/api/http/proxy/factory/utils" "github.com/portainer/portainer/api/http/proxy/factory/utils"
"github.com/portainer/portainer/api/http/security" "github.com/portainer/portainer/api/http/security"
@ -647,7 +646,7 @@ func (transport *Transport) executeGenericResourceDeletionOperation(request *htt
if response.StatusCode == http.StatusNoContent || response.StatusCode == http.StatusOK { if response.StatusCode == http.StatusNoContent || response.StatusCode == http.StatusOK {
resourceControl, err := transport.dataStore.ResourceControl().ResourceControlByResourceIDAndType(resourceIdentifierAttribute, resourceType) resourceControl, err := transport.dataStore.ResourceControl().ResourceControlByResourceIDAndType(resourceIdentifierAttribute, resourceType)
if err != nil { if err != nil {
if err == dataerrors.ErrObjectNotFound { if dataservices.IsErrObjectNotFound(err) {
return response, nil return response, nil
} }

View File

@ -18,6 +18,7 @@ func EdgeStackRelatedEndpoints(edgeGroupIDs []portainer.EdgeGroupID, endpoints [
var edgeGroup *portainer.EdgeGroup var edgeGroup *portainer.EdgeGroup
for _, group := range edgeGroups { for _, group := range edgeGroups {
group := group
if group.ID == edgeGroupID { if group.ID == edgeGroupID {
edgeGroup = &group edgeGroup = &group
break break

View File

@ -81,7 +81,7 @@ func (service *Service) PersistEdgeStack(
relatedEndpointIds, err := edge.EdgeStackRelatedEndpoints(stack.EdgeGroups, relationConfig.Endpoints, relationConfig.EndpointGroups, relationConfig.EdgeGroups) relatedEndpointIds, err := edge.EdgeStackRelatedEndpoints(stack.EdgeGroups, relationConfig.Endpoints, relationConfig.EndpointGroups, relationConfig.EdgeGroups)
if err != nil { if err != nil {
if err == edge.ErrEdgeGroupNotFound { if errors.Is(err, edge.ErrEdgeGroupNotFound) {
return nil, httperrors.NewInvalidPayloadError(err.Error()) return nil, httperrors.NewInvalidPayloadError(err.Error())
} }
return nil, fmt.Errorf("unable to persist environment relation in database: %w", err) return nil, fmt.Errorf("unable to persist environment relation in database: %w", err)

View File

@ -1,6 +1,7 @@
package cli package cli
import ( import (
"context"
"errors" "errors"
"io" "io"
@ -50,13 +51,14 @@ func (kcl *KubeClient) StartExecProcess(token string, useAdminToken bool, namesp
return return
} }
err = exec.Stream(remotecommand.StreamOptions{ err = exec.StreamWithContext(context.TODO(), remotecommand.StreamOptions{
Stdin: stdin, Stdin: stdin,
Stdout: stdout, Stdout: stdout,
Tty: true, Tty: true,
}) })
if err != nil { if err != nil {
if _, ok := err.(utilexec.ExitError); !ok { var exitError utilexec.ExitError
if !errors.As(err, &exitError) {
errChan <- errors.New("unable to start exec process") errChan <- errors.New("unable to start exec process")
} }
} }

View File

@ -2,6 +2,7 @@ package cli
import ( import (
"context" "context"
"errors"
"testing" "testing"
"time" "time"
@ -30,7 +31,7 @@ func Test_waitForPodStatus(t *testing.T) {
ctx, cancel := context.WithCancel(context.TODO()) ctx, cancel := context.WithCancel(context.TODO())
cancel() cancel()
err := k.waitForPodStatus(ctx, v1.PodRunning, podSpec) err := k.waitForPodStatus(ctx, v1.PodRunning, podSpec)
if err != context.Canceled { if !errors.Is(err, context.Canceled) {
t.Errorf("waitForPodStatus should throw context cancellation error; err=%s", err) t.Errorf("waitForPodStatus should throw context cancellation error; err=%s", err)
} }
}) })
@ -59,7 +60,7 @@ func Test_waitForPodStatus(t *testing.T) {
ctx, cancelFunc := context.WithTimeout(context.TODO(), 0*time.Second) ctx, cancelFunc := context.WithTimeout(context.TODO(), 0*time.Second)
defer cancelFunc() defer cancelFunc()
err = k.waitForPodStatus(ctx, v1.PodRunning, podSpec) err = k.waitForPodStatus(ctx, v1.PodRunning, podSpec)
if err != context.DeadlineExceeded { if !errors.Is(err, context.DeadlineExceeded) {
t.Errorf("waitForPodStatus should throw deadline exceeded error; err=%s", err) t.Errorf("waitForPodStatus should throw deadline exceeded error; err=%s", err)
} }
}) })

View File

@ -55,7 +55,7 @@ func (s *Scheduler) Shutdown() error {
s.mu.Unlock() s.mu.Unlock()
err := ctx.Err() err := ctx.Err()
if err == context.Canceled { if errors.Is(err, context.Canceled) {
return nil return nil
} }
return err return err

View File

@ -27,7 +27,7 @@ func DownloadGitRepository(config gittypes.RepoConfig, gitService portainer.GitS
projectPath := getProjectPath() projectPath := getProjectPath()
err := gitService.CloneRepository(projectPath, config.URL, config.ReferenceName, username, password, config.TLSSkipVerify) err := gitService.CloneRepository(projectPath, config.URL, config.ReferenceName, username, password, config.TLSSkipVerify)
if err != nil { if err != nil {
if err == gittypes.ErrAuthenticationFailure { if errors.Is(err, gittypes.ErrAuthenticationFailure) {
newErr := ErrInvalidGitCredential newErr := ErrInvalidGitCredential
return "", newErr return "", newErr
} }

4
golangci-lint.sh Normal file → Executable file
View File

@ -2,10 +2,10 @@
#!/bin/bash #!/bin/bash
cd api cd api
if golangci-lint run -c .golangci.yaml if golangci-lint run --timeout=10m -c .golangci.yaml
then then
echo "golangci-lint run successfully" echo "golangci-lint run successfully"
else else
echo "golangci-lint run failed" echo "golangci-lint run failed"
exit 1 exit 1
fi fi