diff --git a/Makefile b/Makefile index 45e663d0..7497693b 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ ################################################################################ GIT_VERSION = $(shell git describe --always --abbrev=7 --dirty) -TARGETS ?= darwin +TARGETS ?= darwin windows linux ARCH ?= amd64 CGO ?= 0 @@ -13,11 +13,11 @@ else CLI_VERSION := edge endif -################################################################################ -# Go build details # -################################################################################ - -BASE_PACKAGE_NAME := github.com/actionscore/cli +ifdef API_VERSION + RUNTIME_API_VERSION = $(API_VERSION) +else + RUNTIME_API_VERSION = 1.0 +endif ################################################################################ # Dependencies # @@ -41,7 +41,7 @@ deps: dep build: for t in $(TARGETS); do \ CGO_ENABLED=$(CGO) GOOS=$$t GOARCH=$(ARCH) go build \ - -ldflags "-X $(BASE_PACKAGE_NAME)/pkg/version.version=$(CLI_VERSION)" \ + -ldflags "-X main.version=$(CLI_VERSION) -X main.apiVersion=$(RUNTIME_API_VERSION)" \ -o dist/"$$t"_$(ARCH)/actions; \ done; diff --git a/README.md b/README.md index c5d83b96..4c23d4f7 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Build Status](https://dev.azure.com/azure-octo/Actions/_apis/build/status/builds/cli%20build?branchName=master)](https://dev.azure.com/azure-octo/Actions/_build/latest?definitionId=6&branchName=master) -The Actions CLI allows you to setup Actions on your local dev machine or on a Kubernetes cluster, provides debugging suppors, launches and manages Actions instances. +The Actions CLI allows you to setup Actions on your local dev machine or on a Kubernetes cluster, provides debugging support, launches and manages Actions instances. ## Setup @@ -12,6 +12,8 @@ The Actions CLI allows you to setup Actions on your local dev machine or on a Ku ### Usage +#### Install Actions + To setup Actions on your local machine: __*Note: For Windows users, run the cmd terminal in administrator mode*__ @@ -29,3 +31,80 @@ $ actions init --kubernetes ⌛ Making the jump to hyperspace... ✅ Success! Get ready to rumble ``` + +#### Launch Actions and your app + +The Actions CLI lets you debug easily by launching both Actions and your app. +Logs from both the Actions Runtime and your app will be displayed in real time! + +Example of launching Actions with a node app: + +``` +$ actions run --app-id nodeapp node app.js +``` + +Example of launching Actions with a node app listening on port 3000: + +``` +$ actions run --app-id nodeapp --app-port 3000 node app.js +``` + +Example of launching Actions on port 6000: + +``` +$ actions run --app-id nodeapp --app-port 3000 --port 6000 node app.js +``` + +#### Publish/Subscribe + +To use pub-sub with your app, make sure that your app has a ```POST``` HTTP endpoint with some name, say ```myevent```. +This sample assumes your app is listening on port 3000. + +Launch Actions and your app: + +``` +$ actions run --app-id nodeapp --app-port 3000 node app.js +``` + +Publish a message: + +``` +$ actions publish --app-id nodeapp --topic myevent +``` + +Publish a message with a payload: + +``` +$ actions publish --app-id nodeapp --topic myevent --payload '{ "name": "yoda" }' +``` + +#### Invoking + +To test your endpoints with Actions, simply expose any ```POST``` HTTP endpoint. +For this sample, we'll assume a node app listening on port 300 with a ```/mymethod``` endpoint. + +Launch Actions and your app: + +``` +$ actions run --app-id nodeapp --app-port 3000 node app.js +``` + +Invoke your app: + +``` +$ actions send --app-id nodeapp --method mymethod +``` + +#### List + +To list all Actions instances running on your machine: + +``` +$ actions list +``` + +To list all Actions instances running in a Kubernetes cluster: + +``` +$ actions list --kubernetes +``` diff --git a/cmd/actions.go b/cmd/actions.go index 6845d61f..f008ddc0 100644 --- a/cmd/actions.go +++ b/cmd/actions.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + "github.com/actionscore/cli/pkg/api" "github.com/spf13/cobra" ) @@ -20,8 +21,9 @@ A serverless runtime for hyperscale, distributed systems`, } // Execute adds all child commands to the root command -func Execute(version string) { +func Execute(version, apiVersion string) { RootCmd.Version = version + api.RuntimeAPIVersion = apiVersion if err := RootCmd.Execute(); err != nil { fmt.Println(err) diff --git a/cmd/run.go b/cmd/run.go index f8f3dc73..4143f0d3 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -33,7 +33,7 @@ var RunCmd = &cobra.Command{ return } - actionsRunId := uuid.String() + actionsRunID := uuid.String() if kubernetesMode { output, err := kubernetes.Run(&kubernetes.RunConfig{ @@ -148,7 +148,7 @@ var RunCmd = &cobra.Command{ <-appRunning rundata.AppendRunData(&rundata.RunData{ - ActionsRunId: actionsRunId, + ActionsRunId: actionsRunID, AppId: output.AppID, ActionsPort: output.ActionsPort, AppPort: appPort, @@ -161,7 +161,7 @@ var RunCmd = &cobra.Command{ <-sigCh print.InfoStatusEvent(os.Stdout, "\nterminated signal recieved: shutting down") - rundata.ClearRunData(actionsRunId) + rundata.ClearRunData(actionsRunID) err = output.ActionsCMD.Process.Kill() if err != nil { diff --git a/main.go b/main.go index efffb04c..5b2aafc5 100644 --- a/main.go +++ b/main.go @@ -1,12 +1,15 @@ package main -import "github.com/actionscore/cli/cmd" +import ( + "github.com/actionscore/cli/cmd" +) -// Value for version is injected by the build +// Values for version and apiVersion are injected by the build var ( - version = "edge" + version = "" + apiVersion = "1.0" ) func main() { - cmd.Execute(version) + cmd.Execute(version, apiVersion) } diff --git a/pkg/api/api.go b/pkg/api/api.go new file mode 100644 index 00000000..191175f8 --- /dev/null +++ b/pkg/api/api.go @@ -0,0 +1,6 @@ +package api + +// RuntimeAPIVersion represents the version for the Actions runtime API +var ( + RuntimeAPIVersion = "1.0" +) diff --git a/pkg/kubernetes/list.go b/pkg/kubernetes/list.go index e68e1acb..b312625e 100644 --- a/pkg/kubernetes/list.go +++ b/pkg/kubernetes/list.go @@ -27,13 +27,13 @@ func List() ([]ListOutput, error) { l := []ListOutput{} for _, p := range podList.Items { for _, c := range p.Spec.Containers { - if c.Name == "action" { + if c.Name == "actionsrt" { lo := ListOutput{} for i, a := range c.Args { if a == "--app-port" { port := c.Args[i+1] lo.AppPort = port - } else if a == "--action-id" { + } else if a == "--actions-id" { id := c.Args[i+1] lo.AppID = id } diff --git a/pkg/publish/publish.go b/pkg/publish/publish.go index 7f084c7c..e9f6fd7a 100644 --- a/pkg/publish/publish.go +++ b/pkg/publish/publish.go @@ -2,21 +2,14 @@ package publish import ( "bytes" - "encoding/json" "errors" "fmt" "net/http" - "time" + "github.com/actionscore/cli/pkg/api" "github.com/actionscore/cli/pkg/standalone" ) -type messageEnvelope struct { - Topic string `json:"topic,omitempty"` - Data interface{} `json:"data,omitempty"` - CreatedAt string `json:"createdAt"` -} - func PublishTopic(appID, topic, payload string) error { if topic == "" { return errors.New("topic is missing") @@ -31,26 +24,14 @@ func PublishTopic(appID, topic, payload string) error { for _, lo := range l { if lo.AppID == appID { - m := messageEnvelope{ - CreatedAt: time.Now().Format(time.RFC3339), - } + b := []byte{} if payload != "" { - var data interface{} - err := json.Unmarshal([]byte(payload), &data) - if err != nil { - return err - } - - m.Data = data + b = []byte(payload) } - b, err := json.Marshal(&m) - if err != nil { - return err - } - - _, err = http.Post(fmt.Sprintf("http://localhost:%s/invoke/%s", fmt.Sprintf("%v", lo.ActionsPort), topic), "application/json", bytes.NewBuffer(b)) + url := fmt.Sprintf("http://localhost:%s/v%s/publish/%s", fmt.Sprintf("%v", lo.ActionsPort), api.RuntimeAPIVersion, topic) + _, err = http.Post(url, "application/json", bytes.NewBuffer(b)) if err != nil { return err } diff --git a/pkg/send/send.go b/pkg/send/send.go index 73935a19..28afed7c 100644 --- a/pkg/send/send.go +++ b/pkg/send/send.go @@ -6,6 +6,8 @@ import ( "io/ioutil" "net/http" + "github.com/actionscore/cli/pkg/api" + "github.com/actionscore/cli/pkg/standalone" ) @@ -17,7 +19,7 @@ func InvokeApp(appID, method, payload string) (string, error) { for _, lo := range list { if lo.AppID == appID { - r, err := http.Post(fmt.Sprintf("http://localhost:%s/invoke/%s", fmt.Sprintf("%v", lo.ActionsPort), method), "application/json", bytes.NewBuffer([]byte(payload))) + r, err := http.Post(fmt.Sprintf("http://localhost:%s/v%s/invoke/%s", fmt.Sprintf("%v", lo.ActionsPort), api.RuntimeAPIVersion, method), "application/json", bytes.NewBuffer([]byte(payload))) if err != nil { return "", err } @@ -35,5 +37,5 @@ func InvokeApp(appID, method, payload string) (string, error) { } } - return "", fmt.Errorf("App id %s not found", appID) + return "", fmt.Errorf("App ID %s not found", appID) } diff --git a/pkg/standalone/run.go b/pkg/standalone/run.go index 567d38e5..79f2302d 100644 --- a/pkg/standalone/run.go +++ b/pkg/standalone/run.go @@ -30,18 +30,15 @@ type RunOutput struct { AppCMD *exec.Cmd } -type eventSource struct { +type component struct { APIVersion string `json:"apiVersion"` Kind string `json:"kind"` Metadata struct { Name string `json:"name"` } `json:"metadata"` Spec struct { - Type string `json:"type"` - ConnectionInfo struct { - RedisHost string `json:"redisHost"` - RedisPassword string `json:"redisPassword"` - } `json:"connectionInfo"` + Type string `json:"type"` + ConnectionInfo map[string]string `json:"connectionInfo"` } `json:"spec"` } @@ -55,22 +52,22 @@ func getActionsCommand(appID string, actionsPort int, appPort int) (*exec.Cmd, i actionsPort = port } - actionsCMD := "action" + actionsCMD := "actionsrt" if runtime.GOOS == "windows" { - actionsCMD = actionsCMD + ".exe" + actionsCMD = fmt.Sprintf("%s.exe", actionsCMD) } - args := []string{"--action-id", appID, "--action-http-port", fmt.Sprintf("%v", actionsPort)} + args := []string{"--actions-id", appID, "--actions-http-port", fmt.Sprintf("%v", actionsPort)} if appPort > -1 { args = append(args, "--app-port") args = append(args, fmt.Sprintf("%v", appPort)) } - args = append(args, "--assigner-address") + args = append(args, "--placement-address") if runtime.GOOS == "windows" { args = append(args, "localhost:6050") - args = append(args, "--action-grpc-port", "6051") + args = append(args, "--actions-grpc-port", "6051") } else { args = append(args, "localhost:50005") } @@ -87,30 +84,61 @@ func getAppCommand(actionsPort int, command string, args []string) (*exec.Cmd, e return cmd, nil } -func createStateEventSource() error { +func createRedisStateStore() error { wd, err := os.Getwd() if err != nil { return err } - es := eventSource{ + redisStore := component{ APIVersion: "actions.io/v1alpha1", - Kind: "EventSource", + Kind: "Component", } - es.Metadata.Name = "statestore" - es.Spec.Type = "actions.state.redis" - es.Spec.ConnectionInfo.RedisHost = "localhost:6379" - es.Spec.ConnectionInfo.RedisPassword = "" + redisStore.Metadata.Name = "statestore" + redisStore.Spec.Type = "state.redis" + redisStore.Spec.ConnectionInfo = map[string]string{} + redisStore.Spec.ConnectionInfo["redisHost"] = "localhost:6379" + redisStore.Spec.ConnectionInfo["redisPassword"] = "" - b, err := yaml.Marshal(&es) + b, err := yaml.Marshal(&redisStore) if err != nil { return err } - os.Mkdir(path.Join(wd, "eventsources"), 0777) + os.Mkdir(path.Join(wd, "components"), 0777) + err = ioutil.WriteFile(path.Join(path.Join(wd, "components"), "redis.yaml"), b, 0644) + if err != nil { + return err + } - err = ioutil.WriteFile(path.Join(path.Join(wd, "eventsources"), "redis.yaml"), b, 0644) + return nil +} + +func createRedisPubSub() error { + wd, err := os.Getwd() + if err != nil { + return err + } + + redisMessageBus := component{ + APIVersion: "actions.io/v1alpha1", + Kind: "Component", + } + + redisMessageBus.Metadata.Name = "messagebus" + redisMessageBus.Spec.Type = "pubsub.redis" + redisMessageBus.Spec.ConnectionInfo = map[string]string{} + redisMessageBus.Spec.ConnectionInfo["redisHost"] = "localhost:6379" + redisMessageBus.Spec.ConnectionInfo["password"] = "" + + b, err := yaml.Marshal(&redisMessageBus) + if err != nil { + return err + } + + os.Mkdir(path.Join(wd, "components"), 0777) + err = ioutil.WriteFile(path.Join(path.Join(wd, "components"), "redis_messagebus.yaml"), b, 0644) if err != nil { return err } @@ -124,7 +152,12 @@ func Run(config *RunConfig) (*RunOutput, error) { appID = strings.Replace(sillyname.GenerateStupidName(), " ", "-", -1) } - err := createStateEventSource() + err := createRedisStateStore() + if err != nil { + return nil, err + } + + err = createRedisPubSub() if err != nil { return nil, err } diff --git a/pkg/standalone/standalone.go b/pkg/standalone/standalone.go index 68f4226f..b744d82d 100644 --- a/pkg/standalone/standalone.go +++ b/pkg/standalone/standalone.go @@ -17,9 +17,7 @@ import ( ) const baseDownloadURL = "https://actionsreleases.blob.core.windows.net/bin" - -// this should be configurable by versioning -const actionsImageURL = "yaron2/actionsedge:v2" +const actionsImageURL = "actionscore.azurecr.io/actions:latest" func Init() error { dir, err := getActionsDir() @@ -94,7 +92,7 @@ func installAssignerBinary(wg *sync.WaitGroup, errorChan chan<- error, dir strin osPort = 6050 } - err := runCmd("docker", "run", "--restart", "always", "-d", "-p", fmt.Sprintf("%v:50005", osPort), "--entrypoint", "./assigner", actionsImageURL) + err := runCmd("docker", "run", "--restart", "always", "-d", "-p", fmt.Sprintf("%v:50005", osPort), "--entrypoint", "./placement", actionsImageURL) if err != nil { errorChan <- err return @@ -105,7 +103,7 @@ func installAssignerBinary(wg *sync.WaitGroup, errorChan chan<- error, dir strin func installActionsBinary(wg *sync.WaitGroup, errorChan chan<- error, dir string) { defer wg.Done() - actionsURL := fmt.Sprintf("%s/action_%s_%s.zip", baseDownloadURL, runtime.GOOS, runtime.GOARCH) + actionsURL := fmt.Sprintf("%s/actionsrt_%s_%s.zip", baseDownloadURL, runtime.GOOS, runtime.GOARCH) filepath, err := downloadFile(dir, actionsURL) if err != nil { errorChan <- fmt.Errorf("Error downloading actions binary: %s", err) @@ -200,10 +198,12 @@ func moveFileToPath(filepath string) (string, error) { if runtime.GOOS == "windows" { p := os.Getenv("PATH") if !strings.Contains(strings.ToLower(string(p)), strings.ToLower("c:\\actions")) { - runCmd("SETX", "PATH", p+";c:\\actions") + err := runCmd("SETX", "PATH", p+";c:\\actions") + if err != nil { + return "", err + } } - runCmd("rename", "c:\\actions\\action", "c:\\actions\\action.exe") - return "c:\\actions\\action.exe", nil + return "c:\\actions\\actionsrt.exe", nil } destFilePath = path.Join("/usr/local/bin", fileName)