diff --git a/.envrc b/.envrc new file mode 100644 index 00000000..c7a862b2 --- /dev/null +++ b/.envrc @@ -0,0 +1,5 @@ +#!/bin/bash +SCRIPT=`python -c "import os,sys; print(os.path.realpath(os.path.expanduser(sys.argv[1])))" "${BASH_SOURCE:-$0}"` +export GO111MODULE=on +export GOPROXY=direct +export DOCKER_HOST_IP=127.0.0.1 diff --git a/.envsh b/.envsh new file mode 100644 index 00000000..5e0629d5 --- /dev/null +++ b/.envsh @@ -0,0 +1,5 @@ +#!/bin/bash +SCRIPT=`python -c "import os,sys; print(os.path.realpath(os.path.expanduser(sys.argv[1])))" "${BASH_SOURCE:-$0}"` +export GO111MODULE=on +export DOCKER_HOST_IP=127.0.0.1 +export GOPROXY=direct diff --git a/.gitignore b/.gitignore index dc495bfc..2aad06dd 100644 --- a/.gitignore +++ b/.gitignore @@ -17,5 +17,8 @@ aws.toml credentials.json token.json -.idea/ dist/ + +# IDE +.idea/ +.vscode/ diff --git a/.goreleaser.yml b/.goreleaser.yml index b715a508..4fb9f52c 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -20,6 +20,8 @@ builds: goarch: 386 checksum: name_template: '{{ .ProjectName }}_checksums.txt' +ldflags: + - -s -w -X github.com/awslabs/ssosync/cmd.version={{.Version}} -X github.com/awslabs/ssosync/cmd.commit={{.Commit}} -X github.com/awslabs/ssosync/cmd.date={{.Date}} -X github.com/awslabs/ssosync/cmd.builtBy=goreleaser changelog: sort: asc filters: diff --git a/cmd/google.go b/cmd/google.go index c67d9841..7512bb17 100644 --- a/cmd/google.go +++ b/cmd/google.go @@ -15,10 +15,7 @@ package cmd import ( - "github.com/awslabs/ssosync/internal" "github.com/spf13/cobra" - "go.uber.org/zap" - "go.uber.org/zap/zapcore" "github.com/awslabs/ssosync/internal/google" ) @@ -27,32 +24,16 @@ var googleCmd = &cobra.Command{ Use: "google", Short: "Log in to Google", Long: `Log in to Google - use me to generate the files needed for the main command`, - Run: func(cmd *cobra.Command, args []string) { - config := zap.NewDevelopmentConfig() - config.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder - config.Level.SetLevel(zap.DebugLevel) - logger, _ := config.Build() - defer internal.QuietLogSync(logger) - - credPath, err := cmd.Flags().GetString("path") - if err != nil { - logger.Fatal("No path available", zap.Error(err)) - } - tokenPath, err := cmd.Flags().GetString("tokenPath") + RunE: func(cmd *cobra.Command, args []string) error { + g, err := google.NewAuthClient(cfg.GoogleCredentialsPath, cfg.GoogleTokenPath) if err != nil { - logger.Fatal("No tokenPath available", zap.Error(err)) + return err } - g, err := google.NewAuthClient(logger, credPath, tokenPath) - if err != nil { - logger.Fatal("Unable to create google auth client", zap.Error(err)) + if _, err := g.GetTokenFromWeb(); err != nil { + return err } - g.GetTokenFromWeb() + return nil }, } - -func init() { - googleCmd.Flags().String("path", "credentials.json", "set the path to find credentials") - googleCmd.Flags().String("tokenPath", "token.json", "set the path to put token.json output into") -} diff --git a/cmd/lambda.go b/cmd/lambda.go index d9b975fe..d233cbf5 100644 --- a/cmd/lambda.go +++ b/cmd/lambda.go @@ -24,12 +24,7 @@ import ( "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/secretsmanager" "github.com/awslabs/ssosync/internal" -) - -const ( - envSecretGoogleCredentials = "SSOSYNC_GOOGLE_CREDENTIALS" - envSecretGoogleToken = "SSOSYNC_GOOGLE_TOKEN" - envSecretAwsToml = "SSOSYNC_AWS_TOML" + "github.com/awslabs/ssosync/internal/config" ) // inLambda detects if we are running Lambda and will @@ -91,29 +86,12 @@ func removeFileSilently(name string) { } // lambdaHandler is the Lambda entry point -func lambdaHandler() error { - cred, err := writeSecretToFile(os.Getenv(envSecretGoogleCredentials), "gcredentials") - if err != nil { - return err - } - defer removeFileSilently(cred) - - t, err := writeSecretToFile(os.Getenv(envSecretGoogleToken), "gtoken") - if err != nil { - return err - } - defer removeFileSilently(t) - - a, err := writeSecretToFile(os.Getenv(envSecretAwsToml), "awstoml") - if err != nil { - return err - } - defer removeFileSilently(a) +func lambdaHandler(cfg *config.Config) func() error { + return func() error { + if err := internal.DoSync(cfg); err != nil { + return err + } - err = internal.DoSync(true, cred, t, a) - if err != nil { - return err + return nil } - - return nil } diff --git a/cmd/root.go b/cmd/root.go index 432b797b..e1e30e8f 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -16,23 +16,26 @@ package cmd import ( "fmt" - "log" - "os" "github.com/awslabs/ssosync/internal" + "github.com/awslabs/ssosync/internal/config" + "github.com/pkg/errors" "github.com/aws/aws-lambda-go/lambda" + log "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "github.com/spf13/viper" ) var ( - googleCredPath string - googleTokenPath string - scimConfig string - - logDebug bool + version = "dev" + commit = "none" + date = "unknown" + builtBy = "unknown" ) +var cfg *config.Config + var rootCmd = &cobra.Command{ Version: "dev", Use: "ssosync", @@ -40,34 +43,85 @@ var rootCmd = &cobra.Command{ Long: `A command line tool to enable you to synchronise your Google Apps (G-Suite) users to AWS Single Sign-on (AWS SSO) Complete documentation is available at https://github.com/awslabs/ssosync`, - Run: func(cmd *cobra.Command, args []string) { - err := internal.DoSync(logDebug, googleCredPath, googleTokenPath, scimConfig) + RunE: func(cmd *cobra.Command, args []string) error { + err := internal.DoSync(cfg) if err != nil { - log.Fatal(err) + return err } + + return nil }, } // Execute is the entry point of the command. If we are // running inside of AWS Lambda, we use the Lambda // execution path. -func Execute(v string) { - if !inLambda() { - rootCmd.SetVersionTemplate(v) - rootCmd.AddCommand(googleCmd) - - if err := rootCmd.Execute(); err != nil { - fmt.Println(err) - os.Exit(1) - } - } else { +func Execute() { + if inLambda() { lambda.Start(lambdaHandler) } + + if err := rootCmd.Execute(); err != nil { + log.Fatal(err) + } } func init() { - rootCmd.Flags().StringVarP(&googleCredPath, "googleCredentialsPath", "c", "credentials.json", "set the path to find credentials for Google") - rootCmd.Flags().StringVarP(&googleTokenPath, "googleTokenPath", "t", "token.json", "set the path to find token for Google") - rootCmd.Flags().StringVarP(&scimConfig, "scimConfig", "s", "aws.toml", "AWS SSO SCIM Configuration") - rootCmd.Flags().BoolVarP(&logDebug, "debug", "d", false, "Enable verbose / debug logging") + // init config + cfg = config.New() + + // initialize cobra + cobra.OnInitialize(initConfig) + + addFlags(rootCmd, cfg) + + rootCmd.SetVersionTemplate(fmt.Sprintf("%s, commit %s, built at %s by %s\n", version, commit, date, builtBy)) + rootCmd.AddCommand(googleCmd) + + // silence on the root cmd + rootCmd.SilenceUsage = true + rootCmd.SilenceErrors = true +} + +// initConfig reads in config file and ENV variables if set. +func initConfig() { + // allow to read in from environment + viper.SetEnvPrefix("ssosync") + viper.AutomaticEnv() + + viper.BindEnv("google_credentials") + viper.BindEnv("google_token") + viper.BindEnv("aws_toml") + + if err := viper.Unmarshal(&cfg); err != nil { + log.Fatalf(errors.Wrap(err, "cannot unmarshal config").Error()) + } + + // config logger + logConfig(cfg) +} + +func addFlags(cmd *cobra.Command, cfg *config.Config) { + rootCmd.PersistentFlags().StringVarP(&cfg.GoogleCredentialsPath, "googleCredentialsPath", "c", config.DefaultGoogleCredentialsPath, "set the path to find credentials for Google") + rootCmd.PersistentFlags().StringVarP(&cfg.GoogleTokenPath, "googleTokenPath", "t", config.DefaultGoogleTokenPath, "set the path to find token for Google") + rootCmd.PersistentFlags().BoolVarP(&cfg.Debug, "debug", "d", config.DefaultDebug, "Enable verbose / debug logging") + rootCmd.PersistentFlags().StringVarP(&cfg.LogFormat, "log-format", "", config.DefaultLogFormat, "log format") + rootCmd.PersistentFlags().StringVarP(&cfg.LogLevel, "log-level", "", config.DefaultLogLevel, "log level") + rootCmd.Flags().StringVarP(&cfg.SCIMConfig, "scimConfig", "s", config.DefaultSCIMConfig, "AWS SSO SCIM Configuration") +} + +func logConfig(cfg *config.Config) { + // reset log format + if cfg.LogFormat == "json" { + log.SetFormatter(&log.JSONFormatter{}) + } + + if cfg.Debug { + cfg.LogLevel = "debug" + } + + // set the configured log level + if level, err := log.ParseLevel(cfg.LogLevel); err == nil { + log.SetLevel(level) + } } diff --git a/go.mod b/go.mod index 9756e52a..5bb81d0a 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,10 @@ require ( github.com/aws/aws-sdk-go v1.31.7 github.com/golang/mock v1.4.3 github.com/golang/protobuf v1.4.1 // indirect + github.com/pkg/errors v0.9.1 + github.com/sirupsen/logrus v1.2.0 github.com/spf13/cobra v1.0.0 + github.com/spf13/viper v1.4.0 github.com/stretchr/testify v1.5.1 go.uber.org/zap v1.15.0 golang.org/x/mod v0.3.0 // indirect diff --git a/go.sum b/go.sum index 3f48cf3b..98b83ee5 100644 --- a/go.sum +++ b/go.sum @@ -59,6 +59,7 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -121,6 +122,7 @@ github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= @@ -133,6 +135,7 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= @@ -140,16 +143,20 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -167,16 +174,21 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= @@ -215,6 +227,7 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -369,8 +382,6 @@ google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsb google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.23.0 h1:YlvGEOq2NA2my8cZ/9V8BcEO9okD48FlJcdqN0xJL3s= -google.golang.org/api v0.23.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.24.0 h1:cG03eaksBzhfSIk7JRGctfp3lanklcOM/mTGvow7BbQ= google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= diff --git a/internal/aws/client.go b/internal/aws/client.go index 2ee65a47..fe41dfb9 100644 --- a/internal/aws/client.go +++ b/internal/aws/client.go @@ -25,7 +25,7 @@ import ( "path" "strconv" - "go.uber.org/zap" + log "github.com/sirupsen/logrus" ) // OperationType handle patch operations for add/remove @@ -56,7 +56,6 @@ type IClient interface { // Client represents an AWS SSO SCIM client type Client struct { - logger *zap.Logger httpClient IHttpClient endpointURL *url.URL bearerToken string @@ -65,13 +64,12 @@ type Client struct { // NewClient creates a new client to talk with AWS SSO's SCIM endpoint. It // required a http.Client{} as well as the URL and bearer token from the /// console. If the URL is not parsable, an error will be thrown. -func NewClient(logger *zap.Logger, client IHttpClient, config *Config) (IClient, error) { +func NewClient(client IHttpClient, config *Config) (IClient, error) { u, err := url.Parse(config.Endpoint) if err != nil { return nil, err } return &Client{ - logger: logger, httpClient: client, endpointURL: u, bearerToken: config.Token, @@ -93,7 +91,7 @@ func (c *Client) sendRequestWithBody(method string, url string, body interface{} return } - c.logger.Debug("sendRequestWithBody", zap.String("url", url), zap.String("method", method)) + log.WithFields(log.Fields{"url": url, "method": method}) // Set the content-type and authorization headers r.Header.Set("Content-Type", "application/scim+json") @@ -126,7 +124,7 @@ func (c *Client) sendRequest(method string, url string) (response []byte, err er return } - c.logger.Debug("sendRequest", zap.String("url", url), zap.String("method", method)) + log.WithFields(log.Fields{"url": url, "method": method}) r.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.bearerToken)) @@ -194,20 +192,21 @@ func (c *Client) GetUsers() (results *map[string]User, err error) { si := 1 for { - c.logger.Debug("Getting Users Page", zap.Int("startIndex", si)) + log.WithFields(log.Fields{"startIndex": si}).Debug("Getting Users Page") + r, err := c.getUserPage(startURL, si) if err != nil { return nil, err } for _, user := range r.Resources { - c.logger.Debug("Add user to map", zap.String("username", user.Username)) + log.WithFields(log.Fields{"username": user.Username}).Debug("Add user to map") resultMap[user.Username] = user } si = si + 10 if si > r.TotalResults { - c.logger.Debug("Last Page obtained", zap.Int("totalResults", r.TotalResults)) + log.WithFields(log.Fields{"totalResults": r.TotalResults}).Debug("Last Page obtained") break } } @@ -256,7 +255,7 @@ func (c *Client) GetGroups() (results *map[string]Group, err error) { si := 1 for { - c.logger.Debug("Getting Groups Page", zap.Int("startIndex", si)) + log.WithFields(log.Fields{"startIndex": si}).Debug("Getting Groups Page") r, err := c.getGroupPage(startURL, si) if err != nil { @@ -264,13 +263,13 @@ func (c *Client) GetGroups() (results *map[string]Group, err error) { } for _, group := range r.Resources { - c.logger.Debug("Add group to map", zap.String("group", group.DisplayName)) + log.WithFields(log.Fields{"group": group.DisplayName}).Debug("Add group to map") resultGroup[group.DisplayName] = group } si = si + 10 if si > r.TotalResults { - c.logger.Debug("Last Page obtained", zap.Int("totalResults", r.TotalResults)) + log.WithFields(log.Fields{"totalResults": r.TotalResults}).Debug("Last Page obtained") break } } @@ -325,11 +324,7 @@ func (c *Client) groupChangeOperation(op OperationType, u *User, g *Group) error return errors.New("no user specified") } - c.logger.Debug("groupChangeOperation", - zap.String("operation", string(op)), - zap.String("user", u.Username), - zap.String("group", g.DisplayName), - ) + log.WithFields(log.Fields{"operations": op, "user": u.Username, "group": g.DisplayName}).Debug("Group Change") gc := &GroupMemberChange{ Schemas: []string{"urn:ietf:params:scim:api:messages:2.0:PatchOp"}, diff --git a/internal/aws/client_test.go b/internal/aws/client_test.go index 3893e823..e117d28c 100644 --- a/internal/aws/client_test.go +++ b/internal/aws/client_test.go @@ -26,7 +26,6 @@ import ( "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" - "go.uber.org/zap" "github.com/awslabs/ssosync/internal/aws/mock" ) @@ -76,7 +75,7 @@ func TestNewClient(t *testing.T) { x := mock.NewMockIHttpClient(ctrl) - c, err := NewClient(zap.NewNop(), x, &Config{ + c, err := NewClient(x, &Config{ Endpoint: ":foo", Token: "bearerToken", }) @@ -90,7 +89,7 @@ func TestClient_GetUsers(t *testing.T) { x := mock.NewMockIHttpClient(ctrl) - c, err := NewClient(zap.NewNop(), x, &Config{ + c, err := NewClient(x, &Config{ Endpoint: "https://scim.example.com/", Token: "bearerToken", }) @@ -169,7 +168,7 @@ func TestClient_GetGroups(t *testing.T) { x := mock.NewMockIHttpClient(ctrl) - c, err := NewClient(zap.NewNop(), x, &Config{ + c, err := NewClient(x, &Config{ Endpoint: "https://scim.example.com/", Token: "bearerToken", }) @@ -256,7 +255,7 @@ func TestSendRequestBadUrl(t *testing.T) { x := mock.NewMockIHttpClient(ctrl) - c, err := NewClient(zap.NewNop(), x, &Config{ + c, err := NewClient(x, &Config{ Endpoint: "https://scim.example.com/", Token: "bearerToken", }) @@ -274,7 +273,7 @@ func TestSendRequestBadStatusCode(t *testing.T) { x := mock.NewMockIHttpClient(ctrl) - c, err := NewClient(zap.NewNop(), x, &Config{ + c, err := NewClient(x, &Config{ Endpoint: "https://scim.example.com/", Token: "bearerToken", }) @@ -304,7 +303,7 @@ func TestSendRequestCheckAuthHeader(t *testing.T) { x := mock.NewMockIHttpClient(ctrl) - c, err := NewClient(zap.NewNop(), x, &Config{ + c, err := NewClient(x, &Config{ Endpoint: "https://scim.example.com/", Token: "bearerToken", }) @@ -339,7 +338,7 @@ func TestSendRequestWithBodyCheckHeaders(t *testing.T) { x := mock.NewMockIHttpClient(ctrl) - c, err := NewClient(zap.NewNop(), x, &Config{ + c, err := NewClient(x, &Config{ Endpoint: "https://scim.example.com/", Token: "bearerToken", }) @@ -376,7 +375,7 @@ func TestClient_IsUserInGroup(t *testing.T) { x := mock.NewMockIHttpClient(ctrl) - c, err := NewClient(zap.NewNop(), x, &Config{ + c, err := NewClient(x, &Config{ Endpoint: "https://scim.example.com/", Token: "bearerToken", }) @@ -464,7 +463,7 @@ func TestClient_FindUserByEmail(t *testing.T) { x := mock.NewMockIHttpClient(ctrl) - c, err := NewClient(zap.NewNop(), x, &Config{ + c, err := NewClient(x, &Config{ Endpoint: "https://scim.example.com/", Token: "bearerToken", }) @@ -540,7 +539,7 @@ func TestClient_DeleteGroup(t *testing.T) { x := mock.NewMockIHttpClient(ctrl) - c, err := NewClient(zap.NewNop(), x, &Config{ + c, err := NewClient(x, &Config{ Endpoint: "https://scim.example.com/", Token: "bearerToken", }) @@ -579,7 +578,7 @@ func TestClient_DeleteUser(t *testing.T) { x := mock.NewMockIHttpClient(ctrl) - c, err := NewClient(zap.NewNop(), x, &Config{ + c, err := NewClient(x, &Config{ Endpoint: "https://scim.example.com/", Token: "bearerToken", }) @@ -622,7 +621,7 @@ func TestClient_CreateUser(t *testing.T) { x := mock.NewMockIHttpClient(ctrl) - c, err := NewClient(zap.NewNop(), x, &Config{ + c, err := NewClient(x, &Config{ Endpoint: "https://scim.example.com/", Token: "bearerToken", }) @@ -667,7 +666,7 @@ func TestClient_CreateGroup(t *testing.T) { x := mock.NewMockIHttpClient(ctrl) - c, err := NewClient(zap.NewNop(), x, &Config{ + c, err := NewClient(x, &Config{ Endpoint: "https://scim.example.com/", Token: "bearerToken", }) @@ -708,7 +707,7 @@ func TestClient_AddUserToGroup(t *testing.T) { x := mock.NewMockIHttpClient(ctrl) - c, err := NewClient(zap.NewNop(), x, &Config{ + c, err := NewClient(x, &Config{ Endpoint: "https://scim.example.com/", Token: "bearerToken", }) @@ -754,7 +753,7 @@ func TestClient_RemoveUserFromGroup(t *testing.T) { x := mock.NewMockIHttpClient(ctrl) - c, err := NewClient(zap.NewNop(), x, &Config{ + c, err := NewClient(x, &Config{ Endpoint: "https://scim.example.com/", Token: "bearerToken", }) diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 00000000..6e49b3a1 --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,44 @@ +package config + +// Config contains a configuration for Autobot +type Config struct { + // Verbose toggles the verbosity + Debug bool + // LogLevel is the level with with to log for this config + LogLevel string `mapstructure:"log_level"` + // LogFormat is the format that is used for logging + LogFormat string `mapstructure:"log_format"` + // GoogleCredentialsPath is the path to the credentials + GoogleCredentialsPath string `mapstructure:"google_credentials"` + // GoogleTokenPath is the path to the token + GoogleTokenPath string `mapstructure:"google_token"` + // SCIMConfig is the path to the AWS SSO SCIM Config + SCIMConfig string `mapstructure:"aws_toml"` +} + +const ( + // DefaultLogLevel is the default logging level. + DefaultLogLevel = "warn" + // DefaultLogFormat is the default format of the logger + DefaultLogFormat = "text" + // DefaultDebug is the default debug status. + DefaultDebug = false + // DefaultGoogleCredentialsPath is the default credentials path + DefaultGoogleCredentialsPath = "credentials.json" + // DefaultGoogleTokenPath is the default token path + DefaultGoogleTokenPath = "token.json" + // DefaultSCIMConfig is the default for the AWS SSO SCIM Configuraiton + DefaultSCIMConfig = "aws.toml" +) + +// New returns a new Config +func New() *Config { + return &Config{ + Debug: DefaultDebug, + LogLevel: DefaultLogLevel, + LogFormat: DefaultLogFormat, + GoogleCredentialsPath: DefaultGoogleCredentialsPath, + GoogleTokenPath: DefaultGoogleTokenPath, + SCIMConfig: DefaultSCIMConfig, + } +} diff --git a/internal/config/config_test.go b/internal/config/config_test.go new file mode 100644 index 00000000..2c144d2f --- /dev/null +++ b/internal/config/config_test.go @@ -0,0 +1,24 @@ +package config_test + +import ( + "testing" + + . "github.com/awslabs/ssosync/internal/config" + + "github.com/stretchr/testify/assert" +) + +func TestConfig(t *testing.T) { + assert := assert.New(t) + + cfg := New() + + assert.NotNil(cfg) + + assert.Equal(cfg.LogLevel, DefaultLogLevel) + assert.Equal(cfg.LogFormat, DefaultLogFormat) + assert.Equal(cfg.Debug, DefaultDebug) + assert.Equal(cfg.GoogleCredentialsPath, DefaultGoogleCredentialsPath) + assert.Equal(cfg.GoogleTokenPath, DefaultGoogleTokenPath) + assert.Equal(cfg.SCIMConfig, DefaultSCIMConfig) +} diff --git a/internal/google/auth.go b/internal/google/auth.go index 3832abdf..95d365a8 100644 --- a/internal/google/auth.go +++ b/internal/google/auth.go @@ -22,7 +22,6 @@ import ( "net/http" "os" - "go.uber.org/zap" "golang.org/x/oauth2" "golang.org/x/oauth2/google" admin "google.golang.org/api/admin/directory/v1" @@ -31,7 +30,6 @@ import ( // AuthClient is for authenticating with Google and optionally // getting a token from the web interface interactively type AuthClient struct { - logger *zap.Logger credentialsPath string tokenPath string @@ -39,10 +37,9 @@ type AuthClient struct { } // NewAuthClient creates a new AuthClient with the paths given -func NewAuthClient(logger *zap.Logger, credPath string, tokenPath string) (*AuthClient, error) { +func NewAuthClient(credPath string, tokenPath string) (*AuthClient, error) { b, err := ioutil.ReadFile(credPath) if err != nil { - logger.Error("Unable to read client secret file", zap.Error(err)) return nil, err } @@ -53,12 +50,10 @@ func NewAuthClient(logger *zap.Logger, credPath string, tokenPath string) (*Auth ) if err != nil { - logger.Error("unable to parse config from JSON", zap.Error(err)) return nil, err } return &AuthClient{ - logger: logger, credentialsPath: credPath, tokenPath: tokenPath, config: config, @@ -75,35 +70,37 @@ func (a *AuthClient) GetClient() (*http.Client, error) { } // GetTokenFromWeb will interactively get a token from Google -func (a *AuthClient) GetTokenFromWeb() *oauth2.Token { +func (a *AuthClient) GetTokenFromWeb() (*oauth2.Token, error) { authURL := a.config.AuthCodeURL("state-token", oauth2.AccessTypeOffline) fmt.Printf("Go to the following link in your browser then type the "+ "authorization code: \n%v\nAuth Code: ", authURL) var authCode string if _, err := fmt.Scan(&authCode); err != nil { - a.logger.Fatal("Unable to read authorization code", zap.Error(err)) + return nil, err } tok, err := a.config.Exchange(context.TODO(), authCode) if err != nil { - a.logger.Fatal("Unable to retrieve token from web", zap.Error(err)) + return nil, err } a.saveToken(tok) - return tok + return tok, nil } // saveToken will save the token to the token.json file -func (a *AuthClient) saveToken(token *oauth2.Token) { +func (a *AuthClient) saveToken(token *oauth2.Token) error { fmt.Printf("Saving credential file to: %s\n", a.tokenPath) f, err := os.OpenFile(a.tokenPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) if err != nil { - a.logger.Fatal("Unable to cache oauth token", zap.Error(err)) + return err } defer f.Close() _ = json.NewEncoder(f).Encode(token) + + return nil } // tokenFromFile retrieves a token from a local file. diff --git a/internal/google/client.go b/internal/google/client.go index 733817b9..7edf0769 100644 --- a/internal/google/client.go +++ b/internal/google/client.go @@ -18,7 +18,6 @@ import ( "context" "net/http" - "go.uber.org/zap" admin "google.golang.org/api/admin/directory/v1" "google.golang.org/api/option" ) @@ -32,13 +31,12 @@ type IClient interface { // Client is the Google Apps for Domains Client type Client struct { - logger *zap.Logger client *http.Client service *admin.Service } // NewClient creates a new client for Google's Admin API -func NewClient(logger *zap.Logger, client *AuthClient) (IClient, error) { +func NewClient(client *AuthClient) (IClient, error) { c, err := client.GetClient() if err != nil { return nil, err @@ -50,7 +48,6 @@ func NewClient(logger *zap.Logger, client *AuthClient) (IClient, error) { } return &Client{ - logger: logger, client: c, service: srv, }, nil diff --git a/internal/sync.go b/internal/sync.go index 1c170074..3011b460 100644 --- a/internal/sync.go +++ b/internal/sync.go @@ -17,12 +17,13 @@ package internal import ( "net/http" - "go.uber.org/zap" - "go.uber.org/zap/zapcore" - admin "google.golang.org/api/admin/directory/v1" - "github.com/awslabs/ssosync/internal/aws" + "github.com/awslabs/ssosync/internal/config" "github.com/awslabs/ssosync/internal/google" + "go.uber.org/zap" + + log "github.com/sirupsen/logrus" + admin "google.golang.org/api/admin/directory/v1" ) // ISyncGSuite is the interface for synchronising users/groups @@ -35,46 +36,47 @@ type ISyncGSuite interface { type SyncGSuite struct { aws aws.IClient google google.IClient - logger *zap.Logger users map[string]*aws.User } // New will create a new SyncGSuite object -func New(logger *zap.Logger, a aws.IClient, g google.IClient) ISyncGSuite { +func New(a aws.IClient, g google.IClient) ISyncGSuite { return &SyncGSuite{ aws: a, google: g, - logger: logger, users: make(map[string]*aws.User), } } // SyncUsers will Sync Google Users to AWS SSO SCIM func (s *SyncGSuite) SyncUsers() error { - s.logger.Info("Start user sync") - s.logger.Debug("Get AWS Users") + log.Info("Start user sync") + log.Info("Get AWS Users") + awsUsers, err := s.aws.GetUsers() if err != nil { return err } - s.logger.Debug("Get Google Users") + + log.Debug("Get Google Users") googleUsers, err := s.google.GetUsers() if err != nil { return err } for _, u := range googleUsers { - logUser := []zap.Field{ - zap.String("email", u.PrimaryEmail), - } + log := log.WithFields(log.Fields{ + "email": u.PrimaryEmail, + }) + + log.Debug("Check user") - s.logger.Debug("Check user", logUser...) if awsUser, ok := (*awsUsers)[u.PrimaryEmail]; ok { - s.logger.Debug("Found user", logUser...) + log.Debug("Found user") s.users[awsUser.Username] = &awsUser } else { - s.logger.Info("Create user in AWS", logUser...) + log.Info("Create user in AWS") newUser, err := s.aws.CreateUser(aws.NewUser( u.Name.GivenName, u.Name.FamilyName, @@ -83,14 +85,16 @@ func (s *SyncGSuite) SyncUsers() error { if err != nil { return err } + s.users[newUser.Username] = newUser } } - s.logger.Info("Clean up AWS Users") + log.Info("Clean up AWS Users") for _, u := range *awsUsers { if _, ok := s.users[u.Username]; !ok { - s.logger.Info("Delete User in AWS", zap.String("email", u.Username)) + log.WithField("email", u.Username).Info("Delete User in AWS") + err := s.aws.DeleteUser(&u) if err != nil { return err @@ -103,15 +107,15 @@ func (s *SyncGSuite) SyncUsers() error { // SyncGroups will sync groups from Google -> AWS SSO func (s *SyncGSuite) SyncGroups() error { - s.logger.Info("Start group sync") + log.Info("Start group sync") - s.logger.Debug("Get AWS Groups") + log.Debug("Get AWS Groups") awsGroups, err := s.aws.GetGroups() if err != nil { return err } - s.logger.Debug("Get Google Groups") + log.Debug("Get Google Groups") googleGroups, err := s.google.GetGroups() if err != nil { return err @@ -120,20 +124,20 @@ func (s *SyncGSuite) SyncGroups() error { correlatedGroups := make(map[string]*aws.Group) for _, g := range googleGroups { - logGroup := []zap.Field{ - zap.String("group", g.Name), - } + log := log.WithFields(log.Fields{ + "group": g.Name, + }) - s.logger.Debug("Check group", logGroup...) + log.Debug("Check group") var group *aws.Group if awsGroup, ok := (*awsGroups)[g.Name]; ok { - s.logger.Debug("Found group", logGroup...) + log.Debug("Found group") correlatedGroups[awsGroup.DisplayName] = &awsGroup group = &awsGroup } else { - s.logger.Info("Creating group in AWS", logGroup...) + log.Info("Creating group in AWS") newGroup, err := s.aws.CreateGroup(aws.NewGroup(g.Name)) if err != nil { return err @@ -149,7 +153,7 @@ func (s *SyncGSuite) SyncGroups() error { memberList := make(map[string]*admin.Member) - s.logger.Info("Start group user sync", logGroup...) + log.Info("Start group user sync") for _, m := range groupMembers { if _, ok := s.users[m.Email]; ok { @@ -158,9 +162,7 @@ func (s *SyncGSuite) SyncGroups() error { } for _, u := range s.users { - logDetail := append(logGroup, zap.String("user", u.Username)) - - s.logger.Debug("Checking user is in group already", logDetail...) + log.WithField("user", u.Username).Debug("Checking user is in group already") b, err := s.aws.IsUserInGroup(u, group) if err != nil { return err @@ -168,7 +170,7 @@ func (s *SyncGSuite) SyncGroups() error { if _, ok := memberList[u.Username]; ok { if !b { - s.logger.Info("Adding user to group", logDetail...) + log.WithField("user", u.Username).Info("Adding user to group") err := s.aws.AddUserToGroup(u, group) if err != nil { return err @@ -176,7 +178,7 @@ func (s *SyncGSuite) SyncGroups() error { } } else { if b { - s.logger.Info("Removing user from group", logDetail...) + log.WithField("user", u.Username).Info("Removing user from group") err := s.aws.RemoveUserFromGroup(u, group) if err != nil { return err @@ -186,10 +188,10 @@ func (s *SyncGSuite) SyncGroups() error { } } - s.logger.Info("Clean up AWS groups") + log.Info("Clean up AWS groups") for _, g := range *awsGroups { if _, ok := correlatedGroups[g.DisplayName]; !ok { - s.logger.Info("Delete Group in AWS", zap.String("group", g.DisplayName)) + log.Info("Delete Group in AWS", zap.String("group", g.DisplayName)) err := s.aws.DeleteGroup(&g) if err != nil { return err @@ -197,59 +199,39 @@ func (s *SyncGSuite) SyncGroups() error { } } - s.logger.Info("Done sync groups") - return nil -} + log.Info("Done sync groups") -// QuietLogSync will squash logging errors when calling -// sync on the logger. -func QuietLogSync(l *zap.Logger) { - err := l.Sync() - if err != nil { - return - } + return nil } // DoSync will create a logger and run the sync with the paths // given to do the sync. -func DoSync(debug bool, credPath string, tokenPath string, awsTomlPath string) error { - config := zap.NewDevelopmentConfig() - config.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder - if debug { - config.Level.SetLevel(zap.DebugLevel) - } else { - config.Level.SetLevel(zap.InfoLevel) - } +func DoSync(cfg *config.Config) error { + log.Info("Creating the Google and AWS Clients needed") - logger, _ := config.Build() - defer QuietLogSync(logger) - - logger.Info("Creating the Google and AWS Clients needed") - - googleAuthClient, err := google.NewAuthClient(logger, credPath, tokenPath) + googleAuthClient, err := google.NewAuthClient(cfg.GoogleCredentialsPath, cfg.GoogleTokenPath) if err != nil { - logger.Fatal("Failed to create Google Auth Client", zap.Error(err)) + return err } - googleClient, err := google.NewClient(logger, googleAuthClient) + googleClient, err := google.NewClient(googleAuthClient) if err != nil { - logger.Fatal("Failed to create Google Client", zap.Error(err)) + return err } - awsConfig, err := aws.ReadConfigFromFile(awsTomlPath) + awsConfig, err := aws.ReadConfigFromFile(cfg.SCIMConfig) if err != nil { - logger.Fatal("Failed to read AWS Config", zap.Error(err)) + return err } awsClient, err := aws.NewClient( - logger, &http.Client{}, awsConfig) if err != nil { - logger.Fatal("Failed to create awsClient", zap.Error(err)) + return err } - c := New(logger, awsClient, googleClient) + c := New(awsClient, googleClient) err = c.SyncUsers() if err != nil { return err diff --git a/main.go b/main.go index 782b36ad..3639aa01 100644 --- a/main.go +++ b/main.go @@ -15,18 +15,16 @@ package main import ( - "fmt" + "math/rand" + "time" "github.com/awslabs/ssosync/cmd" ) -var ( - version = "dev" - commit = "none" - date = "unknown" - builtBy = "unknown" -) +func init() { + rand.Seed(time.Now().UnixNano()) +} func main() { - cmd.Execute(fmt.Sprintf("%s, commit %s, built at %s by %s\n", version, commit, date, builtBy)) + cmd.Execute() }