Skip to content

Commit

Permalink
Merge pull request #207 from open-sauced/feat/offboarding-command
Browse files Browse the repository at this point in the history
feat: implement `pizza offboard` command
  • Loading branch information
zeucapua authored Oct 7, 2024
2 parents 695a6ea + 5a60659 commit 8044105
Show file tree
Hide file tree
Showing 4 changed files with 250 additions and 0 deletions.
139 changes: 139 additions & 0 deletions cmd/offboard/offboard.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package offboard

import (
"errors"
"fmt"
"slices"
"strings"

"github.com/spf13/cobra"

"github.com/open-sauced/pizza-cli/v2/pkg/config"
"github.com/open-sauced/pizza-cli/v2/pkg/constants"
"github.com/open-sauced/pizza-cli/v2/pkg/utils"
)

type Options struct {
offboardingUsers []string

// config file path
configPath string

// repository path
path string

// from global config
ttyDisabled bool

// telemetry for capturing CLI events via PostHog
telemetry *utils.PosthogCliClient
}

const offboardLongDesc string = `CAUTION: Experimental Command. Removes users from the \".sauced.yaml\" config and \"CODEOWNERS\" files.
Requires the users' name OR email.`

func NewConfigCommand() *cobra.Command {
opts := &Options{}
cmd := &cobra.Command{
Use: "offboard <username/email> [flags]",
Short: "CAUTION: Experimental Command. Removes users from the \".sauced.yaml\" config and \"CODEOWNERS\" files.",
Long: offboardLongDesc,
Args: func(_ *cobra.Command, args []string) error {
if len(args) == 0 {
return errors.New("you must provide at least one argument: the offboarding user's email/username")
}

opts.offboardingUsers = args

return nil
},
RunE: func(cmd *cobra.Command, _ []string) error {
opts.ttyDisabled, _ = cmd.Flags().GetBool("tty-disable")
opts.configPath, _ = cmd.Flags().GetString("config")
disableTelem, _ := cmd.Flags().GetBool(constants.FlagNameTelemetry)

opts.telemetry = utils.NewPosthogCliClient(!disableTelem)

opts.path, _ = cmd.Flags().GetString("path")
err := run(opts)
_ = opts.telemetry.Done()

return err
},
}

cmd.PersistentFlags().StringP("path", "p", "", "the path to the repository (required)")
if err := cmd.MarkPersistentFlagRequired("path"); err != nil {
fmt.Printf("error MarkPersistentFlagRequired: %v", err)
}
return cmd
}

func run(opts *Options) error {
var spec *config.Spec
var err error
if len(opts.configPath) != 0 {
spec, _, err = config.LoadConfig(opts.configPath)
} else {
var configPath string
if strings.Compare(string(opts.path[len(opts.path)-1]), "/") == 0 {
configPath = opts.path + ".sauced.yaml"
} else {
configPath = opts.path + "/.sauced.yaml"
}
spec, _, err = config.LoadConfig(configPath)
}

if err != nil {
_ = opts.telemetry.CaptureFailedOffboard()
return fmt.Errorf("error loading config: %v", err)
}

var offboardingNames []string
attributions := spec.Attributions
for _, user := range opts.offboardingUsers {
added := false

// deletes if the user is a name (key)
delete(attributions, user)

// delete if the user is an email (value)
for k, v := range attributions {
if slices.Contains(v, user) {
offboardingNames = append(offboardingNames, k)
delete(attributions, k)
added = true
}
}

if !added {
offboardingNames = append(offboardingNames, user)
}
}

if len(opts.configPath) != 0 {
err = generateConfigFile(opts.configPath, attributions)
} else {
var configPath string
if strings.Compare(string(opts.path[len(opts.path)-1]), "/") == 0 {
configPath = opts.path + ".sauced.yaml"
} else {
configPath = opts.path + "/.sauced.yaml"
}
err = generateConfigFile(configPath, attributions)
}

if err != nil {
_ = opts.telemetry.CaptureFailedOffboard()
return fmt.Errorf("error generating config file: %v", err)
}

err = generateOwnersFile(opts.path, offboardingNames)
if err != nil {
_ = opts.telemetry.CaptureFailedOffboard()
return fmt.Errorf("error generating owners file: %v", err)
}

_ = opts.telemetry.CaptureOffboard()
return nil
}
87 changes: 87 additions & 0 deletions cmd/offboard/output.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package offboard

import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"

"github.com/open-sauced/pizza-cli/v2/pkg/config"
"github.com/open-sauced/pizza-cli/v2/pkg/utils"
)

func generateConfigFile(outputPath string, attributionMap map[string][]string) error {
file, err := os.Create(outputPath)
if err != nil {
return fmt.Errorf("error creating %s file: %w", outputPath, err)
}
defer file.Close()

var config config.Spec
config.Attributions = attributionMap

// for pretty print test
yaml, err := utils.OutputYAML(config)

if err != nil {
return fmt.Errorf("failed to turn into YAML: %w", err)
}

_, err = file.WriteString(yaml)

if err != nil {
return fmt.Errorf("failed to turn into YAML: %w", err)
}

return nil
}

func generateOwnersFile(path string, offboardingUsers []string) error {
outputType := "/CODEOWNERS"
var owners []byte
var err error

var ownersPath string

if _, err = os.Stat(filepath.Join(path, "/CODEOWNERS")); !errors.Is(err, os.ErrNotExist) {
outputType = "CODEOWNERS"
ownersPath = filepath.Join(path, "/CODEOWNERS")
owners, err = os.ReadFile(ownersPath)
} else if _, err = os.Stat(filepath.Join(path, "OWNERS")); !errors.Is(err, os.ErrNotExist) {
outputType = "OWNERS"
ownersPath = filepath.Join(path, "/OWNERS")
owners, err = os.ReadFile(ownersPath)
}

if err != nil {
fmt.Printf("will create a new %s file in the path %s", outputType, path)
}

lines := strings.Split(string(owners), "\n")
var newLines []string
for _, line := range lines {
newLine := line
for _, name := range offboardingUsers {
result, _, found := strings.Cut(newLine, "@"+name)
if found {
newLine = result
}
}
newLines = append(newLines, newLine)
}

output := strings.Join(newLines, "\n")
file, err := os.Create(ownersPath)
if err != nil {
return fmt.Errorf("error creating %s file: %w", outputType, err)
}
defer file.Close()

_, err = file.WriteString(output)
if err != nil {
return fmt.Errorf("failed writing file %s: %w", path+outputType, err)
}

return nil
}
2 changes: 2 additions & 0 deletions cmd/root/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/open-sauced/pizza-cli/v2/cmd/docs"
"github.com/open-sauced/pizza-cli/v2/cmd/generate"
"github.com/open-sauced/pizza-cli/v2/cmd/insights"
"github.com/open-sauced/pizza-cli/v2/cmd/offboard"
"github.com/open-sauced/pizza-cli/v2/cmd/version"
"github.com/open-sauced/pizza-cli/v2/pkg/constants"
)
Expand Down Expand Up @@ -44,6 +45,7 @@ func NewRootCommand() (*cobra.Command, error) {
cmd.AddCommand(generate.NewGenerateCommand())
cmd.AddCommand(insights.NewInsightsCommand())
cmd.AddCommand(version.NewVersionCommand())
cmd.AddCommand(offboard.NewConfigCommand())

// The docs command is hidden as it's only used by the pizza-cli maintainers
docsCmd := docs.NewDocsCommand()
Expand Down
22 changes: 22 additions & 0 deletions pkg/utils/posthog.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,28 @@ func (p *PosthogCliClient) CaptureFailedConfigGenerate() error {
return nil
}

func (p *PosthogCliClient) CaptureOffboard() error {
if p.activated {
return p.client.Enqueue(posthog.Capture{
DistinctId: p.uniqueID,
Event: "pizza_cli_offboard",
})
}

return nil
}

func (p *PosthogCliClient) CaptureFailedOffboard() error {
if p.activated {
return p.client.Enqueue(posthog.Capture{
DistinctId: p.uniqueID,
Event: "pizza_cli_failed_to_offboard",
})
}

return nil
}

// CaptureInsights gathers telemetry on successful Insights command runs
func (p *PosthogCliClient) CaptureInsights() error {
if p.activated {
Expand Down

0 comments on commit 8044105

Please sign in to comment.