Skip to content

Commit

Permalink
feat: Http Client for accessing OpenSauced API client (#23)
Browse files Browse the repository at this point in the history
- Integrates using the opensauced api client for doing "bake" operations
- Integrates using the opensauced http client for doing "repo-query" operations

Signed-off-by: John McBride <[email protected]>
  • Loading branch information
jpmcb authored Aug 16, 2023
1 parent 34728fb commit ec2b357
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 33 deletions.
5 changes: 3 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

all: lint test build

fmt:
go fmt ./...

lint:
docker run \
-t \
Expand All @@ -22,5 +25,3 @@ install: build

run:
go run main.go


28 changes: 17 additions & 11 deletions cmd/bake/bake.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"net/http"
"os"

"github.com/open-sauced/pizza-cli/pkg/api"
"github.com/spf13/cobra"

"gopkg.in/yaml.v3"
Expand All @@ -19,8 +20,8 @@ import (
// Options are the options for the pizza bake command including user
// defined configurations
type Options struct {
// Endpoint is the service endpoint to reach out to
Endpoint string
// The API Client for the calls to bake git repos
APIClient *api.Client

// URLs are the git repo URLs that will be sourced via 'pizza bake'
URLs []string
Expand Down Expand Up @@ -52,14 +53,20 @@ func NewBakeCommand() *cobra.Command {
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
endpoint, _ := cmd.Flags().GetString("endpoint")
useBeta, _ := cmd.Flags().GetBool("beta")

if useBeta {
fmt.Printf("Using beta API endpoint - %s\n", api.BetaAPIEndpoint)
endpoint = api.BetaAPIEndpoint
}
opts.APIClient = api.NewClient(endpoint)

opts.URLs = append(opts.URLs, args...)
return run(opts)
},
}

// TODO - this will need to be a live service URL by default.
// For now, localhost is fine.
cmd.Flags().StringVarP(&opts.Endpoint, "endpoint", "e", "http://localhost:8080", "The endpoint to send requests to")
cmd.Flags().BoolVarP(&opts.Wait, "wait", "w", false, "Wait for bake processing to finish")
cmd.Flags().StringVarP(&opts.FilePath, "file", "f", "", "The yaml file containing a series of repos to batch to /bake")

Expand All @@ -68,7 +75,7 @@ func NewBakeCommand() *cobra.Command {

type bakePostRequest struct {
URL string `json:"url"`
Wait bool `json:"wait,omitempty"`
Wait bool `json:"wait"`
}

type repos struct {
Expand Down Expand Up @@ -110,7 +117,7 @@ func run(opts *Options) error {
Wait: opts.Wait,
}

err := bakeRepo(bodyPostReq, opts.Endpoint)
err := bakeRepo(bodyPostReq, opts.APIClient)
if err != nil {
fmt.Printf("Error: failed fetch of %s repository (%s)\n", url, err.Error())
}
Expand All @@ -119,15 +126,14 @@ func run(opts *Options) error {
return nil
}

func bakeRepo(bodyPostReq bakePostRequest, endpoint string) error {
func bakeRepo(bodyPostReq bakePostRequest, apiClient *api.Client) error {
bodyPostJSON, err := json.Marshal(bodyPostReq)
if err != nil {
return err
}

requestBody := bytes.NewBuffer(bodyPostJSON)
resp, err := http.Post(fmt.Sprintf("%s/bake", endpoint), "application/json", requestBody)

responseBody := bytes.NewBuffer(bodyPostJSON)
resp, err := apiClient.HTTPClient.Post(fmt.Sprintf("%s/bake", apiClient.Endpoint), "application/json", responseBody)
if err != nil {
return err
}
Expand Down
4 changes: 3 additions & 1 deletion cmd/bake/bake_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"net/http"
"net/http/httptest"
"testing"

"github.com/open-sauced/pizza-cli/pkg/api"
)

func TestSendsPost(t *testing.T) {
Expand Down Expand Up @@ -42,7 +44,7 @@ func TestSendsPost(t *testing.T) {
}))
defer testServer.Close()

tt.opts.Endpoint = testServer.URL
tt.opts.APIClient = api.NewClient(testServer.URL)

err := run(tt.opts)
if err != nil {
Expand Down
69 changes: 50 additions & 19 deletions cmd/repo-query/repo-query.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@ import (
"os/signal"
"strings"

"github.com/open-sauced/pizza-cli/pkg/api"
"github.com/spf13/cobra"
)

const repoQueryURL string = "https://opensauced.tools"

type Options struct {
APIClient *api.Client

// URL is the git repo URL that will be indexed
URL string

Expand Down Expand Up @@ -46,6 +49,22 @@ func NewRepoQueryCommand() *cobra.Command {
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
endpoint, _ := cmd.Flags().GetString("endpoint")
useBeta, _ := cmd.Flags().GetBool("beta")

if useBeta {
fmt.Printf("Warning!! Using beta API endpoint not supported for repo-query - using: %s\n", endpoint)
}

// The repo-query is currently not deployed behind "api.opensauced.pizza"
// So, if the user has not changed the desired "endpoint", use the default
// tools URL to send SSE to the repo-query engine
if endpoint == api.APIEndpoint {
endpoint = repoQueryURL
}

opts.APIClient = api.NewClient(endpoint)

opts.URL = args[0]
return run(opts)
},
Expand All @@ -56,7 +75,17 @@ func NewRepoQueryCommand() *cobra.Command {
return cmd
}

func getOwnerAndRepo(url string) (owner, repo string, err error) {
type repoQueryAgent struct {
client *api.Client
}

func newRepoQueryAgent(apiClient *api.Client) *repoQueryAgent {
return &repoQueryAgent{
client: apiClient,
}
}

func (rq *repoQueryAgent) getOwnerAndRepo(url string) (owner, repo string, err error) {
if !strings.HasPrefix(url, "https://github.com/") {
return "", "", fmt.Errorf("invalid URL: %s", url)
}
Expand All @@ -79,14 +108,16 @@ func getOwnerAndRepo(url string) (owner, repo string, err error) {
}

func run(opts *Options) error {
agent := newRepoQueryAgent(opts.APIClient)

// get repo name and owner name from URL
owner, repo, err := getOwnerAndRepo(opts.URL)
owner, repo, err := agent.getOwnerAndRepo(opts.URL)
if err != nil {
return err
}

fmt.Printf("Checking if %s/%s is indexed by us...⏳\n", owner, repo)
resp, err := http.Get(fmt.Sprintf("%s/collection?owner=%s&name=%s&branch=%s", repoQueryURL, owner, repo, opts.branch))
resp, err := agent.client.HTTPClient.Get(fmt.Sprintf("%s/collection?owner=%s&name=%s&branch=%s", agent.client.Endpoint, owner, repo, opts.branch))
if err != nil {
return err
}
Expand All @@ -96,20 +127,20 @@ func run(opts *Options) error {
// repo is not indexed
fmt.Println("Repo not found❗")
fmt.Println("Indexing repo...⏳")
err := indexRepo(owner, repo, opts.branch)
err := agent.indexRepo(owner, repo, opts.branch)
if err != nil {
return err
}

err = startQnALoop(owner, repo, opts.branch)
err = agent.startQnALoop(owner, repo, opts.branch)
if err != nil {
return err
}
case http.StatusOK:
// repo is indexed
fmt.Println("Repo found ✅")

err = startQnALoop(owner, repo, opts.branch)
err = agent.startQnALoop(owner, repo, opts.branch)
if err != nil {
return err
}
Expand All @@ -126,7 +157,7 @@ func run(opts *Options) error {
return nil
}

func startQnALoop(owner string, repo string, branch string) error {
func (rq *repoQueryAgent) startQnALoop(owner string, repo string, branch string) error {
for {
// if ctrl+c is pressed, exit
c := make(chan os.Signal, 1)
Expand All @@ -148,7 +179,7 @@ func startQnALoop(owner string, repo string, branch string) error {
fmt.Println("🍕Exiting...")
os.Exit(0)
}
err := askQuestion(input, owner, repo, branch)
err := rq.askQuestion(input, owner, repo, branch)
if err != nil {
return err
}
Expand Down Expand Up @@ -176,7 +207,7 @@ const (
ChatChunk
)

func indexRepo(owner string, repo string, branch string) error {
func (rq *repoQueryAgent) indexRepo(owner string, repo string, branch string) error {
indexPostReq := &indexPostRequest{
Owner: owner,
Name: repo,
Expand All @@ -188,7 +219,7 @@ func indexRepo(owner string, repo string, branch string) error {
return err
}

resp, err := http.Post(fmt.Sprintf("%s/embed", repoQueryURL), "application/json", bytes.NewBuffer(indexPostJSON))
resp, err := rq.client.HTTPClient.Post(fmt.Sprintf("%s/embed", rq.client.Endpoint), "application/json", bytes.NewBuffer(indexPostJSON))
if err != nil {
return err
}
Expand All @@ -204,15 +235,15 @@ func indexRepo(owner string, repo string, branch string) error {
}

reader := bufio.NewReader(resp.Body)
err = listenForSSEs(reader, IndexChunk)
err = rq.listenForSSEs(reader, IndexChunk)
if err != nil {
return err
}

return nil
}

func askQuestion(question string, owner string, repo string, branch string) error {
func (rq *repoQueryAgent) askQuestion(question string, owner string, repo string, branch string) error {
queryPostReq := &queryPostRequest{
Query: question,
Repository: struct {
Expand All @@ -231,7 +262,7 @@ func askQuestion(question string, owner string, repo string, branch string) erro
return err
}

resp, err := http.Post(fmt.Sprintf("%s/query", repoQueryURL), "application/json", bytes.NewBuffer(queryPostJSON))
resp, err := rq.client.HTTPClient.Post(fmt.Sprintf("%s/query", rq.client.Endpoint), "application/json", bytes.NewBuffer(queryPostJSON))
if err != nil {
return err
}
Expand All @@ -249,15 +280,15 @@ func askQuestion(question string, owner string, repo string, branch string) erro

// listen for SSEs and send data,event pairs to processChatChunk
reader := bufio.NewReader(resp.Body)
err = listenForSSEs(reader, ChatChunk)
err = rq.listenForSSEs(reader, ChatChunk)
if err != nil {
return err
}

return nil
}

func listenForSSEs(reader *bufio.Reader, chunkType int) error {
func (rq *repoQueryAgent) listenForSSEs(reader *bufio.Reader, chunkType int) error {
// listen for SSEs and send data, event pairs to processChunk
// we send 2 lines at a time to processChunk so it can process the event and data together.
// the server sends empty events sometimes, so we ignore those.
Expand Down Expand Up @@ -295,9 +326,9 @@ func listenForSSEs(reader *bufio.Reader, chunkType int) error {

switch chunkType {
case IndexChunk:
err = processIndexChunk(chunk)
err = rq.processIndexChunk(chunk)
case ChatChunk:
err = processChatChunk(chunk)
err = rq.processChatChunk(chunk)
default:
break
}
Expand All @@ -309,7 +340,7 @@ func listenForSSEs(reader *bufio.Reader, chunkType int) error {
}
}

func processIndexChunk(chunk string) error {
func (rq *repoQueryAgent) processIndexChunk(chunk string) error {
// we only care about the first line of the chunk, which is the event, when indexing.
// the data is irrelevant for now, but we still got it so we can process it later if we need to.
// Also, for grouping the events and data together.
Expand Down Expand Up @@ -337,7 +368,7 @@ func processIndexChunk(chunk string) error {
return nil
}

func processChatChunk(chunk string) error {
func (rq *repoQueryAgent) processChatChunk(chunk string) error {
// The event is the first line of the chunk, and the data is the second line.
chunkLines := strings.Split(chunk, "\n")
eventLine := chunkLines[0]
Expand Down
6 changes: 6 additions & 0 deletions cmd/root/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@
package root

import (
"fmt"

"github.com/spf13/cobra"

"github.com/open-sauced/pizza-cli/cmd/auth"
"github.com/open-sauced/pizza-cli/cmd/bake"
repoquery "github.com/open-sauced/pizza-cli/cmd/repo-query"
"github.com/open-sauced/pizza-cli/pkg/api"
)

// NewRootCommand bootstraps a new root cobra command for the pizza CLI
Expand All @@ -18,6 +21,9 @@ func NewRootCommand() (*cobra.Command, error) {
RunE: run,
}

cmd.PersistentFlags().StringP("endpoint", "e", api.APIEndpoint, "The API endpoint to send requests to")
cmd.PersistentFlags().Bool("beta", false, fmt.Sprintf("Shorthand for using the beta OpenSauced API endpoint (\"%s\"). Superceds the '--endpoint' flag", api.BetaAPIEndpoint))

cmd.AddCommand(bake.NewBakeCommand())
cmd.AddCommand(repoquery.NewRepoQueryCommand())
cmd.AddCommand(auth.NewLoginCommand())
Expand Down
20 changes: 20 additions & 0 deletions pkg/api/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package api

import "net/http"

type Client struct {
// The configured http client for making API requests
HTTPClient *http.Client

// The API endpoint to use when making requests
// Example: https://api.opensauced.pizza or https://beta.api.opensauced.pizza
Endpoint string
}

// NewClient creates a new OpenSauced API client for making http requests
func NewClient(endpoint string) *Client {
return &Client{
HTTPClient: &http.Client{},
Endpoint: endpoint,
}
}
6 changes: 6 additions & 0 deletions pkg/api/constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package api

const (
APIEndpoint = "https://api.opensauced.pizza/v1"
BetaAPIEndpoint = "https://beta.api.opensauced.pizza/v1"
)

0 comments on commit ec2b357

Please sign in to comment.