diff --git a/.inventory-api.yaml b/.inventory-api.yaml index ea9f8ab5..3e48a82e 100644 --- a/.inventory-api.yaml +++ b/.inventory-api.yaml @@ -1,9 +1,9 @@ server: - public_url: http://localhost:8081 + public_url: http://localhost:8000 http: - address: localhost:8081 + address: localhost:8000 grpc: - address: localhost:9081 + address: localhost:9000 authn: allow-unauthenticated: true authz: diff --git a/README.md b/README.md index 628d61f0..4f78d441 100644 --- a/README.md +++ b/README.md @@ -188,5 +188,33 @@ authz: sa-client-secret: "" sso-token-endpoint: "http://localhost:8084/realms/redhat-external/protocol/openid-connect/token" ``` + +## Running in Ephemeral Cluster with Relations API using Bonfire + +Deploy Relations API first with Bonfire following the steps available [HERE](https://cuddly-tribble-gq7r66v.pages.github.io/kessel/ephemeral/) + +Once its running, deploy Inventory using Bonfire: + +`bonfire deploy kessel -C inventory-api --no-get-dependencies` + +If you wish to test changes you've made that are unmerged, you can deploy them to ephemeral using a local config file +Note: this requires building the image first and pushing to your local quay (see make docker-build-push) + +```yaml +# example local config under $HOME/.config/bonfire/config +apps: +- name: kessel + components: + - name: inventory-api + host: local + repo: /path/to/inventory-api-repo + path: deploy/kessel-inventory.yaml + parameters: + INVENTORY_IMAGE: quay.io/your-repo/image-name + IMAGE_TAG: your-image-tag # latest is not recommended due to pull policy +``` + +Then run `bonfire deploy kessel -c $HOME/.config/bonfire/config.yaml --local-config-method override --no-get-dependencies` + ## Debugging Inventory API using Vscode Follow the [DEBUG](./DEBUG.md) guide diff --git a/cmd/.inventory-api.yaml b/cmd/.inventory-api.yaml index e36fd150..fdea446b 100644 --- a/cmd/.inventory-api.yaml +++ b/cmd/.inventory-api.yaml @@ -1,9 +1,9 @@ server: - public_url: http://localhost:8081 + public_url: http://localhost:8000 http: - address: localhost:8081 + address: localhost:8000 grpc: - address: localhost:9081 + address: localhost:9000 authn: allow-unauthenticated: true authz: diff --git a/cmd/root.go b/cmd/root.go index 5614e834..0f62fc5e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -2,26 +2,19 @@ package cmd import ( "fmt" + "github.com/project-kessel/inventory-api/internal/config" + clowder "github.com/redhatinsights/app-common-go/pkg/api/v1" + "github.com/spf13/cobra" + "github.com/spf13/viper" "os" "path/filepath" "strings" - "strconv" - - "github.com/spf13/cobra" - "github.com/spf13/viper" "github.com/go-kratos/kratos/v2/log" "github.com/go-kratos/kratos/v2/middleware/tracing" "github.com/project-kessel/inventory-api/cmd/migrate" "github.com/project-kessel/inventory-api/cmd/serve" - - "github.com/project-kessel/inventory-api/internal/authn" - "github.com/project-kessel/inventory-api/internal/authz" - "github.com/project-kessel/inventory-api/internal/eventing" - "github.com/project-kessel/inventory-api/internal/server" - "github.com/project-kessel/inventory-api/internal/storage" - clowder "github.com/redhatinsights/app-common-go/pkg/api/v1" ) // go build -ldflags "-X cmd.Version=x.y.z" @@ -40,19 +33,7 @@ var ( Short: "A simple common inventory system", } - options = struct { - Authn *authn.Options `mapstructure:"authn"` - Authz *authz.Options `mapstructure:"authz"` - Storage *storage.Options `mapstructure:"storage"` - Eventing *eventing.Options `mapstructure:"eventing"` - Server *server.Options `mapstructure:"server"` - }{ - authn.NewOptions(), - authz.NewOptions(), - storage.NewOptions(), - eventing.NewOptions(), - server.NewOptions(), - } + options = config.NewOptionsConfig() ) // Execute is called by main.main(). It only needs to happen once to the rootCmd. @@ -85,20 +66,21 @@ func init() { panic(err) } - if clowder.IsClowderEnabled() { - options.Storage.Postgres.Host = clowder.LoadedConfig.Database.Hostname - options.Storage.Postgres.Port = strconv.Itoa(clowder.LoadedConfig.Database.Port) - options.Storage.Postgres.User = clowder.LoadedConfig.Database.Username - options.Storage.Postgres.Password = clowder.LoadedConfig.Database.Password - options.Storage.Postgres.DbName = clowder.LoadedConfig.Database.Name - } - // TODO: Find a cleaner approach than explicitly calling initConfig // Here we are calling the initConfig to ensure that the log level can be pulled from the inventory configuration file initConfig() logLevel := getLogLevel() logger, baseLogger = initLogger(logLevel) + if clowder.IsClowderEnabled() { + options.InjectClowdAppConfig() + } + + // for troubleshoot, when set to debug, configuration info is logged in more detail to stdout + if logLevel == "debug" { + config.LogConfigurationInfo(options) + } + migrateCmd := migrate.NewCommand(options.Storage, baseLogger) rootCmd.AddCommand(migrateCmd) err = viper.BindPFlags(migrateCmd.Flags()) diff --git a/deploy/kessel-inventory.yaml b/deploy/kessel-inventory.yaml index 1c16bf34..5a931f3f 100644 --- a/deploy/kessel-inventory.yaml +++ b/deploy/kessel-inventory.yaml @@ -9,27 +9,15 @@ objects: name: inventory-api-config data: inventory-api-config.yaml: | - server: - http: - address: 0.0.0.0:8000 - grpc: - address: 0.0.0.0:9000 authn: psk: pre-shared-key-file: /psks/psks.yaml authz: - impl: kessel kessel: insecure-client: true - url: kessel-relations-api:9000 - enable_oidc_auth: false - eventing: - eventer: stdout - kafka: - storage: - database: postgres - sqlite3: - dsn: inventory.db + enable-oidc-auth: false + log: + level: "info" - apiVersion: v1 kind: ConfigMap @@ -51,6 +39,8 @@ objects: envName: ${ENV_NAME} database: name: kessel-inventory + optionalDependencies: + - kessel-relations deployments: - name: api replicas: 1 diff --git a/internal/authz/kessel/options.go b/internal/authz/kessel/options.go index 4973731a..530bae0c 100644 --- a/internal/authz/kessel/options.go +++ b/internal/authz/kessel/options.go @@ -17,7 +17,10 @@ type Options struct { } func NewOptions() *Options { - return &Options{} + return &Options{ + Insecure: false, + EnableOidcAuth: true, + } } func (o *Options) AddFlags(fs *pflag.FlagSet, prefix string) { diff --git a/internal/authz/options.go b/internal/authz/options.go index 11951bd0..9e6a64d6 100644 --- a/internal/authz/options.go +++ b/internal/authz/options.go @@ -15,8 +15,9 @@ type Options struct { } const ( - AllowAll = "allow-all" - Kessel = "kessel" + AllowAll = "allow-all" + Kessel = "kessel" + RelationsAPI = "kessel-relations" ) func NewOptions() *Options { diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 00000000..c0ebcb8a --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,94 @@ +package config + +import ( + "fmt" + "github.com/go-kratos/kratos/v2/log" + "github.com/project-kessel/inventory-api/internal/authn" + "github.com/project-kessel/inventory-api/internal/authz" + "github.com/project-kessel/inventory-api/internal/eventing" + "github.com/project-kessel/inventory-api/internal/server" + "github.com/project-kessel/inventory-api/internal/storage" + clowder "github.com/redhatinsights/app-common-go/pkg/api/v1" + "strconv" +) + +// OptionsConfig contains the settings for each configuration option +type OptionsConfig struct { + Authn *authn.Options + Authz *authz.Options + Storage *storage.Options + Eventing *eventing.Options + Server *server.Options +} + +// NewOptionsConfig returns a new OptionsConfig with default options set +func NewOptionsConfig() *OptionsConfig { + return &OptionsConfig{ + authn.NewOptions(), + authz.NewOptions(), + storage.NewOptions(), + eventing.NewOptions(), + server.NewOptions(), + } +} + +// LogConfigurationInfo outputs connection details to logs when in debug for testing (no secret data is output) +func LogConfigurationInfo(options *OptionsConfig) { + log.Debugf("Server Configuration: Public URL: %s, HTTP Listener: %s, GRPC Listener: %s", + options.Server.PublicUrl, + options.Server.HttpOptions.Addr, + options.Server.GrpcOptions.Addr) + + if options.Authn.Oidc.AuthorizationServerURL != "" { + log.Debugf("Authn Configuration: URL: %s, ClientID: %s", + options.Authn.Oidc.AuthorizationServerURL, + options.Authn.Oidc.ClientId, + ) + } + + if options.Storage.Database == storage.Postgres { + log.Debugf("Storage Configuration: Host: %s, DB: %s, Port: %s", + options.Storage.Postgres.Host, + options.Storage.Postgres.DbName, + options.Storage.Postgres.Port, + ) + } + + if options.Authz.Authz == authz.Kessel { + log.Debugf("Authz Configuration: URL: %s, Insecure?: %t, OIDC?: %t", + options.Authz.Kessel.URL, + options.Authz.Kessel.Insecure, + options.Authz.Kessel.EnableOidcAuth, + ) + } +} + +// InjectClowdAppConfig updates service options based on values in the ClowdApp AppConfig +func (o *OptionsConfig) InjectClowdAppConfig() { + // check for authz config + for _, endpoint := range clowder.LoadedConfig.Endpoints { + if endpoint.App == authz.RelationsAPI { + o.ConfigureAuthz(endpoint) + } + } + // check for db config + if clowder.LoadedConfig.Database != nil { + o.ConfigureStorage(clowder.LoadedConfig.Database) + } +} + +// ConfigureAuthz updates Authz settings based on ClowdApp AppConfig +func (o *OptionsConfig) ConfigureAuthz(endpoint clowder.DependencyEndpoint) { + o.Authz.Authz = authz.Kessel + o.Authz.Kessel.URL = fmt.Sprintf("%s:%d", endpoint.Hostname, 9000) +} + +// ConfigureStorage updates Storage settings based on ClowdApp AppConfig +func (o *OptionsConfig) ConfigureStorage(database *clowder.DatabaseConfig) { + o.Storage.Database = storage.Postgres + o.Storage.Postgres.Host = database.Hostname + o.Storage.Postgres.Port = strconv.Itoa(database.Port) + o.Storage.Postgres.User = database.Username + o.Storage.Postgres.Password = database.Password + o.Storage.Postgres.DbName = database.Name +} diff --git a/internal/config/config_test.go b/internal/config/config_test.go new file mode 100644 index 00000000..4fb4910e --- /dev/null +++ b/internal/config/config_test.go @@ -0,0 +1,69 @@ +package config + +import ( + clowder "github.com/redhatinsights/app-common-go/pkg/api/v1" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestConfigureStorage(t *testing.T) { + tests := []struct { + name string + appconfig *clowder.AppConfig + options *OptionsConfig + }{ + { + name: "ensures DB info is set", + appconfig: &clowder.AppConfig{ + Database: &clowder.DatabaseConfig{ + Hostname: "postgres", + Name: "postgres", + Port: 5432, + Username: "db-user", + Password: "db-password", + }, + }, + options: NewOptionsConfig(), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + test.options.ConfigureStorage(test.appconfig.Database) + assert.Equal(t, "postgres", test.options.Storage.Database) + assert.Equal(t, "postgres", test.options.Storage.Postgres.Host) + assert.Equal(t, "postgres", test.options.Storage.Postgres.DbName) + assert.Equal(t, "5432", test.options.Storage.Postgres.Port) + assert.Equal(t, "db-user", test.options.Storage.Postgres.User) + assert.Equal(t, "db-password", test.options.Storage.Postgres.Password) + }) + } +} + +func TestConfigureAuthz(t *testing.T) { + tests := []struct { + name string + appconfig *clowder.AppConfig + options *OptionsConfig + }{ + { + name: "ensures Authz info is set", + appconfig: &clowder.AppConfig{ + Endpoints: []clowder.DependencyEndpoint{ + clowder.DependencyEndpoint{ + Hostname: "kessel-relations", + }, + }, + }, + options: NewOptionsConfig(), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + test.options.ConfigureAuthz(test.appconfig.Endpoints[0]) + assert.Equal(t, "kessel", test.options.Authz.Authz) + assert.Equal(t, "kessel-relations:9000", test.options.Authz.Kessel.URL) + }) + } +} diff --git a/internal/server/grpc/options.go b/internal/server/grpc/options.go index 3063accc..9987df43 100644 --- a/internal/server/grpc/options.go +++ b/internal/server/grpc/options.go @@ -20,7 +20,7 @@ type Options struct { func NewOptions() *Options { return &Options{ - Addr: "localhost:9080", + Addr: "0.0.0.0:9000", Timeout: 300, CertOpt: 3, // https://pkg.go.dev/crypto/tls#ClientAuthType } diff --git a/internal/server/http/http_test.go b/internal/server/http/http_test.go index e619dadd..8c7ebbf7 100644 --- a/internal/server/http/http_test.go +++ b/internal/server/http/http_test.go @@ -11,7 +11,7 @@ func TestNewOptions(t *testing.T) { opts := NewOptions() expectedOpts := &Options{ - Addr: "localhost:8080", + Addr: "0.0.0.0:8000", Timeout: 300, CertOpt: 3, } diff --git a/internal/server/http/options.go b/internal/server/http/options.go index c5786ba7..c8c4ba80 100644 --- a/internal/server/http/options.go +++ b/internal/server/http/options.go @@ -20,7 +20,7 @@ type Options struct { func NewOptions() *Options { return &Options{ - Addr: "localhost:8080", + Addr: "0.0.0.0:8000", Timeout: 300, CertOpt: 3, // https://pkg.go.dev/crypto/tls#ClientAuthType } diff --git a/internal/server/options.go b/internal/server/options.go index d02f5127..c85e8b54 100644 --- a/internal/server/options.go +++ b/internal/server/options.go @@ -22,8 +22,8 @@ func NewOptions() *Options { id, _ := os.Hostname() return &Options{ Id: id, - Name: "kessel-asset-inventory", - PublicUrl: "http://localhost:8081", + Name: "kessel-inventory-api", + PublicUrl: "http://localhost:8000", GrpcOptions: grpc.NewOptions(), HttpOptions: http.NewOptions(), diff --git a/internal/storage/options.go b/internal/storage/options.go index 703a448b..a350c0a5 100644 --- a/internal/storage/options.go +++ b/internal/storage/options.go @@ -14,6 +14,11 @@ type Options struct { Database string `mapstructure:"database"` } +const ( + Postgres = "postgres" + Sqlite3 = "sqlite3" +) + func NewOptions() *Options { return &Options{ Postgres: postgres.NewOptions(), diff --git a/inventory-api-compose.yaml b/inventory-api-compose.yaml index 27f3f942..baec56f2 100644 --- a/inventory-api-compose.yaml +++ b/inventory-api-compose.yaml @@ -11,7 +11,7 @@ authz: kessel: insecure-client: true url: relations-api:9000 - enable_oidc_auth: false + enable-oidc-auth: false eventing: eventer: stdout kafka: @@ -26,7 +26,7 @@ storage: password: "yPsw5e6ab4bvAGe5H" dbname: "spicedb" log: - level: "info" + level: "debug" livez: true readyz: true