Skip to content

Commit

Permalink
Merge pull request #5 from uitml/golang-redux
Browse files Browse the repository at this point in the history
Switch to Go
  • Loading branch information
thomasjo authored Jul 17, 2020
2 parents a48cfe6 + 71497b9 commit 9ee6d73
Show file tree
Hide file tree
Showing 23 changed files with 1,242 additions and 44 deletions.
11 changes: 11 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
root = true

[*]
charset = utf-8
indent_style = space
insert_final_newline = true
tab_width = 2

[*.go]
indent_style = tab
tab_width = 4
28 changes: 28 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Release

on:
push:
tags:
- "v*"

jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch_depth: 0

- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.14

- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
with:
version: latest
args: release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,10 @@
# Platform.
.DS_Store

# Generic.
.vscode/
.envrc

# Project.
/dist/
/out/
35 changes: 35 additions & 0 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
project_name: frink

before:
hooks:
- go mod tidy
- go mod download

release:
prerelease: auto

builds:
- env:
- CGO_ENABLED=0
goos:
- darwin
- linux
goarch:
- amd64

archives:
- format: binary
name_template: '{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}'
replacements:
darwin: macOS
linux: Linux
amd64: x86_64

checksum:
name_template: 'checksums.txt'

snapshot:
name_template: '{{ replace .Tag "v" "" }}-{{ .ShortCommit }}'

changelog:
skip: true
11 changes: 0 additions & 11 deletions bin/kubectl-job-kill

This file was deleted.

6 changes: 0 additions & 6 deletions bin/kubectl-job-list

This file was deleted.

18 changes: 0 additions & 18 deletions bin/kubectl-job-run

This file was deleted.

8 changes: 0 additions & 8 deletions bin/kubectl-job-watch

This file was deleted.

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

import (
"fmt"
"os"
"strings"
"text/tabwriter"
"time"

"github.com/dustin/go-humanize"
"github.com/hako/durafmt"
"github.com/spf13/cobra"
batchv1 "k8s.io/api/batch/v1"
)

var showAll bool

var listCmd = &cobra.Command{
Use: "ls",
Short: "List jobs",

RunE: func(cmd *cobra.Command, args []string) error {
jobs, err := kubectx.ListJobs()
if err != nil {
return fmt.Errorf("could not list jobs: %w", err)
}

w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
defer w.Flush()

fmt.Fprintln(w, header())
for _, job := range jobs.Items {
fmt.Fprintln(w, row(job))
}

return nil
},
}

func init() {
rootCmd.AddCommand(listCmd)

flags := listCmd.Flags()
flags.BoolVarP(&showAll, "all", "a", false, "show all jobs; active and terminated")
}

var (
headerNames = []string{
"NAME",
"STATUS",
"COMPLETIONS",
"DURATION",
"AGE",
}
)

func header() string {
return strings.Join(headerNames, "\t") + "\t"
}

func row(job batchv1.Job) string {
columns := []string{
job.Name,
status(job),
completions(job),
duration(job),
age(job),
}

return strings.Join(columns, "\t") + "\t"
}

func status(job batchv1.Job) string {
switch {
case job.Status.Active > 0:
return "Active"
case job.Spec.Completions == nil || *job.Spec.Completions == job.Status.Succeeded:
return "Succeeded"
case job.Status.Failed > 0:
return "Failed"
}

return "Stopped"
}

func completions(job batchv1.Job) string {
succeeded := job.Status.Succeeded
total := succeeded + job.Status.Active + job.Status.Failed

return fmt.Sprintf("%d/%d", succeeded, total)
}

func duration(job batchv1.Job) string {
_, duration := timing(job)
// TODO: Implement "smart" truncation scheme.
humanized := durafmt.Parse(duration).LimitFirstN(2).String()

return humanized
}

func age(job batchv1.Job) string {
start, _ := timing(job)
humanized := humanize.Time(start)

return humanized
}

func timing(job batchv1.Job) (time.Time, time.Duration) {
var start time.Time
duration := time.Duration(0)
if job.Status.StartTime != nil {
start = job.Status.StartTime.Time
end := time.Now()
if job.Status.CompletionTime != nil {
end = job.Status.CompletionTime.Time
}
duration = end.Sub(start)
}

return start, duration
}
52 changes: 52 additions & 0 deletions cmd/logs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package cmd

import (
"bufio"
"errors"
"fmt"
"io"
"os"

"github.com/spf13/cobra"
"github.com/uitml/frink/internal/k8s"
)

var logsCmd = &cobra.Command{
Use: "logs <name>",
Short: "Fetch the logs of a job",

RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return fmt.Errorf("job name must be specified")
}

name := args[0]
req, err := kubectx.GetJobLogs(name, k8s.DefaultLogOptions)
if err != nil {
return fmt.Errorf("unable to get logs: %w", errors.Unwrap(err))
}

if req == nil {
return fmt.Errorf("unable to get logs: request not returned (nil)")
}

stream, err := req.Stream()
if err != nil {
return fmt.Errorf("unable to stream logs: %w", err)
}
defer stream.Close()

reader := bufio.NewReader(stream)
if _, err := io.Copy(os.Stdout, reader); err != nil {
return fmt.Errorf("unable to write output: %w", err)
}

return nil
},

Aliases: []string{"watch"},
}

func init() {
rootCmd.AddCommand(logsCmd)
}
29 changes: 29 additions & 0 deletions cmd/remove.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package cmd

import (
"fmt"

"github.com/spf13/cobra"
)

var removeCmd = &cobra.Command{
Use: "rm <name>",
Short: "Remove job from cluster",

RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return fmt.Errorf("job name must be specified")
}

name := args[0]
if err := kubectx.DeleteJob(name); err != nil {
return fmt.Errorf("unable to delete job: %w", err)
}

return nil
},
}

func init() {
rootCmd.AddCommand(removeCmd)
}
54 changes: 54 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Package cmd provides implementations of CLI commands.
package cmd

import (
"fmt"
"os"

"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/uitml/frink/internal/cli"
"github.com/uitml/frink/internal/k8s"
)

// NOTE: Global package state; bad idea, but works for the time being.
var kubectx *k8s.KubeContext

var rootCmd = &cobra.Command{
Use: "frink",
Short: "Frink simplifies your Springfield workflows",

PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
context := viper.GetString("context")
namespace := viper.GetString("namespace")

ctx, err := k8s.Client(context, namespace)
if err != nil {
return fmt.Errorf("unable to get kube client: %w", err)
}

kubectx = ctx
return nil
},

// Do not display usage when an error occurs.
SilenceUsage: true,
}

func init() {
cobra.OnInitialize(cli.InitConfig)

pflags := rootCmd.PersistentFlags()
pflags.String("context", "", "name of the kubeconfig context to use")
pflags.StringP("namespace", "n", "", "cluster namespace to use")
viper.BindPFlags(pflags)

cli.DisableFlagsInUseLine(rootCmd)
}

// Execute executes the root command.
func Execute() {
if err := rootCmd.Execute(); err != nil {
os.Exit(1)
}
}
Loading

0 comments on commit 9ee6d73

Please sign in to comment.