sg/msp: improve DX by enforcing repo structure (#56841)

Previously, the command expected the full YAML path, which is a bit annoying to write. Now, we just codify the repo structure of https://github.com/sourcegraph/managed-services and allow users to just use the service ID from anywhere in the repository.

## Test plan

```sh
$ sg msp generate telemetry-gateway dev
Terraform assets generated in "services/telemetry-gateway/terraform/dev"!
$ cd services/telemetry-gateway/terraform/dev/stacks/project
$ sg msp generate telemetry-gateway dev
💡 Using repo root /Users/robert@sourcegraph.com/Projects/sourcegraph/managed-services as working directory
Terraform assets generated in "services/telemetry-gateway/terraform/dev"!
```
This commit is contained in:
Robert Lin 2023-09-20 17:15:40 -07:00 committed by GitHub
parent 6fa37b2223
commit ebb3b6ca4a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 98 additions and 18 deletions

33
dev/sg/msp/repo/repo.go Normal file
View File

@ -0,0 +1,33 @@
package repo
import (
"os"
"path/filepath"
"github.com/urfave/cli/v2"
"github.com/sourcegraph/sourcegraph/dev/sg/internal/std"
)
// UseManagedServicesRepo is a cli.BeforeFunc that enforces that we are in the
// sourcegraph/managed-services repository by setting the current working
// directory.
func UseManagedServicesRepo(c *cli.Context) error {
cwd, err := os.Getwd()
if err != nil {
return err
}
repoRoot, err := repositoryRoot(cwd)
if err != nil {
return err
}
if repoRoot != cwd {
std.Out.WriteSuggestionf("Using repo root %s as working directory", repoRoot)
return os.Chdir(repoRoot)
}
return nil
}
func ServiceYAMLPath(serviceID string) string {
return filepath.Join("services", serviceID, "service.yaml")
}

50
dev/sg/msp/repo/root.go Normal file
View File

@ -0,0 +1,50 @@
package repo
import (
"errors"
"os"
"path/filepath"
"strings"
"sync"
)
var once sync.Once
var repositoryRootValue string
var repositoryRootError error
var ErrNotInsideManagedServices = errors.New("not running inside sourcegraph/managed-services")
// RepositoryRoot caches and returns the value of findRoot.
func repositoryRoot(cwd string) (string, error) {
once.Do(func() {
if forcedRoot := os.Getenv("SG_MSP_FORCE_REPO_ROOT"); forcedRoot != "" {
repositoryRootValue = forcedRoot
} else {
repositoryRootValue, repositoryRootError = findRoot(cwd)
}
})
return repositoryRootValue, repositoryRootError
}
// findRoot finds the root path of sourcegraph/managed-services from wd
func findRoot(wd string) (string, error) {
for {
contents, err := os.ReadFile(filepath.Join(wd, ".repository"))
if err == nil {
for _, line := range strings.Split(string(contents), "\n") {
if strings.HasPrefix(line, "sourcegraph/managed-services") {
return wd, nil
}
}
} else if !os.IsNotExist(err) {
return "", err
}
if parent := filepath.Dir(wd); parent != wd {
wd = parent
continue
}
return "", ErrNotInsideManagedServices
}
}

View File

@ -18,6 +18,7 @@ import (
"github.com/sourcegraph/sourcegraph/dev/managedservicesplatform/terraformcloud"
"github.com/sourcegraph/sourcegraph/dev/sg/internal/secrets"
"github.com/sourcegraph/sourcegraph/dev/sg/internal/std"
msprepo "github.com/sourcegraph/sourcegraph/dev/sg/msp/repo"
"github.com/sourcegraph/sourcegraph/dev/sg/msp/schema"
"github.com/sourcegraph/sourcegraph/lib/errors"
"github.com/sourcegraph/sourcegraph/lib/output"
@ -42,6 +43,7 @@ func init() {
// Override no-op implementation with our real implementation.
Command.Hidden = false
Command.Action = nil
// All 'sg msp ...' subcommands
Command.Subcommands = []*cli.Command{
{
Name: "init",
@ -55,6 +57,7 @@ func init() {
Value: "services",
},
},
Before: msprepo.UseManagedServicesRepo,
Action: func(c *cli.Context) error {
if c.Args().Len() != 1 {
return errors.New("exactly 1 argument required: service ID")
@ -125,8 +128,9 @@ func init() {
},
{
Name: "generate",
ArgsUsage: "<service spec file> <environment ID>",
ArgsUsage: "<service ID> <environment ID>",
Description: "Generate Terraform assets for a Managed Services Platform service spec.",
Before: msprepo.UseManagedServicesRepo,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "output",
@ -142,14 +146,12 @@ func init() {
},
Action: func(c *cli.Context) error {
if c.Args().Len() != 2 {
return errors.New("exactly 2 arguments required: service spec file and environment ID")
return errors.New("exactly 2 arguments required: service ID and environment ID")
}
// Load specification
serviceSpecPath, err := getYAMLPathArg(c, 0)
if err != nil {
return err
}
serviceSpecPath := msprepo.ServiceYAMLPath(c.Args().First())
serviceSpecData, err := os.ReadFile(serviceSpecPath)
if err != nil {
return err
@ -205,11 +207,12 @@ func init() {
Name: "terraform-cloud",
Aliases: []string{"tfc"},
Description: "Manage Terraform Cloud workspaces for a service",
Before: msprepo.UseManagedServicesRepo,
Subcommands: []*cli.Command{
{
Name: "sync",
Description: "Create or update all required Terraform Cloud workspaces for a service",
ArgsUsage: "<service spec file>",
ArgsUsage: "<service ID>",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "workspace-run-mode",
@ -223,10 +226,12 @@ func init() {
},
},
Action: func(c *cli.Context) error {
serviceSpecPath, err := getYAMLPathArg(c, 0)
if err != nil {
return err
serviceID := c.Args().First()
if serviceID == "" {
return errors.New("argument service is required")
}
serviceSpecPath := msprepo.ServiceYAMLPath(serviceID)
serviceSpecData, err := os.ReadFile(serviceSpecPath)
if err != nil {
return err
@ -355,11 +360,3 @@ func init() {
},
}
}
func getYAMLPathArg(c *cli.Context, n int) (string, error) {
v := c.Args().Get(n)
if strings.HasSuffix(v, ".yaml") || strings.HasSuffix(v, ".yml") {
return v, nil
}
return v, errors.Newf("expected argument %d %q to be a path to a YAML file", n, v)
}