diff --git a/.env.template b/.env.template new file mode 100644 index 0000000..ca20f38 --- /dev/null +++ b/.env.template @@ -0,0 +1,3 @@ +SPACE_ROOT=https://deta.space/api +SPACE_ACCESS_TOKEN=YOUR_ACCESS_TOKEN +SPACE_PROJECT_ID=YOUR_PROJECT_ID diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..857a64a --- /dev/null +++ b/.envrc @@ -0,0 +1,2 @@ +# dotenv .env.staging +dotenv .env.prod diff --git a/.gitignore b/.gitignore index 12a1079..58ccbae 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ starters .env .env.* +!.env.template diff --git a/README.md b/README.md index a7ecc27..e518fa9 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,16 @@ You can also set the `SPACE_ROOT` environment variable in a `.env` file in the r Other configuration options can be set in the .env file as well: - SPACE_ACCESS_TOKEN +- SPACE_PROJECT_ID +- SPACE_PROJECT_KEY + +A good way to manage different environment is too use the [direnv](https://direnv.net/). Exampleas `.envrc` and `.env.template` file are provided. + +To use them: + +- Copy `.env.template` to `.env.prod` or `.env.` +- Fill in the values +- Comment out the proper line in `.envrc` to load the correct file ## Running unit tests diff --git a/cmd/dev.go b/cmd/dev.go index da7e409..9c12bf5 100644 --- a/cmd/dev.go +++ b/cmd/dev.go @@ -118,7 +118,6 @@ func GetFreePort(start int) (int, error) { } func dev(projectDir string, projectID string, host string, port int, open bool) error { - routeDir := filepath.Join(projectDir, ".space", "micros") spacefile, err := spacefile.LoadSpacefile(projectDir) if err != nil { utils.Logger.Printf("%s Failed to parse Spacefile: %s", emoji.ErrorExclamation, err) @@ -134,6 +133,7 @@ func dev(projectDir string, projectID string, host string, port int, open bool) } utils.Logger.Printf("\n%s Checking for running micros...", emoji.Eyes) + routeDir := filepath.Join(projectDir, ".space", "micros") var stoppedMicros []*types.Micro for _, micro := range spacefile.Micros { _, err := getMicroPort(micro, routeDir) diff --git a/cmd/utils/checks.go b/cmd/utils/checks.go index 2092088..784b5a3 100644 --- a/cmd/utils/checks.go +++ b/cmd/utils/checks.go @@ -44,6 +44,10 @@ func CheckExists(flagName ...string) PreRunFunc { func CheckProjectInitialized(dirFlag string) PreRunFunc { return CheckAll(CheckExists(dirFlag), func(cmd *cobra.Command, args []string) error { + if os.Getenv(runtime.SpaceProjectIDEnv) != "" { + return nil + } + dir, _ := cmd.Flags().GetString(dirFlag) if _, err := os.Stat(filepath.Join(dir, ".space", "meta")); os.IsNotExist(err) { diff --git a/cmd/utils/keys.go b/cmd/utils/keys.go index 945ce12..35fa1a9 100644 --- a/cmd/utils/keys.go +++ b/cmd/utils/keys.go @@ -1,8 +1,6 @@ package utils import ( - "fmt" - "github.com/deta/space/internal/api" "github.com/deta/space/internal/auth" ) @@ -11,7 +9,9 @@ func GenerateDataKeyIfNotExists(projectID string) (string, error) { // check if we have already stored the project key based on the project's id projectKey, err := auth.GetProjectKey(projectID) if err == nil { - return projectKey, nil + if err := Client.CheckProjectKey(projectKey); err == nil { + return projectKey, nil + } } listRes, err := Client.ListProjectKeys(projectID) @@ -19,9 +19,19 @@ func GenerateDataKeyIfNotExists(projectID string) (string, error) { return "", err } - keyName := findAvailableKey(listRes.Keys, "space cli") + // delete the project key if it exists + keyName := "space cli" + for _, key := range listRes.Keys { + if key.Name == keyName { + err := Client.DeleteProjectKey(projectID, keyName) + if err != nil { + return "", err + } + break + } + } - // create a new project key using the api + // create a new project key r, err := Client.CreateProjectKey(projectID, &api.CreateProjectKeyRequest{ Name: keyName, }) @@ -37,21 +47,3 @@ func GenerateDataKeyIfNotExists(projectID string) (string, error) { return r.Value, nil } - -func findAvailableKey(keys []api.ProjectKey, name string) string { - keyMap := make(map[string]struct{}) - for _, key := range keys { - keyMap[key.Name] = struct{}{} - } - - if _, ok := keyMap[name]; !ok { - return name - } - - for i := 1; ; i++ { - newName := fmt.Sprintf("%s (%d)", name, i) - if _, ok := keyMap[newName]; !ok { - return newName - } - } -} diff --git a/internal/api/api.go b/internal/api/api.go index da5d625..2b220f4 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -5,7 +5,10 @@ import ( "errors" "fmt" "io" + "net/http" + "net/url" "os" + "strings" "github.com/deta/space/internal/auth" "github.com/deta/space/shared" @@ -839,6 +842,54 @@ func (c *DetaClient) ListProjectKeys(AppID string) (*ListProjectResponse, error) return &resp, nil } +func (c *DetaClient) CheckProjectKey(projectKey string) error { + parts := strings.Split(projectKey, "_") + if len(parts) != 2 { + return fmt.Errorf("invalid project key") + } + + instanceID := parts[0] + req, err := http.NewRequest("GET", fmt.Sprintf("https://database.deta.sh/v1/%s/dummy/items/dummy", instanceID), nil) + if err != nil { + return err + } + req.Header.Set("X-API-Key", projectKey) + + res, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + defer res.Body.Close() + + if res.StatusCode == 401 { + return fmt.Errorf("invalid project key") + } + + return nil +} + +func (c *DetaClient) DeleteProjectKey(appID string, keyName string) error { + o, err := c.request(&requestInput{ + Root: spaceRoot, + Path: fmt.Sprintf("/%s/apps/%s/keys/%s", version, appID, url.QueryEscape(keyName)), + Method: "DELETE", + NeedsAuth: true, + }) + if err != nil { + return err + } + + if o.Status != 200 { + msg := o.Error.Detail + if msg == "" && len(o.Error.Errors) > 0 { + msg = o.Error.Errors[0] + } + return fmt.Errorf("failed to delete project key: %v", msg) + } + + return nil +} + type Release struct { ID string `json:"id"` Tag string `json:"tag"` diff --git a/internal/auth/auth.go b/internal/auth/auth.go index 9522a47..f3256ee 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -15,7 +15,9 @@ import ( ) const ( - spaceAccessTokenEnv = "SPACE_ACCESS_TOKEN" + SpaceAccessTokenEnv = "SPACE_ACCESS_TOKEN" + SpaceProjectKeyEnv = "SPACE_PROJECT_KEY" + DetaProjectKeyEnv = "DETA_PROJECT_KEY" spaceTokensFile = "space_tokens" spaceSignVersion = "v0" spaceDir = ".detaspace" @@ -62,7 +64,7 @@ func getAccessTokenFromFile(filepath string) (string, error) { // GetAccessToken retrieves the tokens from storage or env var func GetAccessToken() (string, error) { // preference to env var first - spaceAccessToken := os.Getenv(spaceAccessTokenEnv) + spaceAccessToken := os.Getenv(SpaceAccessTokenEnv) if spaceAccessToken != "" { return spaceAccessToken, nil } @@ -167,6 +169,14 @@ type Keys map[string]string // GetProjectKey retrieves a project key storage or env var func GetProjectKey(projectId string) (string, error) { + if env, ok := os.LookupEnv(SpaceProjectKeyEnv); ok { + return env, nil + } + + if env, ok := os.LookupEnv(DetaProjectKeyEnv); ok { + return env, nil + } + home, err := os.UserHomeDir() if err != nil { return "", nil diff --git a/internal/runtime/manager.go b/internal/runtime/manager.go index 3f67632..1961329 100644 --- a/internal/runtime/manager.go +++ b/internal/runtime/manager.go @@ -18,6 +18,8 @@ const ( ) var ( + SpaceProjectIDEnv = "SPACE_PROJECT_ID" + PythonSkipPattern = `__pycache__` NodeSkipPattern = `node_modules` @@ -46,6 +48,10 @@ func StoreProjectMeta(projectDir string, p *ProjectMeta) error { } func GetProjectID(projectDir string) (string, error) { + if env, ok := os.LookupEnv(SpaceProjectIDEnv); ok { + return env, nil + } + projectMeta, err := GetProjectMeta(projectDir) if err != nil { return "", err