diff --git a/cmd/cli/agent/config.go b/cmd/cli/agent/config.go new file mode 100644 index 0000000000..1abc072d09 --- /dev/null +++ b/cmd/cli/agent/config.go @@ -0,0 +1,48 @@ +package agent + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/bacalhau-project/bacalhau/cmd/util" + "github.com/bacalhau-project/bacalhau/cmd/util/flags/cliflags" + "github.com/bacalhau-project/bacalhau/cmd/util/output" + "github.com/bacalhau-project/bacalhau/pkg/config/types" + "github.com/bacalhau-project/bacalhau/pkg/publicapi/client/v2" +) + +func NewConfigCmd() *cobra.Command { + o := output.NonTabularOutputOptions{ + Format: output.YAMLFormat, + Pretty: true, + } + configCmd := &cobra.Command{ + Use: "config", + Short: "Get the agent's configuration.", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + cfg, err := util.SetupRepoConfig(cmd) + if err != nil { + return fmt.Errorf("failed to setup repo: %w", err) + } + api, err := util.GetAPIClientV2(cmd, cfg) + if err != nil { + return fmt.Errorf("failed to create api client: %w", err) + } + return run(cmd, api, o) + }, + } + configCmd.Flags().AddFlagSet(cliflags.OutputNonTabularFormatFlags(&o)) + return configCmd +} + +func run(cmd *cobra.Command, api client.API, o output.NonTabularOutputOptions) error { + ctx := cmd.Context() + response, err := api.Agent().Config(ctx) + if err != nil { + return fmt.Errorf("cannot get agent config: %w", err) + } + + return output.OutputOneNonTabular[types.Bacalhau](cmd, o, response.Config) +} diff --git a/cmd/cli/agent/config_test.go b/cmd/cli/agent/config_test.go new file mode 100644 index 0000000000..13e8868f7a --- /dev/null +++ b/cmd/cli/agent/config_test.go @@ -0,0 +1,46 @@ +//go:build unit || !integration + +package agent_test + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/suite" + "gopkg.in/yaml.v3" + + cmdtesting "github.com/bacalhau-project/bacalhau/cmd/testing" + "github.com/bacalhau-project/bacalhau/cmd/util/output" + "github.com/bacalhau-project/bacalhau/pkg/config/types" +) + +func TestConfigSuite(t *testing.T) { + suite.Run(t, new(ConfigSuite)) +} + +type ConfigSuite struct { + cmdtesting.BaseSuite +} + +func (s *ConfigSuite) TestConfigJSONOutput() { + _, out, err := s.ExecuteTestCobraCommand( + "agent", "config", "--output", string(output.JSONFormat), "--pretty=false", + ) + s.Require().NoError(err, "Could not request config with json output.") + + var cfg types.Bacalhau + err = json.Unmarshal([]byte(out), &cfg) + s.Require().NoError(err, "Could not unmarshal the output into json - %+v", err) + s.Require().True(cfg.Orchestrator.Enabled) +} + +func (s *ConfigSuite) TestConfigYAMLOutput() { + // NB: the default output is yaml, thus we don't specify it here. + _, out, err := s.ExecuteTestCobraCommand("agent", "config") + s.Require().NoError(err, "Could not request config with yaml output.") + + var cfg types.Bacalhau + err = yaml.Unmarshal([]byte(out), &cfg) + s.Require().NoError(err, "Could not unmarshal the output into yaml - %+v", out) + s.Require().True(cfg.Orchestrator.Enabled) +} diff --git a/cmd/cli/agent/root.go b/cmd/cli/agent/root.go index b903bdbbe4..9e9c9d9660 100644 --- a/cmd/cli/agent/root.go +++ b/cmd/cli/agent/root.go @@ -16,5 +16,6 @@ func NewCmd() *cobra.Command { cmd.AddCommand(NewAliveCmd()) cmd.AddCommand(NewNodeCmd()) cmd.AddCommand(NewVersionCmd()) + cmd.AddCommand(NewConfigCmd()) return cmd } diff --git a/pkg/config/types/bacalhau.go b/pkg/config/types/bacalhau.go index 51e4b25f0f..db47baab4a 100644 --- a/pkg/config/types/bacalhau.go +++ b/pkg/config/types/bacalhau.go @@ -34,7 +34,8 @@ type Bacalhau struct { Logging Logging `yaml:"Logging,omitempty" json:"Logging,omitempty"` UpdateConfig UpdateConfig `yaml:"UpdateConfig,omitempty" json:"UpdateConfig,omitempty"` FeatureFlags FeatureFlags `yaml:"FeatureFlags,omitempty" json:"FeatureFlags,omitempty"` - DisableAnalytics bool `yaml:"DisableAnalytics,omitempty" json:"DisableAnalytics,omitempty"` + // DisableAnalytics, when true, disables sharing anonymous analytics data with the Bacalhau development team + DisableAnalytics bool `yaml:"DisableAnalytics,omitempty" json:"DisableAnalytics,omitempty"` } // Copy returns a deep copy of the Bacalhau configuration. diff --git a/pkg/publicapi/apimodels/agent.go b/pkg/publicapi/apimodels/agent.go index a4177bbb1b..a0a9830146 100644 --- a/pkg/publicapi/apimodels/agent.go +++ b/pkg/publicapi/apimodels/agent.go @@ -1,6 +1,9 @@ package apimodels -import "github.com/bacalhau-project/bacalhau/pkg/models" +import ( + "github.com/bacalhau-project/bacalhau/pkg/config/types" + "github.com/bacalhau-project/bacalhau/pkg/models" +) // IsAliveResponse is the response to the IsAlive request. type IsAliveResponse struct { @@ -30,3 +33,8 @@ type GetAgentNodeResponse struct { BaseGetResponse *models.NodeState } + +type GetAgentConfigResponse struct { + BaseGetResponse + Config types.Bacalhau `json:"config"` +} diff --git a/pkg/publicapi/client/v2/api_agent.go b/pkg/publicapi/client/v2/api_agent.go index 0325c81adc..9f0abc1457 100644 --- a/pkg/publicapi/client/v2/api_agent.go +++ b/pkg/publicapi/client/v2/api_agent.go @@ -30,3 +30,9 @@ func (c *Agent) Node(ctx context.Context, req *apimodels.GetAgentNodeRequest) (* err := c.client.Get(ctx, "/api/v1/agent/node", req, &res) return &res, err } + +func (c *Agent) Config(ctx context.Context) (*apimodels.GetAgentConfigResponse, error) { + var res apimodels.GetAgentConfigResponse + err := c.client.Get(ctx, "/api/v1/agent/config", &apimodels.BaseGetRequest{}, &res) + return &res, err +} diff --git a/pkg/publicapi/endpoint/agent/endpoint.go b/pkg/publicapi/endpoint/agent/endpoint.go index 5ab98bd5bf..27d9869157 100644 --- a/pkg/publicapi/endpoint/agent/endpoint.go +++ b/pkg/publicapi/endpoint/agent/endpoint.go @@ -1,6 +1,7 @@ package agent import ( + "fmt" "net/http" "github.com/labstack/echo/v4" @@ -113,7 +114,7 @@ func (e *Endpoint) debug(c echo.Context) error { return c.JSON(http.StatusOK, debugInfoMap) } -// debug godoc +// config godoc // // @ID agent/config // @Summary Returns the current configuration of the node. @@ -123,6 +124,17 @@ func (e *Endpoint) debug(c echo.Context) error { // @Failure 500 {object} string // @Router /api/v1/agent/config [get] func (e *Endpoint) config(c echo.Context) error { - cfg := e.bacalhauConfig - return c.JSON(http.StatusOK, cfg) + cfg, err := e.bacalhauConfig.Copy() + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("could not copy bacalhau config: %s", err)) + } + if cfg.Compute.Auth.Token != "" { + cfg.Compute.Auth.Token = "" + } + if cfg.Orchestrator.Auth.Token != "" { + cfg.Orchestrator.Auth.Token = "" + } + return c.JSON(http.StatusOK, apimodels.GetAgentConfigResponse{ + Config: cfg, + }) } diff --git a/pkg/publicapi/endpoint/agent/endpoint_test.go b/pkg/publicapi/endpoint/agent/endpoint_test.go new file mode 100644 index 0000000000..937a718acf --- /dev/null +++ b/pkg/publicapi/endpoint/agent/endpoint_test.go @@ -0,0 +1,52 @@ +//go:build unit || !integration + +package agent + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/labstack/echo/v4" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/bacalhau-project/bacalhau/pkg/config/types" + "github.com/bacalhau-project/bacalhau/pkg/publicapi/apimodels" +) + +// TestEndpointConfigRedactFields asserts that auth tokens in the config are redacted. +func TestEndpointConfigRedactFields(t *testing.T) { + router := echo.New() + + // populate the fields that should be redacted with "secret" values. + NewEndpoint(EndpointParams{ + Router: router, + BacalhauConfig: types.Bacalhau{ + Orchestrator: types.Orchestrator{ + Auth: types.OrchestratorAuth{ + Token: "super-secret-orchestrator-token", + }, + }, + Compute: types.Compute{ + Auth: types.ComputeAuth{ + Token: "super-secret-orchestrator-token", + }, + }, + }, + }) + + req := httptest.NewRequest(http.MethodGet, "/api/v1/agent/config", nil) + rr := httptest.NewRecorder() + router.ServeHTTP(rr, req) + + require.Equal(t, http.StatusOK, rr.Code) + + // assert the secret values are not present. + var payload apimodels.GetAgentConfigResponse + err := json.NewDecoder(rr.Body).Decode(&payload) + require.NoError(t, err) + assert.Equal(t, payload.Config.Orchestrator.Auth.Token, "") + assert.Equal(t, payload.Config.Compute.Auth.Token, "") +} diff --git a/test_integration/1_orchestrator_basic_config_suite_test.go b/test_integration/1_orchestrator_basic_config_suite_test.go index 6bfb5875e1..5d3d327ba1 100644 --- a/test_integration/1_orchestrator_basic_config_suite_test.go +++ b/test_integration/1_orchestrator_basic_config_suite_test.go @@ -3,11 +3,12 @@ package test_integration import ( "context" "fmt" + "strings" + "testing" + "github.com/google/uuid" "github.com/stretchr/testify/suite" "github.com/testcontainers/testcontainers-go/exec" - "strings" - "testing" ) type OrchestratorBasicConfigSuite struct { @@ -65,7 +66,7 @@ func (s *OrchestratorBasicConfigSuite) TestOrchestratorNodeUpAndEnabled() { marshalledOutput, err := s.unmarshalJSONString(agentConfigOutput, JSONObject) s.Require().NoErrorf(err, "Error unmarshalling response: %q", err) - orchestratorEnabled := marshalledOutput.(map[string]interface{})["Orchestrator"].(map[string]interface{})["Enabled"].(bool) + orchestratorEnabled := marshalledOutput.(map[string]interface{})["config"].(map[string]interface{})["Orchestrator"].(map[string]interface{})["Enabled"].(bool) s.Require().Truef(orchestratorEnabled, "Expected orchestrator to be enabled, got: %t", orchestratorEnabled) } diff --git a/test_integration/2_orchestrator_config_override_suite_test.go b/test_integration/2_orchestrator_config_override_suite_test.go index a6d43760f1..96f28ec706 100644 --- a/test_integration/2_orchestrator_config_override_suite_test.go +++ b/test_integration/2_orchestrator_config_override_suite_test.go @@ -3,10 +3,11 @@ package test_integration import ( "context" "fmt" - "github.com/google/uuid" - "github.com/stretchr/testify/suite" "strings" "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/suite" ) type OrchestratorConfigOverrideSuite struct { diff --git a/test_integration/3_orchestrator_config_override_and_flag_suite_test.go b/test_integration/3_orchestrator_config_override_and_flag_suite_test.go index 750c2f9dd9..312c7ef8ca 100644 --- a/test_integration/3_orchestrator_config_override_and_flag_suite_test.go +++ b/test_integration/3_orchestrator_config_override_and_flag_suite_test.go @@ -3,10 +3,11 @@ package test_integration import ( "context" "fmt" - "github.com/google/uuid" - "github.com/stretchr/testify/suite" "strings" "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/suite" ) type OrchestratorConfigOverrideAndFlagSuite struct { diff --git a/test_integration/4_orchestrator_config_override_and_flag_and_config_flag_suite_test.go b/test_integration/4_orchestrator_config_override_and_flag_and_config_flag_suite_test.go index bc459c1609..c5690285bb 100644 --- a/test_integration/4_orchestrator_config_override_and_flag_and_config_flag_suite_test.go +++ b/test_integration/4_orchestrator_config_override_and_flag_and_config_flag_suite_test.go @@ -3,10 +3,11 @@ package test_integration import ( "context" "fmt" - "github.com/google/uuid" - "github.com/stretchr/testify/suite" "strings" "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/suite" ) type OrchestratorConfigOverrideAndFlagAndConfigFlagSuite struct { @@ -67,7 +68,7 @@ func (s *OrchestratorConfigOverrideAndFlagAndConfigFlagSuite) TestConfigOverride unmarshalledAgentOutput, err := s.unmarshalJSONString(agentConfigOutput, JSONObject) s.Require().NoErrorf(err, "Error unmarshalling response: %q", err) - webuiEnabled := unmarshalledAgentOutput.(map[string]interface{})["WebUI"].(map[string]interface{})["Enabled"].(bool) + webuiEnabled := unmarshalledAgentOutput.(map[string]interface{})["config"].(map[string]interface{})["WebUI"].(map[string]interface{})["Enabled"].(bool) s.Require().Truef(webuiEnabled, "Expected orchestrator to be enabled, got: %t", webuiEnabled) } diff --git a/test_integration/5_orchestrator_no_config_suite_test.go b/test_integration/5_orchestrator_no_config_suite_test.go index 9507ec0b77..1d5097f4c0 100644 --- a/test_integration/5_orchestrator_no_config_suite_test.go +++ b/test_integration/5_orchestrator_no_config_suite_test.go @@ -2,10 +2,11 @@ package test_integration import ( "context" - "github.com/google/uuid" - "github.com/stretchr/testify/suite" "strings" "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/suite" ) type OrchestratorNoConfigSuite struct { @@ -56,7 +57,7 @@ func (s *OrchestratorNoConfigSuite) TestStartingOrchestratorNodeWithConfigFile() unmarshalledOutput, err := s.unmarshalJSONString(agentConfigOutput, JSONObject) s.Require().NoErrorf(err, "Error unmarshalling response: %q", err) - unmarshalledOutputMap := unmarshalledOutput.(map[string]interface{}) + unmarshalledOutputMap := unmarshalledOutput.(map[string]interface{})["config"].(map[string]interface{}) orchestratorEnabled := unmarshalledOutputMap["Orchestrator"].(map[string]interface{})["Enabled"].(bool) s.Require().Truef(orchestratorEnabled, "Expected orchestrator to be enabled, got: %t", orchestratorEnabled) diff --git a/test_integration/6_jobs_basic_runs_scenarios_suite_test.go b/test_integration/6_jobs_basic_runs_scenarios_suite_test.go index 10e75bf1d8..dc4b7f79f3 100644 --- a/test_integration/6_jobs_basic_runs_scenarios_suite_test.go +++ b/test_integration/6_jobs_basic_runs_scenarios_suite_test.go @@ -1,14 +1,15 @@ package test_integration import ( - "bacalhau/integration_tests/utils" "context" "fmt" - "github.com/google/uuid" - "github.com/stretchr/testify/suite" "strings" "testing" "time" + + "bacalhau/integration_tests/utils" + "github.com/google/uuid" + "github.com/stretchr/testify/suite" ) type JobsBasicRunsScenariosSuite struct {