app-manager: add Actions
diff --git a/appmanager/actions.go b/appmanager/actions.go
new file mode 100644
index 0000000..88d2551
--- /dev/null
+++ b/appmanager/actions.go
@@ -0,0 +1,34 @@
+package appmanager
+
+import (
+ "io/ioutil"
+ "os"
+
+ "gopkg.in/yaml.v2"
+)
+
+type Action struct {
+ Name string `yaml:"name"`
+ Template string `yaml:"template"`
+}
+
+type Actions struct {
+ Actions []Action `yaml:"actions"`
+}
+
+func FromYaml(str string, out interface{}) error {
+ return yaml.Unmarshal([]byte(str), out)
+}
+
+func FromYamlFile(actionsFile string, out interface{}) error {
+ f, err := os.Open(actionsFile)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+ b, err := ioutil.ReadAll(f)
+ if err != nil {
+ return err
+ }
+ return FromYaml(string(b), out)
+}
diff --git a/appmanager/cmd/main.go b/appmanager/cmd/main.go
index 9d55071..9bf0b4e 100644
--- a/appmanager/cmd/main.go
+++ b/appmanager/cmd/main.go
@@ -7,6 +7,7 @@
"flag"
"fmt"
"io"
+ "io/ioutil"
"log"
"net/http"
"os"
@@ -45,8 +46,9 @@
`
type handler struct {
+ client *kubernetes.Clientset
manager *app.Manager
- nsClient corev1.NamespaceInterface
+ launcher app.Launcher
}
func (hn *handler) handleInstall(w http.ResponseWriter, r *http.Request) {
@@ -113,9 +115,6 @@
triggerOnEvent := r.Form["trigger_on_event"][0]
var triggers []trigger
for _, a := range hn.manager.Apps {
- if a.Triggers == nil {
- continue
- }
for _, t := range a.Triggers.Triggers {
if t.TriggerOn.Type == triggerOnType && t.TriggerOn.Event == triggerOnEvent {
triggers = append(triggers, trigger{a.Namespace, t.Template})
@@ -133,6 +132,44 @@
}
}
+type actionReq struct {
+ App string `json:"app"`
+ Action string `json:"action"`
+ Args map[string]interface{} `json:"args"`
+}
+
+func (hn *handler) handleLaunchAction(w http.ResponseWriter, r *http.Request) {
+ actionStr, err := ioutil.ReadAll(r.Body)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ var req actionReq
+ if err := json.Unmarshal(actionStr, &req); err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+ for _, a := range hn.manager.Apps {
+ if a.Name != req.App {
+ continue
+ }
+ for _, action := range a.Actions.Actions {
+ if action.Name != req.Action {
+ continue
+ }
+ err := hn.launcher.Launch(a.Namespace, action.Template, req.Args)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ }
+ return
+ }
+ }
+ http.Error(
+ w,
+ fmt.Sprintf("Application action not found: %s %s", req.App, req.Action),
+ http.StatusBadRequest)
+}
+
func (hn *handler) installHelmChart(path string) error {
h, err := app.HelmChartFromTar(path)
if err != nil {
@@ -143,7 +180,8 @@
}
glog.Infof("Installed schema: %s", h.Schema)
namespace := fmt.Sprintf("app-%s", h.Name)
- if err = createNamespace(hn.nsClient, namespace); err != nil {
+ err = createNamespace(hn.client.CoreV1().Namespaces(), namespace)
+ if err != nil {
return err
}
glog.Infof("Created namespaces: %s", namespace)
@@ -157,7 +195,7 @@
} else {
glog.Info("Skipping deployment as we got library chart.")
}
- hn.manager.Apps[h.Name] = app.App{namespace, h.Triggers}
+ hn.manager.Apps[h.Name] = app.App{h.Name, namespace, h.Triggers, h.Actions}
app.StoreManagerStateToFile(hn.manager, *managerStoreFile)
glog.Info("Installed")
return nil
@@ -191,13 +229,13 @@
if err != nil {
glog.Fatalf("Could not create Kubernetes API client: %v", err)
}
- namespaces := clientset.CoreV1().Namespaces()
manager, err := app.LoadManagerStateFromFile(*managerStoreFile)
if err != nil {
glog.Fatalf("Could ot initialize manager: %v", err)
}
- h := handler{manager, namespaces}
+ h := handler{clientset, manager, app.NewK8sLauncher(clientset)}
http.HandleFunc("/triggers", h.handleTriggers)
+ http.HandleFunc("/launch_action", h.handleLaunchAction)
http.HandleFunc("/", h.handleInstall)
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil))
diff --git a/appmanager/helm.go b/appmanager/helm.go
index 9fdf4ed..ede5faf 100644
--- a/appmanager/helm.go
+++ b/appmanager/helm.go
@@ -23,29 +23,31 @@
type HelmChart struct {
Chart
chartDir string
- Schema *Schema
- Triggers *Triggers
+ Schema Schema
+ Triggers Triggers
+ Actions Actions
Yamls []string
}
func HelmChartFromDir(chartDir string) (*HelmChart, error) {
var chart HelmChart
chart.chartDir = chartDir
- c, err := ReadChart(path.Join(chartDir, "Chart.yaml"))
+ err := FromYamlFile(path.Join(chartDir, "Chart.yaml"), &chart.Chart)
if err != nil {
return nil, err
}
- chart.Chart = *c
- schema, err := ReadSchema(path.Join(chartDir, "Schema.yaml"))
+ err = FromYamlFile(path.Join(chartDir, "Schema.yaml"), &chart.Schema)
if err != nil && !os.IsNotExist(err) {
return nil, err
}
- chart.Schema = schema
- triggers, err := ReadTriggers(path.Join(chartDir, "Triggers.yaml"))
+ err = FromYamlFile(path.Join(chartDir, "Triggers.yaml"), &chart.Triggers)
if err != nil && !os.IsNotExist(err) {
return nil, err
}
- chart.Triggers = triggers
+ err = FromYamlFile(path.Join(chartDir, "Actions.yaml"), &chart.Actions)
+ if err != nil && !os.IsNotExist(err) {
+ return nil, err
+ }
return &chart, nil
}
diff --git a/appmanager/installer.go b/appmanager/installer.go
index 006fc31..7628686 100644
--- a/appmanager/installer.go
+++ b/appmanager/installer.go
@@ -7,8 +7,8 @@
"strings"
)
-func InstallSchema(schema *Schema, apiAddr string) error {
- if schema == nil || len(schema.Schema) == 0 {
+func InstallSchema(schema Schema, apiAddr string) error {
+ if len(schema.Schema) == 0 {
return nil
}
resp, err := http.Post(apiAddr, "application/text", strings.NewReader(schema.Schema))
diff --git a/appmanager/launcher.go b/appmanager/launcher.go
new file mode 100644
index 0000000..aeed5b7
--- /dev/null
+++ b/appmanager/launcher.go
@@ -0,0 +1,57 @@
+package appmanager
+
+import (
+ "bytes"
+ "context"
+ "text/template"
+
+ apiv1 "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/util/yaml"
+ "k8s.io/client-go/kubernetes"
+
+ "github.com/golang/glog"
+)
+
+type Launcher interface {
+ Launch(ns, tmpl string, args map[string]interface{}) error
+}
+
+type k8sLauncher struct {
+ client *kubernetes.Clientset
+}
+
+func NewK8sLauncher(client *kubernetes.Clientset) Launcher {
+ return &k8sLauncher{client}
+}
+
+func (k *k8sLauncher) Launch(ns, tmpl string, args map[string]interface{}) error {
+ pod, err := renderTemplate(tmpl, args)
+ if err != nil {
+ return err
+ }
+ pods := k.client.CoreV1().Pods(ns)
+ resp, err := pods.Create(context.TODO(), pod, metav1.CreateOptions{})
+ if err != nil {
+ return err
+ }
+ glog.Infof("Pod created: %s", resp)
+ return nil
+}
+
+func renderTemplate(tmpl string, args map[string]interface{}) (*apiv1.Pod, error) {
+ t, err := template.New("action").Parse(tmpl)
+ if err != nil {
+ return nil, err
+ }
+ var b bytes.Buffer
+ if err := t.Execute(&b, args); err != nil {
+ return nil, err
+ }
+ var pod apiv1.Pod
+ dec := yaml.NewYAMLOrJSONDecoder(&b, 100)
+ if err := dec.Decode(&pod); err != nil {
+ return nil, err
+ }
+ return &pod, nil
+}
diff --git a/appmanager/manager.go b/appmanager/manager.go
index e54e8e1..fa12c33 100644
--- a/appmanager/manager.go
+++ b/appmanager/manager.go
@@ -6,8 +6,10 @@
)
type App struct {
+ Name string
Namespace string
- Triggers *Triggers
+ Triggers Triggers
+ Actions Actions
}
// TODO(giolekva): add interface
diff --git a/appmanager/schema.go b/appmanager/schema.go
index d1da046..a5774e1 100644
--- a/appmanager/schema.go
+++ b/appmanager/schema.go
@@ -1,34 +1,5 @@
package appmanager
-import (
- "io/ioutil"
- "os"
-
- "gopkg.in/yaml.v2"
-)
-
type Schema struct {
Schema string `yaml:"schema"`
}
-
-func SchemaFromYaml(str string) (*Schema, error) {
- var s Schema
- err := yaml.Unmarshal([]byte(str), &s)
- if err != nil {
- return nil, err
- }
- return &s, nil
-}
-
-func ReadSchema(schemaFile string) (*Schema, error) {
- f, err := os.Open(schemaFile)
- if err != nil {
- return nil, err
- }
- defer f.Close()
- b, err := ioutil.ReadAll(f)
- if err != nil {
- return nil, err
- }
- return SchemaFromYaml(string(b))
-}
diff --git a/appmanager/triggers.go b/appmanager/triggers.go
index 3eb88d9..aee109f 100644
--- a/appmanager/triggers.go
+++ b/appmanager/triggers.go
@@ -1,12 +1,5 @@
package appmanager
-import (
- "io/ioutil"
- "os"
-
- "gopkg.in/yaml.v2"
-)
-
type TriggerOn struct {
Type string `yaml:"type"`
Event string `yaml:"event"`
@@ -21,25 +14,3 @@
type Triggers struct {
Triggers []Trigger `yaml:"triggers"`
}
-
-func TriggersFromYaml(str string) (*Triggers, error) {
- var s Triggers
- err := yaml.Unmarshal([]byte(str), &s)
- if err != nil {
- return nil, err
- }
- return &s, nil
-}
-
-func ReadTriggers(actionsFile string) (*Triggers, error) {
- f, err := os.Open(actionsFile)
- if err != nil {
- return nil, err
- }
- defer f.Close()
- b, err := ioutil.ReadAll(f)
- if err != nil {
- return nil, err
- }
- return TriggersFromYaml(string(b))
-}