Skip to content

Commit

Permalink
Initial CLI Version
Browse files Browse the repository at this point in the history
  • Loading branch information
chris-cmsoft committed Nov 21, 2024
0 parents commit 89e9999
Show file tree
Hide file tree
Showing 14 changed files with 862 additions and 0 deletions.
33 changes: 33 additions & 0 deletions .github/workflows/goreleaser.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Build and Release Binaries

on:
push:
tags:
- '*'

jobs:
test:
permissions:
contents: read
uses: ./.github/workflows/test.yml

release:
runs-on: ubuntu-latest

needs:
- test

permissions:
contents: write

steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
with:
# 'latest', 'nightly', or a semver
version: '~> v2'
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
19 changes: 19 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: Test

on:
push:
workflow_call:

jobs:
test:
runs-on: ubuntu-latest

permissions:
contents: read

steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5

- run: go mod download
- run: go test ./...
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Goreleaer directory
dist/

# Local building binary
gooci

# Goland / Jetbrains IDE
.idea/
44 changes: 44 additions & 0 deletions .gorelreaser.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# This is an example .goreleaser.yml file with some sensible defaults.
# Make sure to check the documentation at https://goreleaser.com

# The lines below are called `modelines`. See `:help modeline`
# Feel free to remove those if you don't want/need to use them.
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
# vim: set ts=2 sw=2 tw=0 fo=cnqoj

version: 2

before:
hooks:
# You may remove this if you don't use go modules.
- go mod tidy
# you may remove this if you don't need go generate
- go generate ./...

builds:
- binary: plugin
env:
- CGO_ENABLED=0
goos:
- linux
- darwin

archives:
- format: tar.gz
# this name template makes the OS and Arch compatible with the results of `uname`.
files:
- README.MD
name_template: >-
{{ .ProjectName }}_
{{- title .Os }}_
{{- if eq .Arch "amd64" }}x86_64
{{- else if eq .Arch "386" }}i386
{{- else }}{{ .Arch }}{{ end }}
{{- if .Arm }}v{{ .Arm }}{{ end }}
changelog:
sort: asc
filters:
exclude:
- "^docs:"
- "^test:"
47 changes: 47 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Goreleaser to OCI

In the compliance framework, we distribute plugins as OCI artifacts for use in the collection agent.

In order to efficiently distribute the plugins for multiple operating systems and architectures, we took
a page from the Homebrew playbook, and decided to upload our binaries and files to OCI, so we could easily
version and distribute them for all sorts of runtimes.

`gooci` is a CLI we use to take goreleaser builds and archives and upload them to an OCI registry.

When the plugins are then used it's easy for us to just specify the OCI path
`ghcr.io/compliance-framework/plugin-ubuntu-vulnerabilities:1.0.1`, and know it will work no matter
where it runs.

## Installation

Installation is very rudimentary at this point as we are still building a stable version.

```shell
# Build from source
git clone [email protected]:compliance-framework/goreleaser-oci.git
cd goreleaser-oci
go mod download
go build -o gooci main.go
sudo cp gooci /usr/local/bin
```

## Usage

```shell
$ ./gooci help
gooci handles uploading and downloading of GoReleaser archives to an OCI registry

Usage:
gooci [command]

Available Commands:
completion Generate the autocompletion script for the specified shell
download Download GoReleaser archives to a local directory
help Help about any command
upload Upload GoReleaser archives to an OCI registry

Flags:
-h, --help help for gooci

Use "gooci [command] --help" for more information about a command.
```
79 changes: 79 additions & 0 deletions cmd/download.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package cmd

import (
"fmt"
"github.com/compliance-framework/goreleaser-oci/pkg/oci"
"github.com/google/go-containerregistry/pkg/name"
"github.com/spf13/cobra"
"log"
"os"
"path"
)

func DownloadReleaseCmd() *cobra.Command {
command := &cobra.Command{
Use: "download [source oci artifact] [destination]",
Short: "Download GoReleaser archives to a local directory",
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
downloadCmd := &downloadRelease{}
err := downloadCmd.run(cmd, args)
if err != nil {
log.Fatal(err)
}
},
}

return command
}

type downloadConfig struct {
directory string
source name.Tag
}

type downloadRelease struct {
}

func (d *downloadRelease) run(cmd *cobra.Command, args []string) error {
config, err := d.validateArgs(args)
if err != nil {
return err
}

downloader, err := oci.NewDownloader(config.source, config.directory)
if err != nil {
return err
}

err = downloader.Download()
if err != nil {
return err
}

return nil
}

func (d *downloadRelease) validateArgs(args []string) (*downloadConfig, error) {
// Validate the second arg is a valid OCI registry
repositoryName := args[0]
tag, err := name.NewTag(repositoryName, name.StrictValidation)
if err != nil {
return nil, fmt.Errorf("failed to parse repository: %v", err)
}

// Validate the first arg is a directory.
destinationDir := args[1]
if !path.IsAbs(destinationDir) {
workDir, err := os.Getwd()
if err != nil {
return nil, fmt.Errorf("failed to get current working directory: %v", err)
}
destinationDir = path.Join(workDir, destinationDir)
}

return &downloadConfig{
directory: destinationDir,
source: tag,
}, nil
}
70 changes: 70 additions & 0 deletions cmd/download_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package cmd

import (
"os"
"path"
"testing"
)

func Test_DownloadCmd_ValidateArgs(t *testing.T) {
downloadCmd := downloadRelease{}

tests := []struct {
name string
args []string
shouldFail bool
}{
{
name: "Directory and repository are valid",
args: []string{
validRespository,
validDirectory,
},
shouldFail: false,
},
{
name: "Repository is not valid",
args: []string{
"invalid/invalid",
validDirectory,
},
shouldFail: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := downloadCmd.validateArgs(tt.args)
if (err != nil) != tt.shouldFail {
t.Errorf("downloadCmd.validateArgs() error = %v, shouldFail %v", err, tt.shouldFail)
}
})
}
}

func Test_DownloadCmd_ValidateArgs_Response(t *testing.T) {
t.Run("Returns correct Config", func(t *testing.T) {
downloadCmd := downloadRelease{}

config, err := downloadCmd.validateArgs([]string{validRespository, validDirectory})
if err != nil {
t.Errorf("downloadCmd.validateArgs() error = %v", err)
}

if config == nil {
t.Errorf("downloadCmd.validateArgs() config = %v", config)
}

if config.source.String() != validRespository {
t.Errorf("downloadCmd.validateArgs() config = %v", config)
}

workDir, err := os.Getwd()
if err != nil {
t.Errorf("downloadCmd.validateArgs() error = %v", err)
}
if config.directory != path.Join(workDir, validDirectory) {
t.Errorf("downloadCmd.validateArgs() config = %v", config)
}
})
}
Loading

0 comments on commit 89e9999

Please sign in to comment.