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 2 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 ../../tenants
dlactin marked this conversation as resolved.
Show resolved Hide resolved
```

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`).
94 changes: 94 additions & 0 deletions tenants/cmd/deploymentType.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package cmd

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

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

// deploymentTypeCmd represents the deploymentType command
var deploymentTypeCmd = &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`,
RunE: func(cmd *cobra.Command, args []string) error {
return runAnalysis(cmd)
},
}

func init() {
dlactin marked this conversation as resolved.
Show resolved Hide resolved
rootCmd.AddCommand(deploymentTypeCmd)

deploymentTypeCmd.Flags().StringP("directory", "d", "../tenants", "Path to the tenants directory containing YAML files")
dlactin marked this conversation as resolved.
Show resolved Hide resolved
deploymentTypeCmd.Flags().StringP("output", "o", "deployment_type.csv", "Output file")
deploymentTypeCmd.Flags().StringP("format", "f", "csv", "Output format: csv or json")
}

func runAnalysis(cmd *cobra.Command) error {
dlactin marked this conversation as resolved.
Show resolved Hide resolved
directory, err := cmd.Flags().GetString("directory")
if err != nil {
return fmt.Errorf("failed to get directory flag: %v", err)
}

outputFile, err := cmd.Flags().GetString("output")
if err != nil {
return fmt.Errorf("failed to get output flag: %v", err)
}

format, err := cmd.Flags().GetString("format")
if err != nil {
return fmt.Errorf("failed to get format flag: %v", err)
}

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("Error reading tenant file %s: %v\n", fileName, err)
dlactin marked this conversation as resolved.
Show resolved Hide resolved
continue
}
deploymentType, migrationStatus := io.ParseDeploymentTypeAndMigration(string(content))
results = append(results, map[string]string{
"Tenant Name": fileName,
"Deployment Type": deploymentType,
"Migration Status": migrationStatus,
})

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

percentageArgocd := float64(argocdTenants) / float64(totalTenants) * 100
fmt.Printf("Total tenants: %d\n", totalTenants)
fmt.Printf("Tenants on argocd: %d\n", argocdTenants)
dlactin marked this conversation as resolved.
Show resolved Hide resolved
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)
}
}
27 changes: 27 additions & 0 deletions tenants/cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package cmd

import (
"os"

"github.com/spf13/cobra"
)

// rootCmd represents the base command when called without any subcommands
var 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",
}

// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
err := rootCmd.Execute()
if err != nil {
os.Exit(1)
}
}

func init() {
dlactin marked this conversation as resolved.
Show resolved Hide resolved
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
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=
101 changes: 101 additions & 0 deletions tenants/internal/io/input.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package io

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

"gopkg.in/yaml.v2"
)

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 config struct {
dlactin marked this conversation as resolved.
Show resolved Hide resolved
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"`
}

err := yaml.Unmarshal([]byte(fileContent), &config)
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(config.Globals.Deployment.Type, "\" ")
if globalType != "" {
deploymentTypes[globalType] = true
}

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

prodType := strings.Trim(config.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"
}

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
}
73 changes: 73 additions & 0 deletions tenants/internal/io/output.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package io

import (
"encoding/csv"
"encoding/json"
"fmt"
"os"
)

func WriteCSV(outputFile string, results []map[string]string) error {
output, err := os.Create(outputFile)
if err != nil {
return fmt.Errorf("error creating output file: %v", err)
}
defer output.Close()

csvWriter := csv.NewWriter(output)
defer csvWriter.Flush()

// Define the explicit order of fields
headers := []string{"Tenant Name", "Deployment Type", "Migration Status"}
err = csvWriter.Write(headers)
if err != nil {
fmt.Printf("Error writing CSV Headers")
}

// Write rows
for _, result := range results {
row := []string{
result["Tenant Name"],
result["Deployment Type"],
result["Migration Status"],
}
err = csvWriter.Write(row)
if err != nil {
fmt.Printf("Error writing CSV Rows")
}
}

return nil
}

type Result struct {
TenantName string `json:"Tenant Name"`
dlactin marked this conversation as resolved.
Show resolved Hide resolved
DeploymentType string `json:"Deployment Type"`
MigrationStatus string `json:"Migration Status"`
}

func WriteJSON(outputFile string, results []map[string]string) error {
output, err := os.Create(outputFile)
if err != nil {
return fmt.Errorf("error creating output file: %v", err)
}
defer output.Close()

// Convert map results to structured results
structResults := []Result{}
for _, result := range results {
structResults = append(structResults, Result{
TenantName: result["Tenant Name"],
DeploymentType: result["Deployment Type"],
MigrationStatus: result["Migration Status"],
})
}

encoder := json.NewEncoder(output)
encoder.SetIndent("", " ")
if err := encoder.Encode(structResults); err != nil {
return fmt.Errorf("error writing JSON output: %v", err)
}

return nil
}
7 changes: 7 additions & 0 deletions tenants/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package main

import "github.com/mozilla-services/rapid-release-model/tenants/cmd"

func main() {
cmd.Execute()
}
Loading