Skip to content

Commit

Permalink
fix: fix for temporary errors (#201)
Browse files Browse the repository at this point in the history
* fix: fix for temporary errors

The atlantis API isn't very graceful when errors
happen.  Best we can do is log it and move on.

* fix: ready for v1
  • Loading branch information
Jack Lindamood authored Mar 11, 2024
1 parent 2026d20 commit 4d1f96f
Show file tree
Hide file tree
Showing 9 changed files with 58 additions and 12 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ jobs:
type=ref,event=branch
type=ref,event=tag
type=ref,event=pr
type=semver,pattern={{major}},enable=${{ github.event_name == 'push' && contains(github.ref, 'refs/tags/') }}
type=semver,pattern=v{{major}},enable=${{ github.event_name == 'push' && contains(github.ref, 'refs/tags/') }}
images: |
ghcr.io/cresta/atlantis-drift-detection
- name: Build and push
Expand Down
2 changes: 1 addition & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ runs:
using: 'docker'
# TODO: Figure out a way to auto update this. It's very useful for speeding up the action to not have it build the
# container each run
image: 'docker://ghcr.io/cresta/atlantis-drift-detection:v0.0.11'
image: 'docker://ghcr.io/cresta/atlantis-drift-detection:v1'
13 changes: 9 additions & 4 deletions cmd/atlantis-drift-detection/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ package main
import (
"context"
"fmt"
"net/http"
"os"
"time"

"github.com/cresta/atlantis-drift-detection/internal/atlantis"
"github.com/cresta/atlantis-drift-detection/internal/drifter"
"github.com/cresta/atlantis-drift-detection/internal/notification"
Expand All @@ -11,9 +15,6 @@ import (
"github.com/cresta/gogit"
"github.com/cresta/gogithub"
"github.com/joho/godotenv"
"net/http"
"os"
"time"

// Empty import allows pinning to version atlantis uses
_ "github.com/nlopes/slack"
Expand Down Expand Up @@ -100,7 +101,11 @@ func main() {
logger.Info("setting up slack webhook notification")
notif.Notifications = append(notif.Notifications, slackClient)
}
ghClient, err := gogithub.NewGQLClient(ctx, logger, nil)
var existingConfig *gogithub.NewGQLClientConfig
if os.Getenv("GITHUB_TOKEN") != "" {
existingConfig = &gogithub.NewGQLClientConfig{Token: os.Getenv("GITHUB_TOKEN")}
}
ghClient, err := gogithub.NewGQLClient(ctx, logger, existingConfig)
if err != nil {
logger.Panic("failed to create github client", zap.Error(err))
}
Expand Down
20 changes: 17 additions & 3 deletions internal/atlantis/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import (
"context"
"encoding/json"
"fmt"
"github.com/runatlantis/atlantis/server/controllers"
"github.com/runatlantis/atlantis/server/events/command"
"io"
"net/http"
"strings"

"github.com/runatlantis/atlantis/server/controllers"
"github.com/runatlantis/atlantis/server/events/command"
)

type Client struct {
Expand Down Expand Up @@ -65,6 +66,10 @@ type TemporaryError interface {
error
}

type errorResponse struct {
Error string `json:"error"`
}

func (p *possiblyTemporaryError) Temporary() bool {
return true
}
Expand Down Expand Up @@ -107,11 +112,20 @@ func (c *Client) PlanSummary(ctx context.Context, req *PlanSummaryRequest) (*Pla
if err := resp.Body.Close(); err != nil {
return nil, fmt.Errorf("unable to close response body: %w", err)
}
if resp.StatusCode == http.StatusUnauthorized {
var errResp errorResponse
if err := json.NewDecoder(&fullBody).Decode(&errResp); err != nil {
return nil, fmt.Errorf("unauthorized request to %s: %w", destination, err)
}
return nil, fmt.Errorf("unauthorized request to %s: %s", destination, errResp.Error)
}

var bodyResult command.Result
if err := json.NewDecoder(&fullBody).Decode(&bodyResult); err != nil {
retErr := fmt.Errorf("error decoding plan response(code:%d)(status:%s)(body:%s): %w", resp.StatusCode, resp.Status, fullBody.String(), err)
if resp.StatusCode == http.StatusServiceUnavailable {
if resp.StatusCode == http.StatusServiceUnavailable || resp.StatusCode == http.StatusInternalServerError {
// This is a bit of a hack, but atlantis sometimes returns errors we can't fully process. These could be
// because the workspace won't apply, or because the service is just overloaded. We cannot tell.
return nil, &possiblyTemporaryError{retErr}
}
return nil, retErr
Expand Down
9 changes: 9 additions & 0 deletions internal/notification/multi.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ type Multi struct {
Notifications []Notification
}

func (m *Multi) TemporaryError(ctx context.Context, dir string, workspace string, err error) error {
for _, n := range m.Notifications {
if err := n.TemporaryError(ctx, dir, workspace, err); err != nil {
return err
}
}
return nil
}

func (m *Multi) ExtraWorkspaceInRemote(ctx context.Context, dir string, workspace string) error {
for _, n := range m.Notifications {
if err := n.ExtraWorkspaceInRemote(ctx, dir, workspace); err != nil {
Expand Down
2 changes: 2 additions & 0 deletions internal/notification/notification.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,6 @@ type Notification interface {
ExtraWorkspaceInRemote(ctx context.Context, dir string, workspace string) error
MissingWorkspaceInRemote(ctx context.Context, dir string, workspace string) error
PlanDrift(ctx context.Context, dir string, workspace string) error
// TemporaryError is called when an error occurs but we can't really tell what it means
TemporaryError(ctx context.Context, dir string, workspace string, err error) error
}
4 changes: 4 additions & 0 deletions internal/notification/slackwebhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ type SlackWebhook struct {
HTTPClient *http.Client
}

func (s *SlackWebhook) TemporaryError(ctx context.Context, dir string, workspace string, err error) error {
return s.sendSlackMessage(ctx, fmt.Sprintf("Unknown error in remote\nDirectory: %s\nWorkspace: %s\nError: %s", dir, workspace, err.Error()))
}

func NewSlackWebhook(webhookURL string, HTTPClient *http.Client) *SlackWebhook {
if webhookURL == "" {
return nil
Expand Down
12 changes: 9 additions & 3 deletions internal/notification/workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ package notification

import (
"context"
"github.com/cresta/gogithub"
"sync"

"github.com/cresta/gogithub"
)

func NewWorkflow(ghClient gogithub.GitHub, owner string, repo string, id string, ref string) *Workflow {
Expand All @@ -30,11 +31,16 @@ type Workflow struct {
directoriesDone map[string]struct{}
}

func (w *Workflow) ExtraWorkspaceInRemote(ctx context.Context, dir string, workspace string) error {
func (w *Workflow) TemporaryError(_ context.Context, _ string, _ string, _ error) error {
// Ignored
return nil
}

func (w *Workflow) ExtraWorkspaceInRemote(_ context.Context, _ string, _ string) error {
return nil
}

func (w *Workflow) MissingWorkspaceInRemote(ctx context.Context, dir string, workspace string) error {
func (w *Workflow) MissingWorkspaceInRemote(_ context.Context, _ string, _ string) error {
return nil
}

Expand Down
6 changes: 6 additions & 0 deletions internal/notification/zap.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,19 @@ package notification

import (
"context"

"go.uber.org/zap"
)

type Zap struct {
Logger *zap.Logger
}

func (I *Zap) TemporaryError(_ context.Context, dir string, workspace string, err error) error {
I.Logger.Error("Unknown error in remote", zap.String("dir", dir), zap.String("workspace", workspace), zap.Error(err))
return nil
}

func (I *Zap) PlanDrift(_ context.Context, dir string, workspace string) error {
I.Logger.Info("Plan has drifted", zap.String("dir", dir), zap.String("workspace", workspace))
return nil
Expand Down

0 comments on commit 4d1f96f

Please sign in to comment.