Skip to content

Commit

Permalink
(refactor) logging, parsing, config
Browse files Browse the repository at this point in the history
  • Loading branch information
katallaxie committed Jul 17, 2020
1 parent 36ac217 commit dd4f473
Show file tree
Hide file tree
Showing 17 changed files with 283 additions and 205 deletions.
5 changes: 5 additions & 0 deletions .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
5 changes: 5 additions & 0 deletions .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
5 changes: 4 additions & 1 deletion .gitignore
Expand Up @@ -17,5 +17,8 @@
aws.toml
credentials.json
token.json
.idea/
dist/

# IDE
.idea/
.vscode/
2 changes: 2 additions & 0 deletions .goreleaser.yml
Expand Up @@ -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:
Expand Down
31 changes: 6 additions & 25 deletions cmd/google.go
Expand Up @@ -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"
)
Expand All @@ -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")
}
36 changes: 7 additions & 29 deletions cmd/lambda.go
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
102 changes: 78 additions & 24 deletions cmd/root.go
Expand Up @@ -16,58 +16,112 @@ 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",
Short: "SSO Sync, making AWS SSO be populated automagically",
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)
}
}
3 changes: 3 additions & 0 deletions go.mod
Expand Up @@ -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
Expand Down
15 changes: 13 additions & 2 deletions go.sum
Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand All @@ -133,23 +135,28 @@ 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=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
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=
Expand All @@ -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=
Expand Down Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand Down

0 comments on commit dd4f473

Please sign in to comment.