From 6f9bc1bf30dec02e0ca4734b5326bf148356db5e Mon Sep 17 00:00:00 2001 From: Jordan Jensen Date: Tue, 24 Sep 2024 15:04:34 -0700 Subject: [PATCH] Add GET configuration secrets endpoint --- internal/services/api/api_service.go | 4 + internal/services/api/get_config_secrets.go | 57 ++++++++++ .../services/api/get_config_secrets_test.go | 104 ++++++++++++++++++ 3 files changed, 165 insertions(+) create mode 100644 internal/services/api/get_config_secrets.go create mode 100644 internal/services/api/get_config_secrets_test.go diff --git a/internal/services/api/api_service.go b/internal/services/api/api_service.go index 66d614535..280c30285 100644 --- a/internal/services/api/api_service.go +++ b/internal/services/api/api_service.go @@ -126,6 +126,10 @@ func RouterHandlerFunc(base util.AbsolutePath, lister accounts.AccountList, log r.Handle(ToPath("configurations", "{name}", "files"), PostConfigFilesHandlerFunc(base, log)). Methods(http.MethodPost) + // GET /api/configurations/$NAME/secrets + r.Handle(ToPath("configurations", "{name}", "secrets"), GetConfigSecretsHandlerFunc(base, log)). + Methods(http.MethodGet) + // GET /api/configurations/$NAME/packages/python r.Handle(ToPath("configurations", "{name}", "packages", "python"), NewGetConfigPythonPackagesHandler(base, log)). Methods(http.MethodGet) diff --git a/internal/services/api/get_config_secrets.go b/internal/services/api/get_config_secrets.go new file mode 100644 index 000000000..aec193978 --- /dev/null +++ b/internal/services/api/get_config_secrets.go @@ -0,0 +1,57 @@ +package api + +// Copyright (C) 2024 by Posit Software, PBC. + +import ( + "errors" + "io/fs" + "net/http" + + "github.com/gorilla/mux" + "github.com/posit-dev/publisher/internal/config" + "github.com/posit-dev/publisher/internal/logging" + "github.com/posit-dev/publisher/internal/types" + "github.com/posit-dev/publisher/internal/util" +) + +func GetConfigSecretsHandlerFunc(base util.AbsolutePath, log logging.Logger) http.HandlerFunc { + return func(w http.ResponseWriter, req *http.Request) { + name := mux.Vars(req)["name"] + + projectDir, _, err := ProjectDirFromRequest(base, w, req, log) + if err != nil { + // Response already returned by ProjectDirFromRequest + return + } + + configPath := config.GetConfigPath(projectDir, name) + cfg, err := configFromFile(configPath) + if err != nil { + if aerr, ok := err.(*types.AgentError); ok { + if aerr.Code == types.ErrorUnknownTOMLKey { + apiErr := APIErrorUnknownTOMLKeyFromAgentError(*aerr) + apiErr.JSONResponse(w) + return + } + + if aerr.Code == types.ErrorInvalidTOML { + apiErr := APIErrorInvalidTOMLFileFromAgentError(*aerr) + apiErr.JSONResponse(w) + return + } + } + + if errors.Is(err, fs.ErrNotExist) { + http.NotFound(w, req) + } else { + InternalError(w, req, log, err) + } + return + } + + response := make([]string, 0) + response = append(response, cfg.Secrets...) + + JsonResult(w, http.StatusOK, response) + } +} diff --git a/internal/services/api/get_config_secrets_test.go b/internal/services/api/get_config_secrets_test.go new file mode 100644 index 000000000..7fafc2a85 --- /dev/null +++ b/internal/services/api/get_config_secrets_test.go @@ -0,0 +1,104 @@ +package api + +// Copyright (C) 2024 by Posit Software, PBC. + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/gorilla/mux" + "github.com/posit-dev/publisher/internal/config" + "github.com/posit-dev/publisher/internal/logging" + "github.com/posit-dev/publisher/internal/util" + "github.com/posit-dev/publisher/internal/util/utiltest" + "github.com/spf13/afero" + "github.com/stretchr/testify/suite" +) + +type GetConfigSecretsSuite struct { + utiltest.Suite + cwd util.AbsolutePath + log logging.Logger + h http.HandlerFunc +} + +func TestGetConfigSecretsSuite(t *testing.T) { + suite.Run(t, new(GetConfigSecretsSuite)) +} + +func (s *GetConfigSecretsSuite) SetupSuite() { + s.log = logging.New() +} + +func (s *GetConfigSecretsSuite) SetupTest() { + fs := afero.NewMemMapFs() + cwd, err := util.Getwd(fs) + s.Nil(err) + s.cwd = cwd + s.h = GetConfigSecretsHandlerFunc(s.cwd, s.log) +} + +func (s *GetConfigSecretsSuite) TestGetConfigSecrets() { + cfg := config.New() + cfg.Type = config.ContentTypeHTML + cfg.Secrets = []string{ + "secret1", + "secret2", + } + err := cfg.WriteFile(config.GetConfigPath(s.cwd, "myConfig")) + s.NoError(err) + + rec := httptest.NewRecorder() + req, err := http.NewRequest("GET", "/api/configurations/myConfig/secrets", nil) + s.NoError(err) + req = mux.SetURLVars(req, map[string]string{"name": "myConfig"}) + + s.h(rec, req) + + s.Equal(http.StatusOK, rec.Result().StatusCode) + s.Equal("application/json", rec.Header().Get("content-type")) + + res := []string{} + dec := json.NewDecoder(rec.Body) + dec.DisallowUnknownFields() + s.NoError(dec.Decode(&res)) + s.NotNil(res) + s.Equal(cfg.Secrets, res) +} + +func (s *GetConfigSecretsSuite) TestGetConfigSecretsEmptySecrets() { + cfg := config.New() + cfg.Type = config.ContentTypeHTML + err := cfg.WriteFile(config.GetConfigPath(s.cwd, "myConfig")) + s.NoError(err) + + rec := httptest.NewRecorder() + req, err := http.NewRequest("GET", "/api/configurations/myConfig/secrets", nil) + s.NoError(err) + req = mux.SetURLVars(req, map[string]string{"name": "myConfig"}) + + s.h(rec, req) + + s.Equal(http.StatusOK, rec.Result().StatusCode) + s.Equal("application/json", rec.Header().Get("content-type")) + + res := []string{} + dec := json.NewDecoder(rec.Body) + dec.DisallowUnknownFields() + s.NoError(dec.Decode(&res)) + s.NotNil(res) + s.Equal([]string{}, res) +} + +func (s *GetConfigSecretsSuite) TestGetConfigSecretsNotFound() { + rec := httptest.NewRecorder() + req, err := http.NewRequest("GET", "/api/configurations/myConfig/secrets", nil) + s.NoError(err) + req = mux.SetURLVars(req, map[string]string{"name": "myConfig"}) + + s.h(rec, req) + + s.Equal(http.StatusNotFound, rec.Result().StatusCode) +}