Skip to content
This repository has been archived by the owner on Feb 7, 2023. It is now read-only.

Commit

Permalink
KM498 ✅ Rotate GitHub deploy key (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
yngvark authored Feb 21, 2022
1 parent a42ad1d commit 43aae4e
Show file tree
Hide file tree
Showing 13 changed files with 2,886 additions and 14 deletions.
20 changes: 6 additions & 14 deletions template/pkg/somecomponent/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,8 @@ import (

// SomeComponent is a sample okctl component
type SomeComponent struct {
flags cmdflags.Flags
log logger.Logger
dryRun bool
confirm bool
flags cmdflags.Flags
log logger.Logger
}

// Upgrade upgrades the component
Expand All @@ -22,7 +20,7 @@ func (c SomeComponent) Upgrade() error {

c.log.Debug("SomeComponent is on version 0.5. Updating to 0.6")

if !c.dryRun && !c.confirm {
if !c.flags.DryRun && !c.flags.Confirm {
c.log.Info("This will delete all logs.")

answer, err := c.askUser("Do you want to continue?")
Expand All @@ -35,7 +33,7 @@ func (c SomeComponent) Upgrade() error {
}
}

if c.dryRun {
if c.flags.DryRun {
c.log.Info("Simulating some stuff")
} else {
c.log.Info("Doing some stuff")
Expand All @@ -60,15 +58,9 @@ func (c SomeComponent) askUser(question string) (bool, error) {
return answer, nil
}

type Opts struct {
DryRun bool
Confirm bool
}

func New(logger logger.Logger, flags cmdflags.Flags) SomeComponent {
return SomeComponent{
log: logger,
dryRun: flags.DryRun,
confirm: flags.Confirm,
log: logger,
flags: flags,
}
}
23 changes: 23 additions & 0 deletions upgrades/0.0.92.rotate-argocd-ssh-key/context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package main

import (
"github.com/oslokommune/okctl-upgrade/upgrades/0.0.92.rotate-argocd-ssh-key/pkg/cmdflags"
"github.com/oslokommune/okctl-upgrade/upgrades/0.0.92.rotate-argocd-ssh-key/pkg/logger"
)

type Context struct {
logger logger.Logger
}

func newContext(flags cmdflags.Flags) Context {
var level logger.Level
if flags.Debug {
level = logger.Debug
} else {
level = logger.Info
}

return Context{
logger: logger.New(level),
}
}
11 changes: 11 additions & 0 deletions upgrades/0.0.92.rotate-argocd-ssh-key/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module github.com/oslokommune/okctl-upgrade/upgrades/0.0.92.rotate-argocd-ssh-key

go 1.16

require (
github.com/google/go-github/v32 v32.1.0
github.com/oslokommune/okctl v0.0.92
github.com/spf13/cobra v1.3.0
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8
sigs.k8s.io/yaml v1.3.0
)
2,250 changes: 2,250 additions & 0 deletions upgrades/0.0.92.rotate-argocd-ssh-key/go.sum

Large diffs are not rendered by default.

68 changes: 68 additions & 0 deletions upgrades/0.0.92.rotate-argocd-ssh-key/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package main

import (
"errors"
"fmt"
"github.com/oslokommune/okctl-upgrade/upgrades/0.0.92.rotate-argocd-ssh-key/pkg/cmdflags"
"github.com/oslokommune/okctl-upgrade/upgrades/0.0.92.rotate-argocd-ssh-key/pkg/commonerrors"
"github.com/spf13/cobra"
"os"
"path/filepath"
)

func main() {
cmd := buildRootCommand()

err := cmd.Execute()

if err != nil && errors.Is(err, commonerrors.ErrUserAborted) {
fmt.Println("Upgrade aborted by user.")
} else if err != nil {
_, _ = fmt.Fprintln(os.Stderr, err.Error())
}

if err != nil {
os.Exit(1)
}
}

func buildRootCommand() *cobra.Command {
flags := cmdflags.Flags{}

var context Context

filename := filepath.Base(os.Args[0])

cmd := &cobra.Command{
Short: "Upgrades an okctl cluster",
Long: "Note, boolean arguments must be specified on the form --arg=bool (and not on the form --arg bool).",
Use: filename,
Example: fmt.Sprintf("%s --debug=false", filename),
SilenceErrors: true, // true as we print errors in the main() function
SilenceUsage: true, // true because we don't want to show usage if an errors occurs
PreRunE: func(_ *cobra.Command, args []string) error {
context = newContext(flags)
return nil
},
RunE: func(_ *cobra.Command, args []string) error {
return upgrade(context, flags)
},
}

/*
* Flags supported. Expected behavior is as following:
*
* --debug: Outputs extra output for debugging.
*
* --dry-run: If set to true, the upgrade will not make any changes, but only print what would be done, as if
* running a simulation.
* If set to false, the upgrade will make actual changes.
*
* --confirm: Skips all confirmation prompts, if any.
*/
cmd.PersistentFlags().BoolVarP(&flags.Debug, "debug", "d", false, "Set this to enable debug output.")
cmd.PersistentFlags().BoolVarP(&flags.DryRun, "dry-run", "n", true, "Don't actually do any changes, just show what would be done.")
cmd.PersistentFlags().BoolVarP(&flags.Confirm, "confirm", "c", false, "Set this to skip confirmation prompts.")

return cmd
}
7 changes: 7 additions & 0 deletions upgrades/0.0.92.rotate-argocd-ssh-key/pkg/cmdflags/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package cmdflags

type Flags struct {
Debug bool
DryRun bool
Confirm bool
}
5 changes: 5 additions & 0 deletions upgrades/0.0.92.rotate-argocd-ssh-key/pkg/commonerrors/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package commonerrors

import "errors"

var ErrUserAborted = errors.New("aborted by user")
102 changes: 102 additions & 0 deletions upgrades/0.0.92.rotate-argocd-ssh-key/pkg/github/github.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Package github provides a client for interacting with the Github API
package github

import (
"context"
"errors"
"fmt"

"github.com/google/go-github/v32/github"
githubAuth "github.com/oslokommune/okctl/pkg/credentials/github"
"golang.org/x/oauth2"
)

// ErrNotFound means something was not found
var ErrNotFound = errors.New("not found")

// Githuber invokes the github API
type Githuber interface {
GetDeployKeys(org, repository, deployKeyName string) ([]*Key, error)
}

// Github contains the state for interacting with the github API
type Github struct {
Ctx context.Context
Client *github.Client
}

func (g *Github) GetDeployKeys(org, repository, deployKeyName string) ([]*Key, error) {
allKeys, err := g.ListDeployKey(org, repository)
if err != nil {
return nil, fmt.Errorf("getting deploy key: %w", err)
}

var keysWithName []*Key

for _, key := range allKeys {
if key.GetTitle() == deployKeyName {
keysWithName = append(keysWithName, key)
}
}

if len(keysWithName) == 0 {
return nil, ErrNotFound
}

return keysWithName, nil
}

func (g *Github) ListDeployKey(org, repository string) ([]*Key, error) {
opts := &github.ListOptions{
Page: 0,
PerPage: 100,
}

var allKeys []*Key

for {
keys, response, err := g.Client.Repositories.ListKeys(g.Ctx, org, repository, opts)
if err != nil {
return nil, fmt.Errorf("listing deploy keys: %w", err)
}

allKeys = append(allKeys, keys...)

if response.NextPage == 0 {
break
}

opts.Page = response.NextPage
}

return allKeys, nil
}

// Ensure that Github implements Githuber
var _ Githuber = &Github{}

// Key shadows github.Key
type Key = github.Key

// New returns an initialised github API client
func New(ctx context.Context, auth githubAuth.Authenticator) (*Github, error) {
credentials, err := auth.Raw()
if err != nil {
return nil, fmt.Errorf("failed to get github credentials: %w", err)
}

client := github.NewClient(
oauth2.NewClient(ctx,
oauth2.StaticTokenSource(
&oauth2.Token{
AccessToken: credentials.AccessToken,
},
),
),
)

return &Github{
Ctx: ctx,
Client: client,
}, nil
}
73 changes: 73 additions & 0 deletions upgrades/0.0.92.rotate-argocd-ssh-key/pkg/logger/logger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package logger

import (
"fmt"
"os"
)

type Level int

const (
// Debug means the system will output more technical messages
Debug Level = iota

// Info means the system will output default messages
Info

// Error means the system will output error messages
Error
)

type Logger struct {
level Level
}

func (l Logger) Debug(args ...interface{}) {
if l.levelIsEnabled(Debug) {
out := make([]interface{}, 0)
out = append(out, "[DEBUG]")
out = append(out, args...)

_, _ = fmt.Println(out...)
}
}

func (l Logger) Debugf(format string, args ...interface{}) {
if l.levelIsEnabled(Debug) {
_, _ = fmt.Printf("[DEBUG] "+format, args...)
}
}

func (l Logger) Info(args ...interface{}) {
if l.levelIsEnabled(Info) {
_, _ = fmt.Println(args...)
}
}

func (l Logger) Infof(format string, args ...interface{}) {
if l.levelIsEnabled(Info) {
_, _ = fmt.Printf(format, args...)
}
}

func (l Logger) Error(args ...interface{}) {
if l.levelIsEnabled(Error) {
_, _ = fmt.Fprintln(os.Stderr, args...)
}
}

func (l Logger) Errorf(format string, args ...interface{}) {
if l.levelIsEnabled(Error) {
_, _ = fmt.Fprintf(os.Stderr, format, args...)
}
}

func (l Logger) levelIsEnabled(level Level) bool {
return l.level <= level
}

func New(level Level) Logger {
return Logger{
level: level,
}
}
Loading

0 comments on commit 43aae4e

Please sign in to comment.