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

KM498 ✅ Rotate GitHub deploy key #14

Merged
merged 8 commits into from
Feb 21, 2022
Merged
Show file tree
Hide file tree
Changes from 5 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
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.89.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.89.rotate-argocd-ssh-key/pkg/cmdflags"
"github.com/oslokommune/okctl-upgrade/upgrades/0.0.89.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.89.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.89.rotate-argocd-ssh-key

go 1.16

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

Large diffs are not rendered by default.

68 changes: 68 additions & 0 deletions upgrades/0.0.89.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.89.rotate-argocd-ssh-key/pkg/cmdflags"
"github.com/oslokommune/okctl-upgrade/upgrades/0.0.89.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.89.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.89.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")
112 changes: 112 additions & 0 deletions upgrades/0.0.89.rotate-argocd-ssh-key/pkg/github/github.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// 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
}

// DeleteDeployKey removes a read-only deploy key
func (g *Github) DeleteDeployKey(org, repository string, identifier int64) error {
_, err := g.Client.Repositories.DeleteKey(g.Ctx, org, repository, identifier)
if err != nil {
return fmt.Errorf("deleting deploy key: %w", err)
}

return 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.89.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