Skip to content

Commit

Permalink
Expose configv2 endpoint in graphql (#7490)
Browse files Browse the repository at this point in the history
* Expose configv2 endpoint in graphql

* core/web/resolver: test config v2

Co-authored-by: Jordan Krage <[email protected]>
  • Loading branch information
samsondav and jmank88 authored Oct 26, 2022
1 parent fb013fa commit 2da9c4e
Show file tree
Hide file tree
Showing 17 changed files with 1,423 additions and 16 deletions.
15 changes: 13 additions & 2 deletions core/cmd/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -356,14 +356,25 @@ func NewApp(client *Client) *cli.App {
Subcommands: []cli.Command{
{
Name: "dump",
Usage: "Dump a TOML file equivalent to the current environment and database configuration",
Usage: "LEGACY CONFIG (ENV) ONLY - Dump a TOML file equivalent to the current environment and database configuration",
Action: client.ConfigDump,
},
{
Name: "list",
Usage: "Show the node's environment variables",
Usage: "LEGACY CONFIG (ENV) ONLY - Show the node's environment variables",
Action: client.GetConfiguration,
},
{
Name: "show",
Usage: "V2 CONFIG (TOML) ONLY - Show the application configuration",
Action: client.ConfigV2,
Flags: []cli.Flag{
cli.BoolFlag{
Name: "user-only",
Usage: "If set, show only the user-provided TOML configuration, omitting application defaults",
},
},
},
{
Name: "setgasprice",
Usage: "Set the default gas price to use for outgoing transactions",
Expand Down
5 changes: 5 additions & 0 deletions core/cmd/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,8 @@ func (cli *Client) CheckRemoteBuildCompatibility(lggr logger.Logger, onlyWarn bo
func (cli *Client) ConfigDumpStr() (string, error) {
return cli.configDumpStr()
}

// ConfigDumpStr exposes configV2Str for testing.
func (cli *Client) ConfigV2Str(userOnly bool) (string, error) {
return cli.configV2Str(userOnly)
}
37 changes: 36 additions & 1 deletion core/cmd/remote_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ func (cli *Client) GetConfiguration(c *clipkg.Context) (err error) {
}

func (cli *Client) configDumpStr() (string, error) {
resp, err := cli.HTTP.Get("/v2/config/v2")
resp, err := cli.HTTP.Get("/v2/config/dump-v1-as-v2")
if err != nil {
return "", cli.errorOut(err)
}
Expand Down Expand Up @@ -462,6 +462,41 @@ func (cli *Client) ConfigDump(c *clipkg.Context) (err error) {
return nil
}

func (cli *Client) ConfigV2(c *clipkg.Context) error {
userOnly := c.Bool("user-only")
s, err := cli.configV2Str(userOnly)
if err != nil {
return err
}
fmt.Println(s)
return nil
}

func (cli *Client) configV2Str(userOnly bool) (string, error) {
resp, err := cli.HTTP.Get(fmt.Sprintf("/v2/config/v2?userOnly=%t", userOnly))
if err != nil {
return "", cli.errorOut(err)
}
defer func() {
if cerr := resp.Body.Close(); cerr != nil {
err = multierr.Append(err, cerr)
}
}()
respPayload, err := io.ReadAll(resp.Body)
if err != nil {
return "", cli.errorOut(err)
}
if resp.StatusCode != 200 {
return "", cli.errorOut(errors.Errorf("got HTTP status %d: %s", resp.StatusCode, respPayload))
}
var configV2Resource web.ConfigV2Resource
err = web.ParseJSONAPIResponse(respPayload, &configV2Resource)
if err != nil {
return "", cli.errorOut(err)
}
return configV2Resource.Config, nil
}

func (cli *Client) ConfigFileValidate(c *clipkg.Context) error {
err := cli.Config.Validate()
if err != nil {
Expand Down
23 changes: 23 additions & 0 deletions core/cmd/remote_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"testing"
"time"

"github.com/kylelemons/godebug/diff"
"github.com/pelletier/go-toml"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -594,6 +595,28 @@ func TestClient_GetConfiguration(t *testing.T) {
assert.Equal(t, cp.EnvPrinter.SessionTimeout, cfg.SessionTimeout())
}

func TestClient_ConfigV2(t *testing.T) {
t.Parallel()

app := startNewApplicationV2(t, nil)
client, _ := app.NewClientAndRenderer()
assert.Error(t, client.GetConfiguration(cltest.EmptyCLIContext()))
cfg, ok := app.Config.(chainlink.ConfigV2)
require.True(t, ok)
user, effective := cfg.ConfigTOML()

t.Run("user", func(t *testing.T) {
got, err := client.ConfigV2Str(true)
require.NoError(t, err)
assert.Equal(t, user, got, diff.Diff(user, got))
})
t.Run("effective", func(t *testing.T) {
got, err := client.ConfigV2Str(false)
require.NoError(t, err)
assert.Equal(t, effective, got, diff.Diff(effective, got))
})
}

// https://app.shortcut.com/chainlinklabs/story/33622/remove-legacy-config
func TestClient_ConfigDump(t *testing.T) {
t.Parallel()
Expand Down
5 changes: 3 additions & 2 deletions core/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,9 @@ func ExampleRun_config() {
// core.test config command [command options] [arguments...]
//
// COMMANDS:
// dump Dump a TOML file equivalent to the current environment and database configuration
// list Show the node's environment variables
// dump LEGACY CONFIG (ENV) ONLY - Dump a TOML file equivalent to the current environment and database configuration
// list LEGACY CONFIG (ENV) ONLY - Show the node's environment variables
// show V2 CONFIG (TOML) ONLY - Show the application configuration
// setgasprice Set the default gas price to use for outgoing transactions
// loglevel Set log level
// logsql Enable/disable sql statement logging
Expand Down
10 changes: 10 additions & 0 deletions core/services/chainlink/config_general.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ import (
"github.com/smartcontractkit/chainlink/core/utils"
)

type ConfigV2 interface {
// ConfigTOML returns both the user provided and effective configuration as TOML.
ConfigTOML() (user, effective string)
}

// generalConfig is a wrapper to adapt Config to the config.GeneralConfig interface.
type generalConfig struct {
lggr logger.Logger
Expand Down Expand Up @@ -141,6 +146,11 @@ func (g *generalConfig) LogConfiguration(log coreconfig.LogFn) {
log("Effective Configuration, with defaults applied:\n", g.effectiveTOML)
}

// ConfigTOML implements chainlink.ConfigV2
func (g *generalConfig) ConfigTOML() (user, effective string) {
return g.inputTOML, g.effectiveTOML
}

func (g *generalConfig) Dev() bool {
return g.c.DevMode
}
Expand Down
49 changes: 39 additions & 10 deletions core/web/config_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package web
import (
"fmt"
"net/http"
"strconv"

"github.com/smartcontractkit/chainlink/core/config"
v2 "github.com/smartcontractkit/chainlink/core/config/v2"
"github.com/smartcontractkit/chainlink/core/logger/audit"
"github.com/smartcontractkit/chainlink/core/services/chainlink"
"github.com/smartcontractkit/chainlink/core/utils"
Expand All @@ -22,10 +24,31 @@ type ConfigController struct {
//
// "<application>/config"
func (cc *ConfigController) Show(c *gin.Context) {
cw := config.NewConfigPrinter(cc.App.GetConfig())

cc.App.GetAuditLogger().Audit(audit.EnvNoncriticalEnvDumped, map[string]interface{}{})
jsonAPIResponse(c, cw, "config")
cfg := cc.App.GetConfig()
if cfg2, ok := cfg.(chainlink.ConfigV2); ok {
var userOnly bool
if s, has := c.GetQuery("userOnly"); has {
var err error
userOnly, err = strconv.ParseBool(s)
if err != nil {
jsonAPIError(c, http.StatusBadRequest, fmt.Errorf("invalid bool for userOnly: %v", err))
}
}
var toml string
user, effective := cfg2.ConfigTOML()
if userOnly {
toml = user
} else {
toml = effective
}
jsonAPIResponse(c, ConfigV2Resource{toml}, "config")
} else {
// Legacy config
cw := config.NewConfigPrinter(cc.App.GetConfig())

cc.App.GetAuditLogger().Audit(audit.EnvNoncriticalEnvDumped, map[string]interface{}{})
jsonAPIResponse(c, cw, "config")
}
}

type ConfigV2Resource struct {
Expand All @@ -41,13 +64,19 @@ func (c *ConfigV2Resource) SetID(string) error {
}

func (cc *ConfigController) Dump(c *gin.Context) {
tomlStr, err := cc.App.ConfigDump(c)
if err != nil {
cc.App.GetLogger().Errorw("Failed to dump TOML config", "err", err)
jsonAPIError(c, http.StatusInternalServerError, err)
return
cfg := cc.App.GetConfig()
if _, ok := cfg.(chainlink.ConfigV2); ok {
jsonAPIError(c, http.StatusUnprocessableEntity, v2.ErrUnsupported)
} else {
// Legacy config mode
userToml, err := cc.App.ConfigDump(c)
if err != nil {
cc.App.GetLogger().Errorw("Failed to dump TOML config", "err", err)
jsonAPIError(c, http.StatusInternalServerError, err)
return
}
jsonAPIResponse(c, ConfigV2Resource{userToml}, "config")
}
jsonAPIResponse(c, ConfigV2Resource{tomlStr}, "config")
}

type configPatchRequest struct {
Expand Down
80 changes: 80 additions & 0 deletions core/web/resolver/config_test.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,94 @@
package resolver

import (
_ "embed"
"encoding/json"
"fmt"
"testing"

"github.com/stretchr/testify/require"
"go.uber.org/zap/zapcore"
"gopkg.in/guregu/null.v4"

"github.com/smartcontractkit/chainlink/core/internal/testutils/configtest"
"github.com/smartcontractkit/chainlink/core/logger"
"github.com/smartcontractkit/chainlink/core/services/chainlink"
)

var (
//go:embed testdata/config-empty-effective.toml
configEmptyEffective string
//go:embed testdata/config-full.toml
configFull string
//go:embed testdata/config-multi-chain.toml
configMulti string
//go:embed testdata/config-multi-chain-effective.toml
configMultiEffective string
)

func TestResolver_ConfigV2(t *testing.T) {
t.Parallel()

query := `
query FetchConfigV2 {
configv2 {
user
effective
}
}`

testCases := []GQLTestCase{
unauthorizedTestCase(GQLTestCase{query: query}, "configv2"),
{
name: "empty",
authenticated: true,
before: func(f *gqlTestFramework) {
var opts chainlink.GeneralConfigOpts
require.NoError(f.t, opts.ParseTOML("", ""))
cfg, err := opts.New(logger.TestLogger(f.t))
require.NoError(t, err)
f.App.On("GetConfig").Return(cfg)
},
query: query,
result: fmt.Sprintf(`{"configv2":{"user":"","effective":%s}}`, mustJSONMarshal(t, configEmptyEffective)),
},
{
name: "full",
authenticated: true,
before: func(f *gqlTestFramework) {
var opts chainlink.GeneralConfigOpts
require.NoError(f.t, opts.ParseTOML(configFull, ""))
cfg, err := opts.New(logger.TestLogger(f.t))
require.NoError(t, err)
f.App.On("GetConfig").Return(cfg)
},
query: query,
result: fmt.Sprintf(`{"configv2":{"user":%s,"effective":%s}}`, mustJSONMarshal(t, configFull), mustJSONMarshal(t, configFull)),
},
{
name: "partial",
authenticated: true,
before: func(f *gqlTestFramework) {
var opts chainlink.GeneralConfigOpts
require.NoError(f.t, opts.ParseTOML(configMulti, ""))
cfg, err := opts.New(logger.TestLogger(f.t))
require.NoError(t, err)
f.App.On("GetConfig").Return(cfg)
},
query: query,
result: fmt.Sprintf(`{"configv2":{"user":%s,"effective":%s}}`, mustJSONMarshal(t, configMulti), mustJSONMarshal(t, configMultiEffective)),
},
}

RunGQLTests(t, testCases)
}

func mustJSONMarshal(t *testing.T, s string) string {
b, err := json.Marshal(s)
require.NoError(t, err)
return string(b)
}

func TestResolver_Config(t *testing.T) {
t.Parallel()

Expand Down
18 changes: 18 additions & 0 deletions core/web/resolver/config_v2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package resolver

type ConfigV2PayloadResolver struct {
user string
effective string
}

func NewConfigV2Payload(user, effective string) *ConfigV2PayloadResolver {
return &ConfigV2PayloadResolver{user, effective}
}

func (r *ConfigV2PayloadResolver) User() string {
return r.user
}

func (r *ConfigV2PayloadResolver) Effective() string {
return r.effective
}
20 changes: 20 additions & 0 deletions core/web/resolver/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/smartcontractkit/chainlink/core/bridges"
"github.com/smartcontractkit/chainlink/core/chains/evm"
"github.com/smartcontractkit/chainlink/core/config"
"github.com/smartcontractkit/chainlink/core/services/chainlink"
"github.com/smartcontractkit/chainlink/core/services/keystore"
"github.com/smartcontractkit/chainlink/core/services/keystore/keys/vrfkey"
"github.com/smartcontractkit/chainlink/core/utils"
Expand Down Expand Up @@ -436,6 +437,25 @@ func (r *Resolver) Config(ctx context.Context) (*ConfigPayloadResolver, error) {
return NewConfigPayload(printer.EnvPrinter), nil
}

// ConfigV2 retrieves the Chainlink node's configuration (V2 mode)
func (r *Resolver) ConfigV2(ctx context.Context) (*ConfigV2PayloadResolver, error) {
if err := authenticateUser(ctx); err != nil {
return nil, err
}

cfg := r.App.GetConfig()
if v2, ok := cfg.(chainlink.ConfigV2); ok {
return NewConfigV2Payload(v2.ConfigTOML()), nil
}
// Legacy config mode
userToml, err := r.App.ConfigDump(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to dump application V2 config")
}

return NewConfigV2Payload(userToml, "N/A"), nil
}

func (r *Resolver) EthTransaction(ctx context.Context, args struct {
Hash graphql.ID
}) (*EthTransactionPayloadResolver, error) {
Expand Down
Loading

0 comments on commit 2da9c4e

Please sign in to comment.