diff --git a/.gitignore b/.gitignore index 6c25c84..186d6e6 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,6 @@ pom.xml.asc lib classes /target +.iml +.idea/ +*.iml diff --git a/README.md b/README.md index e585ce8..6484555 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,13 @@ using the following command: $ lein beanstalk deploy development +#### Custom WAR + +If you're so inclined, you can also deploy a custom WAR by passing the file +to the deploy command. + + $ lein beanstalk deploy development target/myproject.war + ### Info To get information about the application itself run @@ -186,6 +193,35 @@ environment ``` By default the CNAME prefix is `-`. +### Aliases + +If you deploy multiple services to Elastic Beanstalk, you'll realize +that environment names must be unique across all of your applications. +Aliases allow you to refer to a standard name across your projects, +while deploying to an environment named suing either the defaults or +what is supplied for `:name`. + + +Below are the defaults. +```clojure +:aws {:beanstalk {:environments [{:alias "development" + :name "myproject-dev"} + {:alias "staging" + :name "myproject-staging"} + {:alias "production" + :name "myproject"}] + ...} + ...} +``` + +You may refer to either the alias or the name when running lein-beanstalk +commands. + + $ lein beanstalk deploy development + $ lein beanstalk deploy myproject-dev + +Both of the above commands deploy to `myproject-dev.elasticbeanstalk.com` + ### Environment Variables You can specify environment variables that will be added to the system @@ -203,6 +239,44 @@ If the environment variable name is a keyword, it is upper-cased and underscores ("_") are substituted for dashes ("-"). e.g. `:database-url` becomes `"DATABASE_URL"`. +### Choosing an alternate stack + +The default stack chosen is 32bit Amazon Linux running Tomcat 7. You +can customize the stack used: + + :aws {:beanstalk {:stack-name "64bit Amazon Linux running Tomcat 7"}} + +The [full list][4] of available stacks that you are likely to use: + +* 32bit Amazon Linux running Tomcat 7 +* 64bit Amazon Linux running Tomcat 7 +* 32bit Amazon Linux running Tomcat 6 +* 64bit Amazon Linux running Tomcat 6 +======= +### Configuring instance type, autoscaling, VPC, SSH, AMI, SSL + +You can customize many [other settings][5] on a per beanstalk environment +basis with an options key: + + :aws + {:beanstalk + {:environments + [{:name "dev" + :options {"aws:autoscaling:asg" {"MinSize" "1" "MaxSize" "1"} + "aws:autoscaling:launchconfiguration" {"InstanceType" "m1.medium" + "EC2KeyName" "mykey" + "ImageId" "ami-cbab67a2"}}}]}} + +### Configuring the application tier ### + +Amazon recently added support for [worker tiers][6], which are useful for running background tasks. +The default stack is built for a web application. To deploy as a worker, supply the following options +for the `:app-tier` key. + + :aws + {:beanstalk + {:app-tier {:name "Worker" :type "SQS/HTTP" :version "1.0"}}} + ### S3 Buckets [Amazon Elastic Beanstalk][1] uses @@ -230,7 +304,7 @@ The following regions are recognized: * `eu-west-1` * `us-west-1` * `us-west-2` - +* `eu-central-1` ## Trouble-Shooting @@ -247,3 +321,6 @@ application. e.g. for Compojure add [1]: http://aws.amazon.com/elasticbeanstalk [2]: http://aws.amazon.com [3]: http://aws.amazon.com/s3 +[4]: http://docs.aws.amazon.com/elasticbeanstalk/latest/APIReference/API_ListAvailableSolutionStacks.html +[5]: http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/command-options.html +[6]: http://aws.typepad.com/aws/2013/12/background-task-handling-for-aws-elastic-beanstalk.html \ No newline at end of file diff --git a/project.clj b/project.clj index fdb5175..3330af1 100644 --- a/project.clj +++ b/project.clj @@ -1,7 +1,12 @@ (defproject lein-beanstalk "0.2.7" :description "Leiningen plugin for Amazon's Elastic Beanstalk" :url "https://github.com/weavejester/lein-beanstalk" - :dependencies [[org.clojure/clojure "1.2.1"] - [com.amazonaws/aws-java-sdk "1.3.31"] - [lein-ring "0.8.2"]] + :dependencies [[com.amazonaws/aws-java-sdk "1.10.5.1"] + + ;; The dependencies prevent the runtime error: + ;; NoSuchMethodError JsonFactory.requiresPropertyOrdering()Z + [com.fasterxml.jackson.core/jackson-core "2.2.3"] + [com.fasterxml.jackson.core/jackson-databind "2.2.3"] + + [lein-ring "0.9.6"]] :eval-in-leiningen true) diff --git a/src/leiningen/beanstalk.clj b/src/leiningen/beanstalk.clj index a6770c4..7d30af9 100644 --- a/src/leiningen/beanstalk.clj +++ b/src/leiningen/beanstalk.clj @@ -9,25 +9,28 @@ (defn default-environments [project] (let [project-name (:name project)] - [{:name "development" :cname-prefix (str project-name "-dev")} - {:name "staging" :cname-prefix (str project-name "-staging")} - {:name "production" :cname-prefix project-name}])) + [{:alias "development" :name (str project-name "-dev") :cname-prefix (str project-name "-dev")} + {:alias "staging" :name (str project-name "-staging") :cname-prefix (str project-name "-staging")} + {:alias "production" :name project-name :cname-prefix project-name}])) (defn project-environments [project] (for [env (-> project :aws :beanstalk :environments)] (if (map? env) - (merge {:cname-prefix (str (:name project) "-" (:name env))} env) + (merge {:cname-prefix (str (:name project) "-" (or (:alias env) (:name env)))} + (merge {:name (or (:name env) (str (:name project) "-" (:alias env)))} env)) {:name env, :cname-prefix (str (:name project) "-" env)}))) (defn get-project-env [project env-name] (->> (or (seq (project-environments project)) (default-environments project)) - (filter #(= (:name %) env-name)) + (filter #(or (= (:name %) env-name) (= (:alias %) env-name))) (first))) (defn war-filename [project] - (str (:name project) "-" (aws/app-version project) ".war")) + (if (nil? (:name project)) + (str (clojure.string/replace project #".war" "") "-" (aws/app-version project) ".war") + (str (:name project) "-" (aws/app-version project) ".war"))) (defn deploy "Deploy the current project to Amazon Elastic Beanstalk." @@ -40,6 +43,14 @@ (aws/s3-upload-file project path) (aws/create-app-version project filename) (aws/deploy-environment project env)) + (println (str "Environment '" env-name "' not defined!")))) + ([project env-name war-file] + (if-let [env (get-project-env project env-name)] + (let [filename (war-filename war-file) + path war-file] + (aws/s3-upload-file project path filename) + (aws/create-app-version project filename) + (aws/deploy-environment project env)) (println (str "Environment '" env-name "' not defined!"))))) (defn terminate @@ -47,9 +58,10 @@ ([project] (println "Usage: lein beanstalk terminate ")) ([project env-name] - (if-not (get-project-env project env-name) - (println (str "Environment '" env-name "' not in project.clj")) - (aws/terminate-environment project env-name)))) + (let [name (:name (get-project-env project env-name))] + (if-not name + (println (str "Environment '" name "' not in project.clj")) + (aws/terminate-environment project name))))) (def app-info-indent "\n ") diff --git a/src/leiningen/beanstalk/aws.clj b/src/leiningen/beanstalk/aws.clj index 5c9d149..91607bc 100644 --- a/src/leiningen/beanstalk/aws.clj +++ b/src/leiningen/beanstalk/aws.clj @@ -18,6 +18,7 @@ com.amazonaws.services.elasticbeanstalk.model.UpdateEnvironmentRequest com.amazonaws.services.elasticbeanstalk.model.S3Location com.amazonaws.services.elasticbeanstalk.model.TerminateEnvironmentRequest + com.amazonaws.services.elasticbeanstalk.model.EnvironmentTier com.amazonaws.services.s3.AmazonS3Client com.amazonaws.services.s3.model.Region)) @@ -51,8 +52,20 @@ (or (-> project :aws :beanstalk :app-name) (:name project))) +(defn app-tier [project] + (doto (EnvironmentTier.) + (.setName (or (-> project :aws :beanstalk :app-tier :name) + "WebServer")) + (.setType (or (-> project :aws :beanstalk :app-tier :type) + "Standard")) + (.setVersion (or (-> project :aws :beanstalk :app-tier :version) + "1.0")))) + (defn app-version [project] - (str (:version project) "-" current-timestamp)) + (if (nil? (:version project)) + (str current-timestamp) + (str (:version project) "-" current-timestamp))) + (defn s3-bucket-name [project] (or (-> project :aws :beanstalk :s3-bucket) @@ -65,6 +78,7 @@ {:us-east-1 (ep "s3.amazonaws.com" "US_Standard") :us-west-1 (ep "s3-us-west-1.amazonaws.com" "US_West") :us-west-2 (ep "s3-us-west-2.amazonaws.com" "US_West_2") + :eu-central-1 (ep "s3-eu-central-1.amazonaws.com" "EU_Frankfurt") :eu-west-1 (ep "s3-eu-west-1.amazonaws.com" "EU_Ireland") :ap-southeast-1 (ep "s3-ap-southeast-1.amazonaws.com" "AP_Singapore") :ap-southeast-2 (ep "s3-ap-southeast-2.amazonaws.com" "AP_Sydney") @@ -75,6 +89,7 @@ {:us-east-1 "elasticbeanstalk.us-east-1.amazonaws.com" :us-west-1 "elasticbeanstalk.us-west-1.amazonaws.com" :us-west-2 "elasticbeanstalk.us-west-2.amazonaws.com" + :eu-central-1 "elasticbeanstalk.eu-central-1.amazonaws.com" :eu-west-1 "elasticbeanstalk.eu-west-1.amazonaws.com" :ap-southeast-1 "elasticbeanstalk.ap-southeast-1.amazonaws.com" :ap-southeast-2 "elasticbeanstalk.ap-southeast-2.amazonaws.com" @@ -88,15 +103,25 @@ (when-not (.doesBucketExist client bucket) (.createBucket client bucket region))) -(defn s3-upload-file [project filepath] - (let [bucket (s3-bucket-name project) - file (io/file filepath) - ep-desc (project-endpoint project s3-endpoints)] - (doto (AmazonS3Client. (credentials project)) - (.setEndpoint (:ep ep-desc)) - (create-bucket bucket (:region ep-desc)) - (.putObject bucket (.getName file) file)) - (println "Uploaded" (.getName file) "to S3 Bucket"))) +(defn s3-upload-file + ([project filepath] + (let [bucket (s3-bucket-name project) + file (io/file filepath) + ep-desc (project-endpoint project s3-endpoints)] + (doto (AmazonS3Client. (credentials project)) + (.setEndpoint (:ep ep-desc)) + (create-bucket bucket (:region ep-desc)) + (.putObject bucket (.getName file) file)) + (println "Uploaded" (.getName file) "to S3 Bucket"))) + ([project filepath filename] + (let [bucket (s3-bucket-name project) + file (io/file filepath) + ep-desc (project-endpoint project s3-endpoints)] + (doto (AmazonS3Client. (credentials project)) + (.setEndpoint (:ep ep-desc)) + (create-bucket bucket (:region ep-desc)) + (.putObject bucket filename file)) + (println "Uploaded" filename "to S3 Bucket")))) (defn- beanstalk-client [project] (doto (AWSElasticBeanstalkClient. (credentials project)) @@ -143,6 +168,7 @@ (defn env-var-options [project options] (for [[key value] (merge (default-env-vars project) + (-> project :aws :beanstalk :env) (:env options))] (ConfigurationOptionSetting. "aws:elasticbeanstalk:application:environment" @@ -151,19 +177,32 @@ key) value))) +(defn extra-options + [options] + (apply concat + (for [[namespace keyvals] options] + (for [[key value] keyvals] + (ConfigurationOptionSetting. namespace key value))))) + (defn create-environment [project env] (println (str "Creating '" (:name env) "' environment") "(this may take several minutes)") (.createEnvironment (beanstalk-client project) - (doto (CreateEnvironmentRequest.) - (.setApplicationName (app-name project)) - (.setEnvironmentName (:name env)) - (.setVersionLabel (app-version project)) - (.setOptionSettings (env-var-options project env)) - (.setCNAMEPrefix (:cname-prefix env)) - (.setSolutionStackName (or (-> project :aws :beanstalk :stack-name) - "32bit Amazon Linux running Tomcat 7"))))) + (let [request (CreateEnvironmentRequest.)] + (doto request + (.setApplicationName (app-name project)) + (.setEnvironmentName (:name env)) + (.setTier (app-tier project)) + (.setVersionLabel (app-version project)) + (.setOptionSettings (concat (env-var-options project env) + (extra-options (merge (-> project :aws :beanstalk :options) + (:options env))))) + (.setSolutionStackName (or (-> project :aws :beanstalk :stack-name) + "32bit Amazon Linux running Tomcat 7"))) + (if (= (.getName (.getTier request)) "WebServer") + (.setCNAMEPrefix request (:cname-prefix env))) + request))) (defn update-environment-settings [project env options] (.updateEnvironment