Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement slog #291

Merged
merged 4 commits into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ jobs:
GIT_VERSION=${{ github.ref_name }} make build-dist

- name: Upload Distribution files
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: dist
path: ./dist/
Expand Down Expand Up @@ -96,7 +96,7 @@ jobs:
echo ${{ github.ref_name }}

- name: Download Distribution files
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: dist
path: ./dist/
Expand Down
2 changes: 2 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"cmpopts",
"codecov",
"codeql",
"curesp",
"Debugf",
"deepcopy",
"displayname",
Expand All @@ -18,6 +19,7 @@
"familyname",
"fxxckin",
"ggmo",
"github",
"givenname",
"gmrb",
"gmrs",
Expand Down
120 changes: 64 additions & 56 deletions cmd/idpscim/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cmd
import (
"context"
"fmt"
"log/slog"
"os"
"path/filepath"
"strings"
Expand All @@ -14,7 +15,6 @@ import (
"github.com/hashicorp/go-retryablehttp"
"github.com/pkg/errors"
"github.com/slashdevops/idp-scim-sync/internal/config"
"github.com/slashdevops/idp-scim-sync/internal/convert"
"github.com/slashdevops/idp-scim-sync/internal/core"
"github.com/slashdevops/idp-scim-sync/internal/idp"
"github.com/slashdevops/idp-scim-sync/internal/repository"
Expand All @@ -24,11 +24,14 @@ import (
"github.com/slashdevops/idp-scim-sync/pkg/google"
"github.com/spf13/cobra"
"github.com/spf13/viper"

log "github.com/sirupsen/logrus"
)

var cfg config.Config
var (
cfg config.Config
logHandler slog.Handler
logHandlerOptions *slog.HandlerOptions
logger *slog.Logger
)

// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Expand Down Expand Up @@ -129,7 +132,8 @@ func initConfig() {
}
for _, e := range envVars {
if err := viper.BindEnv(e); err != nil {
log.Fatalf(errors.Wrap(err, "cannot bind environment variable").Error())
slog.Error("cannot bind environment variable", "error", err)
os.Exit(1)
}
}

Expand All @@ -156,34 +160,42 @@ func initConfig() {
fileName := fileNameExt[0 : len(fileNameExt)-len(fileExtension)]
viper.SetConfigName(fileName)

log.Debugf("configuration file: dir: %s, name: %s, ext: %s", fileDir, fileName, fileExtension)
slog.Debug("configuration file", "dir", fileDir, "name", fileName, "extension", fileExtension)

if err := viper.ReadInConfig(); err == nil {
log.Infof("using config file: %s", viper.ConfigFileUsed())
slog.Info("using config file", "file", viper.ConfigFileUsed())
}
}

if err := viper.Unmarshal(&cfg); err != nil {
log.Fatalf(errors.Wrap(err, "cannot unmarshal config").Error())
slog.Error("cannot unmarshal config", "error", err)
}

switch strings.ToLower(cfg.LogFormat) {
case "json":
log.SetFormatter(&log.JSONFormatter{})
logHandler = slog.NewJSONHandler(os.Stdout, logHandlerOptions)
case "text":
log.SetFormatter(&log.TextFormatter{})
logHandler = slog.NewTextHandler(os.Stdout, logHandlerOptions)
default:
log.Warnf("unknown log format: %s, using text", cfg.LogFormat)
log.SetFormatter(&log.TextFormatter{})
slog.Warn("unknown log format, using text", "format", cfg.LogFormat)
logHandler = slog.NewTextHandler(os.Stdout, logHandlerOptions)
}

if cfg.Debug {
cfg.LogLevel = "debug"
switch strings.ToLower(cfg.LogLevel) {
case "debug":
logHandlerOptions = &slog.HandlerOptions{Level: slog.LevelDebug, AddSource: true}
case "info":
logHandlerOptions = &slog.HandlerOptions{Level: slog.LevelInfo}
case "warn":
logHandlerOptions = &slog.HandlerOptions{Level: slog.LevelWarn}
case "error":
logHandlerOptions = &slog.HandlerOptions{Level: slog.LevelError, AddSource: true}
default:
slog.Warn("unknown log level, setting it to info", "level", cfg.LogLevel)
}

// set the configured log level
if level, err := log.ParseLevel(strings.ToLower(cfg.LogLevel)); err == nil {
log.SetLevel(level)
if cfg.Debug {
cfg.LogLevel = "debug"
}

if cfg.IsLambda || cfg.UseSecretsManager {
Expand All @@ -192,79 +204,74 @@ func initConfig() {

// not implemented yet block
if cfg.SyncMethod != "groups" {
log.Fatal("only 'sync-method=groups' are implemented")
slog.Error("only 'sync-method=groups' are implemented")
os.Exit(1)
}
}

func getSecrets() {
log.Info("reading secrets from AWS Secrets Manager")
slog.Info("reading secrets from AWS Secrets Manager")

awsConf, err := aws.NewDefaultConf(context.Background())
if err != nil {
log.Fatalf(errors.Wrap(err, "cannot load aws config").Error())
slog.Error("cannot load aws config", "error", err)
os.Exit(1)
}

svc := secretsmanager.NewFromConfig(awsConf)

secrets, err := aws.NewSecretsManagerService(svc)
if err != nil {
log.Fatalf(errors.Wrap(err, "cannot create aws secrets manager service").Error())
slog.Error("cannot create aws secrets manager service", "error", err)
os.Exit(1)
}

log.WithField("name", cfg.GWSUserEmailSecretName).Debug("reading secret")
slog.Debug("reading secret", "name", cfg.GWSUserEmailSecretName)
unwrap, err := secrets.GetSecretValue(context.Background(), cfg.GWSUserEmailSecretName)
if err != nil {
log.Fatalf(errors.Wrap(err, "cannot get secretmanager value").Error())
slog.Error("cannot get secretmanager value", "error", err)
os.Exit(1)
}
cfg.GWSUserEmail = unwrap
log.WithFields(
log.Fields{"secretARN": cfg.GWSUserEmailSecretName},
).Debug("read secret")

log.WithField("name", cfg.GWSServiceAccountFileSecretName).Debug("reading secret")
slog.Debug("reading secret", "name", cfg.GWSServiceAccountFileSecretName)
unwrap, err = secrets.GetSecretValue(context.Background(), cfg.GWSServiceAccountFileSecretName)
if err != nil {
log.Fatalf(errors.Wrap(err, "cannot get secretmanager value").Error())
slog.Error("cannot get secretmanager value", "error", err)
os.Exit(1)
}
cfg.GWSServiceAccountFile = unwrap
log.WithFields(
log.Fields{"secretARN": cfg.GWSServiceAccountFileSecretName},
).Debug("read secret")

log.WithField("name", cfg.AWSSCIMAccessTokenSecretName).Debug("reading secret")
slog.Debug("reading secret", "name", cfg.AWSSCIMAccessTokenSecretName)
unwrap, err = secrets.GetSecretValue(context.Background(), cfg.AWSSCIMAccessTokenSecretName)
if err != nil {
log.Fatalf(errors.Wrap(err, "cannot get secretmanager value").Error())
slog.Error("cannot get secretmanager value", "error", err)
os.Exit(1)
}
cfg.AWSSCIMAccessToken = unwrap
log.WithFields(
log.Fields{"secretARN": cfg.AWSSCIMAccessTokenSecretName},
).Debug("read secret")

log.WithField("name", cfg.AWSSCIMEndpointSecretName).Debug("reading secret")
slog.Debug("reading secret", "name", cfg.AWSSCIMEndpointSecretName)
unwrap, err = secrets.GetSecretValue(context.Background(), cfg.AWSSCIMEndpointSecretName)
if err != nil {
log.Fatalf(errors.Wrap(err, "cannot get secretmanager value").Error())
slog.Error("cannot get secretmanager value", "error", err)
os.Exit(1)
}
cfg.AWSSCIMEndpoint = unwrap
log.WithFields(
log.Fields{"secretARN": cfg.AWSSCIMEndpointSecretName},
).Debug("read secret")
}

func sync() error {
log.Tracef("viper config: %s", convert.ToJSONString(viper.AllSettings(), true))
slog.Debug("viper config", "config", viper.AllSettings())

if cfg.SyncMethod == "groups" {
return syncGroups()
if cfg.SyncMethod != "groups" {
slog.Error("only 'sync-method=groups' are implemented")
return fmt.Errorf("unknown sync method: %s", cfg.SyncMethod)
}
return fmt.Errorf("unknown sync method: %s", cfg.SyncMethod)

return syncGroups()
}

func syncGroups() error {
log.WithFields(
log.Fields{"codeVersion": version.Version},
).Info("starting sync groups")
slog.Info("starting sync groups", "codeVersion", version.Version)
timeStart := time.Now()

// cfg.GWSServiceAccountFile could be a file path or a content of the file
Expand All @@ -273,7 +280,7 @@ func syncGroups() error {
if !cfg.IsLambda {
gwsServiceAccount, err := os.ReadFile(cfg.GWSServiceAccountFile)
if err != nil {
log.Fatalf(errors.Wrap(err, "cannot read service account file").Error())
slog.Error("cannot read service account file", "error", err)
}
gwsServiceAccountContent = gwsServiceAccount
}
Expand Down Expand Up @@ -309,8 +316,9 @@ func syncGroups() error {
retryClient.RetryMax = 10
retryClient.RetryWaitMin = time.Millisecond * 100

// set the logger only in debug mode
if cfg.Debug {
retryClient.Logger = log.StandardLogger()
retryClient.Logger = logger
} else {
retryClient.Logger = nil
}
Expand All @@ -331,29 +339,29 @@ func syncGroups() error {

awsConf, err := aws.NewDefaultConf(context.Background())
if err != nil {
log.Fatalf(errors.Wrap(err, "cannot load aws config").Error())
slog.Error("cannot load aws config", "error", err)
os.Exit(1)
}

s3Client := s3.NewFromConfig(awsConf)
repo, err := repository.NewS3Repository(s3Client, repository.WithBucket(cfg.AWSS3BucketName), repository.WithKey(cfg.AWSS3BucketKey))
if err != nil {
log.Fatalf(errors.Wrap(err, "cannot create s3 repository").Error())
slog.Error("cannot create s3 repository", "error", err)
os.Exit(1)
}

ss, err := core.NewSyncService(idpService, scimService, repo, core.WithIdentityProviderGroupsFilter(cfg.GWSGroupsFilter))
if err != nil {
return errors.Wrap(err, "cannot create sync service")
}

log.Tracef("app config: %s", convert.ToJSONString(cfg, true))
slog.Debug("app config", "config", cfg)

if err := ss.SyncGroupsAndTheirMembers(ctx); err != nil {
return errors.Wrap(err, "cannot sync groups and their members")
}

log.WithFields(log.Fields{
"duration": time.Since(timeStart).String(),
}).Info("sync groups completed")
slog.Info("sync groups completed", "duration", time.Since(timeStart).String())

return nil
}
18 changes: 9 additions & 9 deletions cmd/idpscimcli/cmd/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ package cmd

import (
"context"
"log/slog"
"net/http"

log "github.com/sirupsen/logrus"
"github.com/slashdevops/idp-scim-sync/internal/version"
"github.com/slashdevops/idp-scim-sync/pkg/aws"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -102,14 +102,14 @@ func runAWSServiceConfig(_ *cobra.Command, _ []string) error {

awsSCIMService, err := aws.NewSCIMService(httpClient, cfg.AWSSCIMEndpoint, cfg.AWSSCIMAccessToken)
if err != nil {
log.Errorf("error creating SCIM service: %s", err.Error())
slog.Error("error creating SCIM service", "error", err.Error())
return err
}
awsSCIMService.UserAgent = "idp-scim-sync/" + version.Version

awsServiceConfig, err := awsSCIMService.ServiceProviderConfig(ctx)
if err != nil {
log.Errorf("error getting service provider config, error: %s", err.Error())
slog.Error("error getting service provider config", "error", err.Error())
return err
}

Expand All @@ -134,17 +134,17 @@ func runAWSGroupsList(_ *cobra.Command, _ []string) error {

awsSCIMService, err := aws.NewSCIMService(httpClient, cfg.AWSSCIMEndpoint, cfg.AWSSCIMAccessToken)
if err != nil {
log.Errorf("error creating SCIM service: %s", err.Error())
slog.Error("error creating SCIM service", "error", err.Error())
return err
}
awsSCIMService.UserAgent = "idp-scim-sync/" + version.Version

awsGroupsResponse, err := awsSCIMService.ListGroups(ctx, filter)
if err != nil {
log.Errorf("error listing groups, error: %s", err.Error())
slog.Error("error listing groups", "error", err.Error())
return err
}
log.Infof("%d groups found", awsGroupsResponse.TotalResults)
slog.Info("groups found", "groups", awsGroupsResponse.TotalResults)

show(outFormat, awsGroupsResponse)

Expand All @@ -167,17 +167,17 @@ func runAWSUsersList(_ *cobra.Command, _ []string) error {

awsSCIMService, err := aws.NewSCIMService(httpClient, cfg.AWSSCIMEndpoint, cfg.AWSSCIMAccessToken)
if err != nil {
log.Errorf("error creating SCIM service: %s", err.Error())
slog.Error("error creating SCIM service", "error", err.Error())
return err
}
awsSCIMService.UserAgent = "idp-scim-sync/" + version.Version

awsUsersResponse, err := awsSCIMService.ListUsers(ctx, filter)
if err != nil {
log.Errorf("error listing groups, error: %s", err.Error())
slog.Error("error listing users", "error", err.Error())
return err
}
log.Infof("%d users found", awsUsersResponse.TotalResults)
slog.Info("users found", "users", awsUsersResponse.TotalResults)

show(outFormat, awsUsersResponse)

Expand Down
9 changes: 5 additions & 4 deletions cmd/idpscimcli/cmd/common.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
package cmd

import (
log "github.com/sirupsen/logrus"
"fmt"

"github.com/slashdevops/idp-scim-sync/internal/convert"
)

// show resource structure as outFormat
func show(outFormat string, resource interface{}) {
switch outFormat {
case "json":
log.Infof("%s", convert.ToJSONString(resource, true))
fmt.Print(convert.ToJSONString(resource, true))
case "yaml":
log.Infof("%s", convert.ToYAML(resource))
fmt.Print(convert.ToYAML(resource))
default:
log.Infof("%s", convert.ToJSONString(resource, true))
fmt.Print(convert.ToJSONString(resource, true))
}
}
Loading
Loading