diff --git a/AUTHORS b/AUTHORS index 846df5522..2a69bcd20 100644 --- a/AUTHORS +++ b/AUTHORS @@ -32,3 +32,4 @@ - Vadim Spivak { email: vspivak@vmware.com } - Wilson Bilkovich { email: wilsonb@vmware.com } - Woon Jung { email: whj@vmware.com } +- Lucas Carlson { email: lucas@phpfog.com, company: "PHP Fog" } \ No newline at end of file diff --git a/cloud_controller/app/models/app.rb b/cloud_controller/app/models/app.rb index 15a6e826e..485200241 100644 --- a/cloud_controller/app/models/app.rb +++ b/cloud_controller/app/models/app.rb @@ -19,8 +19,8 @@ class App < ActiveRecord::Base AppStates = %w[STOPPED STARTED] PackageStates = %w[PENDING STAGED FAILED] - Runtimes = %w[ruby18 ruby19 java node erlangR14B02] - Frameworks = %w[sinatra rails3 java_web spring grails node otp_rebar lift unknown] + Runtimes = %w[ruby18 ruby19 java node php erlangR14B02] + Frameworks = %w[sinatra rails3 java_web spring grails node php otp_rebar lift unknown] validates_presence_of :name, :framework, :runtime diff --git a/cloud_controller/spec/fixtures/apps/phpinfo/source/index.php b/cloud_controller/spec/fixtures/apps/phpinfo/source/index.php new file mode 100644 index 000000000..147cebcdd --- /dev/null +++ b/cloud_controller/spec/fixtures/apps/phpinfo/source/index.php @@ -0,0 +1 @@ + diff --git a/cloud_controller/spec/staging/php_spec.rb b/cloud_controller/spec/staging/php_spec.rb new file mode 100644 index 000000000..db77cb27c --- /dev/null +++ b/cloud_controller/spec/staging/php_spec.rb @@ -0,0 +1,55 @@ +require 'spec_helper' + +describe "A PHP application being staged" do + before do + app_fixture :phpinfo + end + + it "is packaged with a startup script" do + stage :php do |staged_dir| + executable = '%VCAP_LOCAL_RUNTIME%' + start_script = File.join(staged_dir, 'startup') + start_script.should be_executable_file + webapp_root = staged_dir.join('app') + webapp_root.should be_directory + script_body = File.read(start_script) + script_body.should == <<-EXPECTED +#!/bin/bash +env > env.log +ruby resources/generate_apache_conf $VCAP_APP_PORT $HOME $VCAP_SERVICES 512m +cd apache +bash ./start.sh > ../logs/stdout.log 2> ../logs/stderr.log & +STARTED=$! +echo "$STARTED" >> ../run.pid +echo "#!/bin/bash" >> ../stop +echo "kill -9 $STARTED" >> ../stop +echo "kill -9 $PPID" >> ../stop +chmod 755 ../stop +wait $STARTED + EXPECTED + end + end + + it "requests the specified amount of memory from PHP" do + environment = { :resources => {:memory => 256} } + stage(:php, environment) do |staged_dir| + start_script = File.join(staged_dir, 'startup') + start_script.should be_executable_file + script_body = File.read(start_script) + script_body.should == <<-EXPECTED +#!/bin/bash +env > env.log +ruby resources/generate_apache_conf $VCAP_APP_PORT $HOME $VCAP_SERVICES 256m +cd apache +bash ./start.sh > ../logs/stdout.log 2> ../logs/stderr.log & +STARTED=$! +echo "$STARTED" >> ../run.pid +echo "#!/bin/bash" >> ../stop +echo "kill -9 $STARTED" >> ../stop +echo "kill -9 $PPID" >> ../stop +chmod 755 ../stop +wait $STARTED + EXPECTED + end + end +end \ No newline at end of file diff --git a/cloud_controller/staging/apache_common/apache.rb b/cloud_controller/staging/apache_common/apache.rb new file mode 100644 index 000000000..c625afcf9 --- /dev/null +++ b/cloud_controller/staging/apache_common/apache.rb @@ -0,0 +1,17 @@ +require 'nokogiri' +require 'fileutils' + +class Apache + def self.resource_dir + File.join(File.dirname(__FILE__), 'resources') + end + + def self.prepare(dir) + FileUtils.cp_r(resource_dir, dir) + output = %x[cd #{dir}; unzip -q resources/apache.zip] + raise "Could not unpack Apache: #{output}" unless $? == 0 + FileUtils.rm(File.join(dir, "resources", "apache.zip")) + dir + end + +end diff --git a/cloud_controller/staging/apache_common/resources/apache.zip b/cloud_controller/staging/apache_common/resources/apache.zip new file mode 100644 index 000000000..8d974348c Binary files /dev/null and b/cloud_controller/staging/apache_common/resources/apache.zip differ diff --git a/cloud_controller/staging/apache_common/resources/generate_apache_conf b/cloud_controller/staging/apache_common/resources/generate_apache_conf new file mode 100755 index 000000000..660562e3d --- /dev/null +++ b/cloud_controller/staging/apache_common/resources/generate_apache_conf @@ -0,0 +1,88 @@ +#!/usr/bin/env ruby + +# This script executes at app startup time because the +# actual appserver port must be in the apache conf. + +apache_port = ARGV[0] +base_dir = ARGV[1] +vcap_services = ARGV[2] +php_ram = ARGV[3] +exit 1 unless apache_port && base_dir && vcap_services && php_ram + +require 'erb' +output_path = "apache/sites-available/default" + +template = <<-ERB +> + ServerAdmin webmaster@localhost + DocumentRoot <%= base_dir %>/app + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + SetEnv VCAP_SERVICES <%= vcap_services %> + +ERB + +template = ERB.new(template) +File.open(File.expand_path(output_path), 'wb') do |file| + file.puts(template.result(binding)) +end + +system "cd apache/sites-enabled && ln -s ../sites-available/default 000-default && cd ../.." + +output_path = 'apache/envvars' + +template = <<-ERB +unset HOME +export APACHE_BASEDIR=<%= base_dir %>/apache +export APACHE_RUN_USER=www-data +export APACHE_RUN_GROUP=www-data +export APACHE_PID_FILE=<%= base_dir %>/run.pid +export APACHE_RUN_DIR=$APACHE_BASEDIR/run/apache2 +export APACHE_LOCK_DIR=$APACHE_BASEDIR/lock/apache2 +export APACHE_LOG_DIR=<%= base_dir %>/logs +export LANG=C +export PHP_INI_SCAN_DIR=$APACHE_BASEDIR/php +ERB + +template = ERB.new(template) +File.open(File.expand_path(output_path), 'wb') do |file| + file.puts(template.result(binding)) +end + +output_path = 'apache/ports.conf' + +template = <<-ERB +NameVirtualHost *:<%= apache_port %> +Listen <%= apache_port %> +ERB + +template = ERB.new(template) +File.open(File.expand_path(output_path), 'wb') do |file| + file.puts(template.result(binding)) +end + +output_path = 'apache/start.sh' + +template = <<-ERB +#!/bin/bash +source <%= base_dir %>/apache/envvars +/usr/sbin/apache2 -d <%= base_dir %>/apache -f <%= base_dir %>/apache/apache2.conf -D FOREGROUND +ERB + +template = ERB.new(template) +File.open(File.expand_path(output_path), 'wb') do |file| + file.puts(template.result(binding)) +end + +`cp -r /etc/php5/conf.d/* apache/php` + +output_path = 'apache/php/memory.ini' + +template = <<-ERB +memory_limit = <%= php_ram %> +ERB + +template = ERB.new(template) +File.open(File.expand_path(output_path), 'wb') do |file| + file.puts(template.result(binding)) +end diff --git a/cloud_controller/staging/manifests/php.yml b/cloud_controller/staging/manifests/php.yml new file mode 100644 index 000000000..b5ca07549 --- /dev/null +++ b/cloud_controller/staging/manifests/php.yml @@ -0,0 +1,22 @@ +--- +name: "php" +runtimes: + - "php": + description: "PHP 5" + version: "5.3" + executable: "php" + default: true +app_servers: + - "apache": + description: "Apache" + executable: "false" + default: true +detection: + - "*.php": true +staged_services: + - "name": "mysql" + "version": "*" + - "name": "postgresql" + "version": "*" + +# vim: filetype=yaml diff --git a/cloud_controller/staging/php/plugin.rb b/cloud_controller/staging/php/plugin.rb new file mode 100644 index 000000000..b4a8a8bcf --- /dev/null +++ b/cloud_controller/staging/php/plugin.rb @@ -0,0 +1,41 @@ +require File.expand_path('../../apache_common/apache', __FILE__) + +class PhpPlugin < StagingPlugin + def framework + 'php' + end + + def stage_application + Dir.chdir(destination_directory) do + create_app_directories + Apache.prepare(destination_directory) + copy_source_files + create_startup_script + end + end + + # The Apache start script runs from the root of the staged application. + def change_directory_for_start + "cd apache" + end + + def start_command + "bash ./start.sh" + end + + private + + def startup_script + vars = environment_hash + generate_startup_script(vars) do + <<-PHPEOF +env > env.log +ruby resources/generate_apache_conf $VCAP_APP_PORT $HOME $VCAP_SERVICES #{application_memory}m + PHPEOF + end + end + + def apache_server_root + File.join(destination_directory, 'apache') + end +end \ No newline at end of file diff --git a/cloud_controller/staging/php/stage b/cloud_controller/staging/php/stage new file mode 100644 index 000000000..35689245b --- /dev/null +++ b/cloud_controller/staging/php/stage @@ -0,0 +1,7 @@ +#!/usr/bin/env ruby +require File.expand_path('../../common', __FILE__) +plugin_class = StagingPlugin.load_plugin_for('php') +plugin_class.validate_arguments! +plugin_class.new(*ARGV).stage_application + +# vim: ts=2 sw=2 filetype=ruby diff --git a/dea/config/dea.yml b/dea/config/dea.yml index e8a596945..307d32638 100644 --- a/dea/config/dea.yml +++ b/dea/config/dea.yml @@ -43,6 +43,11 @@ runtimes: version: 1.6.0 version_flag: '-version' environment: + php: + executable: php + version: 5.3.[2-6] + version_flag: '-v' + environment: erlangR14B02: executable: /var/vcap/runtimes/erlang-R14B02/bin/erl version: ".* 5.8.3" diff --git a/docs/php.md b/docs/php.md new file mode 100644 index 000000000..a884a43bd --- /dev/null +++ b/docs/php.md @@ -0,0 +1,51 @@ +# PHP Support + +## Architecture + +PHP applications are deployed using Apache and mod_php. For each CloudFoundry instance of the application, an Apache instance is started. + +## Demo: Installing Wordpress ## +The Wordpress CMS can be run using CloudFoundry PHP support with very minimal changes. + +Steps to get the application to run: + +1. curl -O http://wordpress.org/latest.tar.gz +2. tar -xzf latest.tar.gz +3. rm latest.tar.gz +4. cd wordpress +5. echo " wp-salt.php +6. curl https://api.wordpress.org/secret-key/1.1/salt/ >> wp-salt.php +7. Create wp-config.php, and set it to: + + vmc push wordpresscf --url wordpresscf.vcap.me -n +9. vmc create-service mysql --bind wordpresscf +10. Visit http://wordpresscf.vcap.me and enjoy your Wordpress install! \ No newline at end of file diff --git a/setup/vcap_setup b/setup/vcap_setup index a9c0bcd9f..16d2550c4 100755 --- a/setup/vcap_setup +++ b/setup/vcap_setup @@ -255,6 +255,38 @@ if [[ $DEA == true || $ALL == true ]]; then rm -fr node-v$NODE_VERSION fi + # For PHP + if [[ $PLATFORM == 'Linux' ]]; then + apt-get install -qqy libpcre3 libpcre3-dev apache2 php5 php5-mysql php5-mysqli php5-pgsql php5-dev php-pear php5-gd libapr1 libaprutil1 php5-common php5-curl php5-mcrypt php5-imagick php5-xmlrpc zend-framework php5-imap + + /etc/init.d/apache2 stop + update-rc.d -f apache2 remove + + pear channel-discover pear.phpunit.de + pear channel-discover pear.symfony-project.com + pear channel-discover components.ez.no + pear install phpunit/PHPUnit + yes | pecl install apc + yes | pecl install memcache + yes | pecl install mongo + git clone git://github.com/owlient/phpredis.git + cd phpredis + phpize + ./configure + make && make install + cd .. + rm -rf phpredis + + echo "extension=apc.so" > /etc/php5/apache2/conf.d/cf.ini + echo "extension=memcache.so" >> /etc/php5/apache2/conf.d/cf.ini + echo "extension=mongo.so" >> /etc/php5/apache2/conf.d/cf.ini + echo "extension=mysql.so" >> /etc/php5/apache2/conf.d/cf.ini + echo "extensions=pgsql.so" >> /etc/php5/apache2/conf.d/cf.ini + + elif [[ $PLATFORM == 'MacOSX' ]]; then + echo -e "\033[31mFIXME: Implement for MacOSX \033[0m" + fi + ERLANG_VERSION=R14B02 if [ ! -d "/var/vcap/runtimes/erlang-$ERLANG_VERSION" ]; then @@ -293,6 +325,10 @@ if [[ $DEA == true || $ALL == true ]]; then node -v echo "" + echo -e "\nPHP should now be installed -->" + php -v + echo "" + echo -e "\nErlang should now be installed -->" erl -version echo ""