Skip to content

Commit 9ef15c5

Browse files
christiangdaclaude
andcommitted
refactor: apply Go 1.26 best practices across codebase
- Remove github.com/pkg/errors dependency entirely from Go source. Replace errors.Wrap with fmt.Errorf(%w) in internal/setup and errors.Errorf with errors.New in internal/repository. - Migrate errors.As to errors.AsType[T] (Go 1.26) in internal/core/sync.go for compile-time type safety. - Fix internal/model.Hash() calling os.Exit(1) on nil input or encoding failure. Use panic instead (programming error, recoverable, produces stack trace). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent c3ce7f1 commit 9ef15c5

5 files changed

Lines changed: 37 additions & 32 deletions

File tree

docs/Whats-New.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@ Several code quality improvements and bug fixes in the AWS SCIM client:
3030
* **Removed redundant context set:** `do()` no longer calls `req.WithContext(ctx)` since the request is already created with `http.NewRequestWithContext`.
3131
* **Simplified type conversions:** `CreateOrGetUser` and `CreateOrGetGroup` use type conversions instead of manual field-by-field struct copies.
3232

33+
### Go 1.26 Modernization
34+
35+
Applied Go 1.26 best practices across the codebase:
36+
37+
* **Removed `github.com/pkg/errors` dependency:** Replaced all `errors.Wrap` and `errors.Errorf` with stdlib `fmt.Errorf` (with `%w`) and `errors.New` in `internal/setup`, `internal/repository`, and `pkg/aws`.
38+
* **`errors.AsType[T]`:** Migrated `errors.As` calls to the generic `errors.AsType[T]` in `internal/core/sync.go` for type safety and performance.
39+
* **Fixed `os.Exit` in `Hash()`:** `internal/model.Hash()` no longer calls `os.Exit(1)` on nil input or encoding failure. It panics instead (appropriate for programming errors, recoverable, produces stack trace).
40+
3341
## v0.44.0
3442

3543
### Configurable User Fields

internal/core/sync.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,10 @@ func (ss *SyncService) SyncGroupsAndTheirMembers(ctx context.Context) error {
101101
slog.Info("getting state data")
102102
state, err := ss.repo.GetState(ctx)
103103
if err != nil {
104-
var nsk *types.NoSuchKey
105-
var StateFileEmpty *repository.ErrStateFileEmpty
106-
107-
if errors.As(err, &nsk) || errors.As(err, &StateFileEmpty) {
104+
if _, ok := errors.AsType[*types.NoSuchKey](err); ok {
105+
slog.Warn("no state file found in the state repository, creating a new one")
106+
state = model.StateBuilder().Build()
107+
} else if _, ok := errors.AsType[*repository.ErrStateFileEmpty](err); ok {
108108
slog.Warn("no state file found in the state repository, creating a new one")
109109
state = model.StateBuilder().Build()
110110
} else {

internal/model/hash.go

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,20 @@ import (
55
"crypto/sha256"
66
"encoding/gob"
77
"fmt"
8-
"log/slog"
9-
"os"
108
)
119

12-
// Hash returns a sha256 hash of value pass as argument
10+
// Hash returns a sha256 hash of value pass as argument.
11+
// It panics if value is nil or cannot be gob-encoded, since these
12+
// conditions indicate a programming error in the caller.
1313
func Hash(value any) string {
1414
if value == nil {
15-
slog.Error("value is nil")
16-
os.Exit(1)
15+
panic("model: Hash called with nil value")
1716
}
1817

1918
buf := new(bytes.Buffer)
2019
enc := gob.NewEncoder(buf)
2120
if err := enc.Encode(value); err != nil {
22-
slog.Error("error encoding value")
23-
os.Exit(1)
21+
panic(fmt.Sprintf("model: Hash encoding error: %v", err))
2422
}
2523

2624
return fmt.Sprintf("%x", sha256.Sum256(buf.Bytes()))

internal/repository/s3.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import (
44
"bytes"
55
"context"
66
"encoding/json"
7+
"errors"
78
"fmt"
89

910
"github.com/aws/aws-sdk-go-v2/aws"
1011
"github.com/aws/aws-sdk-go-v2/service/s3"
11-
"github.com/pkg/errors"
1212
"github.com/slashdevops/idp-scim-sync/internal/model"
1313
)
1414

internal/setup/setup.go

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111

1212
"github.com/aws/aws-sdk-go-v2/service/s3"
1313
"github.com/aws/aws-sdk-go-v2/service/secretsmanager"
14-
"github.com/pkg/errors"
1514
"github.com/slashdevops/httpx"
1615
"github.com/slashdevops/idp-scim-sync/internal/config"
1716
"github.com/slashdevops/idp-scim-sync/internal/core"
@@ -83,7 +82,7 @@ func Configuration(cfg *config.Config) error {
8382
}
8483
for _, e := range envVars {
8584
if err := viper.BindEnv(e); err != nil {
86-
return errors.Wrap(err, "cannot bind environment variable")
85+
return fmt.Errorf("cannot bind environment variable: %w", err)
8786
}
8887
}
8988

@@ -92,13 +91,13 @@ func Configuration(cfg *config.Config) error {
9291
if !cfg.IsLambda {
9392
home, err := os.UserHomeDir()
9493
if err != nil {
95-
return errors.Wrap(err, "cannot get user home directory")
94+
return fmt.Errorf("cannot get user home directory: %w", err)
9695
}
9796
viper.AddConfigPath(home)
9897

9998
currentDir, err := os.Getwd()
10099
if err != nil {
101-
return errors.Wrap(err, "cannot get current directory")
100+
return fmt.Errorf("cannot get current directory: %w", err)
102101
}
103102
viper.AddConfigPath(currentDir)
104103

@@ -122,7 +121,7 @@ func Configuration(cfg *config.Config) error {
122121
}
123122

124123
if err := viper.Unmarshal(cfg); err != nil {
125-
return errors.Wrap(err, "cannot unmarshal config")
124+
return fmt.Errorf("cannot unmarshal config: %w", err)
126125
}
127126

128127
if cfg.Debug {
@@ -138,14 +137,14 @@ func Secrets(cfg *config.Config) error {
138137

139138
awsConf, err := aws.NewDefaultConf(context.Background())
140139
if err != nil {
141-
return errors.Wrap(err, "cannot load aws config")
140+
return fmt.Errorf("cannot load aws config: %w", err)
142141
}
143142

144143
svc := secretsmanager.NewFromConfig(awsConf)
145144

146145
secrets, err := aws.NewSecretsManagerService(svc)
147146
if err != nil {
148-
return errors.Wrap(err, "cannot create aws secrets manager service")
147+
return fmt.Errorf("cannot create aws secrets manager service: %w", err)
149148
}
150149

151150
// create a channel to receive the results
@@ -155,7 +154,7 @@ func Secrets(cfg *config.Config) error {
155154
slog.Debug("reading secret", "name", cfg.GWSUserEmailSecretName)
156155
unwrap, err := secrets.GetSecretValue(context.Background(), cfg.GWSUserEmailSecretName)
157156
if err != nil {
158-
results <- errors.Wrap(err, "cannot get secretmanager value")
157+
results <- fmt.Errorf("cannot get secretmanager value: %w", err)
159158
return
160159
}
161160
cfg.GWSUserEmail = unwrap
@@ -166,7 +165,7 @@ func Secrets(cfg *config.Config) error {
166165
slog.Debug("reading secret", "name", cfg.GWSServiceAccountFileSecretName)
167166
unwrap, err := secrets.GetSecretValue(context.Background(), cfg.GWSServiceAccountFileSecretName)
168167
if err != nil {
169-
results <- errors.Wrap(err, "cannot get secretmanager value")
168+
results <- fmt.Errorf("cannot get secretmanager value: %w", err)
170169
return
171170
}
172171
cfg.GWSServiceAccountFile = unwrap
@@ -177,7 +176,7 @@ func Secrets(cfg *config.Config) error {
177176
slog.Debug("reading secret", "name", cfg.AWSSCIMAccessTokenSecretName)
178177
unwrap, err := secrets.GetSecretValue(context.Background(), cfg.AWSSCIMAccessTokenSecretName)
179178
if err != nil {
180-
results <- errors.Wrap(err, "cannot get secretmanager value")
179+
results <- fmt.Errorf("cannot get secretmanager value: %w", err)
181180
return
182181
}
183182
cfg.AWSSCIMAccessToken = unwrap
@@ -188,7 +187,7 @@ func Secrets(cfg *config.Config) error {
188187
slog.Debug("reading secret", "name", cfg.AWSSCIMEndpointSecretName)
189188
unwrap, err := secrets.GetSecretValue(context.Background(), cfg.AWSSCIMEndpointSecretName)
190189
if err != nil {
191-
results <- errors.Wrap(err, "cannot get secretmanager value")
190+
results <- fmt.Errorf("cannot get secretmanager value: %w", err)
192191
return
193192
}
194193
cfg.AWSSCIMEndpoint = unwrap
@@ -213,7 +212,7 @@ func SyncService(ctx context.Context, cfg *config.Config) (*core.SyncService, er
213212
if !cfg.IsLambda {
214213
gwsServiceAccount, err := os.ReadFile(cfg.GWSServiceAccountFile)
215214
if err != nil {
216-
return nil, errors.Wrap(err, "cannot read google workspace service account file")
215+
return nil, fmt.Errorf("cannot read google workspace service account file: %w", err)
217216
}
218217
gwsServiceAccountContent = gwsServiceAccount
219218
}
@@ -238,7 +237,7 @@ func SyncService(ctx context.Context, cfg *config.Config) (*core.SyncService, er
238237
// Google Client Service
239238
gwsService, err := google.NewService(ctx, gServiceConfig)
240239
if err != nil {
241-
return nil, errors.Wrap(err, "cannot create google service")
240+
return nil, fmt.Errorf("cannot create google service: %w", err)
242241
}
243242

244243
// Build the sync field set from configuration
@@ -247,13 +246,13 @@ func SyncService(ctx context.Context, cfg *config.Config) (*core.SyncService, er
247246
// Google Directory Service
248247
gwsDS, err := google.NewDirectoryService(gwsService, google.WithSyncFieldSet(syncFieldSet))
249248
if err != nil {
250-
return nil, errors.Wrap(err, "cannot create google directory service")
249+
return nil, fmt.Errorf("cannot create google directory service: %w", err)
251250
}
252251

253252
// Identity Provider Service
254253
idpService, err := idp.NewIdentityProvider(gwsDS, idp.WithSyncFieldSet(syncFieldSet))
255254
if err != nil {
256-
return nil, errors.Wrap(err, "cannot create identity provider service")
255+
return nil, fmt.Errorf("cannot create identity provider service: %w", err)
257256
}
258257

259258
// AWS SCIM Service
@@ -268,29 +267,29 @@ func SyncService(ctx context.Context, cfg *config.Config) (*core.SyncService, er
268267

269268
awsSCIM, err := aws.NewSCIMService(scimClient, cfg.AWSSCIMEndpoint, cfg.AWSSCIMAccessToken)
270269
if err != nil {
271-
return nil, errors.Wrap(err, "cannot create aws scim service")
270+
return nil, fmt.Errorf("cannot create aws scim service: %w", err)
272271
}
273272
awsSCIM.UserAgent = userAgent
274273

275274
scimService, err := scim.NewProvider(awsSCIM)
276275
if err != nil {
277-
return nil, errors.Wrap(err, "cannot create scim provider")
276+
return nil, fmt.Errorf("cannot create scim provider: %w", err)
278277
}
279278

280279
awsConf, err := aws.NewDefaultConf(context.Background())
281280
if err != nil {
282-
return nil, errors.Wrap(err, "cannot load aws config")
281+
return nil, fmt.Errorf("cannot load aws config: %w", err)
283282
}
284283

285284
s3Client := s3.NewFromConfig(awsConf)
286285
repo, err := repository.NewS3Repository(s3Client, repository.WithBucket(cfg.AWSS3BucketName), repository.WithKey(cfg.AWSS3BucketKey))
287286
if err != nil {
288-
return nil, errors.Wrap(err, "cannot create s3 repository")
287+
return nil, fmt.Errorf("cannot create s3 repository: %w", err)
289288
}
290289

291290
ss, err := core.NewSyncService(idpService, scimService, repo, core.WithIdentityProviderGroupsFilter(cfg.GWSGroupsFilter))
292291
if err != nil {
293-
return nil, errors.Wrap(err, "cannot create sync service")
292+
return nil, fmt.Errorf("cannot create sync service: %w", err)
294293
}
295294

296295
return ss, nil

0 commit comments

Comments
 (0)