From 2da765c31c1ad4b4f3a54a8853c0a57c695c9bf8 Mon Sep 17 00:00:00 2001 From: Pavan Krishna Nimmagadda Date: Tue, 10 Apr 2018 18:02:35 -0700 Subject: [PATCH] add appdynamics support --- fixtures/fake_appd_service_broker/Procfile | 1 + fixtures/fake_appd_service_broker/broker.py | 81 +++++ .../fake_appd_service_broker/requirements.txt | 8 + fixtures/fake_appd_service_broker/runtime.txt | 1 + fixtures/with_appdynamics/Procfile | 1 + fixtures/with_appdynamics/requirements.txt | 7 + fixtures/with_appdynamics/runtime.txt | 1 + fixtures/with_appdynamics/sample.py | 27 ++ src/python/hooks/appdynamics.go | 208 +++++++++++++ src/python/hooks/appdynamics_test.go | 294 ++++++++++++++++++ ...deploy_python_app_with_appdynamics_test.go | 134 ++++++++ 11 files changed, 763 insertions(+) create mode 100644 fixtures/fake_appd_service_broker/Procfile create mode 100644 fixtures/fake_appd_service_broker/broker.py create mode 100644 fixtures/fake_appd_service_broker/requirements.txt create mode 100644 fixtures/fake_appd_service_broker/runtime.txt create mode 100644 fixtures/with_appdynamics/Procfile create mode 100644 fixtures/with_appdynamics/requirements.txt create mode 100644 fixtures/with_appdynamics/runtime.txt create mode 100644 fixtures/with_appdynamics/sample.py create mode 100644 src/python/hooks/appdynamics.go create mode 100644 src/python/hooks/appdynamics_test.go create mode 100644 src/python/integration/deploy_python_app_with_appdynamics_test.go diff --git a/fixtures/fake_appd_service_broker/Procfile b/fixtures/fake_appd_service_broker/Procfile new file mode 100644 index 000000000..577fbb315 --- /dev/null +++ b/fixtures/fake_appd_service_broker/Procfile @@ -0,0 +1 @@ +web: python broker.py \ No newline at end of file diff --git a/fixtures/fake_appd_service_broker/broker.py b/fixtures/fake_appd_service_broker/broker.py new file mode 100644 index 000000000..7d182d930 --- /dev/null +++ b/fixtures/fake_appd_service_broker/broker.py @@ -0,0 +1,81 @@ +from flask import Flask, request, jsonify, make_response +import os + +broker = Flask(__name__) + +fake_service = { + "services": [ + { + "id": "1e3d32a0-c979-11e7-8e98-7bebc67e38ac", + "name": "appdynamics", + "description": "fake appdynamics broker", + "bindable": True, + "tags": [], + "metadata": { + "displayName": "appdynamics", + "imageUrl": "http://via.placeholder.com/100x100", + "longDescription": "fake appdynamics broker", + "providerDisplayName": "appdynamics", + "documentationUrl": "http://example.com", + "supportUrl": "http://example.com" + }, + "plans": [ + { + "id": "24ba3a06-c979-11e7-a5c2-7743f1a8115a", + "name": "public", + "description": "fake appdynamics broker", + "metadata": { + "bullets": [], + "costs": [ + { + "amount": { + "usd": 0 + }, + "unit": "MONTHLY" + } + ], + "displayName": "public" + } + } + ] + } + ] +} + + +fake_credentials = {'account-access-key': 'test-key', + 'account-name': 'test-account', + 'host-name': 'test-sb-host', + 'port': '1234', + 'ssl-enabled': True + } + + +@broker.route("/") +def hello(): + return "Service Broker Up and Running" + + +@broker.route("/v2/catalog") +def catalog(): + return jsonify(fake_service) + + +@broker.route('/v2/service_instances/', methods=['PUT', 'DELETE', 'PATCH']) +def service_instances(instance_id): + if request.method == 'PUT': + return make_response(jsonify({}), 201) + else: + return jsonify({}) + + +@broker.route('/v2/service_instances//service_bindings/', methods=['PUT', 'DELETE']) +def bind_instances(instance_id, binding_id): + if request.method == 'PUT': + return make_response(jsonify({'credentials': fake_credentials}), 201) + else: + return jsonify({}) + + +if __name__ == '__main__': + broker.run(host='0.0.0.0', port=int(os.getenv('VCAP_APP_PORT', '5000'))) \ No newline at end of file diff --git a/fixtures/fake_appd_service_broker/requirements.txt b/fixtures/fake_appd_service_broker/requirements.txt new file mode 100644 index 000000000..7e5d2047f --- /dev/null +++ b/fixtures/fake_appd_service_broker/requirements.txt @@ -0,0 +1,8 @@ +Flask==0.10.1 +Jinja2==2.7.2 +MarkupSafe==0.21 +Werkzeug==0.10.4 +gunicorn==19.3.0 +itsdangerous==0.24 +pylibmc==1.4.2 +cffi==0.9.2 diff --git a/fixtures/fake_appd_service_broker/runtime.txt b/fixtures/fake_appd_service_broker/runtime.txt new file mode 100644 index 000000000..e7617732d --- /dev/null +++ b/fixtures/fake_appd_service_broker/runtime.txt @@ -0,0 +1 @@ +\r\r\n\npython-3.6.x\n\n\r\r \ No newline at end of file diff --git a/fixtures/with_appdynamics/Procfile b/fixtures/with_appdynamics/Procfile new file mode 100644 index 000000000..c042361a4 --- /dev/null +++ b/fixtures/with_appdynamics/Procfile @@ -0,0 +1 @@ +web: python sample.py diff --git a/fixtures/with_appdynamics/requirements.txt b/fixtures/with_appdynamics/requirements.txt new file mode 100644 index 000000000..38651afde --- /dev/null +++ b/fixtures/with_appdynamics/requirements.txt @@ -0,0 +1,7 @@ +Flask==0.10.1 +Jinja2==2.7.2 +MarkupSafe==0.21 +Werkzeug==0.10.4 +itsdangerous==0.24 +pylibmc==1.4.2 +cffi==0.9.2 \ No newline at end of file diff --git a/fixtures/with_appdynamics/runtime.txt b/fixtures/with_appdynamics/runtime.txt new file mode 100644 index 000000000..e7617732d --- /dev/null +++ b/fixtures/with_appdynamics/runtime.txt @@ -0,0 +1 @@ +\r\r\n\npython-3.6.x\n\n\r\r \ No newline at end of file diff --git a/fixtures/with_appdynamics/sample.py b/fixtures/with_appdynamics/sample.py new file mode 100644 index 000000000..15b18bf4c --- /dev/null +++ b/fixtures/with_appdynamics/sample.py @@ -0,0 +1,27 @@ +"""Cloud Foundry test""" +from flask import Flask, jsonify +import os +import collections + +app = Flask(__name__) + +port = int(os.getenv('VCAP_APP_PORT', 8080)) + + +@app.route('/vcap') +def vcap(): + vcap_services = os.getenv('VCAP_SERVICES', "") + return vcap_services + + +@app.route('/appd') +def appd(): + env_vars = ["APPD_ACCOUNT_ACCESS_KEY", "APPD_ACCOUNT_NAME", "APPD_APP_NAME", "APPD_CONTROLLER_HOST", + "APPD_CONTROLLER_PORT", "APPD_NODE_NAME", "APPD_SSL_ENABLED","APPD_TIER_NAME"] + env_vars.sort() + env_dict = collections.OrderedDict([(envKey, os.getenv(envKey))for envKey in env_vars]) + return jsonify(env_dict) + + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=port) diff --git a/src/python/hooks/appdynamics.go b/src/python/hooks/appdynamics.go new file mode 100644 index 000000000..f586914a0 --- /dev/null +++ b/src/python/hooks/appdynamics.go @@ -0,0 +1,208 @@ +package hooks + +import ( + "encoding/json" + "errors" + "fmt" + "github.com/cloudfoundry/libbuildpack" + "io" + "io/ioutil" + "os" + "path/filepath" + "sort" + "strings" +) + +type Command interface { + Execute(string, io.Writer, io.Writer, string, ...string) error +} + +type AppdynamicsHook struct { + libbuildpack.DefaultHook + Log *libbuildpack.Logger + Command Command +} + +type Plan struct { + Credentials Credential `json:"credentials"` +} + +type Credential struct { + ControllerHost string `json:"host-name"` + ControllerPort string `json:"port"` + SslEnabled bool `json:"ssl-enabled"` + AccountAccessKey string `json:"account-access-key"` + AccountName string `json:"account-name"` +} + +type VcapApplication struct { + ApplicationName string `json:"application_name"` + ApplicationId string `json:"application_id"` +} + +func (h AppdynamicsHook) getEnv(key, fallback string) string { + if value, ok := os.LookupEnv(key); ok { + return value + } + return fallback +} + +func (h AppdynamicsHook) GenerateAppdynamicsScript(envVars map[string]string) string { + var envKeys []string + for k := range envVars { + envKeys = append(envKeys, k) + } + sort.Strings(envKeys) // unnecessary but just to be deterministic for tests + + scriptContents := "# Autogenerated Appdynamics Script \n" + for _, envKey := range envKeys { + envStr := fmt.Sprintf("export %s=%s", envKey, envVars[envKey]) + scriptContents += "\n" + envStr + } + return scriptContents +} + +func (h AppdynamicsHook) GenerateStartUpCommand(startCommand string) (string, error) { + webCommands := strings.SplitN(startCommand, ":", 2) + if len(webCommands) != 2 { + return "", errors.New("improper format found in Procfile") + } + return fmt.Sprintf("web: pyagent run -- %s", webCommands[1]), nil +} + +func (h AppdynamicsHook) RewriteProcFile(procFilePath string) error { + startCommand, err := ioutil.ReadFile(procFilePath) + if err != nil { + return err + } + if newCommand, err := h.GenerateStartUpCommand(string(startCommand)); err != nil { + return err + } else { + if err := ioutil.WriteFile(procFilePath, []byte(newCommand), 0644); err != nil { + return err + } + } + return nil +} + +func (h AppdynamicsHook) RewriteRequirementsFile(stager *libbuildpack.Stager) error { + h.Log.BeginStep("Rewriting Requirements file with appdynamics package") + + reqFile := filepath.Join(stager.BuildDir(), "requirements.txt") + writeFlag := os.O_APPEND | os.O_WRONLY + packageName := "\n" + "appdynamics" + + if exists, err := libbuildpack.FileExists(reqFile); err != nil { + return err + } else if !exists { + h.Log.Info("Requirements file not found creating one with appdynamics packages") + writeFlag = os.O_CREATE | os.O_WRONLY + packageName = "appdynamics" + } + + f, err := os.OpenFile(reqFile, writeFlag, 0644) + if err != nil { + panic(err) + } + + defer f.Close() + + if _, err = f.WriteString(packageName); err != nil { + panic(err) + } + fileContents, _ := ioutil.ReadFile(f.Name()) + h.Log.Info(string(fileContents)) + + return nil +} + +func (h AppdynamicsHook) RewriteProcFileWithAppdynamics(stager *libbuildpack.Stager) error { + h.Log.BeginStep("Rewriting ProcFile to start with Appdynamics") + + file := filepath.Join(stager.BuildDir(), "Procfile") + + if exists, _ := libbuildpack.FileExists(file); exists { + if err := h.RewriteProcFile(file); err != nil { + return err + } + fileContents, _ := ioutil.ReadFile(file) + h.Log.Info(string(fileContents)) + } else { + h.Log.Info("Cannot find Procfile, skipping this step!") + } + return nil +} + +func (h AppdynamicsHook) CreateAppDynamicsEnv(stager *libbuildpack.Stager, environmentVars map[string]string) error { + scriptContents := h.GenerateAppdynamicsScript(environmentVars) + h.Log.BeginStep("Writing Appdynamics Environment") + h.Log.Debug(scriptContents) + return stager.WriteProfileD("appdynamics.sh", scriptContents) +} + +func (h AppdynamicsHook) BeforeCompile(stager *libbuildpack.Stager) error { + vcapServices := os.Getenv("VCAP_SERVICES") + services := make(map[string][]Plan) + + err := json.Unmarshal([]byte(vcapServices), &services) + if err != nil { + h.Log.Debug("Could not unmarshall VCAP_SERVICES JSON exiting") + return nil + } + + if val, ok := services["appdynamics"]; ok { // carry the procedure only when Appdynamics service is bound. + h.Log.BeginStep("Setting up Appdynamics") + + appdynamicsPlan := val[0].Credentials + vcapApplication := os.Getenv("VCAP_APPLICATION") + application := VcapApplication{} + + err = json.Unmarshal([]byte(vcapApplication), &application) + if err != nil { + h.Log.Debug("Could not unmarshall VCAP_APPLICATION JSON") + } + + sslFlag := "off" + + if appdynamicsPlan.SslEnabled { + sslFlag = "on" + } + + appdEnv := map[string]string{ + "APPD_APP_NAME": h.getEnv("APPD_APP_NAME", application.ApplicationName), + "APPD_TIER_NAME": h.getEnv("APPD_TIER_NAME", application.ApplicationName), + "APPD_NODE_NAME": h.getEnv("APPD_NODE_NAME", application.ApplicationName), + "APPD_CONTROLLER_HOST": appdynamicsPlan.ControllerHost, + "APPD_CONTROLLER_PORT": appdynamicsPlan.ControllerPort, + "APPD_ACCOUNT_ACCESS_KEY": appdynamicsPlan.AccountAccessKey, + "APPD_ACCOUNT_NAME": appdynamicsPlan.AccountName, + "APPD_SSL_ENABLED": sslFlag, + } + + if err := h.RewriteRequirementsFile(stager); err != nil { + h.Log.Error("Could not write requirements file with Appdynamics packages: %v", err) + return err + } + + if err := h.CreateAppDynamicsEnv(stager, appdEnv); err != nil { + h.Log.Error("Could not create Appdynamics environment: %v", err) + return err + } + + if err := h.RewriteProcFileWithAppdynamics(stager); err != nil { + h.Log.Error("Could not rewrite procfile with Appdynamics start command: %v", err) + return err + } + } + return nil +} + +func init() { + logger := libbuildpack.NewLogger(os.Stdout) + command := &libbuildpack.Command{} + + libbuildpack.AddHook(AppdynamicsHook{ + Log: logger, + Command: command, + }) +} diff --git a/src/python/hooks/appdynamics_test.go b/src/python/hooks/appdynamics_test.go new file mode 100644 index 000000000..61995200c --- /dev/null +++ b/src/python/hooks/appdynamics_test.go @@ -0,0 +1,294 @@ +package hooks_test + +import ( + "bytes" + "io/ioutil" + "os" + "python/hooks" + + "github.com/cloudfoundry/libbuildpack" + "github.com/cloudfoundry/libbuildpack/ansicleaner" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "path/filepath" +) + + +func createFile(dir, filename, command string, perm os.FileMode) error { + procFile := filepath.Join(dir, filename) + f, err := os.OpenFile(procFile, os.O_CREATE|os.O_WRONLY, perm) + if err != nil { + return err + } + + defer f.Close() + + if _, err = f.WriteString(command); err != nil { + return err + } + return nil +} + +var _ = Describe("Appdynamics", func() { + var ( + err error + buildDir string + depsDir string + stager *libbuildpack.Stager + buffer *bytes.Buffer + appdynamics hooks.AppdynamicsHook + ) + + BeforeEach(func() { + buildDir, err = ioutil.TempDir("", "python-buildpack.build.") + Expect(err).To(BeNil()) + + depsDir, err = ioutil.TempDir("", "python-buildpack.deps.") + Expect(err).To(BeNil()) + + buffer = new(bytes.Buffer) + logger := libbuildpack.NewLogger(ansicleaner.New(buffer)) + + args := []string{buildDir, "", depsDir, "9"} + stager = libbuildpack.NewStager(args, logger, &libbuildpack.Manifest{}) + + command := &libbuildpack.Command{} + + appdynamics = hooks.AppdynamicsHook{ + Log: logger, + Command: command, + } + }) + + AfterEach(func() { + Expect(os.RemoveAll(buildDir)).To(Succeed()) + }) + + Describe("GenerateStartUpCommand", func() { + + It("Startup command correct format", func() { + startCommand := "web: python flask.py" + ModifiedCommand, err := appdynamics.GenerateStartUpCommand(startCommand) + Expect(ModifiedCommand).To(Equal("web: pyagent run -- python flask.py")) + Expect(err).To(BeNil()) + }) + + It("Wrong format", func() { + startCommand := "python flask.py" + _, err := appdynamics.GenerateStartUpCommand(startCommand) + Expect(err).ToNot(BeNil()) + }) + + }) + + Describe("RewriteProcFile", func() { + var ( + err error + tempProcDir string + ) + BeforeEach(func() { + tempProcDir, err = ioutil.TempDir("", "Procfiles") + + err = createFile(tempProcDir, "Procfile", "web: python app.py", 0644) + Expect(err).To(BeNil()) + + err = createFile(tempProcDir, "NoPermProcFile", "web: python app.py", 0444) + Expect(err).To(BeNil()) + + err = createFile(tempProcDir, "WrongFormatProcFile", "python app.py", 0644) + Expect(err).To(BeNil()) + + }) + + AfterEach(func() { + Expect(os.RemoveAll(tempProcDir)).To(Succeed()) + }) + + It("Procfile doesn't exist", func() { + err := appdynamics.RewriteProcFile("/doesnt/exist") + Expect(err).ToNot(BeNil()) + }) + + It("Procfile with no write permissions", func() { + err := appdynamics.RewriteProcFile(filepath.Join(tempProcDir, "NoPermProcFile")) + Expect(err).ToNot(BeNil()) + }) + + It("Procfile with no wrong format", func() { + err := appdynamics.RewriteProcFile(filepath.Join(tempProcDir, "WrongFormatProcFile")) + Expect(err).ToNot(BeNil()) + }) + + It("procfile will be rewritten with pyagent", func() { + err := appdynamics.RewriteProcFile(filepath.Join(tempProcDir, "Procfile")) + Expect(err).To(BeNil()) + startCommand, err := ioutil.ReadFile(filepath.Join(tempProcDir, "Procfile")) + Expect(err).To(BeNil()) + Expect(string(startCommand)).To(Equal("web: pyagent run -- python app.py")) + }) + + }) + + Describe("RewriteRequirementsFile when requirements.txt is not packaged", func() { + It("requirements.txt doesn't exist", func() { + Expect(libbuildpack.FileExists(filepath.Join(buildDir, "requirements.txt"))).ToNot(BeTrue()) + err := appdynamics.RewriteRequirementsFile(stager) + Expect(err).To(BeNil()) + Expect(libbuildpack.FileExists(filepath.Join(buildDir, "requirements.txt"))).To(BeTrue()) + packagesList, err := ioutil.ReadFile(filepath.Join(buildDir, "requirements.txt")) + Expect(err).To(BeNil()) + Expect(string(packagesList)).To(Equal("appdynamics")) + }) + }) + + Describe("RewriteRequirementsFile when requirements.txt is packaged", func() { + BeforeEach(func() { + Expect(libbuildpack.FileExists(filepath.Join(buildDir, "requirements.txt"))).ToNot(BeTrue()) + procFile := filepath.Join(buildDir, "requirements.txt") + f, err := os.OpenFile(procFile, os.O_CREATE|os.O_WRONLY, 0644) + Expect(err).To(BeNil()) + defer f.Close() + _, err = f.WriteString("Flask") + Expect(err).To(BeNil()) + }) + AfterEach(func() { + Expect(os.Remove(filepath.Join(buildDir, "requirements.txt"))).To(Succeed()) + }) + + It("requirements.txt doesn't exist", func() { + Expect(libbuildpack.FileExists(filepath.Join(buildDir, "requirements.txt"))).To(BeTrue()) + err := appdynamics.RewriteRequirementsFile(stager) + Expect(err).To(BeNil()) + packages, err := ioutil.ReadFile(filepath.Join(buildDir, "requirements.txt")) + Expect(string(packages)).To(Equal("Flask\nappdynamics")) + }) + }) + + Describe("GenerateScript", func() { + It("Generate script from Env map", func() { + envVal := map[string]string{ + "APPD_KEY_1": "APPD_VAL_1", + "APPD_KEY_2": "APPD_VAL_2", + } + script := appdynamics.GenerateAppdynamicsScript(envVal) + expectedScript := `# Autogenerated Appdynamics Script + +export APPD_KEY_1=APPD_VAL_1 +export APPD_KEY_2=APPD_VAL_2` + Expect(script).To(Equal(expectedScript)) + }) + }) + + Describe("CreateAppDynamicsEnv", func() { + It("Generate script from Env map", func() { + envVal := map[string]string{ + "APPD_KEY_1": "APPD_VAL_1", + "APPD_KEY_2": "APPD_VAL_2", + } + appdynamics.CreateAppDynamicsEnv(stager, envVal) + appdynamicsShellScript := filepath.Join(stager.DepDir(), "profile.d", "appdynamics.sh") + Expect(libbuildpack.FileExists(appdynamicsShellScript)).To(BeTrue()) + expectedScript := `# Autogenerated Appdynamics Script + +export APPD_KEY_1=APPD_VAL_1 +export APPD_KEY_2=APPD_VAL_2` + script, err := ioutil.ReadFile(appdynamicsShellScript) + Expect(err).To(BeNil()) + Expect(string(script)).To(Equal(expectedScript)) + }) + }) + + Describe("BeforeCompile when VCAP_SERVICES is not present", func() { + BeforeEach(func() { + Expect(os.Getenv("VCAP_SERVICES")).To(Equal("")) + err = createFile(buildDir, "Procfile", "web: python app.py", 0644) + Expect(err).To(BeNil()) + }) + + AfterEach(func() { + Expect(os.Remove(filepath.Join(buildDir, "Procfile"))).To(Succeed()) + }) + + It("VCAP_SERVICES is not present", func() { + err := appdynamics.BeforeCompile(stager) + Expect(err).To(BeNil()) + Expect(libbuildpack.FileExists(filepath.Join(stager.DepDir(), "profile.d", "appdynamics.sh"))).To(BeFalse()) + Expect(libbuildpack.FileExists(filepath.Join(buildDir, "requirements.txt"))).To(BeFalse()) + procCommand, err := ioutil.ReadFile(filepath.Join(buildDir, "Procfile")) + Expect(err).To(BeNil()) + Expect(string(procCommand)).To(Equal("web: python app.py")) + }) + }) + + Describe("BeforeCompile when VCAP_SERVICES has no appdynamics", func() { + BeforeEach(func() { + Expect(os.Getenv("VCAP_SERVICES")).To(Equal("")) + os.Setenv("VCAP_SERVICES", `{"service": [{"credentials": {"login": "name"}, "name": "443"}]}`) + err = createFile(buildDir, "Procfile", "web: python app.py", 0644) + Expect(err).To(BeNil()) + }) + + AfterEach(func() { + Expect(os.Remove(filepath.Join(buildDir, "Procfile"))).To(Succeed()) + os.Unsetenv("VCAP_SERVICES") + }) + + It("VCAP_SERVICES has no appdynamics", func() { + err := appdynamics.BeforeCompile(stager) + Expect(err).To(BeNil()) + Expect(libbuildpack.FileExists(filepath.Join(stager.DepDir(), "profile.d", "appdynamics.sh"))).To(BeFalse()) + Expect(libbuildpack.FileExists(filepath.Join(buildDir, "requirements.txt"))).To(BeFalse()) + procCommand, err := ioutil.ReadFile(filepath.Join(buildDir, "Procfile")) + Expect(err).To(BeNil()) + Expect(string(procCommand)).To(Equal("web: python app.py")) + }) + }) + + Describe("BeforeCompile when VCAP_SERVICES has appdynamics", func() { + BeforeEach(func() { + os.Setenv("VCAP_SERVICES", + `{"appdynamics": [{"instance_name": "plan-instance", "tags": [], "name": "plan", "syslog_drain_url": null, "binding_name": null, "credentials": {"host-name": "controller.test.com", "plan-name": "plan", "guid": "guid", "plan-description": "plan", "ssl-enabled": false, "account-access-key": "key", "account-name": "account-name", "port": "7777"}, "label": "appdynamics"}]}`) + os.Setenv("VCAP_APPLICATION", `{"application_id": "applicationId", "name": "test", "application_name": "test"}`) + os.Setenv("APPD_TIER_NAME", "tier" ) + os.Setenv("APPD_NODE_NAME", "node" ) + + err = createFile(buildDir, "Procfile", "web: python app.py", 0644) + Expect(err).To(BeNil()) + }) + + AfterEach(func() { + Expect(os.Remove(filepath.Join(buildDir, "Procfile"))).To(Succeed()) + os.Unsetenv("VCAP_SERVICES") + }) + + It("VCAP_SERVICES has appdynamics", func() { + err := appdynamics.BeforeCompile(stager) + Expect(err).To(BeNil()) + + Expect(libbuildpack.FileExists(filepath.Join(stager.DepDir(), "profile.d", "appdynamics.sh"))).To(BeTrue()) + appdynamicsInfo, err := ioutil.ReadFile(filepath.Join(stager.DepDir(), "profile.d", "appdynamics.sh")) + expectedInfo := `# Autogenerated Appdynamics Script + +export APPD_ACCOUNT_ACCESS_KEY=key +export APPD_ACCOUNT_NAME=account-name +export APPD_APP_NAME=test +export APPD_CONTROLLER_HOST=controller.test.com +export APPD_CONTROLLER_PORT=7777 +export APPD_NODE_NAME=node +export APPD_SSL_ENABLED=off +export APPD_TIER_NAME=tier` + Expect(string(appdynamicsInfo)).To(Equal(expectedInfo)) + Expect(err).To(BeNil()) + + Expect(libbuildpack.FileExists(filepath.Join(buildDir, "requirements.txt"))).To(BeTrue()) + packages, err := ioutil.ReadFile(filepath.Join(buildDir, "requirements.txt")) + Expect(err).To(BeNil()) + Expect(string(packages)).To(Equal("appdynamics")) + + procCommand, err := ioutil.ReadFile(filepath.Join(buildDir, "Procfile")) + Expect(err).To(BeNil()) + Expect(string(procCommand)).To(Equal("web: pyagent run -- python app.py")) + }) + }) + +}) diff --git a/src/python/integration/deploy_python_app_with_appdynamics_test.go b/src/python/integration/deploy_python_app_with_appdynamics_test.go new file mode 100644 index 000000000..a73027e22 --- /dev/null +++ b/src/python/integration/deploy_python_app_with_appdynamics_test.go @@ -0,0 +1,134 @@ +package integration_test + +import ( + "os/exec" + "path/filepath" + "time" + + "github.com/cloudfoundry/libbuildpack/cutlass" + + "fmt" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "os" +) + +func getEnv(key, fallback string) string { + if value, ok := os.LookupEnv(key); ok { + return value + } + return fallback +} + +var _ = Describe("appdynamics", func() { + var app, appdServiceBrokerApp *cutlass.App + var sbUrl string + const serviceName = "appdynamics" + cfUsername := getEnv("CF_USER_NAME", "username") + cfPassword := getEnv("CF_PASSWORD", "password") + + RunCf := func(args ...string) error { + command := exec.Command("cf", args...) + command.Stdout = GinkgoWriter + command.Stderr = GinkgoWriter + return command.Run() + } + + BeforeEach(func() { + appdServiceBrokerApp = cutlass.New(filepath.Join(bpDir, "fixtures", "fake_appd_service_broker")) + Expect(appdServiceBrokerApp.Push()).To(Succeed()) + Eventually(func() ([]string, error) { return appdServiceBrokerApp.InstanceStates() }, 20*time.Second).Should(Equal([]string{"RUNNING"})) + + var err error + sbUrl, err = appdServiceBrokerApp.GetUrl("") + Expect(err).ToNot(HaveOccurred()) + + Expect(RunCf("create-service-broker", serviceName, cfUsername, cfPassword, sbUrl, "--space-scoped")).To(Succeed()) + Expect(RunCf("create-service", serviceName, "public", serviceName)).To(Succeed()) + + app = cutlass.New(filepath.Join(bpDir, "fixtures", "with_appdynamics")) + app.SetEnv("BP_DEBUG", "true") + PushAppAndConfirm(app) + }) + + AfterEach(func() { + if app != nil { + app.Destroy() + } + app = nil + + RunCf("purge-service-offering", "-f", serviceName) + RunCf("delete-service-broker", "-f", serviceName) + + if appdServiceBrokerApp != nil { + appdServiceBrokerApp.Destroy() + } + appdServiceBrokerApp = nil + }) + + It("test if appdynamics was successfully bound", func() { + By("Binding appdynamics service to the test application") + Expect(RunCf("bind-service", app.Name, serviceName)).To(Succeed()) + + By("Restaging the test application") + app.Stdout.Reset() + Expect(RunCf("restage", app.Name)).To(Succeed()) + + By("checking if the application has started fine and has correctly bound to appdynamics") + vcapServicesEnv, err := app.GetBody("/vcap") + Expect(err).To(BeNil()) + vcapServicesExpected := `{"appdynamics":[{ + "name": "appdynamics", + "instance_name": "appdynamics", + "binding_name": null, + "credentials": { + "account-access-key": "test-key", + "account-name": "test-account", + "host-name": "test-sb-host", + "port": "1234", + "ssl-enabled": true + }, + "syslog_drain_url": null, + "volume_mounts": [ + + ], + "label": "appdynamics", + "provider": null, + "plan": "public", + "tags": [ + + ] +}]}` + Expect(vcapServicesEnv).To(Equal(vcapServicesExpected)) + + By("Checking if the build pack installed and started appdynamics") + logs := app.Stdout.String() + + Expect(app.ConfirmBuildpack(buildpackVersion)).To(Succeed()) + Expect(logs).To(ContainSubstring("-----> Setting up Appdynamics")) + Expect(logs).To(ContainSubstring("-----> Rewriting Requirements file with appdynamics package")) + Expect(logs).To(ContainSubstring("-----> Writing Appdynamics Environment")) + Expect(logs).To(ContainSubstring("appdynamics.proxy.watchdog")) + Expect(logs).To(ContainSubstring("Started proxy with pid")) + + By("Checking if the buildpack properly set the APPD environment variables in apps environments") + appEnv, err := app.GetBody("/appd") + Expect(err).To(BeNil()) + expectedAppEnv := fmt.Sprintf(`{ + "APPD_ACCOUNT_ACCESS_KEY": "test-key", + "APPD_ACCOUNT_NAME": "test-account", + "APPD_APP_NAME": "%s", + "APPD_CONTROLLER_HOST": "test-sb-host", + "APPD_CONTROLLER_PORT": "1234", + "APPD_NODE_NAME": "%s", + "APPD_SSL_ENABLED": "on", + "APPD_TIER_NAME": "%s" +}`, app.Name, app.Name, app.Name) + Expect(appEnv).To(Equal(expectedAppEnv)) + + By("unbinding the service") + Expect(RunCf("unbind-service", app.Name, serviceName)).To(Succeed()) + + }) + +})