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

feat: create repo for Go CLI #1

Merged
merged 3 commits into from
May 24, 2024
Merged
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
32 changes: 32 additions & 0 deletions .github/workflows/cli-tool-test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Gh Foundations CLI Tool Test

on:
pull_request:
workflow_dispatch:

jobs:
build:

runs-on: ubuntu-latest
strategy:
matrix:
go-version: [ '1.21.x' ]

steps:
- uses: actions/checkout@v4
- name: Setup Go ${{ matrix.go-version }}
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
- name: Install dependencies
run: |
go get .
- name: Build
run: go build -v ./...
- name: Test
run: go test ./... -json > TestResults-${{ matrix.go-version }}.json
- name: Upload test results
uses: actions/upload-artifact@v4
with:
name: Go-results-${{ matrix.go-version }}
path: ./TestResults-${{ matrix.go-version }}.json
10 changes: 10 additions & 0 deletions .mockery.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
with-expecter: true
packages:
gh_foundations/internal/pkg/types:
config:
recursive: true
all: true
filename: "mock_{{.InterfaceName}}.go"
dir: "{{.InterfaceDir}}/mocks"
mockname: "Mock{{.InterfaceName}}"
outpkg: "mocks"
167 changes: 166 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,167 @@
# github-foundations-cli
# Github Foundations CLI
A command-line tool for the Github Foundations framework.

## Table of Contents

- [Usage](#usage)
- [Generate](#generate)
- [Import](#import)
- [Check](#check)
- [List](#list)
- [Help](#help)
- [Installation](#installation)
- [From releases](#from-releases)
- [Linux](#linux)
- [MacOS](#macos)
- [Windows](#windows)
- [From source](#from-source)







## Usage

There are a few main tools provided by the Github Foundations CLI:

```
Usage:
gh_foundations [command]

Available Commands:
gen Generate HCL input for GitHub Foundations.
import Starts an interactive import process for resources in a Terraform plan.
check Perform checks against a Github configuration.
list List various resources managed by the tool.
help Help about any command.

Flags:
-h, -- help help for gh_foundations
```

### Generate

This command is used to generate HCL input for GitHub Foundations. This tool is used to generate HCL input for GitHub Foundations from state files output by terraformer.

```
Usage:
gh_foundations gen <resource>
```

Where `<resource>` is one of the following:
- `repository`

### Import

This command will start an interactive process to import resources into Terraform state. It uses the results of a terraform plan to determine which resources are available for import.

```
Usage:
gh_foundations import [module_path]

```

Where `<module_path>` is the path to the Terragrunt module to import.

### Check

Perform checks against a Github configuration and generate reports. This is used to validate the compliance stance of your GitHub configuration.

```
Usage:
gh_foundations check <org-slug>

```

Where `<org-slug>` is the organization slug to check.

### List

list various resources managed by the tool.


```
Usage:
gh_foundations list <resource> [ProjectDirectory] [options]

```

Where `<resource>` is one of the following:
- repos
- orgs

`[ProjectDirectory]` is the path to the Terragrunt `Project` directory.

`[options]` is a list of options to filter the list of resources. The options are:
- repos:
- `--ghas`, `-g` List repositories with GHAS enabled.

### Help

Display help for the tool.

## Installation

### From releases
Download the latest release from the [releases page](http:github.com/FociSolutions/github-foundations-cli/releases) and run the following commands:


#### Linux

**ADM64**
```
curl -LO "https://github.com/FociSolutions/github-foundations-cli/releases/download/$(curl -s https://api.github.com/repos/FociSolutions/github-foundations-cli/releases/latest | grep tag_name | cut -d '"' -f 4)/UPDATE_ME_github_foundations_linux_amd64"
chmod +x UPDATE_ME_github_foundations_linux_amd64
sudo mv UPDATE_ME_github_foundations_linux_amd64 /usr/local/bin/gh_foundations
```

**ARM64**
```
curl -LO "https://github.com/FociSolutions/github-foundations-cli/releases/download/$(curl -s https://api.github.com/repos/FociSolutions/github-foundations-cli/releases/latest | grep tag_name | cut -d '"' -f 4)/UPDATE_ME_github_foundations_linux_arm64"
chmod +x UPDATE_ME_github_foundations_linux_arm64
sudo mv UPDATE_ME_github_foundations_linux_arm64 /usr/local/bin/gh_foundations
```

#### MacOS

**ADM64**
```
curl -LO "https://github.com/FociSolutions/github-foundations-cli/releases/download/$(curl -s https://api.github.com/repos/FociSolutions/github-foundations-cli/releases/latest | grep tag_name | cut -d '"' -f 4)/UPDATE_ME_github_foundations_darwin_amd64"
chmod +x UPDATE_ME_github_foundations_darwin_amd64
sudo mv UPDATE_ME_github_foundations_darwin_amd64 /usr/local/bin/gh_foundations
```

**ARM64**
```
curl -LO "https://github.com/FociSolutions/github-foundations-cli/releases/download/$(curl -s https://api.github.com/repos/FociSolutions/github-foundations-cli/releases/latest | grep tag_name | cut -d '"' -f 4)/UPDATE_ME_github_foundations_darwin_arm64"
chmod +x UPDATE_ME_github_foundations_darwin_arm64
sudo mv UPDATE_ME_github_foundations_darwin_arm64 /usr/local/bin/gh_foundations
```

#### Windows

**i386**
```
curl -LO "https://github.com/FociSolutions/github-foundations-cli/releases/download/$(curl -s https://api.github.com/repos/FociSolutions/github-foundations-cli/releases/latest | grep tag_name | cut -d '"' -f 4)/UPDATE_ME_github_foundations_windows_386.exe"
...
```

**ADM64**
```
curl -LO "https://github.com/FociSolutions/github-foundations-cli/releases/download/$(curl -s https://api.github.com/repos/FociSolutions/github-foundations-cli/releases/latest | grep tag_name | cut -d '"' -f 4)/UPDATE_ME_github_foundations_windows_amd64.exe"
...
```

**ARM64**
```
curl -LO "https://github.com/FociSolutions/github-foundations-cli/releases/download/$(curl -s https://api.github.com/repos/FociSolutions/github-foundations-cli/releases/latest | grep tag_name | cut -d '"' -f 4)/UPDATE_ME_github_foundations_windows_arm64.exe"
...
```

### From source
1. Run `git clone <gh_foundations_cli repo> && cd gh-foundations-cli/`
2. Run `go mod download`
3. Run `go build -v` for all providers OR build with one provider

83 changes: 83 additions & 0 deletions cmd/check/check.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package check

import (
"encoding/json"
"errors"
"gh_foundations/internal/pkg/types"
"gh_foundations/internal/pkg/types/github"
"os"
"os/exec"
"strings"

"github.com/spf13/cobra"
)

var outputFile = "check_results.json"

var CheckCmd = &cobra.Command{
Use: "check",
Short: "Perform checks against a Github configuration.",
Long: `Perform checks against a Github configuration and generate reports.`,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return errors.New("requires a GitHub organization slug")
}
return nil
},
Run: func(cmd *cobra.Command, args []string) {
var err error
reports := make([]types.CheckReport, 0)
slug := args[0]
authToken, set := os.LookupEnv("GITHUB_TOKEN")
if !set {
authToken, err = getTokenFromGhCli()
if err != nil {
cmd.PrintErr("GITHUB_TOKEN environment variable not set and unable to authenticate with gh cli")
return
}
}

gs := github.NewGithubService(authToken)
org, err := gs.GetOrganization(slug)
if err == nil {
reports = append(reports, org.Check([]types.CheckType{types.GoCGaurdrails}))
}

repos, err := gs.GetRepositories(slug, nil)
if err == nil {
for _, r := range repos {
reports = append(reports, r.Check([]types.CheckType{types.GoCGaurdrails}))
}
}

file, err := os.OpenFile(outputFile, os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
cmd.PrintErr(err)
return
}
defer file.Close()

bytes, err := json.Marshal(reports)
if err != nil {
cmd.PrintErr(err)
return
}

file.Truncate(0)
file.Seek(0, 0)
file.Write(bytes)
},
}

func getTokenFromGhCli() (string, error) {
cmd, set := os.LookupEnv("GH_PATH")
if !set {
cmd = "gh"
}
out, err := exec.Command(cmd, "auth", "token").Output()
if err != nil {
return "", errors.New("unable to authenticate with gh cli")
}

return strings.TrimSpace(string(out)), nil
}
17 changes: 17 additions & 0 deletions cmd/gen/gen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package gen

import (
repositoryset "gh_foundations/cmd/gen/repository_set"

"github.com/spf13/cobra"
)

var GenCmd = &cobra.Command{
Use: "gen",
Short: "Generate HCL input for GitHub Foundations.",
Long: `Generate HCL input for GitHub Foundations. This tool is used to generate HCL input for GitHub Foundations from state files output by terraformer.`,
}

func init() {
GenCmd.AddCommand(repositoryset.GenRepositorySetCmd)
}
78 changes: 78 additions & 0 deletions cmd/gen/repository_set/repository_set.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package repositoryset

import (
"log"
"os"

"github.com/hashicorp/hcl/v2/hclwrite"
"github.com/spf13/cobra"
"github.com/tidwall/gjson"

"gh_foundations/internal/pkg/functions"
githubfoundations "gh_foundations/internal/pkg/types/github_foundations"
)

var GenRepositorySetCmd = &cobra.Command{
Use: "repository_set",
Short: "Use the results from a terraformer run to generate an hcl file that contains a repository set input.",
Long: `Uses the results from a terraformer run to generate an hcl file that contains a repository set input. It generates inputs for all repositories in the state file generated by terraformer.`,
Args: func(cmd *cobra.Command, args []string) error {
if err := cobra.MinimumNArgs(1)(cmd, args); err != nil {
return err
}
if _, err := os.Stat(args[0]); err != nil {
return err
}
return nil
},
Run: func(cmd *cobra.Command, args []string) {
stateFile := args[0]
stateBytes, err := os.ReadFile(stateFile)
if err != nil {
log.Fatalf("Error reading state file %s. %s", stateFile, err.Error())
}
result := gjson.Parse(string(stateBytes))

list := result.Get("modules.0.resources").Map()
repositorySet := new(githubfoundations.RepositorySetInput)
repositoryUserPermissions := make(map[string]map[string]string)
for resource_id, gjsonResult := range list {
rType := functions.IdentifyFoundationsResourceType(resource_id)
rAttributes := gjsonResult.Get("primary.attributes")
if rType == githubfoundations.Repository {
repository := functions.MapTerraformerRepositoryToGithubFoundationRepository(rAttributes)
visibility := rAttributes.Get("visibility").String()
if visibility == "public" {
repositorySet.PublicRepositories = append(repositorySet.PublicRepositories, repository)
} else {
repositorySet.PrivateRepositories = append(repositorySet.PrivateRepositories, repository)
}
} else if rType == githubfoundations.RepositoryCollaborator {
repositoryName := rAttributes.Get("repository").String()
permission := rAttributes.Get("permission").String()
username := rAttributes.Get("username").String()
userPermission, ok := repositoryUserPermissions[repositoryName]
if !ok {
userPermission = make(map[string]string)
}
userPermission[username] = permission
repositoryUserPermissions[repositoryName] = userPermission
}
}

for _, repository := range repositorySet.PrivateRepositories {
repository.UserPermissions = repositoryUserPermissions[repository.Name]
}
for _, repository := range repositorySet.PublicRepositories {
repository.UserPermissions = repositoryUserPermissions[repository.Name]
}

file := hclwrite.NewEmptyFile()
repositorySet.WriteInputsHCL(file)
output, err := os.Create("repository_set.inputs.hcl")
if err != nil {
log.Fatal(err)
}
file.WriteTo(output)
},
}
Loading