Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tenants cli #29

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions tenants/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# tenants

CLI app for collecting GCPv2 tenant information from our tenant files.

## Overview

The `tenants` CLI app allows users to retrieve deploymentType information from
our tenant files. This should be limited to "argocd", "gha", or "unknown".

## Installation

### Prerequisites

* Go `1.21.3` or newer

### Installation Steps

To install the `tenants` CLI app, run the following command:

```bash
go install github.com/mozilla-services/rapid-release-model/tenants@latest
```

## Usage

### CLI Commands

The main subcommand is `deploymentType`, which retrieves deploymentType information for
our GCPv2 tenants.

```bash
tenants deploymentType -d <path-to-tenants-directory>
```

Example:

```bash
tenants deploymentType -d ../../tenants -f json -o deployment-type.json
```

This command will read tenant information for services from the tenant yaml files in `../../tenants` directory and
parse the `deployment_type` from them.

### CLI Options

The following options are available for `ciplatforms info`:

| Short Option | Long Option | Description | Default |
|--------------|-----------------|--------------------------------------------------|-----------------------|
| `-d` | `--directory` | Directory containing yaml tenant files | `tenants` |
| `-f` | `--format` | Output format: csv or json | `csv` |
| `-o` | `--output` | Output file for results | `deployment_type.csv` |

## Configuration

You can configure the `tenants` CLI app by passing CLI flags.

### Output File Formats

* **Output File** (`--output`): The output file can be specified in either JSON or CSV format. The `tenants` app will determine the output format based on the file extension (`.json` or `.csv`).
113 changes: 113 additions & 0 deletions tenants/cmd/deploymentType.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package cmd

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

"github.com/mozilla-services/rapid-release-model/tenants/internal/io"
"github.com/spf13/cobra"
)

type DeploymentTypeOptions struct {
TenantDirectory string
OutputFile string
Format string
}

func newDeploymentTypeCmd() *cobra.Command {
opts := new(DeploymentTypeOptions)

cmd := &cobra.Command{
Use: "deploymentType",
Short: "Parse tenant files directory for deployment type",
Long: `Parse a local tenant files directory for deployment type
Example:
./tenants deploymentType -d ../../global-platform-admin/tenants`,
PreRunE: func(cmd *cobra.Command, args []string) error {
// Bind the directory flag
directory, err := cmd.Flags().GetString("directory")
if err != nil {
return fmt.Errorf("failed to get directory flag: %w", err)
}
opts.TenantDirectory = directory

// Bind the output file flag
outputFile, err := cmd.Flags().GetString("output")
if err != nil {
return fmt.Errorf("failed to get output flag: %w", err)
}
opts.OutputFile = outputFile

// Bind the format flag
format, err := cmd.Flags().GetString("format")
if err != nil {
return fmt.Errorf("failed to get format flag: %w", err)
}
opts.Format = format

return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
return runAnalysis(opts)
},
}

cmd.Flags().StringP("output", "o", "deployment_type.csv", "Output file")
cmd.Flags().StringP("format", "f", "csv", "Output format: csv or json")

return cmd
}

func runAnalysis(opts *DeploymentTypeOptions) error {
directory := opts.TenantDirectory
outputFile := opts.OutputFile
format := opts.Format

files, err := io.GetYamlFiles(directory)
if err != nil {
return fmt.Errorf("error fetching YAML files: %v", err)
}

if len(files) == 0 {
return fmt.Errorf("no YAML files found in the specified directory")
}

results := []map[string]string{}
totalTenants := len(files)
argocdTenants := 0

for _, file := range files {
fileName := strings.TrimSuffix(filepath.Base(file), ".yaml")
fmt.Printf("Checking tenant: %s\n", fileName)
content, err := os.ReadFile(file)
if err != nil {
fmt.Printf("Skipping tenant file %s due to an error: %v\n", fileName, err)
continue
}
deploymentType, migrationStatus := io.ParseDeploymentTypeAndMigration(string(content))
results = append(results, map[string]string{
"TenantName": fileName,
"DeploymentType": deploymentType,
"MigrationStatus": migrationStatus,
})

if deploymentType == "argocd" {
argocdTenants++
}
}

percentageArgocd := float64(argocdTenants) / float64(totalTenants) * 100
fmt.Printf("Total tenants: %d\n", totalTenants)
fmt.Printf("Tenants on Argo CD: %d\n", argocdTenants)
fmt.Printf("Percentage of tenants on argocd: %.2f%%\n", percentageArgocd)

if format == "csv" {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should check this before running any of the above code.

return io.WriteCSV(outputFile, results)
} else if format == "json" {
return io.WriteJSON(outputFile, results)
} else {
return fmt.Errorf("invalid format: %s, must be 'csv' or 'json'", format)
}
}
40 changes: 40 additions & 0 deletions tenants/cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package cmd

import (
"fmt"
"os"

"github.com/spf13/cobra"
)

// newRootCmd creates a new base command for the metrics CLI app
func newRootCmd() *cobra.Command {
rootCmd := &cobra.Command{
Use: "tenants",
Short: "CLI app for parsing GCPv2 tenant files for cataloguing",
Long: "CLI app for parsing GCPv2 tenant files for cataloguing",
}

rootCmd.PersistentFlags().StringP("directory", "d", "", "Path to the tenants directory containing YAML files")
rootCmd.MarkPersistentFlagRequired("directory")

rootCmd.AddCommand(newDeploymentTypeCmd())

return rootCmd
}

// Execute the CLI application and write errors to os.Stderr
func Execute() {
rootCmd := newRootCmd()
if err := rootCmd.Execute(); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
}

func init() {
dlactin marked this conversation as resolved.
Show resolved Hide resolved
// New in cobra v1.8.0. See https://github.com/spf13/cobra/pull/2044
// Run all PersistentPreRunE hooks, so we don't have to repeat factory
// configuration or CLI flags parsing in sub commands.
cobra.EnableTraverseRunHooks = true
}
13 changes: 13 additions & 0 deletions tenants/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module github.com/mozilla-services/rapid-release-model/tenants

go 1.22.3

require (
github.com/spf13/cobra v1.8.1
gopkg.in/yaml.v2 v2.4.0
)

require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
)
13 changes: 13 additions & 0 deletions tenants/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
108 changes: 108 additions & 0 deletions tenants/internal/io/input.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package io

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

"gopkg.in/yaml.v2"
)

type Tenant struct {
Globals struct {
Deployment struct {
Type string `yaml:"type"`
} `yaml:"deployment"`
} `yaml:"globals"`
Realms struct {
Nonprod struct {
Deployment struct {
Type string `yaml:"type"`
} `yaml:"deployment"`
} `yaml:"nonprod"`
Prod struct {
Deployment struct {
Type string `yaml:"type"`
} `yaml:"deployment"`
} `yaml:"prod"`
} `yaml:"realms"`
}

func GetYamlFiles(directory string) ([]string, error) {
var files []string
err := filepath.Walk(directory, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() && strings.HasSuffix(info.Name(), ".yaml") {
hackebrot marked this conversation as resolved.
Show resolved Hide resolved
files = append(files, path)
}
return nil
})

if err != nil {
return nil, err
}

return files, nil
}

func ParseDeploymentTypeAndMigration(fileContent string) (string, string) {
var tenant Tenant

err := yaml.Unmarshal([]byte(fileContent), &tenant)
if err != nil {
fmt.Printf("Error parsing YAML content: %v\n", err)
return "Error", "Error"
}

deploymentTypes := make(map[string]bool)

// Collect deployment types
globalType := strings.Trim(tenant.Globals.Deployment.Type, "\" ")
if globalType != "" {
deploymentTypes[globalType] = true
}

nonprodType := strings.Trim(tenant.Realms.Nonprod.Deployment.Type, "\" ")
if nonprodType != "" {
deploymentTypes[nonprodType] = true
}

prodType := strings.Trim(tenant.Realms.Prod.Deployment.Type, "\" ")
if prodType != "" {
deploymentTypes[prodType] = true
}

// Determine migration status
_, hasArgocd := deploymentTypes["argocd"]
_, hasGha := deploymentTypes["gha"]

var migrationStatus string
if hasArgocd && hasGha {
migrationStatus = "in_progress"
} else if hasArgocd {
migrationStatus = "complete"
} else if hasGha {
migrationStatus = "not_started"
} else {
migrationStatus = "unknown"
}

/*
This is being used to track live and complete migrations to ArgoCD
On average these migrations have taken less than a week to complete
and the detailed progress is tracked by migrationStatus
*/
deploymentType := ""
dlactin marked this conversation as resolved.
Show resolved Hide resolved
if hasArgocd {
deploymentType = "argocd"
} else if hasGha {
deploymentType = "gha"
} else {
deploymentType = "none"
}

return deploymentType, migrationStatus
}
Loading
Loading