-
Notifications
You must be signed in to change notification settings - Fork 301
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
223 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
// jobapi provides an API with which to interact with and mutate the currently executing job | ||
// Pronunciation: /ˈdʒɑbe̞pi/ /joh-bah-pee/ | ||
package jobapi |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package jobapi | ||
|
||
import ( | ||
"encoding/json" | ||
"net/http" | ||
"strings" | ||
) | ||
|
||
func (s *Server) authMdlw(next http.Handler) http.Handler { | ||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
defer next.ServeHTTP(w, r) | ||
|
||
enc := json.NewEncoder(w) | ||
|
||
auth := r.Header.Get("Authorization") | ||
if auth == "" { | ||
w.WriteHeader(http.StatusUnauthorized) | ||
enc.Encode(ErrorResponse{Error: "authorization header is required"}) | ||
return | ||
} | ||
|
||
authType, token, found := strings.Cut(auth, " ") | ||
if !found { | ||
w.WriteHeader(http.StatusUnauthorized) | ||
enc.Encode(ErrorResponse{Error: "invalid authorization header: must be in the form `Bearer <token>`"}) | ||
return | ||
} | ||
|
||
if authType != "Bearer" { | ||
w.WriteHeader(http.StatusUnauthorized) | ||
enc.Encode(ErrorResponse{Error: "invalid authorization header: type must be Bearer"}) | ||
return | ||
} | ||
|
||
if token != s.token { | ||
w.WriteHeader(http.StatusUnauthorized) | ||
enc.Encode(ErrorResponse{Error: "invalid authorization token"}) | ||
return | ||
} | ||
}) | ||
} | ||
|
||
func (s *Server) headersMdlw(next http.Handler) http.Handler { | ||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
defer next.ServeHTTP(w, r) | ||
|
||
w.Header().Set("Content-Type", "application/json") | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package jobapi | ||
|
||
type ErrorResponse struct { | ||
Error string `json:"error"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package jobapi | ||
|
||
import ( | ||
"net/http" | ||
|
||
"github.com/go-chi/chi/v5" | ||
"github.com/go-chi/chi/v5/middleware" | ||
) | ||
|
||
func (s *Server) router() chi.Router { | ||
r := chi.NewRouter() | ||
r.Use( | ||
middleware.Recoverer, | ||
s.authMdlw, | ||
s.headersMdlw, | ||
) | ||
|
||
r.Route("/api/current-job/v1", func(r chi.Router) { | ||
r.Get("/env", s.getEnv()) | ||
r.Patch("/env", s.patchEnv()) | ||
r.Delete("/env", s.deleteEnv()) | ||
}) | ||
|
||
return r | ||
} | ||
|
||
func (s *Server) getEnv() http.HandlerFunc { | ||
return func(w http.ResponseWriter, r *http.Request) { | ||
// STUB | ||
w.WriteHeader(http.StatusOK) | ||
} | ||
} | ||
|
||
func (s *Server) patchEnv() http.HandlerFunc { | ||
return func(w http.ResponseWriter, r *http.Request) { | ||
// STUB | ||
w.WriteHeader(http.StatusOK) | ||
} | ||
} | ||
|
||
func (s *Server) deleteEnv() http.HandlerFunc { | ||
return func(w http.ResponseWriter, r *http.Request) { | ||
// STUB | ||
w.WriteHeader(http.StatusOK) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
package jobapi | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"net" | ||
"net/http" | ||
"os" | ||
"time" | ||
) | ||
|
||
type Server struct { | ||
SocketPath string | ||
token string | ||
httpSvr *http.Server | ||
} | ||
|
||
func NewServer(socketPath string) (server *Server, token string, err error) { | ||
exists, err := socketExists(socketPath) | ||
if err != nil { | ||
return nil, "", err | ||
} | ||
|
||
if exists { | ||
err = os.RemoveAll(socketPath) | ||
if err != nil { | ||
return nil, "", fmt.Errorf("removing existing socket: %w", err) | ||
} | ||
} | ||
|
||
token, err = generateToken(32) | ||
if err != nil { | ||
return nil, "", fmt.Errorf("generating token: %w", err) | ||
} | ||
|
||
return &Server{ | ||
SocketPath: socketPath, | ||
token: token, | ||
}, token, nil | ||
} | ||
|
||
func (s *Server) Start() error { | ||
r := s.router() | ||
l, err := net.Listen("unix", s.SocketPath) | ||
if err != nil { | ||
return fmt.Errorf("listening on socket: %w", err) | ||
} | ||
|
||
s.httpSvr = &http.Server{Handler: r} | ||
go func() { | ||
_ = s.httpSvr.Serve(l) | ||
}() | ||
|
||
return nil | ||
} | ||
|
||
func (s *Server) Stop() error { | ||
serverCtx, serverStopCtx := context.WithCancel(context.Background()) | ||
defer serverStopCtx() | ||
|
||
// REVIEW: Should we capture signals here? | ||
|
||
// Shutdown signal with grace period of 30 seconds | ||
shutdownCtx, _ := context.WithTimeout(serverCtx, 10*time.Second) | ||
|
||
go func() { | ||
<-shutdownCtx.Done() | ||
if shutdownCtx.Err() == context.DeadlineExceeded { | ||
// REVIEW: What should we do in this situation? Force a return? Log something? | ||
} | ||
}() | ||
|
||
// Trigger graceful shutdown | ||
err := s.httpSvr.Shutdown(shutdownCtx) | ||
if err != nil { | ||
return fmt.Errorf("shutting down Job API server: %w", err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func socketExists(path string) (bool, error) { | ||
_, err := os.Stat(path) | ||
if err == nil { | ||
return true, nil | ||
} | ||
|
||
if errors.Is(err, os.ErrNotExist) { | ||
return false, nil | ||
} | ||
|
||
return false, fmt.Errorf("checking if socket exists: %w", err) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package jobapi | ||
|
||
import ( | ||
"crypto/rand" | ||
"fmt" | ||
"math/big" | ||
) | ||
|
||
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" | ||
|
||
func generateToken(length int) (string, error) { | ||
lettersLen := len(letters) | ||
bigLettersLen := big.NewInt(int64(lettersLen)) | ||
b := make([]byte, length) | ||
for i := range b { | ||
bigRand, err := rand.Int(rand.Reader, bigLettersLen) | ||
if err != nil { | ||
return "", fmt.Errorf("generating crypto-random big.Int: %w", err) | ||
} | ||
b[i] = letters[bigRand.Int64()%int64(lettersLen)] // mod letterslen just in case rand.Int returns a number larger than lettersLen, don't want to have a panic | ||
} | ||
return string(b), nil | ||
} |