GithubHelp home page GithubHelp logo

expediagroup / flyte-client Goto Github PK

View Code? Open in Web Editor NEW
6.0 10.0 6.0 115 KB

A Go library designed to make the writing of flyte packs simple

License: Apache License 2.0

Go 99.98% Makefile 0.02%
flyte flyte-pack golang go oss-portal-listed

flyte-client's Introduction

flyte-client

Build Status

flyte-client is a Go library designed to make the writing of flyte packs simple. The client handles the registration of a pack with the flyte server, consuming and handling command actions, and gives the ability to send pack events to the flyte server. This allows the pack writer to concentrate solely on the functionality of their pack.

Getting started

Install

go get github.com/ExpediaGroup/flyte-client

Usage

import "github.com/ExpediaGroup/flyte-client"
  • Clone this repo The 'entry point' for the client is the flyte package (specifically flyte/pack.go). To build the client locally therefore run:
go build -v ./flyte

Go Docs

The Go docs can be found at:

# start godoc server
godoc -http=:6060
# navigate to
http://localhost:6060/pkg/github.com/ExpediaGroup/flyte-client

Structs

The structs that a pack developer interacts with can be found in flyte/pack.go. The main ones are as follows:

  • PackDef: defines the pack - it's name, what commands it has and what events it can raise.
  • EventDef: defines an event - it's name and an optional help URL to describe the event in more detail
  • Command: defines a command that can be called on the pack - its name, what event it returns, and an optional help URL to describe the event in more detail. Also includes a CommandHandler which is the function that will be executed when the command is called.
  • Event: this struct is sent from the pack to the flyte server api - it contains the name of the event and it's payload

Events

Packs can send events to the flyte server in 3 ways:

  1. The pack can observe something happening and spontaneously send an event to the flyte server. For example a chat-ops pack, may observe an instant message being sent and raise a "MessageSent" event to the flyte server. It would do this by calling the SendEvent() function on the Pack interface.

  2. A flow on the flyte server creates an action for the pack to execute. The client will poll for this action and invoke the relevant CommandHandler that the pack dev has defined. This handler will return an event that the client will then send to the flyte server. For example the same IM pack as above may have a 'sendMessage' command that would return either a 'MessageSent' or 'MessageSendFailure' event.

  3. The client will produce FATAL events as a result of panic happening while handling a Command. This will be intercepted by the client and it will recover. If they need to, packs can also produce FATAL events themselves as a result of the handling if they detect any errors using NewFatalEvent(payload interface{}). This is preferred over panicking in the handler. E.g.

func handle(message json.RawMessage) Event {
        ...
        // Preferred
        event := NewFatalEvent(Foo{"Error message"})
        // Over
        panic(Foo{"Error message"})
        ...
}

When defining the above pack, you will notice that 'EventDefs' are defined at the pack level (PackDef.EventDefs) and at the command level (PackDef.Commands.EventDef). The 'EventDefs' field on a Command is mandatory, so for the example pack above you would have to specify the eventdefs for both 'MessageSent' and 'MessageSendFailure' on the'sendMessage' Command struct. The 'EventDefs' on the PackDef are optional. Here you would specify any events that the pack observes and sends spontaneously. If the event you want to define is already defined in a command (as with 'MessageSent' above) then you are not required to add it to the separate EventDefs section - however there is no harm in doing so.

Health checks

You can add health checks to your pack in the following way:

    // example (hardcoded) healthchecks
    someCheck := func() (name string, health healthcheck.Health) {
      return "Some Check", healthcheck.Health{Healthy:true, Status: "All good"}
    }
    otherCheck := func() (name string, health healthcheck.Health) {
      return "Other Check", healthcheck.Health{Healthy:true, Status: "Ok"}
    }
      
    // now pass in to 'flyte.NewPack(...)'. when the pack is started this will also start a webserver ready to call your healthchecks.
    // if no healthchecks are passed in the pack health check URL will always return a default healthy response.
    p := flyte.NewPack(packDef, client, someCheck, otherCheck) // healthchecks are optional

Then simply go to the pack health check URL i.e. 'http://localhost:8090' and you will be presented with a json response:

    {
        "Some Check": {
            "healthy": true,
            "status": "All good"
        },
        "Other Check": {
            "healthy": true,
            "status": "Ok"
        }
    }

The following http header response codes will also be returned:

  • 200: All health checks passed.
  • 500: One or more of the health checks failed. JSON results will be returned as normal in the response body.
  • 500: JSON marshalling error.

Errors will also be logged.

A simple health check has been provided for you to check on the status of flyte-api: healthcheck.FlyteApiHealthCheck(c client.Client) and can be used in the following way:

    ...
    
    c := client.NewClient(createURL("http://example.com"), 10 * time.Second)
    
    flyteApiHealthCheck := func() (name string, health healthcheck.Health) {
      return "FlyteApiCheck", healthcheck.FlyteApiHealthCheck(c)
    }
    
    p := flyte.NewPack(packDef, c, flyteApiHealthCheck)
    p.Start()

JWT Authorisation

If your pack needs to send a JSON Web Token along with each http request, please set the JWT string value in the following environment variable:

  • FLYTE_JWT

If not provided no authorisation will occur.

Note: You are strongly advised to only use JWT authorisation over https.

Help URLs

You will notice that a helpURL field is present in 3 locations - PackDef, Command, and EventDef. These correspond to the URLs visible in the json on the flyte server at that level. The help URLs are all optional, though you should generally always have the pack level one. It's up to pack developers to provide which ones they think are the most useful. For a simple pack you'd probably just provide a single pack level help link. For a more complex pack you might want to deep link commands & event definitions to a specific piece of documentation.

The URL should link to a page that describes what a flow writer needs to know i.e. what the pack does, the format of the json in the event payloads and the format of the json for command inputs. It's up to the pack dev where and how they host their help docs - for example it could be a link to a README file or a hosted web page.

Example Pack

The example below shows how to create a simplified "Jira Pack". The pack exposes a "createIssue" command allowing users to create tickets. If this command succeeds it returns an "IssueCreated" event, if it fails it returns a "IssueCreationError" event.

package main

import (
	"encoding/json"
	"log"
	"net/url"
	"github.com/ExpediaGroup/flyte-client/client"
	"github.com/ExpediaGroup/flyte-client/flyte"
	"github.com/ExpediaGroup/flyte-client/healthcheck"
	"time"
)

func main() {

    // First we create EventDefs that describe what events our pack can raise. 
    // An EventDef contains the name of the event (mandatory) and a help URL (optional)
	issueCreatedEventDef := flyte.EventDef{
		Name: "IssueCreated",
	}

	issueCreationErrorEventDef := flyte.EventDef{
		Name: "IssueCreationError",
	}

    // This pack has a single "createIssue" command. To implement a command we must provide a "CommandHandler" function matching the signature below.
    // The client will call this handler every time it receives a "createIssue" action from the flyte api. The handler will take the input JSON from the action
    // and must return a flyte.Event. Handlers are where the functionality of the pack is implemented so will likely form the bulk of most packs. 
	createIssueHandler := func(input json.RawMessage) flyte.Event {
	    
	    // deserialize the raw JSON into our domain struct
		var createIssueInput CreateIssueInput
		json.Unmarshal(input, &createIssueInput)

		// call some ticket creation code...
        // ...
        
        // if it succeeds then return something like the following. The payload will be serialised to JSON and sent to the flyte api server.
		return flyte.Event{
			EventDef: issueCreatedEventDef,
			Payload: IssueCreatedPayload{
				Project:  "FOO",
				IssueId:  "123",
				Location: createURL("http://jira/FOO/123"),
			},
		}
	}

    // Next we create a struct that defines the "createIssue" command. Note the handler above is passed to it.
    // Also note that we specify what events the command can output.
    // The help URL is optional
	createIssueCommand := flyte.Command{
		Name: "createIssue",
		OutputEvents: []flyte.EventDef{
			issueCreatedEventDef,
			issueCreationErrorEventDef,
		},
		HelpURL: createURL("http://jirapack/help#create-issue-command"),
		Handler: createIssueHandler,
	}
	
	// The final struct we must define is the PackDef struct which pulls together the above structs to give the full definition of the pack.
	packDef := flyte.PackDef{
		Name:     "JiraPack",
		Commands: []flyte.Command{createIssueCommand},
		HelpURL:  createURL("http://jirapack/help#create-issue-command"),
	}
	
    // Finally we call NewPack() to create a pack struct. This can then be started by calling Start()
    // Start() will register the pack with the Flyte api server and will invoke the relevant CommandHandler for any actions the api posts.
    p := flyte.NewPack(packDef, client.NewClient(createURL("http://example.com"), 10*time.Second))
	p.Start()
	select{} // p.Start() doesn't block anymore, so we need to block here.
}

type CreateIssueInput struct {
	Project    string `json:"project"`
	TicketText string `json:"ticketText"`
}

type IssueCreatedPayload struct {
	Project  string   `json:"project"`
	IssueId  string   `json:"issueId"`
	Location *url.URL `json:"location"`
}

type IssueCreationErrorPayload struct {
	Project   string `json:"project"`
	ErrorText string `json:"error"`
}

func createURL(u string) *url.URL {
	url, _ := url.Parse(u)
	return url
}

func flyteApiHealthCheck(c client.Client) healthcheck.HealthCheck {
	// a provided healthcheck for you to use and check on the status of the flyte-api
    return func() (name string, health healthcheck.Health) {
      return "FlyteApiCheck", healthcheck.FlyteApiHealthCheck(c)
    }
}

func exampleHealthCheck() healthcheck.HealthCheck {
	// a hardcoded example of a healthcheck
	return func() (name string, health healthcheck.Health) {
      return "Some Check", healthcheck.Health{Healthy:true, Status: "All good"}
    }
}

Running Tests

To run just the unit tests use:

go test ./...

flyte-client's People

Contributors

dvdthms avatar fooksca avatar isaacasensio avatar jollinshead avatar nishaarao avatar pamelin avatar ukjasonwright avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

flyte-client's Issues

flyte-client does not wait for command to be processed

flyte-client keeps taking actions and calls commands without waiting for response. This can be a problem if there are new actions: pack starts, it picks all of them, moving them into pending state, but pack can fail after first one, client still feeding it with more. Pack fails, but all the actions are already in pending state.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.