├── .envrc ├── .gitignore ├── .gitmodules ├── CONTRIBUTING.md ├── DEVELOP.md ├── FAQ.md ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── assets ├── keys │ └── key.pem └── scripts │ ├── common │ ├── health-check │ ├── reset │ ├── run │ ├── start │ └── stop ├── bin ├── build ├── compile-release ├── fetch-assets └── setup-packer ├── cla ├── pivotal-ccla.pdf └── pivotal-icla.pdf ├── manifest.yml ├── pcfdev-base.json ├── pcfdev.json ├── preseed └── preseed.cfg ├── src ├── api │ ├── main.go │ ├── main_suite_test.go │ ├── main_test.go │ └── usecases │ │ ├── uaa_credential_replacement.go │ │ ├── uaa_credential_replacement_test.go │ │ └── usecase_suite_test.go └── provisioner │ ├── .envrc │ ├── Dockerfile │ ├── assets │ ├── stub_server.go │ ├── tomcat-web-hsts-disabled.xml │ ├── tomcat-web-invalid.xml │ └── tomcat-web.xml │ ├── bin │ ├── generate-mocks │ └── tests │ ├── cert │ ├── cert.go │ ├── cert_suite_test.go │ └── cert_test.go │ ├── fs │ ├── fs.go │ ├── fs_suite_test.go │ └── fs_test.go │ ├── main.go │ ├── main_suite_test.go │ ├── main_test.go │ └── provisioner │ ├── commands │ ├── close_all_ports.go │ ├── commands_suite_test.go │ ├── configure_dnsmasq.go │ ├── configure_dnsmasq_test.go │ ├── configure_garden_dns.go │ ├── configure_garden_dns_test.go │ ├── disable_uaa_hsts.go │ ├── disable_uaa_hsts_test.go │ ├── open_port.go │ ├── replace_domain.go │ ├── replace_domain_test.go │ ├── setup_api.go │ ├── setup_api_test.go │ ├── setup_cfdot.go │ └── setup_cfdot_test.go │ ├── concrete_cmd_runner.go │ ├── concrete_cmd_runner_test.go │ ├── errors.go │ ├── mocks │ ├── cert.go │ ├── cmd_runner.go │ ├── command.go │ ├── fs.go │ └── ui.go │ ├── provisioner.go │ ├── provisioner_suite_test.go │ └── provisioner_test.go └── versions.json /.envrc: -------------------------------------------------------------------------------- 1 | export GOPATH=$PWD 2 | export PATH=$GOPATH/bin:$PATH 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.box 3 | 4 | /bosh/bin 5 | /bosh/pkg 6 | 7 | /output 8 | /pkg 9 | /releases 10 | /src/provisioner/provision 11 | /src/api/api 12 | /pcfdev-base 13 | 14 | /bin 15 | !/bin/build 16 | !/bin/compile-release 17 | !/bin/fetch-assets 18 | !/bin/setup-packer 19 | 20 | NERD_tree_* 21 | 22 | /*.iml 23 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "src/github.com/onsi/ginkgo"] 2 | path = src/github.com/onsi/ginkgo 3 | url = https://github.com/onsi/ginkgo 4 | [submodule "src/github.com/onsi/gomega"] 5 | path = src/github.com/onsi/gomega 6 | url = https://github.com/onsi/gomega 7 | [submodule "src/gopkg.in/yaml.v2"] 8 | path = src/gopkg.in/yaml.v2 9 | url = https://gopkg.in/yaml.v2 10 | [submodule "src/github.com/cppforlife/bosh-provisioner"] 11 | path = src/github.com/cppforlife/bosh-provisioner 12 | url = https://github.com/pcfdev-forks/bosh-provisioner 13 | [submodule "src/github.com/cppforlife/packer-bosh"] 14 | path = src/github.com/cppforlife/packer-bosh 15 | url = https://github.com/cppforlife/packer-bosh 16 | [submodule "src/provisioner/vendor/github.com/golang/mock"] 17 | path = src/provisioner/vendor/github.com/golang/mock 18 | url = https://github.com/pcfdev-forks/mock 19 | [submodule "src/provisioner/vendor/golang.org/x/net"] 20 | path = src/provisioner/vendor/golang.org/x/net 21 | url = https://github.com/golang/net 22 | [submodule "src/provisioner/vendor/golang.org/x/text"] 23 | path = src/provisioner/vendor/golang.org/x/text 24 | url = https://github.com/golang/text 25 | [submodule "src/api/vendor/gopkg.in/yaml.v2"] 26 | path = src/api/vendor/gopkg.in/yaml.v2 27 | url = https://github.com/go-yaml/yaml 28 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Everyone is encouraged to help improve this project. 4 | 5 | Please submit pull requests against the **master branch**. 6 | 7 | Limited access to our [Concourse](http://concourse.ci) CI system is available at [ci.pcfdev.io](https://ci.pcfdev.io). 8 | 9 | Here are some ways *you* can contribute: 10 | 11 | * by using nightly builds and prerelease versions 12 | * by reporting bugs 13 | * by suggesting new features 14 | * by writing or editing documentation 15 | * by writing specifications 16 | * by writing code (**no patch is too small**: fix typos, add comments, clean up inconsistent whitespace) 17 | * by refactoring code 18 | * by closing [issues](https://github.com/pivotal-cf/pcfdev/issues) 19 | * by reviewing patches 20 | 21 | ## Submitting an Issue 22 | 23 | We use the [GitHub issue tracker](https://github.com/pivotal-cf/pcfdev/issues) to track bugs and features. 24 | Before submitting a bug report or feature request, check to make sure it hasn't already been submitted. 25 | You can indicate support for an existing issue by voting it up. 26 | When submitting a bug report, please include a [Gist](http://gist.github.com/) that includes a stack trace and any 27 | details that may be necessary to reproduce the bug including the PCF Dev version. 28 | 29 | ## Submitting a Pull Request 30 | 31 | 1. Propose a change by opening an issue. 32 | 2. Fork the project. 33 | 3. Create a topic branch. 34 | 4. Implement your feature or bug fix. 35 | 5. Commit and push your changes. 36 | 6. Submit a pull request. 37 | 38 | ## Copyright 39 | 40 | See [LICENSE](LICENSE) for details. 41 | Copyright (c) 2015 [Pivotal Software, Inc](http://www.pivotal.io/). 42 | -------------------------------------------------------------------------------- /DEVELOP.md: -------------------------------------------------------------------------------- 1 | # PCF Dev Development 2 | 3 | To develop PCF Dev you will need to have the following tools installed: 4 | 5 | - [Packer](https://www.packer.io) v0.9.0+ 6 | - [Virtualbox](https://www.virtualbox.org/) 5.0+ 7 | - [Go](https://golang.org) 1.6.1+ 8 | - [jq](https://stedolan.github.io/jq/) 1.5+ 9 | - [spiff](https://github.com/cloudfoundry-incubator/spiff) 1.0.6+ 10 | 11 | ## Clone the PCF Dev source 12 | 13 | ```bash 14 | git clone --recursive https://github.com/pivotal-cf/pcfdev.git 15 | ``` 16 | 17 | ### Building a PCF Dev Box 18 | 19 | To build an OSS-only PCF Dev OVA, run: 20 | 21 | ```bash 22 | ./bin/build -only=virtualbox-iso # pass -debug for more output 23 | ``` 24 | 25 | > Note: Support for VMware Fusion/Workstation has been discontinued. Support for AWS is temporarily suspended until a commercial version of PCF Dev becomes available from the AWS Marketplace. 26 | 27 | ### Deploying a locally-built PCF Dev box 28 | 29 | After the PCF Dev box has been built, you need to use the PCF Dev CLI to launch the OVA. This will disable various checks for system requirements such as system memory. More information on installation of the CLI can be found [here](http://docs.pivotal.io/pcf-dev/index.html#installing). 30 | 31 | ```bash 32 | cf dev start -o output/output-virtualbox-iso/oss-v0.ova 33 | ``` 34 | 35 | ### Customizing PCF Dev 36 | 37 | Our build tool has the ability to build compiled releases or releases from source. By default, it will try to build releases that have been compiled by the PCF Dev team. If you have a *non-compiled* release that present on your workstation, you can configure the build to use it using the **path:** key. Simply edit the versions.json file at the root of this repo like `"cf" :` is done below: 38 | 39 | ```json 40 | { 41 | "releases": { 42 | "cf" : { 43 | "path": "/Users/pivotal/[path-to-release-folder]" 44 | }, 45 | "diego" : { 46 | "version": "v0.1480.0", 47 | "sha1": "bfd87d6ef08458e19e2abc6fc6888ba9ac29fde6", 48 | "source_location": "https://github.com/cloudfoundry/diego-release", 49 | "compiled_release_url" : "https://s3.amazonaws.com/pcfdev/compiled-releases/diego-8d1450da393eae98d565b9e0e7154c742e75e513.tgz" 50 | }, 51 | ``` 52 | 53 | If you would like to a different *compiled* release than is offered in the versions.json, simply make sure that the appropriate keys are modified. 54 | 55 | > Note: any necessary manifest changes can be done to the manifest.yml file at the root of this repo for a successful build. 56 | 57 | ## Contributing 58 | 59 | If you are interested in contributing to PCF Dev, please refer to [CONTRIBUTING](CONTRIBUTING.md). 60 | 61 | ## Copyright 62 | 63 | See [LICENSE](LICENSE) for details. 64 | Copyright (c) 2015 [Pivotal Software, Inc](http://www.pivotal.io/). 65 | -------------------------------------------------------------------------------- /FAQ.md: -------------------------------------------------------------------------------- 1 | # Frequently Asked Questions 2 | 3 | ## General Questions 4 | 5 | ### What is PCF Dev? 6 | 7 | PCF Dev is a new distribution of Cloud Foundry designed to run on a developer’s laptop or workstation. PCF Dev gives application developers the full Cloud Foundry experience in a lightweight, easy to install package. 8 | 9 | ### Who should use PCF Dev? 10 | 11 | PCF Dev is intended for application developers who wish to develop and debug their application locally on a full-featured Cloud Foundry. PCF Dev is also an excellent getting started environment for developers interested in learning and exploring Cloud Foundry. 12 | 13 | ### If my application runs on PCF Dev, will it run on PCF? 14 | 15 | Yes. PCF Dev is designed to mirror PCF exactly. If your application runs on PCF Dev, it will run on PCF with no modification in almost all cases. 16 | 17 | ## Troubleshooting 18 | 19 | ### Why does `cf api` and/or `cf login` fail with an "Invalid SSL Cert" error? 20 | 21 | PCF Dev comes with a self-signed SSL certificate for its API and requires the `--skip-ssl-validation` option. This also applies to the Spring Boot Dashboard, which requires the checkbox "Self-signed" in order to connect. 22 | 23 | ``` 24 | ○ → cf api api.local.cfdev.sh 25 | Setting api endpoint to api.local.cfdev.sh... 26 | FAILED 27 | Invalid SSL Cert for api.local.cfdev.sh 28 | TIP: Use 'cf api --skip-ssl-validation' to continue with an insecure API endpoint 29 | ``` 30 | 31 | ## Networking 32 | 33 | ### Container-to-router 34 | 35 | This is traffic from the app container to the gorouter. It is enabled by default. This allows apps to communicate with each other by using the routes published by gorouter. 36 | 37 | ### Container-to-guest 38 | 39 | This is traffic from the app container to the virtual machine in which PCF Dev is running. It is enabled by default. This may be useful if you want to run other services inside of the guest virtual machine for your applications to use, but doing so is not encouraged. Instead, extra services should be run on the host (see below). The IP address of the guest is `192.168.11.11` or `local.cfdev.sh` (unless this address is already in use). 40 | 41 | ### Container-to-host 42 | 43 | This is traffic from the app container to the host on which the virtual machine is running. It is enabled by default. This can be used to run services on your host that are available to your apps in PCF Dev. The IP address of the host accessible to the app is `192.168.11.1` or `host.cfdev.sh` (unless this address is already in use). For example, in order to connect your app to a MongoDB instance running on the host on port `27017`, run the following commands: 44 | 45 | ```bash 46 | cf create-user-provided-service my-mongo-db -p '{ "uri": "mongodb://:@host.cfdev.sh:27017/" }' 47 | cf bind-service my-mongo-db 48 | cf restage 49 | ``` 50 | 51 | ### Container-to-external 52 | 53 | This is traffic from the app container to a destination external to the host. It allows your application to reach the internet. Traffic to public and private IP addresses is enabled by default in PCF Dev. You may remove the `all_pcfdev` security group to restrict access to only public IP addresses, as a default PCF installation would be configured. 54 | 55 | ### Container-to-container 56 | 57 | This is traffic directly between two containers in the same PCF Dev deployment. It is useful for running applications that must communicate with each other but do not need or want a publicly-accessible route. It is not enabled and will not be available until it is supported in Pivotal Cloud Foundry. 58 | 59 | # Copyright 60 | 61 | See [LICENSE](LICENSE) for details. 62 | Copyright (c) 2015 [Pivotal Software, Inc](http://www.pivotal.io/). 63 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'bosh_cli', '>=1.3094.0' 4 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (2.3.2) 5 | aws-sdk (1.60.2) 6 | aws-sdk-v1 (= 1.60.2) 7 | aws-sdk-v1 (1.60.2) 8 | json (~> 1.4) 9 | nokogiri (>= 1.4.4) 10 | blobstore_client (1.3094.0) 11 | aws-sdk (= 1.60.2) 12 | bosh_common (~> 1.3094.0) 13 | fog (~> 1.31.0) 14 | fog-aws (<= 0.1.1) 15 | httpclient (= 2.4.0) 16 | multi_json (~> 1.1) 17 | bosh-template (1.3094.0) 18 | semi_semantic (~> 1.1.0) 19 | bosh_cli (1.3094.0) 20 | blobstore_client (~> 1.3094.0) 21 | bosh-template (~> 1.3094.0) 22 | bosh_common (~> 1.3094.0) 23 | cf-uaa-lib (~> 3.2.1) 24 | highline (~> 1.6.2) 25 | httpclient (= 2.4.0) 26 | json_pure (~> 1.7) 27 | minitar (~> 0.5.4) 28 | net-scp (~> 1.1.0) 29 | net-ssh (>= 2.2.1) 30 | net-ssh-gateway (~> 1.2.0) 31 | netaddr (~> 1.5.0) 32 | progressbar (~> 0.9.0) 33 | sshkey (~> 1.7.0) 34 | terminal-table (~> 1.4.3) 35 | bosh_common (1.3094.0) 36 | logging (~> 1.8.2) 37 | semi_semantic (~> 1.1.0) 38 | builder (3.2.2) 39 | cf-uaa-lib (3.2.4) 40 | multi_json 41 | excon (0.45.4) 42 | fission (0.5.0) 43 | CFPropertyList (~> 2.2) 44 | fog (1.31.0) 45 | fog-atmos 46 | fog-aws (~> 0.0) 47 | fog-brightbox (~> 0.4) 48 | fog-core (~> 1.30) 49 | fog-ecloud 50 | fog-google (>= 0.0.2) 51 | fog-json 52 | fog-local 53 | fog-powerdns (>= 0.1.1) 54 | fog-profitbricks 55 | fog-radosgw (>= 0.0.2) 56 | fog-riakcs 57 | fog-sakuracloud (>= 0.0.4) 58 | fog-serverlove 59 | fog-softlayer 60 | fog-storm_on_demand 61 | fog-terremark 62 | fog-vmfusion 63 | fog-voxel 64 | fog-xml (~> 0.1.1) 65 | ipaddress (~> 0.5) 66 | nokogiri (~> 1.5, >= 1.5.11) 67 | fog-atmos (0.1.0) 68 | fog-core 69 | fog-xml 70 | fog-aws (0.1.1) 71 | fog-core (~> 1.27) 72 | fog-json (~> 1.0) 73 | fog-xml (~> 0.1) 74 | ipaddress (~> 0.8) 75 | fog-brightbox (0.9.0) 76 | fog-core (~> 1.22) 77 | fog-json 78 | inflecto (~> 0.0.2) 79 | fog-core (1.32.1) 80 | builder 81 | excon (~> 0.45) 82 | formatador (~> 0.2) 83 | mime-types 84 | net-scp (~> 1.1) 85 | net-ssh (>= 2.1.3) 86 | fog-ecloud (0.3.0) 87 | fog-core 88 | fog-xml 89 | fog-google (0.1.1) 90 | fog-core 91 | fog-json 92 | fog-xml 93 | fog-json (1.0.2) 94 | fog-core (~> 1.0) 95 | multi_json (~> 1.10) 96 | fog-local (0.2.1) 97 | fog-core (~> 1.27) 98 | fog-powerdns (0.1.1) 99 | fog-core (~> 1.27) 100 | fog-json (~> 1.0) 101 | fog-xml (~> 0.1) 102 | fog-profitbricks (0.0.5) 103 | fog-core 104 | fog-xml 105 | nokogiri 106 | fog-radosgw (0.0.4) 107 | fog-core (>= 1.21.0) 108 | fog-json 109 | fog-xml (>= 0.0.1) 110 | fog-riakcs (0.1.0) 111 | fog-core 112 | fog-json 113 | fog-xml 114 | fog-sakuracloud (1.3.3) 115 | fog-core 116 | fog-json 117 | fog-serverlove (0.1.2) 118 | fog-core 119 | fog-json 120 | fog-softlayer (0.4.7) 121 | fog-core 122 | fog-json 123 | fog-storm_on_demand (0.1.1) 124 | fog-core 125 | fog-json 126 | fog-terremark (0.1.0) 127 | fog-core 128 | fog-xml 129 | fog-vmfusion (0.1.0) 130 | fission 131 | fog-core 132 | fog-voxel (0.1.0) 133 | fog-core 134 | fog-xml 135 | fog-xml (0.1.2) 136 | fog-core 137 | nokogiri (~> 1.5, >= 1.5.11) 138 | formatador (0.2.5) 139 | highline (1.6.21) 140 | httpclient (2.4.0) 141 | inflecto (0.0.2) 142 | ipaddress (0.8.0) 143 | json (1.8.3) 144 | json_pure (1.8.2) 145 | little-plugger (1.1.4) 146 | logging (1.8.2) 147 | little-plugger (>= 1.1.3) 148 | multi_json (>= 1.8.4) 149 | mime-types (2.6.2) 150 | mini_portile (0.6.2) 151 | minitar (0.5.4) 152 | multi_json (1.11.2) 153 | net-scp (1.1.2) 154 | net-ssh (>= 2.6.5) 155 | net-ssh (3.0.1) 156 | net-ssh-gateway (1.2.0) 157 | net-ssh (>= 2.6.5) 158 | netaddr (1.5.0) 159 | nokogiri (1.6.6.2) 160 | mini_portile (~> 0.6.0) 161 | progressbar (0.9.2) 162 | semi_semantic (1.1.0) 163 | sshkey (1.7.0) 164 | terminal-table (1.4.5) 165 | 166 | PLATFORMS 167 | ruby 168 | 169 | DEPENDENCIES 170 | bosh_cli (>= 1.3094.0) 171 | 172 | BUNDLED WITH 173 | 1.10.5 174 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2016 Pivotal Software Inc. 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Update on PCF Dev 2 | 3 | This is the deprecated version of PCF Dev - please visit the current Github repository https://github.com/cloudfoundry-incubator/cfdev for the latest updates 4 | 5 | ************************************************************* 6 | 7 | # PCF Dev 8 | 9 | PCF Dev is a new distribution of Cloud Foundry designed to run on a developer’s laptop or workstation. PCF Dev gives application developers the full Cloud Foundry experience in a lightweight, easy to install package. PCF Dev is intended for application developers who wish to develop and debug their application locally on a full-featured Cloud Foundry. PCF Dev is also an excellent getting started environment for developers interested in learning and exploring Cloud Foundry. 10 | 11 | > More information about the project can be found on the [FAQ](FAQ.md#general-questions). 12 | 13 | ## Open Source 14 | 15 | This repository contains source code that allows developers to build an open source version of PCF Dev that only contains the Elastic Runtime and the CF MySQL Broker. The binary distribution of PCF Dev that is available on the [Pivotal Network](https://network.pivotal.io/) contains other PCF components (such as the Redis, RabbitMQ and Spring Cloud Services marketplace services as well as Apps Manager) that are not available in this repository. 16 | 17 | However, we encourage you to leave any feedback or issues you may encounter regarding the full, binary distribution of PCF Dev in [this repository's Github issues](https://github.com/pivotal-cf/pcfdev/issues). 18 | 19 | ## Install 20 | 21 | 1. Download the latest `pcfdev-VERSION-PLATFORM.zip` from the [Pivotal Network](https://network.pivotal.io/). 22 | 1. Unzip the zip file and navigate to its containing folder using PowerShell or a Unix terminal. 23 | 1. Run the extracted binary. 24 | 1. Run `cf dev start`. 25 | 26 | > Check out the [documentation](https://docs.pivotal.io/pcf-dev/) for more information. Running `cf dev help` will display an overview of PCF Dev VM management commands. 27 | 28 | ### Prerequisites 29 | 30 | * [CF CLI](https://github.com/cloudfoundry/cli) 31 | * [VirtualBox](https://www.virtualbox.org/): 5.0+ 32 | * Internet connection (or [Dnsmasq](http://www.thekelleys.org.uk/dnsmasq/doc.html) or [Acrylic](http://mayakron.altervista.org/wikibase/show.php?id=AcrylicHome)) required for wildcard DNS resolution 33 | 34 | ### Using the Cloud Foundry CLI Plugin 35 | 36 | Follow the instructions provided at the end of `cf dev start` to connect to PCF Dev: 37 | 38 | ``` 39 | Downloading VM... 40 | Progress: |====================>| 100% 41 | VM downloaded 42 | Importing VM... 43 | Starting VM... 44 | Provisioning VM... 45 | Waiting for services to start... 46 | 40 out of 40 running 47 | _______ _______ _______ ______ _______ __ __ 48 | | || || | | | | || | | | 49 | | _ || || ___| | _ || ___|| |_| | 50 | | |_| || || |___ | | | || |___ | | 51 | | ___|| _|| ___| | |_| || ___|| | 52 | | | | |_ | | | || |___ | | 53 | |___| |_______||___| |______| |_______| |___| 54 | is now running. 55 | To begin using PCF Dev, please run: 56 | cf login -a https://api.local.pcfdev.io --skip-ssl-validation 57 | Admin user => Email: admin / Password: admin 58 | Regular user => Email: user / Password: pass 59 | ``` 60 | 61 | > The `local.pcfdev.io` domain may differ slightly for your PCF Dev instance. 62 | 63 | To stage a simple app on PCF Dev, `cd` into the app directory and run `cf push `. 64 | 65 | See cf documentation for information on [deploying apps](http://docs.cloudfoundry.org/devguide/deploy-apps/) and [attaching services](http://docs.cloudfoundry.org/devguide/services/). 66 | 67 | ### Using a customized PCF Dev OVA 68 | 69 | Specify the path to the custom built OVA with the `-o flag` to the `cf dev start` command. 70 | 71 | ``` 72 | $ cf dev start -o /path/to/custom/ova 73 | Importing VM... 74 | Starting VM... 75 | Provisioning VM.. 76 | ... 77 | ``` 78 | 79 | To build a custom PCF Dev OVA, please see our [DEVELOP](./DEVELOP.md) Documentation. 80 | 81 | ## Uninstall 82 | 83 | To temporarily stop PCF Dev run `cf dev stop`. 84 | 85 | To destroy your PCF Dev VM run `cf dev destroy`. 86 | 87 | To uninstall the PCF Dev cf CLI plugin run `cf uninstall-plugin pcfdev` 88 | 89 | ## Contributing 90 | 91 | If you are interested in contributing to PCF Dev, please refer to the [contributing guidelines](CONTRIBUTING.md) and [development instructions](DEVELOP.md). 92 | 93 | # Copyright 94 | 95 | See [LICENSE](LICENSE) for details. 96 | Copyright (c) 2016 [Pivotal Software, Inc](http://www.pivotal.io/). 97 | 98 | PCF Dev uses a version of Monit that can be found [here](https://github.com/pivotal-cf/pcfdev-monit), under the GPLv3 license. 99 | -------------------------------------------------------------------------------- /assets/keys/key.pem: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC+eYPNeh/gldZCxXCXBmlAUWgJdRTTNClCAx8yV26/hdLjbZGg1StjwOVkUupxpG+5ghm7mxnz6xQUJH+AdzZVXrHaixMl4jwf8RrsTu7OJj/zDjoKNUX9vH6SZzTifUfCigixHJQwOoA+hCJCIbrzh0z2Vvvtn+G/adEFhNwdSl+RaGfuNSh04bTOS7CrPrmPGE2sE192+PMoh+sE5E6RKivWcR0tLR58OA/MN1SEH3LNrKrmxYi4qebp1HxXJTac4nuegqoDdSEUnjdCjZPRdeZiKy/L3B3BAT0+P646IJzwQHv3kUHwVnfBwI3COcEZPwctUOBl/JjyagjJyx5P pcfdev insecure key 2 | -------------------------------------------------------------------------------- /assets/scripts/common: -------------------------------------------------------------------------------- 1 | monit="/var/vcap/bosh/bin/monit" 2 | 3 | monit_summary() { while output=$($monit summary 2>&1) && [[ $output = *"error connecting to the monit daemon"* ]]; do sleep 1; done; echo "$output"; } 4 | total_services() { monit_summary | grep -E '^(Process|File|System)' | wc -l; } 5 | started_service_count() { started_services | wc -l; } 6 | started_services() { monit_summary | grep -E '(running|accessible|Timestamp changed|PID changed)' | awk '{print $2}' | tr -d "'"; } 7 | stopped_service_count() { stopped_services | wc -l; } 8 | stopped_services() { monit_summary | grep 'not monitored' | grep -v 'pending' | awk '{print $2}' | tr -d "'"; } 9 | cc_status_code() { curl -s -I -o /dev/null -w %{http_code} -H "Host: api.$1" http://localhost/v2/info; } 10 | available_buildpacks() { cf curl /v2/buildpacks | jq '.resources | map(select(.entity.filename | length > 0)) | length'; } 11 | wait_for_monit_to_start() { while [[ $(total_services) = 0 ]]; do sleep 1; done; } 12 | wait_for_monit_to_stop() { while [[ $(total_services) != 0 ]]; do sleep 1; done; } 13 | wait_for_services_to_stop() { while [[ $(stopped_service_count) -lt $(total_services) ]]; do sleep 1; done; } 14 | 15 | start_services() { 16 | for service in $@; do 17 | $monit start $service 18 | done 19 | 20 | for service in $@; do 21 | while ! monit_summary | grep $service | grep -q running; do sleep 1; done; 22 | done 23 | } 24 | 25 | start_remaining() { 26 | for service in $(stopped_services); do 27 | $monit start $service 28 | done 29 | } 30 | 31 | move_monit_control_files() { 32 | find /var/vcap/monit/job -name '*.monitrc' | grep -v pcfdev | xargs rm -f 33 | 34 | for file in $(ls /var/vcap/jobs/*/monit) 35 | do 36 | local dirname=$(dirname $file) 37 | local jobname=$(basename $dirname) 38 | local destination_path="/var/vcap/monit/job/$(next_monit_index)-${jobname}.monitrc" 39 | cp $file $destination_path 40 | sed -i '/group vcap/a\ \ mode manual' $destination_path 41 | done 42 | } 43 | 44 | exists_in_monit() { 45 | grep $1 /var/vcap/monit/job/* -q 46 | } 47 | 48 | find_monit_file() { 49 | grep $1 /var/vcap/jobs/*/monit -l 50 | } 51 | 52 | next_monit_index(){ 53 | printf %04d $(ls /var/vcap/monit/job/ | wc -l) 54 | } 55 | 56 | restart_service() { 57 | local service=$1 58 | $monit restart $service 59 | while ! monit_summary | grep $service | grep -v pending | grep -q running; do sleep 1; done; 60 | } 61 | 62 | update_service_broker() { 63 | local broker_name=$1 64 | local broker_url=$2 65 | echo "Service broker already exists - updating broker" 66 | cf update-service-broker ${broker_name} admin admin ${broker_url} 67 | } 68 | 69 | create_service_broker() { 70 | local broker_name=$1 71 | local broker_url=$2 72 | echo "Service broker does not exist - creating broker" 73 | cf create-service-broker ${broker_name} admin admin ${broker_url} 74 | } 75 | 76 | setup_service_broker() { 77 | local broker_name=$1 78 | local broker_url=$2 79 | while [[ $(curl -s -o /dev/null -u admin:admin -w %{http_code} ${broker_url}/v2/catalog) != 200 ]]; do 80 | sleep 1 81 | done 82 | create_service_broker $broker_name $broker_url || update_service_broker $broker_name $broker_url 83 | cf enable-service-access ${broker_name} 84 | } 85 | -------------------------------------------------------------------------------- /assets/scripts/health-check: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | source /var/pcfdev/common 6 | 7 | domain=$(cat /var/pcfdev/domain) 8 | status_code=$(cc_status_code ${domain}) 9 | service_count=$(total_services) 10 | 11 | if [[ ${status_code} == "200" && -f "/run/pcfdev-healthcheck" ]] 12 | then 13 | echo -n 'ok' 14 | exit 0 15 | fi 16 | 17 | exit 1 18 | -------------------------------------------------------------------------------- /assets/scripts/reset: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | 6 | exec 3>&1 4>&2 >>/var/pcfdev/reset.log 2>&1 7 | set -x 8 | 9 | source /var/pcfdev/common 10 | 11 | if status runsvdir | grep -q 'start/running'; then 12 | wait_for_monit_to_start 13 | 14 | $monit stop all 15 | 16 | >&3 echo "Waiting for services to stop..." 17 | wait_for_services_to_stop 18 | 19 | stop runsvdir 20 | 21 | wait_for_monit_to_stop 22 | fi 23 | 24 | >&3 echo "Services stopped. Resetting data..." 25 | rm -rf /var/vcap/nfs /var/vcap/data/{compile,tmp} 26 | 27 | >&3 echo "Deleting stale state files not needed for mysql migrations" 28 | find /var/vcap/store/* -maxdepth 0 ! -name "*.sql" | xargs rm -rf 29 | 30 | set +x 31 | exec 1>&3 2>&4 32 | -------------------------------------------------------------------------------- /assets/scripts/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | if [[ -z $1 ]] || [[ -z $2 ]]; then 6 | >&2 echo "Usage:" 7 | >&2 echo -e "\t$0 " 8 | exit 1 9 | fi 10 | 11 | exec 3>&1 4>&2 >>/var/pcfdev/provision.log 2>&1 12 | set -x 13 | 14 | rm -f /run/pcfdev-healthcheck 15 | 16 | source /var/pcfdev/common 17 | 18 | domain=$1 19 | public_ip=$2 20 | services=$3 21 | registries=$4 22 | 23 | if [[ -x /var/pcfdev/pre-run ]]; then 24 | /var/pcfdev/pre-run "$domain" "$services" 25 | fi 26 | 27 | 28 | >&3 /var/pcfdev/stop 29 | 30 | rm -f /var/vcap/bosh/agent_state.json 31 | 32 | # Add self-signed cert to existing trusted certs 33 | if [[ ! -f /var/pcfdev/trusted_ca.crt ]]; then 34 | cp /var/vcap/jobs/cflinuxfs2-rootfs-setup/config/certs/trusted_ca.crt /var/pcfdev/trusted_ca.crt 35 | fi 36 | cat /var/pcfdev/trusted_ca.crt /var/vcap/jobs/gorouter/config/cert.pem > /var/vcap/jobs/cflinuxfs2-rootfs-setup/config/certs/trusted_ca.crt 37 | /var/vcap/jobs/cflinuxfs2-rootfs-setup/bin/pre-start 38 | 39 | # Replace the old system domain / IP with the new system domain / IP 40 | 41 | config_files=$(find /var/vcap/jobs/*/ /var/vcap/monit/job -type f) 42 | 43 | old_domain=$(cat /var/pcfdev/domain) 44 | perl -p -i -e "s/\\Q$old_domain\\E/$domain/g" $config_files 45 | echo "$domain" > /var/pcfdev/domain 46 | 47 | sed -i '/\/proc\/sys\/net\/ipv4\/ip_local_port_range/d' /var/vcap/jobs/gorouter/bin/gorouter_ctl 48 | 49 | # Point garden at HTTP_PROXY and HTTPS_PROXY 50 | pcfdev_http_proxy=$(. /etc/environment && echo "$HTTP_PROXY") 51 | pcfdev_https_proxy=$(. /etc/environment && echo "$HTTPS_PROXY") 52 | if [[ ! -z $pcfdev_http_proxy || ! -z $pcfdev_https_proxy ]]; then 53 | perl -p -i -e "s/^export.*(http|https|no)_proxy=.*\n//i" /var/vcap/jobs/garden/bin/garden_ctl 54 | result=$(grep -i '\(http\|https\|no\)_proxy=' /etc/environment | xargs -I {} echo 'export {}\n' | tr -d '\n') 55 | if [[ -n "$result" ]]; then 56 | sed -i "/set -x/a$result" /var/vcap/jobs/garden/bin/garden_ctl 57 | fi 58 | fi 59 | 60 | # Fix CC temporary directory 61 | mkdir -p /tmp/cc_tmp 62 | chgrp vcap /tmp/cc_tmp 63 | chmod 1777 /tmp/cc_tmp 64 | cc_worker_ctl=/var/vcap/jobs/cloud_controller_ng/bin/cloud_controller_worker_ctl 65 | grep -q 'export TMPDIR=\/tmp\/cc_tmp' "$cc_worker_ctl" || sed -i '2iexport TMPDIR=/tmp/cc_tmp' "$cc_worker_ctl" 66 | 67 | # Add registries to insecure_docker_registries 68 | if [[ -n "$registries" ]]; then 69 | perl -p -i -e "s/.*-insecureDockerRegistry=.*\n//i" /var/vcap/jobs/garden/bin/garden_ctl 70 | 71 | insecureDockerRegistryOptions="" 72 | for registry in $(echo "$registries" | tr ',' '\n'); do 73 | insecureDockerRegistryOptions="${insecureDockerRegistryOptions}--insecure-docker-registry=$registry " 74 | done 75 | 76 | if [[ -n "$insecureDockerRegistryOptions" ]]; then 77 | sed -i "\|/var/vcap/packages/guardian/bin/guardian|a$insecureDockerRegistryOptions \\\\" /var/vcap/jobs/garden/bin/garden_ctl 78 | fi 79 | 80 | stager_config=$(jq \ 81 | --arg registries "$registries" \ 82 | '.insecure_docker_registries=($registries | split(","))' \ 83 | /var/vcap/jobs/stager/config/stager_config.json 84 | ) 85 | echo "$stager_config" > /var/vcap/jobs/stager/config/stager_config.json 86 | fi 87 | 88 | >&3 2>&4 /var/pcfdev/start "$domain" 89 | 90 | cf api "https://api.$domain" --skip-ssl-validation 91 | cf auth admin admin 92 | 93 | cf create-org pcfdev-org 94 | cf create-space pcfdev-space -o pcfdev-org 95 | cf target -o pcfdev-org -s pcfdev-space 96 | 97 | cf create-user user pass 98 | cf set-org-role user pcfdev-org OrgManager 99 | cf set-space-role user pcfdev-org pcfdev-space SpaceManager 100 | cf set-space-role user pcfdev-org pcfdev-space SpaceDeveloper 101 | cf set-space-role user pcfdev-org pcfdev-space SpaceAuditor 102 | 103 | [[ $domain != $old_domain ]] && cf delete-shared-domain "$old_domain" -f 104 | 105 | if [[ $(cf curl /v2/shared_domains | jq -r ".resources[] | select(.entity.name == \"tcp.$domain\").entity.name") == "" ]] 106 | then 107 | cf create-shared-domain tcp.$domain --router-group default-tcp 108 | quota_definition_url=$(cf curl /v2/quota_definitions?q=name:default | jq -r .resources[0].metadata.url) 109 | cf curl $quota_definition_url -X PUT -d '{"total_routes": 100}' 110 | cf curl $quota_definition_url -X PUT -d '{"total_reserved_route_ports": -1}' 111 | fi 112 | 113 | cf enable-feature-flag diego_docker 114 | 115 | if [[ ! -z $pcfdev_http_proxy ]] || [[ ! -z $pcfdev_https_proxy ]]; then 116 | proxy_environment_variables=$( 117 | echo -n "{" 118 | grep -i '\(http\|https\|no\)_proxy=' /etc/environment | sed -e 's/\(.*\)=\(.*\)/"\1": "\2"/' | paste -sd "," - 119 | echo -n "}" 120 | ) 121 | cf set-staging-environment-variable-group "$proxy_environment_variables" 122 | cf set-running-environment-variable-group "$proxy_environment_variables" 123 | fi 124 | 125 | while [[ $(available_buildpacks) -lt 8 ]]; do 126 | sleep 1 127 | done 128 | 129 | setup_service_broker p-mysql http://mysql-broker.$domain 130 | setup_service_broker local-volume http://localbroker.$domain 131 | 132 | if [[ -x /var/pcfdev/post-run ]]; then 133 | /var/pcfdev/post-run "$domain" "$services" 134 | fi 135 | 136 | set +x 137 | exec 1>&3 2>&4 138 | -------------------------------------------------------------------------------- /assets/scripts/start: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | if [[ -z $1 ]]; then 6 | >&2 echo "Usage:" 7 | >&2 echo -e "\t$0 " 8 | exit 1 9 | fi 10 | 11 | exec 3>&1 4>&2 >>/var/pcfdev/provision.log 2>&1 12 | set -x 13 | source /var/pcfdev/common 14 | 15 | domain=$1 16 | 17 | >&3 echo "Waiting for services to start..." 18 | 19 | move_monit_control_files 20 | start runsvdir 21 | wait_for_monit_to_start 22 | 23 | /var/vcap/jobs/mysql/bin/pre-start 24 | 25 | start_services mariadb_ctrl galera-healthcheck 26 | 27 | while ! nc -z 127.0.0.1 3306; do 28 | sleep 1 29 | done 30 | 31 | /var/vcap/jobs/consul_agent/bin/pre-start 32 | 33 | start_services consul_agent 34 | 35 | for script in $(ls /var/vcap/jobs/*/bin/pre-start | grep -v '/mysql/' | grep -v '/consul_agent/'); do 36 | $script 37 | done 38 | 39 | start_services garden etcd uaa 40 | 41 | while [[ ! /var/vcap/jobs/uaa/bin/dns_health_check ]]; do 42 | sleep 1 43 | done 44 | 45 | start_services bbs 46 | 47 | start_remaining 48 | 49 | total=$(total_services) 50 | 51 | while started=$(started_service_count) && [[ $started -lt $total ]]; do 52 | counter=$(($counter + 1)) 53 | [[ $(($counter % 60)) = 0 ]] && >&3 echo "$started out of $total running" 54 | sleep 1 55 | done 56 | >&3 echo "$total out of $total running" 57 | 58 | while [[ $(cc_status_code "$domain") != 200 ]]; do 59 | sleep 1 60 | done 61 | 62 | for script in /var/vcap/jobs/*/bin/post-start 63 | do 64 | $script 65 | done 66 | exec 1>&3 2>&4 67 | -------------------------------------------------------------------------------- /assets/scripts/stop: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | exec 3>&1 4>&2 >>/var/pcfdev/provision.log 2>&1 5 | set -x 6 | 7 | source /var/pcfdev/common 8 | 9 | if status runsvdir | grep -q 'start/running'; then 10 | >&3 echo "Waiting for services to stop..." 11 | wait_for_monit_to_start 12 | $monit stop all 13 | wait_for_services_to_stop 14 | stop runsvdir 15 | wait_for_monit_to_stop 16 | fi 17 | -------------------------------------------------------------------------------- /bin/build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | pcfdev_dir=$(cd `dirname $0` && cd .. && pwd) 6 | output_dir=$pcfdev_dir/output 7 | 8 | rm -rf "$output_dir" 9 | mkdir "$output_dir" 10 | 11 | "$pcfdev_dir/bin/setup-packer" "$output_dir/packer-bosh" 12 | "$pcfdev_dir/bin/fetch-assets" "$pcfdev_dir/versions.json" "$output_dir" 'oss' 13 | 14 | spiff merge \ 15 | "$pcfdev_dir/manifest.yml" \ 16 | <(echo "properties: {build: $(git -C "$pcfdev_dir" rev-parse HEAD)}") \ 17 | > "$output_dir/manifest.yml" 18 | 19 | base_ova_dir=$pcfdev_dir/pcfdev-base 20 | 21 | mkdir -p $base_ova_dir 22 | 23 | source_ova_path=$base_ova_dir/pcfdev-base.ova 24 | if [[ ! -f $source_ova_path ]] 25 | then 26 | wget -O "$source_ova_path" "https://s3.amazonaws.com/pcfdev/ci/pcfdev-base.ova" 27 | else 28 | echo "Re-using existing pcfdev-base.ova. Remove it to download the latest base ova in the next build." 29 | fi 30 | 31 | source_ami_path=$base_ova_dir/pcfdev-base.box 32 | pushd $base_ova_dir >/dev/null 33 | rm -f pcfdev-base.box 34 | wget -O pcfdev-base.box "https://s3.amazonaws.com/pcfdev/ci/pcfdev-base.box" 35 | tar xzf pcfdev-base.box 36 | source_ami=$(cat Vagrantfile | grep -o 'ami-.*' | tr -d '"') 37 | popd >/dev/null 38 | 39 | pushd "$output_dir" >/dev/null 40 | packer build "$@" \ 41 | -var 'distro=oss' \ 42 | -var "source_ami=$source_ami" \ 43 | -var "source_ova_path=$source_ova_path" \ 44 | "$pcfdev_dir/pcfdev.json" 45 | popd >/dev/null 46 | -------------------------------------------------------------------------------- /bin/compile-release: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | if [[ -z $1 ]] || [[ -z $2 ]] || [[ -z $3 ]] || [[ ! -d $(dirname "$1") ]] || [[ ! -d $(dirname "$3") ]]; then 6 | >&2 echo "Usage:" 7 | >&2 echo -e "\t$0 /path/to/release release-name destination" 8 | exit 1 9 | fi 10 | 11 | release_path=$1 12 | release_name=$2 13 | destination=$3 14 | 15 | pushd "$release_path" >/dev/null 16 | [[ -f Gemfile ]] && gem install bundler && bundle install || true 17 | yes yes | bosh -n reset-release 18 | bosh -n create-release --name="$release_name" --version=0 --tarball=${release_name}-0.tgz --force 19 | mv "${release_name}-0.tgz" "$destination" 20 | popd >/dev/null 21 | -------------------------------------------------------------------------------- /bin/fetch-assets: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | if [[ -z $(which jq) ]]; then 6 | >&2 echo -e "\t'jq' not found in the PATH" 7 | >&2 echo -e "\t Please install jq in order to continue" 8 | >&2 echo -e "\t https://stedolan.github.io/jq/" 9 | exit 1 10 | fi 11 | 12 | if [[ -z $1 ]] || [[ -z $2 ]] || [[ -z $3 ]] || [[ ! -d $(dirname "$2") ]]; then 13 | >&2 echo "Usage:" 14 | >&2 echo -e "\t$0 /path/to/versions.json /path/to/pcfdev-workspace distribution" 15 | exit 1 16 | fi 17 | 18 | shasum_matches() { 19 | path=$1 20 | checksum=$2 21 | 22 | if [[ ! -f "$path" ]]; then 23 | return 1 24 | fi 25 | 26 | if hash shasum 2>/dev/null; then 27 | if [[ $(shasum -a 1 "$path" | cut -d ' ' -f 1) != "$checksum" ]]; then 28 | return 1 29 | fi 30 | elif hash sha1sum 2>/dev/null; then 31 | if [[ $(sha1sum "$path" | cut -d ' ' -f 1) != "$checksum" ]]; then 32 | return 1 33 | fi 34 | else 35 | >&2 echo -e "\t Could not verify hash of downloaded resource, no shasum or sha1sum installed" 36 | exit 1 37 | fi 38 | } 39 | 40 | versions_json_path=$1 41 | output_dir=$2 42 | distro=$3 43 | 44 | assets_dir=$output_dir/assets 45 | releases_dir=$(cd "$output_dir" && cd .. && pwd)/releases 46 | pcfdev_dir=$(cd `dirname $0` && cd .. && pwd) 47 | 48 | mkdir -p "$assets_dir"/{releases,extras,versions} 49 | mkdir -p "$releases_dir" 50 | 51 | GOOS=linux GOARCH=amd64 GOPATH=$pcfdev_dir \ 52 | go build -a -ldflags "-X main.distro=${distro}" -o "$assets_dir/scripts/provision" provisioner 53 | 54 | GOOS=linux GOARCH=amd64 GOPATH=$pcfdev_dir \ 55 | go build -a -o "$assets_dir/scripts/api/api" api 56 | 57 | cp -r "$pcfdev_dir/src/github.com/cppforlife/bosh-provisioner/assets"/{monit,agent} "$assets_dir/" 58 | GOOS=linux GOARCH=amd64 GOPATH=$pcfdev_dir \ 59 | go build -a -o "$assets_dir/bosh-provisioner" github.com/cppforlife/bosh-provisioner/main 60 | 61 | versions=$(cat "$versions_json_path") 62 | releases=$(echo "$versions" | jq -r '.releases | keys[]') 63 | 64 | for name in $releases; do 65 | url=$(echo "$versions" | jq ".releases.\"$name\".compiled_release_url" -r) 66 | sha1=$(echo "$versions" | jq ".releases.\"$name\".sha1" -r) 67 | path=$(echo "$versions" | jq ".releases.\"$name\".path" -r) 68 | output_path="$releases_dir/$name-0.tgz" 69 | 70 | if [[ $path != null ]]; then 71 | "$pcfdev_dir/bin/compile-release" "$path" "$name" "$releases_dir" 72 | elif [[ $url != null ]]; then 73 | if shasum_matches "$output_path" "$sha1"; then 74 | continue 75 | fi 76 | 77 | if [[ ${url:0:5} == "s3://" ]]; then 78 | aws s3 cp --quiet "$url" "$output_path" 79 | elif [[ ${url:0:7} == "http://" ]] || [[ ${url:0:8} == "https://" ]]; then 80 | curl -s -o "$output_path" -L "$url" 81 | else 82 | echo "Invalid scheme for url: $url" 83 | exit 1 84 | fi 85 | 86 | if ! shasum_matches "$output_path" "$sha1"; then 87 | echo "Download failed (sha1 did not match) for: $url" 88 | exit 1 89 | fi 90 | fi 91 | 92 | done 93 | 94 | cp "$releases_dir"/*.tgz "$assets_dir/releases" 95 | 96 | if [[ $(echo "$versions" | jq -r 'has("extras")') == 'true' ]]; then 97 | extras=$(echo "$versions" | jq -r '.extras | keys[]') 98 | for name in $extras; do 99 | url=$(echo "$versions" | jq ".extras.\"$name\".url" -r) 100 | extension=${url##*.} 101 | aws s3 cp --quiet "$url" "$assets_dir/extras/$name.$extension" 102 | done 103 | fi 104 | 105 | cp "$versions_json_path" "$assets_dir/versions" 106 | cp -a "$pcfdev_dir"/preseed "$output_dir" 107 | cp -a "$pcfdev_dir"/assets/keys "$assets_dir" 108 | cp -a "$pcfdev_dir"/assets/scripts "$assets_dir" 109 | -------------------------------------------------------------------------------- /bin/setup-packer: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | if [[ -z $1 ]]; then 6 | >&2 echo "Usage:" 7 | >&2 echo -e "\t$0 /path/to/packer-bosh" 8 | exit 1 9 | fi 10 | 11 | packer_bosh=$1 12 | packer_config_path=${PACKER_CONFIG:-"$HOME/.packerconfig"} 13 | 14 | pcfdev_dir=$(cd `dirname $0` && cd .. && pwd) 15 | packer_bosh_gopath=$pcfdev_dir/src/github.com/cppforlife/packer-bosh/Godeps/_workspace 16 | 17 | GOPATH=$packer_bosh_gopath:$pcfdev_dir \ 18 | go build -a -o "$packer_bosh" github.com/cppforlife/packer-bosh/main 19 | 20 | chmod +x "$packer_bosh" 21 | 22 | if [[ -f "$packer_config_path" ]]; then 23 | packer_config=$(cat $packer_config_path) 24 | echo "Updating existing ~/.packerconfig to point to downloaded packer-bosh." 25 | fi 26 | filter='. + {"provisioners": (.provisioners + {"packer-bosh": "'$packer_bosh'"})}' 27 | echo ${packer_config:-'{}'} | jq "$filter" >$packer_config_path 28 | -------------------------------------------------------------------------------- /cla/pivotal-ccla.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmware-archive/pcfdev/c5fe36ce3be673403d5aec08b93131ababb0812d/cla/pivotal-ccla.pdf -------------------------------------------------------------------------------- /cla/pivotal-icla.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmware-archive/pcfdev/c5fe36ce3be673403d5aec08b93131ababb0812d/cla/pivotal-icla.pdf -------------------------------------------------------------------------------- /pcfdev-base.json: -------------------------------------------------------------------------------- 1 | { 2 | "variables": { 3 | "version": "0", 4 | "cpus": "4", 5 | "memory": "4096", 6 | "disk_size": "60", 7 | "eula_url": "", 8 | "security_group_id": "", 9 | "subnet_id": "", 10 | "vpc_id": "", 11 | "ssh_keypair_name": "", 12 | "ssh_private_key_file": "" 13 | }, 14 | "builders": [ 15 | { 16 | "type": "amazon-ebs", 17 | "region": "us-east-1", 18 | "source_ami": "ami-6012160a", 19 | "instance_type": "c4.2xlarge", 20 | "ami_name": "pcfdev-base-v{{user `version`}}", 21 | "ami_description": "{{user `eula_url`}}", 22 | "associate_public_ip_address": false, 23 | "security_group_id": "{{user `security_group_id`}}", 24 | "subnet_id": "{{user `subnet_id`}}", 25 | "vpc_id": "{{user `vpc_id`}}", 26 | "ssh_keypair_name": "{{user `ssh_keypair_name`}}", 27 | "ssh_private_key_file": "{{user `ssh_private_key_file`}}", 28 | "ami_block_device_mappings": [{ 29 | "device_name": "/dev/sda1", 30 | "volume_type": "gp2", 31 | "volume_size": "{{user `disk_size`}}", 32 | "delete_on_termination": true 33 | }], 34 | "launch_block_device_mappings": [{ 35 | "device_name": "/dev/sda1", 36 | "volume_type": "io1", 37 | "iops": "1800", 38 | "volume_size": "{{user `disk_size`}}", 39 | "delete_on_termination": true 40 | }], 41 | "ssh_username": "ubuntu", 42 | "ssh_timeout": "20m", 43 | "tags": {"Name": "v{{user `version`}}", "License":"{{user `eula_url`}}"} 44 | }, 45 | { 46 | "type": "virtualbox-iso", 47 | "headless": true, 48 | "vm_name": "pcfdev-base-v{{user `version`}}", 49 | "guest_os_type": "Ubuntu_64", 50 | "disk_size": "{{user `disk_size`}}000", 51 | "ssh_username": "vcap", 52 | "ssh_password": "vcap", 53 | "iso_url": "https://pcfdev.s3.amazonaws.com/artifacts/ubuntu-14.04.4-server-amd64.iso", 54 | "iso_checksum": "2ac1f3e0de626e54d05065d6f549fa3a", 55 | "iso_checksum_type": "md5", 56 | "http_directory": "preseed", 57 | "ssh_timeout": "20m", 58 | "shutdown_command": "echo vcap | sudo -S shutdown -P now", 59 | "format": "ova", 60 | "boot_command": [ 61 | "", 62 | "/install/vmlinuz noapic ", 63 | "preseed/url=http://{{ .HTTPIP }}:{{ .HTTPPort }}/preseed.cfg ", 64 | "debian-installer=en_US auto locale=en_US kbd-chooser/method=us ", 65 | "hostname=pcfdev ", 66 | "fb=false debconf/frontend=noninteractive ", 67 | "keyboard-configuration/modelcode=SKIP keyboard-configuration/layout=USA ", 68 | "keyboard-configuration/variant=USA console-setup/ask_detect=false ", 69 | "initrd=/install/initrd.gz -- " 70 | ], 71 | "vboxmanage": [ 72 | [ "modifyvm", "{{.Name}}", "--cpus", "{{user `cpus`}}" ], 73 | [ "modifyvm", "{{.Name}}", "--memory", "{{user `memory`}}" ], 74 | [ "modifyvm", "{{.Name}}", "--natdnshostresolver1", "on" ], 75 | [ "modifyvm", "{{.Name}}", "--nic1", "nat" ], 76 | [ "modifyvm", "{{.Name}}", "--paravirtprovider", "minimal" ] 77 | ] 78 | } 79 | ], 80 | 81 | "provisioners": [ 82 | { 83 | "type": "shell", 84 | "execute_command": "echo vcap | {{ .Vars }} sudo -E -S sh -c '{{ .Path }}'", 85 | "inline": [ 86 | "apt-get -y install software-properties-common", 87 | "add-apt-repository -y ppa:brightbox/ruby-ng", 88 | "echo 'deb http://apt.postgresql.org/pub/repos/apt/ trusty-pgdg main' > /etc/apt/sources.list.d/pgdg.list", 89 | "wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -", 90 | "apt-get -y update", 91 | "apt-get -y --force-yes dist-upgrade", 92 | "apt-get -y update", 93 | "apt-get -y upgrade sudo", 94 | "echo 'vcap ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers", 95 | 96 | "# vvv TODO: instead of doing this, use (or build) a stemcell for PCF Dev vvv", 97 | "sed -i 's/^GRUB_CMDLINE_LINUX=\"\"/GRUB_CMDLINE_LINUX=\"cgroup_enable=memory swapaccount=1\"/' /etc/default/grub", 98 | "# ^^^ TODO: instead of doing this, use (or build) a stemcell for PCF Dev ^^^" 99 | ] 100 | }, 101 | { 102 | "type": "shell", 103 | "only": ["virtualbox-iso"], 104 | "execute_command": "{{ .Vars }} sudo -E sh -c '{{ .Path }}'", 105 | "inline": [ 106 | "apt-get install -y dkms", 107 | "mount -o loop /home/vcap/VBoxGuestAdditions.iso /mnt", 108 | "sh /mnt/VBoxLinuxAdditions.run --nox11", 109 | "umount /mnt", 110 | "apt-get install -y open-vm-tools" 111 | ] 112 | }, 113 | { 114 | "type": "shell", 115 | "only": ["amazon-ebs"], 116 | "execute_command": "{{ .Vars }} sudo -E sh -c '{{ .Path }}'", 117 | "inline": [ 118 | "while [ ! -f /var/lib/cloud/instance/boot-finished ]; do sleep 1; done", 119 | "ln -sf /usr/bin/mawk /usr/bin/awk" 120 | ] 121 | }, 122 | { 123 | "type": "shell", 124 | "execute_command": "{{ .Vars }} sudo -E sh -c '{{ .Path }}'", 125 | "inline": [ 126 | "apt-get -y install curl unzip zip sysstat vim", 127 | "apt-get -y install libruby2.1 ruby2.1", 128 | "apt-get -y install linux-generic", 129 | "apt-get -y install aufs-tools", 130 | "apt-get -y install libgcrypt20 libgcrypt20-dev", 131 | "apt-get -y install postgresql-client-9.4", 132 | "apt-get -y install nginx", 133 | "service nginx stop", 134 | "update-rc.d -f nginx remove", 135 | 136 | "for version in $(ls /lib/modules); do apt-get install -y linux-image-extra-$version; done", 137 | 138 | "curl -L 'https://cli.run.pivotal.io/stable?release=linux64-binary&version=6.25.0&source=github-rel' | tar -C /usr/local/bin -xz", 139 | "curl -L 'https://github.com/cloudfoundry-incubator/routing-api-cli/releases/download/2.6.0/rtr-linux-amd64.tgz' | tar -C /usr/local/bin -xz", 140 | "mv /usr/local/bin/rtr-linux-amd64 /usr/local/bin/rtr", 141 | "curl -L 'https://storage.googleapis.com/golang/go1.6.3.linux-amd64.tar.gz' | tar -C /usr/local -xz", 142 | "ln -sf /usr/local/go/bin/go* /usr/local/bin/", 143 | "curl -o /usr/local/bin/veritas -L https://github.com/pivotal-cf-experimental/veritas/releases/download/latest/veritas", 144 | "chmod +x /usr/local/bin/veritas", 145 | "curl -o /usr/local/bin/jq -L https://github.com/stedolan/jq/releases/download/jq-1.5/jq-linux64", 146 | "chmod +x /usr/local/bin/jq" 147 | ] 148 | } 149 | ], 150 | "post-processors": [ 151 | { 152 | "type": "shell-local", 153 | "only": ["virtualbox-iso"], 154 | "inline": ["ls $PWD/*/*.ova"], 155 | "keep_input_artifact": true 156 | }, 157 | { 158 | "type": "vagrant", 159 | "only": ["amazon-ebs"], 160 | "output": "pcfdev-base-{{.Provider}}-v{{user `version`}}.box", 161 | "keep_input_artifact": true 162 | } 163 | ] 164 | } 165 | -------------------------------------------------------------------------------- /pcfdev.json: -------------------------------------------------------------------------------- 1 | { 2 | "variables": { 3 | "source_ami": "", 4 | "source_ova_path": "", 5 | "version": "0", 6 | "distro": "pcfdev", 7 | "cpus": "4", 8 | "memory": "4096", 9 | "disk_size": "60", 10 | "dev": "false", 11 | "eula_url": "", 12 | "security_group_id": "", 13 | "subnet_id": "", 14 | "vpc_id": "", 15 | "ssh_keypair_name": "", 16 | "ssh_private_key_file": "" 17 | }, 18 | "builders": [ 19 | { 20 | "type": "amazon-ebs", 21 | "region": "us-east-1", 22 | "source_ami": "{{user `source_ami`}}", 23 | "instance_type": "c4.2xlarge", 24 | "ami_name": "{{user `distro`}}-v{{user `version`}}", 25 | "ami_description": "{{user `eula_url`}}", 26 | "associate_public_ip_address": false, 27 | "security_group_id": "{{user `security_group_id`}}", 28 | "subnet_id": "{{user `subnet_id`}}", 29 | "vpc_id": "{{user `vpc_id`}}", 30 | "ssh_keypair_name": "{{user `ssh_keypair_name`}}", 31 | "ssh_private_key_file": "{{user `ssh_private_key_file`}}", 32 | "ami_block_device_mappings": [{ 33 | "device_name": "/dev/sda1", 34 | "volume_type": "gp2", 35 | "volume_size": "{{user `disk_size`}}", 36 | "delete_on_termination": true 37 | }], 38 | "launch_block_device_mappings": [{ 39 | "device_name": "/dev/sda1", 40 | "volume_type": "io1", 41 | "iops": "1800", 42 | "volume_size": "{{user `disk_size`}}", 43 | "delete_on_termination": true 44 | }], 45 | "ssh_username": "ubuntu", 46 | "ssh_timeout": "20m", 47 | "tags": {"Name": "v{{user `version`}}", "License":"{{user `eula_url`}}"} 48 | }, 49 | { 50 | "type": "virtualbox-ovf", 51 | "source_path": "{{user `source_ova_path`}}", 52 | "ssh_username": "vcap", 53 | "ssh_password": "vcap", 54 | "headless": true, 55 | "vm_name": "packer-{{user `distro`}}-v{{user `version`}}", 56 | "http_directory": "preseed", 57 | "ssh_timeout": "20m", 58 | "shutdown_command": "echo vcap | sudo -S shutdown -P now", 59 | "format": "ova", 60 | "boot_command": [ 61 | "", 62 | "/install/vmlinuz noapic ", 63 | "preseed/url=http://{{ .HTTPIP }}:{{ .HTTPPort }}/preseed.cfg ", 64 | "debian-installer=en_US auto locale=en_US kbd-chooser/method=us ", 65 | "hostname=pcfdev ", 66 | "fb=false debconf/frontend=noninteractive ", 67 | "keyboard-configuration/modelcode=SKIP keyboard-configuration/layout=USA ", 68 | "keyboard-configuration/variant=USA console-setup/ask_detect=false ", 69 | "initrd=/install/initrd.gz -- " 70 | ], 71 | "vboxmanage": [ 72 | [ "modifyvm", "{{.Name}}", "--cpus", "{{user `cpus`}}" ], 73 | [ "modifyvm", "{{.Name}}", "--memory", "{{user `memory`}}" ], 74 | [ "modifyvm", "{{.Name}}", "--natdnshostresolver1", "on" ], 75 | [ "modifyvm", "{{.Name}}", "--nic1", "nat" ], 76 | [ "modifyvm", "{{.Name}}", "--paravirtprovider", "minimal" ] 77 | ] 78 | } 79 | ], 80 | "provisioners": [ 81 | { 82 | "type": "shell", 83 | "only": ["amazon-ebs"], 84 | "execute_command": "{{ .Vars }} sudo -E sh -c '{{ .Path }}'", 85 | "inline": [ 86 | "while [ ! -f /var/lib/cloud/instance/boot-finished ]; do sleep 1; done", 87 | "ln -sf /usr/bin/mawk /usr/bin/awk" 88 | ] 89 | }, 90 | { 91 | "type": "file", 92 | "only": ["virtualbox-ovf"], 93 | "source": "assets/keys/key.pem", 94 | "destination": "/tmp/key.pem" 95 | }, 96 | { 97 | "type": "file", 98 | "source": "manifest.yml", 99 | "destination": "/tmp/manifest.yml" 100 | }, 101 | { 102 | "type": "shell", 103 | "only": ["virtualbox-ovf"], 104 | "remote_path": "/home/vcap/add_key.sh", 105 | "execute_command": "{{ .Vars }} sudo -E sh -c '{{ .Path }}'", 106 | "inline": [ 107 | "mkdir -p /home/vcap/.ssh", 108 | "cp /tmp/key.pem /home/vcap/.ssh/authorized_keys" 109 | ] 110 | }, 111 | { 112 | "type": "shell", 113 | "execute_command": "{{ .Vars }} sudo -E sh -c '{{ .Path }}'", 114 | "inline": [ 115 | "echo 'UseDNS no' >> /etc/ssh/sshd_config", 116 | "echo 'PasswordAuthentication no' >> /etc/ssh/sshd_config", 117 | 118 | "mkdir -p /var/pcfdev/api", 119 | "echo local.pcfdev.io > /var/pcfdev/domain", 120 | "mv /tmp/manifest.yml /var/pcfdev/manifest.yml", 121 | 122 | "mkdir -p /var/vcap/store", 123 | 124 | "resolvconf --disable-updates", 125 | "apt-get -y install dnsmasq", 126 | "service dnsmasq stop", 127 | "update-rc.d -f dnsmasq remove", 128 | "echo IGNORE_RESOLVCONF=yes >> /etc/default/dnsmasq", 129 | "echo bind-interfaces > /etc/dnsmasq.d/pcfdev", 130 | "echo net.ipv4.ip_local_port_range = 32768 61000 > /etc/sysctl.d/60-restrict-source-ports.conf" 131 | ] 132 | }, 133 | { 134 | "type": "packer-bosh", 135 | "assets_dir": "assets", 136 | "remote_manifest_path": "/var/pcfdev/manifest.yml" 137 | }, 138 | { 139 | "type": "shell", 140 | "execute_command": "{{ .Vars }} sudo -E sh -c '{{ .Path }}'", 141 | "remote_path": "/home/vcap/start_services.sh", 142 | "override": { 143 | "amazon-ebs": { "remote_path": "/home/ubuntu/start_services.sh" } 144 | }, 145 | "inline": [ 146 | "mkdir -p /var/vcap/monit/job", 147 | "echo manual > /etc/init/runsvdir.override", 148 | "stop runsvdir", 149 | "mv /opt/bosh-provisioner/assets/scripts/* /var/pcfdev", 150 | "mv /opt/bosh-provisioner/assets/extras /var/pcfdev", 151 | "mv /opt/bosh-provisioner/assets/versions /var/pcfdev/", 152 | 153 | "case '{{build_type}}' in", 154 | "amazon-ebs) PROVIDER_TYPE=aws ;;", 155 | "*) PROVIDER_TYPE=virtualbox ;;", 156 | "esac", 157 | 158 | "/var/pcfdev/provision local.pcfdev.io $(ip route get 1 | awk '{print $NF;exit}') redis,rabbitmq '' $PROVIDER_TYPE", 159 | "/var/pcfdev/stop" 160 | ] 161 | }, 162 | { 163 | "type": "shell", 164 | "execute_command": "{{ .Vars }} sudo -E sh -c '{{ .Path }}'", 165 | "remote_path": "/home/vcap/cleanup.sh", 166 | "override": { 167 | "amazon-ebs": { "remote_path": "/home/ubuntu/cleanup.sh" } 168 | }, 169 | "inline": [ 170 | "rm -f /var/pcfdev/external-resolv.conf", 171 | "tar czf /var/pcfdev/packer-syslogs.tgz /var/vcap/sys/log", 172 | "rm -f /var/vcap/sys/log/*/*.log", 173 | "{{user `dev`}} || rm -rf /opt/bosh-provisioner", 174 | "{{user `dev`}} || rm -f /var/pcfdev/manifest.yml", 175 | "apt-get -y autoremove", 176 | "apt-get -y clean", 177 | "service dnsmasq stop", 178 | "/var/pcfdev/reset", 179 | "rm -rf $HOME/.cf", 180 | "chmod 1777 /tmp", 181 | "echo 'vcap:vcap' | chpasswd", 182 | "chown -R vcap: /var/vcap/store", 183 | "chown -R vcap: /var/pcfdev", 184 | "chown -R vcap /home/vcap/.ssh || true", 185 | "dd if=/dev/zero of=/EMPTY bs=1M 2>/dev/null || true", 186 | "rm -f /EMPTY" 187 | ] 188 | } 189 | ], 190 | "post-processors": [ 191 | { 192 | "type": "shell-local", 193 | "only": ["virtualbox-ovf"], 194 | "inline": ["ls $PWD/*/*.ova"], 195 | "keep_input_artifact": true 196 | }, 197 | { 198 | "type": "vagrant", 199 | "only": ["amazon-ebs"], 200 | "output": "{{user `distro`}}-{{.Provider}}-v{{user `version`}}.box", 201 | "keep_input_artifact": true 202 | } 203 | ] 204 | } 205 | -------------------------------------------------------------------------------- /preseed/preseed.cfg: -------------------------------------------------------------------------------- 1 | d-i debian-installer/locale string en_US.utf8 2 | d-i console-setup/ask_detect boolean false 3 | d-i console-setup/layout string USA 4 | 5 | d-i netcfg/get_hostname string unassigned-hostname 6 | d-i netcfg/get_domain string unassigned-domain 7 | 8 | d-i time/zone string UTC 9 | d-i clock-setup/utc-auto boolean true 10 | d-i clock-setup/utc boolean true 11 | 12 | d-i kbd-chooser/method select American English 13 | d-i netcfg/wireless_wep string 14 | d-i base-installer/kernel/override-image string linux-server 15 | d-i debconf debconf/frontend select Noninteractive 16 | 17 | d-i pkgsel/install-language-support boolean false 18 | tasksel tasksel/first multiselect standard, ubuntu-server 19 | 20 | d-i partman-auto/method string regular 21 | d-i partman-auto/choose_recipe select atomic 22 | d-i partman-basicfilesystems/no_swap boolean false 23 | d-i partman-auto/expert_recipe string \ 24 | partman-auto/text/atomic_scheme :: \ 25 | 55000 80000 1000000 ext4 \ 26 | $primary{ } $bootable{ } \ 27 | method{ format } format{ } \ 28 | use_filesystem{ } filesystem{ ext4 } \ 29 | mountpoint{ / } \ 30 | . \ 31 | 1024 2048 8192 linux-swap \ 32 | $primary{ } \ 33 | method{ swap } format{ } \ 34 | . \ 35 | 36 | d-i partman/confirm_write_new_label boolean true 37 | d-i partman/confirm_nooverwrite boolean true 38 | d-i partman/choose_partition select finish 39 | d-i partman/confirm boolean true 40 | 41 | d-i passwd/user-fullname string vcap 42 | d-i passwd/username string vcap 43 | d-i passwd/user-password password vcap 44 | d-i passwd/user-password-again password vcap 45 | d-i user-setup/encrypt-home boolean false 46 | d-i user-setup/allow-password-weak boolean true 47 | 48 | d-i pkgsel/include string openssh-server ntp 49 | d-i pkgsel/upgrade select full-upgrade 50 | 51 | d-i grub-installer/only_debian boolean true 52 | d-i grub-installer/with_other_os boolean true 53 | d-i finish-install/reboot_in_progress note 54 | 55 | d-i pkgsel/update-policy select none 56 | 57 | choose-mirror-bin mirror/http/proxy string 58 | -------------------------------------------------------------------------------- /src/api/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "os" 9 | "api/usecases" 10 | ) 11 | 12 | func fileExists(path string) (bool, error) { 13 | if _, err := os.Stat(path); err != nil { 14 | if os.IsNotExist(err) { 15 | return false, nil 16 | } 17 | return false, err 18 | } 19 | return true, nil 20 | } 21 | 22 | func serverError(w http.ResponseWriter) { 23 | errorHandler(w, "Failed to replace UAA Config Credentials", http.StatusInternalServerError) 24 | } 25 | 26 | func errorHandler(w http.ResponseWriter, message string, statusCode int) { 27 | w.WriteHeader(statusCode) 28 | fmt.Fprintf(w, fmt.Sprintf(`{"error":{"message":"%s"}}`, message)) 29 | } 30 | 31 | func handlerStatus(w http.ResponseWriter, r *http.Request) { 32 | exists, err := fileExists("/run/pcfdev-healthcheck") 33 | if err != nil { 34 | fmt.Fprintf(w, fmt.Sprintf(`{"error":{"message":"%s"}}`, err)) 35 | } 36 | 37 | if exists { 38 | fmt.Fprintf(w, `{"status":"Running"}`) 39 | } else { 40 | fmt.Fprintf(w, `{"status":"Unprovisioned"}`) 41 | } 42 | } 43 | 44 | func replaceSecrets(w http.ResponseWriter, r *http.Request) { 45 | uaaFilePath := "/var/vcap/jobs/uaa/config/uaa.yml" 46 | uaaCredentialReplacement := &usecases.UaaCredentialReplacement{} 47 | 48 | uaaBytes, err := ioutil.ReadAll(r.Body) 49 | if err != nil { 50 | serverError(w) 51 | return 52 | } 53 | 54 | var request struct { 55 | Password string `json:"password"` 56 | } 57 | 58 | if err := json.Unmarshal(uaaBytes, &request); err != nil { 59 | errorHandler(w, "Failed to parse password field from request", http.StatusBadRequest) 60 | return 61 | } 62 | 63 | insecureConfig, err := ioutil.ReadFile(uaaFilePath) 64 | if err != nil { 65 | serverError(w) 66 | return 67 | } 68 | 69 | secureConfig, err := uaaCredentialReplacement.ReplaceUaaConfigAdminCredentials(string(insecureConfig), request.Password) 70 | 71 | if err != nil { 72 | serverError(w) 73 | return 74 | } 75 | 76 | ioutil.WriteFile(uaaFilePath, []byte(secureConfig), 0644) 77 | } 78 | 79 | func main() { 80 | http.HandleFunc("/replace-secrets", replaceSecrets) 81 | http.HandleFunc("/status", handlerStatus) 82 | http.ListenAndServe("localhost:8090", nil) 83 | } 84 | -------------------------------------------------------------------------------- /src/api/main_suite_test.go: -------------------------------------------------------------------------------- 1 | package main_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | ) 9 | 10 | func TestPCFDevApi(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "PCF Dev Api Main Suite") 13 | } 14 | -------------------------------------------------------------------------------- /src/api/main_test.go: -------------------------------------------------------------------------------- 1 | package main_test 2 | 3 | import ( 4 | "github.com/onsi/gomega/gbytes" 5 | "github.com/onsi/gomega/gexec" 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | "os" 9 | "os/exec" 10 | "path/filepath" 11 | "strings" 12 | ) 13 | 14 | var _ = Describe("PCF Dev Api provision", func() { 15 | var ( 16 | dockerID string 17 | pwd string 18 | ) 19 | 20 | BeforeEach(func() { 21 | var err error 22 | pwd, err = os.Getwd() 23 | Expect(err).NotTo(HaveOccurred()) 24 | 25 | output, err := exec.Command("docker", "run", "--privileged", "-d", "-w", "/go/src/api", "-v", pwd+":/go/src/api", "pcfdev/provision", "bash", "-c", "mount -o size=40M -t tmpfs tmpfs /run && sleep 1000").Output() 26 | Expect(err).NotTo(HaveOccurred()) 27 | dockerID = strings.TrimSpace(string(output)) 28 | Expect(exec.Command("docker", "exec", dockerID, "go", "build", "api").Run()).To(Succeed()) 29 | Expect(exec.Command("docker", "exec", dockerID, "bash", "-c", `/go/src/api/api`).Start()) 30 | Eventually(func() error { 31 | return exec.Command("docker", "exec", dockerID, "lsof", "-i", ":8090").Run() 32 | }).Should(Succeed()) 33 | }) 34 | 35 | AfterEach(func() { 36 | os.RemoveAll(filepath.Join(pwd, "pcfdev")) 37 | os.RemoveAll(filepath.Join(pwd, "provisdion-script")) 38 | Expect(exec.Command("docker", "rm", dockerID, "-f").Run()).To(Succeed()) 39 | }) 40 | 41 | Describe("/replace-secrets", func() { 42 | It("should replace secrets on the VM", func() { 43 | session, err := gexec.Start(exec.Command("docker", "exec", dockerID, "curl", "-v", "-X", "PUT", "-d", `{"password":"some-master-password"}`, "localhost:8090/replace-secrets"), GinkgoWriter, GinkgoWriter) 44 | Expect(err).NotTo(HaveOccurred()) 45 | Eventually(session, "10s").Should(gexec.Exit()) 46 | 47 | session, err = gexec.Start(exec.Command("docker", "exec", dockerID, "cat", "/var/vcap/jobs/uaa/config/uaa.yml"), GinkgoWriter, GinkgoWriter) 48 | Expect(err).NotTo(HaveOccurred()) 49 | Eventually(session, "10s").Should(gexec.Exit(0)) 50 | Expect(session).To(gbytes.Say(`- admin\|some-master-password\|scim.write`)) 51 | }) 52 | }) 53 | 54 | Describe("/status", func() { 55 | Context("when the health-check is not written", func() { 56 | It("should reply 'Unprovisioned' in the /status endpoint", func() { 57 | session, err := gexec.Start(exec.Command("docker", "exec", dockerID, "curl", "localhost:8090/status"), GinkgoWriter, GinkgoWriter) 58 | Expect(err).NotTo(HaveOccurred()) 59 | Eventually(session, "10s").Should(gexec.Exit(0)) 60 | Expect(session).To(gbytes.Say(`{"status":"Unprovisioned"}`)) 61 | }) 62 | }) 63 | 64 | Context("when the health-check is written", func() { 65 | It("should reply `Running` in the /status endpoint", func() { 66 | session, err := gexec.Start(exec.Command("docker", "exec", dockerID, "touch", "/run/pcfdev-healthcheck"), GinkgoWriter, GinkgoWriter) 67 | Expect(err).NotTo(HaveOccurred()) 68 | Eventually(session, "10s").Should(gexec.Exit(0)) 69 | 70 | session, err = gexec.Start(exec.Command("docker", "exec", dockerID, "curl", "localhost:8090/status"), GinkgoWriter, GinkgoWriter) 71 | Expect(err).NotTo(HaveOccurred()) 72 | Eventually(session, "10s").Should(gexec.Exit(0)) 73 | Expect(session).To(gbytes.Say(`{"status":"Running"}`)) 74 | }) 75 | }) 76 | }) 77 | 78 | }) 79 | -------------------------------------------------------------------------------- /src/api/usecases/uaa_credential_replacement.go: -------------------------------------------------------------------------------- 1 | package usecases 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "gopkg.in/yaml.v2" 7 | "regexp" 8 | "strings" 9 | ) 10 | 11 | type UaaCredentialReplacement struct{} 12 | 13 | func (u *UaaCredentialReplacement) ReplaceUaaConfigAdminCredentials(uaaConfig string, password string) (_ string, err error) { 14 | users, _ := findArray(uaaConfig, "scim.users") 15 | 16 | adminCredentialPattern := regexp.MustCompile(`admin\|admin\|`) 17 | for index, userSettingInterface := range users { 18 | userSetting := userSettingInterface.(string) 19 | if adminCredentialPattern.MatchString(userSetting) { 20 | securedUserSetting := adminCredentialPattern.ReplaceAllString(userSetting, fmt.Sprintf("admin|%s|", password)) 21 | return setAt(uaaConfig, "scim.users", index, securedUserSetting) 22 | } 23 | } 24 | 25 | return "", errors.New("failed to parse UAA config file") 26 | } 27 | 28 | type interfaceMap map[interface{}]interface{} 29 | 30 | func recoverFromInterfaceConversion(recovered interface{}) error { 31 | if recovered != nil { 32 | errorMessage := recovered.(error).Error() 33 | if strings.Contains(errorMessage, "interface conversion") { 34 | return errors.New("failed to parse yaml") 35 | } else { 36 | panic(recovered) 37 | } 38 | } 39 | return nil 40 | } 41 | 42 | func setAt(contents string, path string, arrayIndex int, value string) (string, error) { 43 | yamlContents := make(interfaceMap) 44 | 45 | if err := yaml.Unmarshal([]byte(contents), &yamlContents); err != nil { 46 | return "", errors.New("failed to parse yaml") 47 | } 48 | 49 | var currentNode interface{} = yamlContents 50 | keys := strings.Split(path, ".") 51 | for _, key := range keys { 52 | currentNode = currentNode.(interfaceMap)[key] 53 | } 54 | currentNode.([]interface{})[arrayIndex] = value 55 | replacedContents, err := yaml.Marshal(yamlContents) 56 | return string(replacedContents), err 57 | } 58 | 59 | func findArray(contents string, path string) (_ []interface{}, err error) { 60 | defer func() { 61 | err = recoverFromInterfaceConversion(recover()) 62 | }() 63 | 64 | yamlContents := make(interfaceMap) 65 | 66 | if err := yaml.Unmarshal([]byte(contents), &yamlContents); err != nil { 67 | return nil, errors.New("failed to parse yaml") 68 | } 69 | 70 | var currentNode interface{} = yamlContents 71 | keys := strings.Split(path, ".") 72 | for _, key := range keys { 73 | currentNode = currentNode.(interfaceMap)[key] 74 | } 75 | 76 | return currentNode.([]interface{}), nil 77 | } 78 | -------------------------------------------------------------------------------- /src/api/usecases/uaa_credential_replacement_test.go: -------------------------------------------------------------------------------- 1 | package usecases_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | "api/usecases" 7 | ) 8 | 9 | var _ = Describe("Usecase: UAA Credential Replacement", func() { 10 | 11 | var u *usecases.UaaCredentialReplacement 12 | goodUaaConfig := ` 13 | --- 14 | name: uaa 15 | scim: 16 | some-key: true 17 | users: 18 | - admin|admin|other-value 19 | ` 20 | expectedSecuredConfig := `name: uaa 21 | scim: 22 | some-key: true 23 | users: 24 | - admin|some-password|other-value 25 | ` 26 | 27 | uaaConfigWithoutScimKey := "name: uaa" 28 | uaaConfigWithoutUsersKey := "scim: {}" 29 | uaaConfigWithoutAdminUser := ` 30 | --- 31 | scim: 32 | users: 33 | - other-user|password|other-value 34 | ` 35 | 36 | BeforeEach(func() { 37 | u = &usecases.UaaCredentialReplacement{} 38 | }) 39 | 40 | Context("when the config file is in the expected state", func() { 41 | It("replaces the admin password", func() { 42 | securedConfig, err := u.ReplaceUaaConfigAdminCredentials(goodUaaConfig, "some-password") 43 | Expect(err).NotTo(HaveOccurred()) 44 | Expect(securedConfig).To(Equal(expectedSecuredConfig)) 45 | }) 46 | }) 47 | 48 | Context("when the config file is not valid yaml", func() { 49 | It("returns an error", func() { 50 | uaaConfig := "some-bad-yaml" 51 | _, err := u.ReplaceUaaConfigAdminCredentials(uaaConfig, "some-password") 52 | Expect(err).To(MatchError("failed to parse UAA config file")) 53 | }) 54 | }) 55 | 56 | Context("When the scim key is missing", func() { 57 | It("returns an error", func() { 58 | _, err := u.ReplaceUaaConfigAdminCredentials(uaaConfigWithoutScimKey, "some-password") 59 | Expect(err).To(MatchError("failed to parse UAA config file")) 60 | }) 61 | }) 62 | 63 | Context("When the scim.users key is missing", func() { 64 | It("returns an error", func() { 65 | _, err := u.ReplaceUaaConfigAdminCredentials(uaaConfigWithoutUsersKey, "some-password") 66 | Expect(err).To(MatchError("failed to parse UAA config file")) 67 | }) 68 | }) 69 | 70 | Context("when the admin credentials are not in scim.users", func() { 71 | It("returns an error", func() { 72 | _, err := u.ReplaceUaaConfigAdminCredentials(uaaConfigWithoutAdminUser, "some-password") 73 | Expect(err).To(MatchError("failed to parse UAA config file")) 74 | }) 75 | }) 76 | }) 77 | -------------------------------------------------------------------------------- /src/api/usecases/usecase_suite_test.go: -------------------------------------------------------------------------------- 1 | package usecases_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | ) 9 | 10 | func TestUsecase(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Usecase Suite") 13 | } 14 | -------------------------------------------------------------------------------- /src/provisioner/.envrc: -------------------------------------------------------------------------------- 1 | export GOPATH=$(cd $PWD && cd ../.. && pwd) 2 | export PATH=$GOPATH/bin:$PATH 3 | -------------------------------------------------------------------------------- /src/provisioner/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.7 2 | 3 | RUN apt-get update 4 | RUN apt-get install -y \ 5 | dnsmasq \ 6 | file \ 7 | host \ 8 | iptables \ 9 | lsof \ 10 | netcat \ 11 | nginx \ 12 | vim 13 | 14 | RUN echo "server {\n listen 443 ssl;\n ssl_certificate /var/vcap/jobs/gorouter/config/cert.pem;\n ssl_certificate_key /var/vcap/jobs/gorouter/config/key.pem;\n}" > /etc/nginx/conf.d/pcfdev.conf 15 | RUN mkdir -p /var/vcap/packages/uaa/tomcat/conf 16 | RUN echo "" > /var/vcap/packages/uaa/tomcat/conf/web.xml 17 | RUN mkdir -p /var/pcfdev/api 18 | RUN mkdir -p /var/vcap/monit/job 19 | RUN mkdir -p /var/vcap/jobs/garden/bin 20 | RUN mkdir -p /var/vcap/jobs/uaa/config 21 | RUN echo "exec /var/vcap/packages/garden-linux/bin/garden-linux \\ \n -dnsServer=some-dns-server \\ \n 1>>\$LOG_DIR/garden.stdout.log \\ \n 2>>\$LOG_DIR/garden.stderr.log" > /var/vcap/jobs/garden/bin/garden_ctl 22 | RUN echo "scim:\n users:\n - admin|admin|scim.write,scim.read,openid" > /var/vcap/jobs/uaa/config/uaa.yml 23 | RUN ln -s /bin/true /usr/local/bin/resolvconf 24 | -------------------------------------------------------------------------------- /src/provisioner/assets/stub_server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "os" 7 | ) 8 | 9 | func main() { 10 | port := os.Args[1] 11 | 12 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 13 | w.Write([]byte("Response from port " + port + " stub server")) 14 | }) 15 | 16 | if err := http.ListenAndServe(":" + port, nil); err != nil { 17 | log.Fatal("ListenAndServe: ", err) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/provisioner/assets/tomcat-web-invalid.xml: -------------------------------------------------------------------------------- 1 | some-bad-xml 2 | -------------------------------------------------------------------------------- /src/provisioner/assets/tomcat-web.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | default 11 | org.apache.catalina.servlets.DefaultServlet 12 | 13 | debug 14 | 0 15 | 16 | 17 | listings 18 | false 19 | 20 | 1 21 | 22 | 23 | 24 | jsp 25 | org.apache.jasper.servlet.JspServlet 26 | 27 | fork 28 | false 29 | 30 | 31 | xpoweredBy 32 | false 33 | 34 | 3 35 | 36 | 37 | 38 | default 39 | / 40 | 41 | 42 | 43 | jsp 44 | *.jsp 45 | *.jspx 46 | 47 | 48 | 49 | 30 50 | 51 | 52 | 53 | 123 54 | application/vnd.lotus-1-2-3 55 | 56 | 57 | 3dml 58 | text/vnd.in3d.3dml 59 | 60 | 61 | 62 | index.html 63 | index.htm 64 | index.jsp 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /src/provisioner/bin/generate-mocks: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | pcfdev_dir=$(cd `dirname $0` && cd .. && pwd) 4 | mocks_dirs=$(go list -f '{{.Dir}}' provisioner/... | grep -v /vendor/ | grep '/mocks$') 5 | if [[ -n "$mocks_dirs" ]]; then 6 | find $mocks_dirs -name "*.go" -exec rm {} \; 7 | fi 8 | 9 | go install provisioner/vendor/github.com/golang/mock/mockgen 10 | go generate $(go list provisioner/... | grep -v /vendor/) 11 | -------------------------------------------------------------------------------- /src/provisioner/bin/tests: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | dir=$(cd `dirname $0` && cd .. && pwd) 4 | 5 | go get github.com/onsi/ginkgo/ginkgo 6 | go get github.com/onsi/gomega 7 | ginkgo "$@" -r $dir 8 | -------------------------------------------------------------------------------- /src/provisioner/cert/cert.go: -------------------------------------------------------------------------------- 1 | package cert 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "crypto/rsa" 7 | "crypto/x509" 8 | "crypto/x509/pkix" 9 | "encoding/pem" 10 | "math/big" 11 | "time" 12 | ) 13 | 14 | type Cert struct{} 15 | 16 | func (c *Cert) GenerateCerts(domain string) ([]byte, []byte, []byte, []byte, error) { 17 | caPrivateKey, err := rsa.GenerateKey(rand.Reader, 2048) 18 | if err != nil { 19 | return nil, nil, nil, nil, err 20 | } 21 | encodedCAPrivateKey := new(bytes.Buffer) 22 | if err := pem.Encode(encodedCAPrivateKey, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(caPrivateKey)}); err != nil { 23 | return nil, nil, nil, nil, err 24 | } 25 | 26 | caTemplate := c.generateTemplate(domain, true) 27 | encodedCACertificate, err := c.generateCert(caTemplate, caTemplate, &caPrivateKey.PublicKey, caPrivateKey) 28 | if err != nil { 29 | return nil, nil, nil, nil, err 30 | } 31 | 32 | privateKey, err := rsa.GenerateKey(rand.Reader, 2048) 33 | if err != nil { 34 | return nil, nil, nil, nil, err 35 | } 36 | encodedPrivateKey := new(bytes.Buffer) 37 | if err := pem.Encode(encodedPrivateKey, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)}); err != nil { 38 | return nil, nil, nil, nil, err 39 | } 40 | 41 | template := c.generateTemplate(domain, false) 42 | encodedCertificate, err := c.generateCert(template, caTemplate, &privateKey.PublicKey, caPrivateKey) 43 | if err != nil { 44 | return nil, nil, nil, nil, err 45 | } 46 | 47 | return encodedCertificate, encodedPrivateKey.Bytes(), encodedCACertificate, encodedCAPrivateKey.Bytes(), nil 48 | } 49 | 50 | func (c *Cert) generateCert(template, parentTemplate *x509.Certificate, publicKey *rsa.PublicKey, privateKey *rsa.PrivateKey) ([]byte, error) { 51 | certificate, err := x509.CreateCertificate(rand.Reader, template, parentTemplate, publicKey, privateKey) 52 | if err != nil { 53 | return nil, err 54 | } 55 | 56 | encodedCertificate := new(bytes.Buffer) 57 | if err := pem.Encode(encodedCertificate, &pem.Block{Type: "CERTIFICATE", Bytes: certificate}); err != nil { 58 | return nil, err 59 | } 60 | 61 | return encodedCertificate.Bytes(), nil 62 | } 63 | 64 | func (c *Cert) generateTemplate(domain string, isCA bool) *x509.Certificate { 65 | notBefore := time.Now().Add(-24 * time.Hour) 66 | notAfter := notBefore.Add(10 * 365 * 24 * time.Hour) 67 | 68 | template := &x509.Certificate{ 69 | DNSNames: []string{ 70 | domain, 71 | "*." + domain, 72 | "*.login." + domain, 73 | "*.uaa." + domain, 74 | }, 75 | EmailAddresses: []string{"pcfdev-eng@pivotal.io"}, 76 | NotBefore: notBefore, 77 | NotAfter: notAfter, 78 | SerialNumber: big.NewInt(1), 79 | Subject: pkix.Name{ 80 | CommonName: domain + " " + time.Now().Format(time.RFC3339), 81 | Country: []string{"US"}, 82 | Locality: []string{"New York"}, 83 | Organization: []string{"Cloud Foundry"}, 84 | OrganizationalUnit: []string{"PCF Dev"}, 85 | Province: []string{"New York"}, 86 | }, 87 | } 88 | 89 | if isCA { 90 | template.Subject.Organization = []string{"Cloud Foundry CA"} 91 | template.BasicConstraintsValid = true 92 | template.IsCA = true 93 | } 94 | 95 | return template 96 | } 97 | -------------------------------------------------------------------------------- /src/provisioner/cert/cert_suite_test.go: -------------------------------------------------------------------------------- 1 | package cert_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | ) 9 | 10 | func TestCert(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Cert Suite") 13 | } 14 | -------------------------------------------------------------------------------- /src/provisioner/cert/cert_test.go: -------------------------------------------------------------------------------- 1 | package cert_test 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | "math/big" 7 | "provisioner/cert" 8 | "time" 9 | 10 | . "github.com/onsi/ginkgo" 11 | . "github.com/onsi/gomega" 12 | ) 13 | 14 | var _ = Describe("Cert", func() { 15 | Describe("#GenerateCert", func() { 16 | var c *cert.Cert 17 | 18 | BeforeEach(func() { 19 | c = &cert.Cert{} 20 | }) 21 | 22 | It("should generate a certificate and private key signed by the CA", func() { 23 | certificateBytes, privateKeyBytes, caCertificateBytes, caPrivateKeyBytes, err := c.GenerateCerts("some-domain") 24 | Expect(err).NotTo(HaveOccurred()) 25 | 26 | yesterday := time.Now().Add(-24 * time.Hour) 27 | tenYearsFromYesterday := yesterday.Add(10 * 365 * 24 * time.Hour) 28 | 29 | certificate := parseCertificate(certificateBytes, privateKeyBytes) 30 | caCertificate := parseCertificate(caCertificateBytes, caPrivateKeyBytes) 31 | 32 | Expect(certificate.DNSNames).To(Equal([]string{ 33 | "some-domain", 34 | "*.some-domain", 35 | "*.login.some-domain", 36 | "*.uaa.some-domain", 37 | })) 38 | Expect(certificate.EmailAddresses).To(Equal([]string{"pcfdev-eng@pivotal.io"})) 39 | Expect(certificate.Issuer).To(Equal(caCertificate.Subject)) 40 | Expect(certificate.NotBefore).To(BeTemporally("~", yesterday, time.Minute)) 41 | Expect(certificate.NotAfter).To(BeTemporally("~", tenYearsFromYesterday, time.Minute)) 42 | Expect(certificate.SerialNumber).To(Equal(big.NewInt(1))) 43 | Expect(certificate.Subject.CommonName).To(ContainSubstring("some-domain")) 44 | Expect(certificate.Subject.Country).To(Equal([]string{"US"})) 45 | Expect(certificate.Subject.Locality).To(Equal([]string{"New York"})) 46 | Expect(certificate.Subject.Organization).To(Equal([]string{"Cloud Foundry"})) 47 | Expect(certificate.Subject.OrganizationalUnit).To(Equal([]string{"PCF Dev"})) 48 | Expect(certificate.Subject.Province).To(Equal([]string{"New York"})) 49 | Expect(certificate.IsCA).To(BeFalse()) 50 | }) 51 | 52 | Context("CA Cert", func() { 53 | It("should be a CA Cert", func() { 54 | _, _, caCertificateBytes, caPrivateKeyBytes, err := c.GenerateCerts("some-domain") 55 | Expect(err).NotTo(HaveOccurred()) 56 | 57 | certificate := parseCertificate(caCertificateBytes, caPrivateKeyBytes) 58 | Expect(certificate.IsCA).To(BeTrue()) 59 | }) 60 | }) 61 | }) 62 | }) 63 | 64 | func parseCertificate(certificateBytes []byte, privateKeyBytes []byte) *x509.Certificate { 65 | certificate, err := tls.X509KeyPair(certificateBytes, privateKeyBytes) 66 | Expect(err).NotTo(HaveOccurred()) 67 | certificates, err := x509.ParseCertificates(certificate.Certificate[0]) 68 | Expect(err).NotTo(HaveOccurred()) 69 | return certificates[0] 70 | } 71 | -------------------------------------------------------------------------------- /src/provisioner/fs/fs.go: -------------------------------------------------------------------------------- 1 | package fs 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "io/ioutil" 7 | "os" 8 | ) 9 | 10 | const ( 11 | FileModeRootReadWrite = 0644 12 | FileModeRootReadWriteExecutable = 0744 13 | ) 14 | 15 | type FS struct{} 16 | 17 | func (fs *FS) Exists(path string) (bool, error) { 18 | if _, err := os.Stat(path); err != nil { 19 | if os.IsNotExist(err) { 20 | return false, nil 21 | } 22 | return false, err 23 | } 24 | return true, nil 25 | } 26 | 27 | func (f *FS) Read(path string) ([]byte, error) { 28 | return ioutil.ReadFile(path) 29 | } 30 | 31 | func (f *FS) Mkdir(path string) error { 32 | if err := os.MkdirAll(path, 0755); err != nil { 33 | return fmt.Errorf("failed to create directory: %s", err) 34 | } 35 | 36 | return nil 37 | } 38 | 39 | func (f *FS) Write(path string, contents io.Reader, perm os.FileMode) error { 40 | file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm) 41 | if err != nil { 42 | return fmt.Errorf("failed to open file: %s", err) 43 | } 44 | defer file.Close() 45 | 46 | if _, err := io.Copy(file, contents); err != nil { 47 | return fmt.Errorf("failed to copy contents to file: %s", err) 48 | } 49 | 50 | return nil 51 | } 52 | -------------------------------------------------------------------------------- /src/provisioner/fs/fs_suite_test.go: -------------------------------------------------------------------------------- 1 | package fs_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | ) 9 | 10 | func TestFS(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "FS Suite") 13 | } 14 | -------------------------------------------------------------------------------- /src/provisioner/fs/fs_test.go: -------------------------------------------------------------------------------- 1 | package fs_test 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "path/filepath" 7 | "provisioner/fs" 8 | "strings" 9 | 10 | . "github.com/onsi/ginkgo" 11 | . "github.com/onsi/gomega" 12 | ) 13 | 14 | var _ = Describe("FS", func() { 15 | var ( 16 | f *fs.FS 17 | tempDir string 18 | ) 19 | 20 | BeforeEach(func() { 21 | f = &fs.FS{} 22 | var err error 23 | tempDir, err = ioutil.TempDir("", "pcfdev-fs") 24 | Expect(err).NotTo(HaveOccurred()) 25 | }) 26 | 27 | AfterEach(func() { 28 | os.RemoveAll(tempDir) 29 | }) 30 | 31 | Describe("#Mkdir", func() { 32 | Context("when the directory does not exist", func() { 33 | It("should create the directory", func() { 34 | Expect(f.Mkdir(filepath.Join(tempDir, "some-dir"))).To(Succeed()) 35 | _, err := os.Stat(filepath.Join(tempDir, "some-dir")) 36 | Expect(err).NotTo(HaveOccurred()) 37 | }) 38 | }) 39 | 40 | Context("when the directory already exists", func() { 41 | BeforeEach(func() { 42 | Expect(os.Mkdir(filepath.Join(tempDir, "some-dir"), 0755)).To(Succeed()) 43 | }) 44 | 45 | It("should do nothing", func() { 46 | Expect(f.Mkdir(filepath.Join(tempDir, "some-dir"))).To(Succeed()) 47 | _, err := os.Stat(filepath.Join(tempDir, "some-dir")) 48 | Expect(err).NotTo(HaveOccurred()) 49 | }) 50 | }) 51 | }) 52 | 53 | Describe("#Write", func() { 54 | Context("when path is valid", func() { 55 | It("should create a file with path and writes contents", func() { 56 | readCloser := ioutil.NopCloser(strings.NewReader("some-contents")) 57 | Expect(f.Write(filepath.Join(tempDir, "some-file"), readCloser, os.FileMode(fs.FileModeRootReadWrite))).To(Succeed()) 58 | data, err := ioutil.ReadFile(filepath.Join(tempDir, "some-file")) 59 | Expect(err).NotTo(HaveOccurred()) 60 | Expect(string(data)).To(Equal("some-contents")) 61 | }) 62 | }) 63 | 64 | Context("when file exists already", func() { 65 | BeforeEach(func() { 66 | Expect(f.Write(filepath.Join(tempDir, "some-file"), ioutil.NopCloser(strings.NewReader("some-content-that-is-really-long")), os.FileMode(fs.FileModeRootReadWrite))).To(Succeed()) 67 | }) 68 | 69 | It("should overwrite the file", func() { 70 | readCloser := ioutil.NopCloser(strings.NewReader("some-other-contents")) 71 | Expect(f.Write(filepath.Join(tempDir, "some-file"), readCloser, os.FileMode(fs.FileModeRootReadWrite))).To(Succeed()) 72 | data, err := ioutil.ReadFile(filepath.Join(tempDir, "some-file")) 73 | Expect(err).NotTo(HaveOccurred()) 74 | 75 | Expect(string(data)).To(Equal("some-other-contents")) 76 | }) 77 | }) 78 | 79 | Context("when path is invalid", func() { 80 | It("should return an error", func() { 81 | Expect(f.Write(filepath.Join("some-bad-dir", "some-other-file"), nil, os.FileMode(fs.FileModeRootReadWrite))).To(MatchError(ContainSubstring("failed to open file:"))) 82 | }) 83 | }) 84 | }) 85 | 86 | Describe("#Read", func() { 87 | Context("when the file exists", func() { 88 | It("should return the contents the file", func() { 89 | Expect(ioutil.WriteFile(filepath.Join(tempDir, "some-file"), []byte("some-contents"), 0644)).To(Succeed()) 90 | Expect(f.Read(filepath.Join(tempDir, "some-file"))).To(Equal([]byte("some-contents"))) 91 | }) 92 | }) 93 | }) 94 | 95 | Describe("#Exists", func() { 96 | Context("when the file exists", func() { 97 | BeforeEach(func() { 98 | _, err := os.Create(filepath.Join(tempDir, "some-file")) 99 | Expect(err).NotTo(HaveOccurred()) 100 | }) 101 | 102 | It("should return true", func() { 103 | Expect(f.Exists(filepath.Join(tempDir, "some-file"))).To(BeTrue()) 104 | }) 105 | }) 106 | 107 | Context("when the file does not exist", func() { 108 | It("should return false", func() { 109 | Expect(f.Exists(filepath.Join(tempDir, "some-bad-file"))).To(BeFalse()) 110 | }) 111 | }) 112 | }) 113 | }) 114 | -------------------------------------------------------------------------------- /src/provisioner/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "os" 7 | "os/exec" 8 | "provisioner/cert" 9 | "provisioner/fs" 10 | "provisioner/provisioner" 11 | "provisioner/provisioner/commands" 12 | "strconv" 13 | "syscall" 14 | "time" 15 | ) 16 | 17 | var ( 18 | provisionScriptPath = "/var/pcfdev/run" 19 | timeoutInSeconds = "3600" 20 | distro = "pcf" 21 | ) 22 | 23 | func main() { 24 | checkArgCount() 25 | 26 | provisionTimeout, err := strconv.Atoi(timeoutInSeconds) 27 | if err != nil { 28 | fmt.Printf("Error: %s.", err) 29 | os.Exit(1) 30 | } 31 | 32 | silentCommandRunner := &provisioner.ConcreteCmdRunner{ 33 | Stdout: ioutil.Discard, 34 | Stderr: ioutil.Discard, 35 | Timeout: time.Duration(provisionTimeout) * time.Second, 36 | } 37 | p := &provisioner.Provisioner{ 38 | Cert: &cert.Cert{}, 39 | CmdRunner: &provisioner.ConcreteCmdRunner{ 40 | Stdout: os.Stdout, 41 | Stderr: os.Stderr, 42 | Timeout: time.Duration(provisionTimeout) * time.Second, 43 | }, 44 | FS: &fs.FS{}, 45 | Commands: buildCommands(silentCommandRunner), 46 | 47 | Distro: distro, 48 | } 49 | 50 | if err := p.Provision(provisionScriptPath, os.Args[1:]...); err != nil { 51 | switch err.(type) { 52 | case *exec.ExitError: 53 | if exitErr, ok := err.(*exec.ExitError); ok { 54 | if status, ok := exitErr.Sys().(syscall.WaitStatus); ok { 55 | os.Exit(status.ExitStatus()) 56 | } else { 57 | os.Exit(1) 58 | } 59 | } 60 | case *provisioner.TimeoutError: 61 | fmt.Printf("Timed out after %s seconds.\n", timeoutInSeconds) 62 | os.Exit(1) 63 | default: 64 | os.Exit(1) 65 | } 66 | } 67 | } 68 | 69 | func checkArgCount() { 70 | if len(os.Args) < 6 { 71 | fmt.Println("Need 5 arguments, Usage: ./provision ") 72 | os.Exit(1) 73 | } 74 | } 75 | 76 | func buildCommands(commandRunner provisioner.CmdRunner) []provisioner.Command { 77 | providerAgnostic := []provisioner.Command{ 78 | &commands.DisableUAAHSTS{ 79 | WebXMLPath: "/var/vcap/packages/uaa/tomcat/conf/web.xml", 80 | }, 81 | &commands.ConfigureDnsmasq{ 82 | Domain: os.Args[1], 83 | ExternalIP: os.Args[2], 84 | FS: &fs.FS{}, 85 | CmdRunner: commandRunner, 86 | }, 87 | &commands.ConfigureGardenDNS{ 88 | FS: &fs.FS{}, 89 | CmdRunner: commandRunner, 90 | }, 91 | &commands.SetupApi{ 92 | CmdRunner: commandRunner, 93 | FS: &fs.FS{}, 94 | }, 95 | &commands.ReplaceDomain{ 96 | CmdRunner: commandRunner, 97 | FS: &fs.FS{}, 98 | NewDomain: os.Args[1], 99 | }, 100 | &commands.SetupCFDot{ 101 | CmdRunner: commandRunner, 102 | FS: &fs.FS{}, 103 | }, 104 | } 105 | 106 | const ( 107 | httpPort = "80" 108 | httpsPort = "443" 109 | sshPort = "22" 110 | sshProxyPort = "2222" 111 | tcpPortLower = 61001 112 | tcpPortHigher = 61100 113 | ) 114 | 115 | forAwsProvider := []provisioner.Command{ 116 | &commands.CloseAllPorts{ 117 | CmdRunner: commandRunner, 118 | }, 119 | &commands.OpenPort{ 120 | CmdRunner: commandRunner, 121 | Port: httpPort, 122 | }, 123 | &commands.OpenPort{ 124 | CmdRunner: commandRunner, 125 | Port: httpsPort, 126 | }, 127 | &commands.OpenPort{ 128 | CmdRunner: commandRunner, 129 | Port: sshPort, 130 | }, 131 | &commands.OpenPort{ 132 | CmdRunner: commandRunner, 133 | Port: sshProxyPort, 134 | }, 135 | } 136 | 137 | for p := tcpPortLower; p <= tcpPortHigher; p++ { 138 | forAwsProvider = append(forAwsProvider, &commands.OpenPort{ 139 | CmdRunner: commandRunner, 140 | Port: strconv.Itoa(p), 141 | }) 142 | } 143 | 144 | if isAwsProvisioner() { 145 | return append(providerAgnostic, forAwsProvider...) 146 | } else { 147 | return providerAgnostic 148 | } 149 | } 150 | 151 | func isAwsProvisioner() bool { 152 | return os.Args[5] == "aws" 153 | } 154 | -------------------------------------------------------------------------------- /src/provisioner/main_suite_test.go: -------------------------------------------------------------------------------- 1 | package main_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | ) 9 | 10 | func TestPCFDev(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "PCF Dev Main Suite") 13 | } 14 | -------------------------------------------------------------------------------- /src/provisioner/main_test.go: -------------------------------------------------------------------------------- 1 | package main_test 2 | 3 | import ( 4 | "os" 5 | "os/exec" 6 | "strings" 7 | 8 | "regexp" 9 | 10 | "path/filepath" 11 | 12 | . "github.com/onsi/ginkgo" 13 | . "github.com/onsi/gomega" 14 | "github.com/onsi/gomega/gbytes" 15 | "github.com/onsi/gomega/gexec" 16 | "math/rand" 17 | "net" 18 | "strconv" 19 | "time" 20 | ) 21 | 22 | var _ = Describe("PCF Dev provision", func() { 23 | var ( 24 | dockerID string 25 | pwd string 26 | dockerExecTimeout string = "5s" 27 | randomTcpPort string 28 | ) 29 | 30 | var portsForwarded struct { 31 | Port80 string 32 | Port8060 string 33 | Port443 string 34 | Port22 string 35 | Port2222 string 36 | Port61001 string 37 | Port61100 string 38 | RandomTcpPort string 39 | } 40 | 41 | BeforeEach(func() { 42 | portsForwarded.Port80 = randomOpenPort() 43 | portsForwarded.Port8060 = randomOpenPort() 44 | portsForwarded.Port443 = randomOpenPort() 45 | portsForwarded.Port22 = randomOpenPort() 46 | portsForwarded.Port2222 = randomOpenPort() 47 | portsForwarded.Port61001 = randomOpenPort() 48 | portsForwarded.Port61100 = randomOpenPort() 49 | portsForwarded.RandomTcpPort = randomOpenPort() 50 | 51 | var err error 52 | pwd, err = os.Getwd() 53 | Expect(err).NotTo(HaveOccurred()) 54 | 55 | randomTcpPort = randomPortInRange("61001", "61100") 56 | 57 | output, err := exec.Command( 58 | "docker", "run", 59 | "-p", portsForwarded.Port80+":80", 60 | "-p", portsForwarded.Port8060+":8060", 61 | "-p", portsForwarded.Port443+":443", 62 | "-p", portsForwarded.Port22+":22", 63 | "-p", portsForwarded.Port2222+":2222", 64 | "-p", portsForwarded.Port61001+":61001", 65 | "-p", portsForwarded.Port61100+":61100", 66 | "-p", portsForwarded.RandomTcpPort+":"+randomTcpPort, 67 | "--privileged", 68 | "-d", 69 | "-v", pwd+":/go/src/provisioner", 70 | "-w", "/go/src/provisioner", 71 | "pcfdev/provision", 72 | "bash", "-c", "umount /etc/resolv.conf && sleep 1000", 73 | ).Output() 74 | Expect(err).NotTo(HaveOccurred()) 75 | dockerID = strings.TrimSpace(string(output)) 76 | 77 | Expect(exec.Command("bash", "-c", "echo \"#!/bin/bash\necho 'Waiting for services to start...'\necho \\$@\" > "+pwd+"/provision-script").Run()).To(Succeed()) 78 | Expect(exec.Command("docker", "exec", dockerID, "chmod", "+x", "/go/src/provisioner/provision-script").Run()).To(Succeed()) 79 | Expect(exec.Command("docker", "exec", dockerID, "go", "build", "-ldflags", "-X main.provisionScriptPath=/go/src/provisioner/provision-script", "-o", "provision", "provisioner").Run()).To(Succeed()) 80 | runSuccessfully(exec.Command("docker", "exec", dockerID, "mkdir", "-p", "/var/pcfdev"), "10s") 81 | runSuccessfully(exec.Command("docker", "exec", dockerID, "bash", "-c", "echo original-domain.pcfdev.io > /var/pcfdev/domain"), "10s") 82 | 83 | runSuccessfully(exec.Command("docker", "exec", dockerID, "mkdir", "-p", "/var/vcap/jobs/cfdot/bin"), "10s") 84 | runSuccessfully(exec.Command("docker", "exec", dockerID, "bash", "-c", "echo 'echo setting up cfdot' > /var/vcap/jobs/cfdot/bin/setup"), "10s") 85 | }) 86 | 87 | AfterEach(func() { 88 | os.RemoveAll(filepath.Join(pwd, "provision")) 89 | os.RemoveAll(filepath.Join(pwd, "provision-script")) 90 | Expect(exec.Command("docker", "rm", dockerID, "-f").Run()).To(Succeed()) 91 | }) 92 | 93 | 94 | It("should provision PCF Dev", func() { 95 | session := provisionForVirtualBox(dockerID) 96 | Expect(session).To(gbytes.Say("Waiting for services to start...")) 97 | 98 | session = runSuccessfully(exec.Command("docker", "exec", dockerID, "file", "/run/pcfdev-healthcheck"), dockerExecTimeout) 99 | Expect(session).NotTo(gbytes.Say("No such file or directory")) 100 | }) 101 | 102 | It("should set up the monitrc files for an HTTP server and an root executable _ctl script running on the box", func() { 103 | provisionForVirtualBox(dockerID) 104 | 105 | session := runSuccessfully(exec.Command("docker", "exec", dockerID, "cat", "/var/vcap/monit/job/1001_pcfdev_api.monitrc"), dockerExecTimeout) 106 | Expect(session).To(gbytes.Say("check process pcfdev-api")) 107 | Expect(session).To(gbytes.Say("with pidfile /var/vcap/sys/run/pcfdev-api/api.pid")) 108 | Expect(session).To(gbytes.Say(`start program "/var/pcfdev/api/api_ctl start"`)) 109 | Expect(session).To(gbytes.Say(`stop program "/var/pcfdev/api/api_ctl stop"`)) 110 | Expect(session).To(gbytes.Say("group vcap")) 111 | Expect(session).To(gbytes.Say("mode manual")) 112 | 113 | session = runSuccessfully(exec.Command("docker", "exec", dockerID, "ls", "-l", "/var/pcfdev/api/api_ctl"), dockerExecTimeout) 114 | 115 | Expect(session).To(gbytes.Say("-rwxr--r--")) 116 | }) 117 | 118 | XIt("should create certificates", func() { 119 | provisionForVirtualBox(dockerID) 120 | runSuccessfully(exec.Command("docker", "exec", dockerID, "bash", "-c", "echo 127.0.0.1 local.pcfdev.io >> /etc/hosts"), dockerExecTimeout) 121 | runSuccessfully(exec.Command("docker", "exec", "-d", dockerID, "service", "nginx", "start"), dockerExecTimeout) 122 | runSuccessfully(exec.Command("docker", "exec", dockerID, "curl", "--cacert", "/var/pcfdev/openssl/ca_cert.pem", "https://local.pcfdev.io:443"), dockerExecTimeout) 123 | }) 124 | 125 | It("should disable HSTS in UAA", func() { 126 | provisionForVirtualBox(dockerID) 127 | 128 | session := runSuccessfully(exec.Command("docker", "exec", dockerID, "grep", "-A", "1", "hstsEnabled", "/var/vcap/packages/uaa/tomcat/conf/web.xml"), dockerExecTimeout) 129 | Eventually(session).Should(gbytes.Say("false")) 130 | }) 131 | 132 | It("should directly insert the internal-ip into the dns_server flag of garden", func() { 133 | provisionForVirtualBox(dockerID) 134 | 135 | output, err := exec.Command("docker", "exec", dockerID, "ip", "route", "get", "1").Output() 136 | Expect(err).NotTo(HaveOccurred()) 137 | regex := regexp.MustCompile(`\s{2}src\s(.*)`) 138 | internalIP := regex.FindStringSubmatch(string(output))[1] 139 | 140 | session := runSuccessfully(exec.Command("docker", "exec", dockerID, "grep", internalIP, "/var/vcap/jobs/garden/bin/garden_ctl"), dockerExecTimeout) 141 | Eventually(session).Should(gbytes.Say("-dnsServer=" + internalIP)) 142 | }) 143 | 144 | It("should copy cfdot setup script to /etc/profile.d", func() { 145 | provisionForVirtualBox(dockerID) 146 | 147 | output, err := exec.Command("docker", "exec", dockerID, "bash", "-c", "cat /etc/profile.d/cfdot.sh").Output() 148 | Expect(err).NotTo(HaveOccurred()) 149 | Expect(string(output)).To(Equal("echo setting up cfdot\n")) 150 | }) 151 | 152 | Describe("Network access", func() { 153 | Context("on AWS", func() { 154 | It("does not allow connections by default", func() { 155 | Expect(exec.Command("docker", "exec", "-d", dockerID, "go", "run", "assets/stub_server.go", "8060").Run()).To(Succeed()) 156 | waitForServer("localhost:"+portsForwarded.Port8060, 5*time.Second) 157 | provisionForAws(dockerID) 158 | runFailure(exec.Command("curl", "localhost:"+portsForwarded.Port8060), "5s") 159 | }) 160 | 161 | It("allows external access to http cf router", func() { 162 | Expect(exec.Command("docker", "exec", "-d", dockerID, "go", "run", "assets/stub_server.go", "80").Run()).To(Succeed()) 163 | waitForServer("localhost:"+portsForwarded.Port80, 5*time.Second) 164 | provisionForAws(dockerID) 165 | 166 | session := runSuccessfully(exec.Command("curl", "localhost:"+portsForwarded.Port80), "5s") 167 | Expect(session).To(gbytes.Say("Response from port 80 stub server")) 168 | }) 169 | 170 | It("allows external access to https cf router", func() { 171 | Expect(exec.Command("docker", "exec", "-d", dockerID, "go", "run", "assets/stub_server.go", "443").Run()).To(Succeed()) 172 | waitForServer("localhost:"+portsForwarded.Port443, 5*time.Second) 173 | provisionForAws(dockerID) 174 | 175 | session := runSuccessfully(exec.Command("curl", "localhost:"+portsForwarded.Port443), "5s") 176 | Expect(session).To(gbytes.Say("Response from port 443 stub server")) 177 | }) 178 | 179 | It("allows external access to ssh", func() { 180 | Expect(exec.Command("docker", "exec", "-d", dockerID, "go", "run", "assets/stub_server.go", "22").Run()).To(Succeed()) 181 | waitForServer("localhost:"+portsForwarded.Port22, 5*time.Second) 182 | provisionForAws(dockerID) 183 | 184 | session := runSuccessfully(exec.Command("curl", "localhost:"+portsForwarded.Port22), "5s") 185 | Expect(session).To(gbytes.Say("Response from port 22 stub server")) 186 | }) 187 | 188 | It("allows external access to the ssh proxy", func() { 189 | Expect(exec.Command("docker", "exec", "-d", dockerID, "go", "run", "assets/stub_server.go", "2222").Run()).To(Succeed()) 190 | waitForServer("localhost:"+portsForwarded.Port2222, 5*time.Second) 191 | provisionForAws(dockerID) 192 | session := runSuccessfully(exec.Command("curl", "localhost:"+portsForwarded.Port2222), "5s") 193 | Expect(session).To(gbytes.Say("Response from port 2222 stub server")) 194 | }) 195 | 196 | It("allow external access to tcp router port for lowest port in range", func() { 197 | Expect(exec.Command("docker", "exec", "-d", dockerID, "go", "run", "assets/stub_server.go", "61001").Run()).To(Succeed()) 198 | waitForServer("localhost:"+portsForwarded.Port61001, 5*time.Second) 199 | provisionForAws(dockerID) 200 | session := runSuccessfully(exec.Command("curl", "localhost:"+portsForwarded.Port61001), "5s") 201 | Expect(session).To(gbytes.Say("Response from port 61001 stub server")) 202 | 203 | }) 204 | 205 | It("allow external access to tcp router port for highest port in range", func() { 206 | Expect(exec.Command("docker", "exec", "-d", dockerID, "go", "run", "assets/stub_server.go", "61100").Run()).To(Succeed()) 207 | waitForServer("localhost:"+portsForwarded.Port61100, 5*time.Second) 208 | provisionForAws(dockerID) 209 | session := runSuccessfully(exec.Command("curl", "localhost:"+portsForwarded.Port61100), "5s") 210 | Expect(session).To(gbytes.Say("Response from port 61100 stub server")) 211 | 212 | }) 213 | 214 | It("allow external access to tcp router port for random port in range", func() { 215 | Expect(exec.Command("docker", "exec", "-d", dockerID, "go", "run", "assets/stub_server.go", randomTcpPort).Run()).To(Succeed()) 216 | waitForServer("localhost:"+portsForwarded.RandomTcpPort, 5*time.Second) 217 | provisionForAws(dockerID) 218 | session := runSuccessfully(exec.Command("curl", "localhost:"+portsForwarded.RandomTcpPort), "5s") 219 | Expect(session).To(gbytes.Say("Response from port " + randomTcpPort + " stub server")) 220 | }) 221 | }) 222 | 223 | Context("on Virtualbox", func() { 224 | It("allows connections by default", func() { 225 | Expect(exec.Command("docker", "exec", "-d", dockerID, "go", "run", "assets/stub_server.go", "8060").Run()).To(Succeed()) 226 | waitForServer("localhost:"+portsForwarded.Port8060, 5*time.Second) 227 | Expect(exec.Command("docker", "exec", "-d", dockerID, "go", "run", "assets/stub_server.go", "80").Run()).To(Succeed()) 228 | waitForServer("localhost:"+portsForwarded.Port80, 5*time.Second) 229 | Expect(exec.Command("docker", "exec", "-d", dockerID, "go", "run", "assets/stub_server.go", "443").Run()).To(Succeed()) 230 | waitForServer("localhost:"+portsForwarded.Port443, 5*time.Second) 231 | Expect(exec.Command("docker", "exec", "-d", dockerID, "go", "run", "assets/stub_server.go", "22").Run()).To(Succeed()) 232 | waitForServer("localhost:"+portsForwarded.Port22, 5*time.Second) 233 | Expect(exec.Command("docker", "exec", "-d", dockerID, "go", "run", "assets/stub_server.go", "2222").Run()).To(Succeed()) 234 | waitForServer("localhost:"+portsForwarded.Port2222, 5*time.Second) 235 | Expect(exec.Command("docker", "exec", "-d", dockerID, "go", "run", "assets/stub_server.go", "61001").Run()).To(Succeed()) 236 | waitForServer("localhost:"+portsForwarded.Port61001, 5*time.Second) 237 | Expect(exec.Command("docker", "exec", "-d", dockerID, "go", "run", "assets/stub_server.go", "61100").Run()).To(Succeed()) 238 | waitForServer("localhost:"+portsForwarded.Port61100, 5*time.Second) 239 | Expect(exec.Command("docker", "exec", "-d", dockerID, "go", "run", "assets/stub_server.go", randomTcpPort).Run()).To(Succeed()) 240 | waitForServer("localhost:"+portsForwarded.RandomTcpPort, 5*time.Second) 241 | 242 | provisionForVirtualBox(dockerID) 243 | 244 | Expect(runSuccessfully(exec.Command("curl", "localhost:"+portsForwarded.Port80), "5s")).To(gbytes.Say("Response from port 80 stub server")) 245 | Expect(runSuccessfully(exec.Command("curl", "localhost:"+portsForwarded.Port8060), "5s")).To(gbytes.Say("Response from port 8060 stub server")) 246 | Expect(runSuccessfully(exec.Command("curl", "localhost:"+portsForwarded.Port443), "5s")).To(gbytes.Say("Response from port 443 stub server")) 247 | Expect(runSuccessfully(exec.Command("curl", "localhost:"+portsForwarded.Port22), "5s")).To(gbytes.Say("Response from port 22 stub server")) 248 | Expect(runSuccessfully(exec.Command("curl", "localhost:"+portsForwarded.Port2222), "5s")).To(gbytes.Say("Response from port 2222 stub server")) 249 | Expect(runSuccessfully(exec.Command("curl", "localhost:"+portsForwarded.Port61001), "5s")).To(gbytes.Say("Response from port 61001 stub server")) 250 | Expect(runSuccessfully(exec.Command("curl", "localhost:"+portsForwarded.Port61100), "5s")).To(gbytes.Say("Response from port 61100 stub server")) 251 | Expect(runSuccessfully(exec.Command("curl", "localhost:"+portsForwarded.RandomTcpPort), "5s")).To(gbytes.Say("Response from port " + randomTcpPort + " stub server")) 252 | }) 253 | }) 254 | }) 255 | 256 | It("should resolve *.cf.internal to localhost using Dnsmasq", func() { 257 | provisionForVirtualBox(dockerID) 258 | session := runSuccessfully(exec.Command("docker", "exec", dockerID, "host", "bbs.service.cf.internal"), "3s") 259 | Eventually(session).Should(gbytes.Say(`bbs.service.cf.internal has address 127.0.0.1`)) 260 | }) 261 | 262 | It("should replace the old domain with the new domain in job files recursively without following symlinks", func() { 263 | runSuccessfully(exec.Command("docker", "exec", dockerID, "mkdir", "-p", "/var/vcap/jobs/some-job/config"), "10s") 264 | runSuccessfully(exec.Command("docker", "exec", dockerID, "bash", "-c", "echo 'domain: original-domain.pcfdev.io' > /var/vcap/jobs/some-job/config/some-file"), "10s") 265 | 266 | runSuccessfully(exec.Command("docker", "exec", dockerID, "mkdir", "-p", "/var/vcap/jobs/some-other-job/config/some-nested-dir"), "10s") 267 | runSuccessfully(exec.Command("docker", "exec", dockerID, "bash", "-c", "echo 'api: api.original-domain.pcfdev.io:443' > /var/vcap/jobs/some-other-job/config/some-nested-dir/some-file"), "10s") 268 | 269 | runSuccessfully(exec.Command("docker", "exec", dockerID, "mkdir", "-p", "/tmp/some-symlinked-dir"), "10s") 270 | runSuccessfully(exec.Command("docker", "exec", dockerID, "bash", "-c", "echo 'domain: original-domain.pcfdev.io' > /tmp/some-symlinked-dir/some-file"), "10s") 271 | runSuccessfully(exec.Command("docker", "exec", dockerID, "mkdir", "-p", "/var/vcap/jobs/some-job-with-symlink/config"), "10s") 272 | runSuccessfully(exec.Command("docker", "exec", dockerID, "ln", "-s", "/tmp/some-symlinked-dir", "/var/vcap/jobs/some-job-with-symlink/config"), "10s") 273 | 274 | runSuccessfully(exec.Command("docker", "exec", dockerID, "/go/src/provisioner/provision", "new-domain.pcfdev.io", "192.168.11.11", "", "", "virtualbox"), "100000s") 275 | 276 | Expect(runSuccessfully(exec.Command("docker", "exec", dockerID, "cat", "/var/vcap/jobs/some-job/config/some-file"), "10s")).To(gbytes.Say("domain: new-domain.pcfdev.io")) 277 | Expect(runSuccessfully(exec.Command("docker", "exec", dockerID, "cat", "/var/vcap/jobs/some-other-job/config/some-nested-dir/some-file"), "10s")).To(gbytes.Say("api: api.new-domain.pcfdev.io:443")) 278 | Expect(runSuccessfully(exec.Command("docker", "exec", dockerID, "cat", "/var/vcap/jobs/some-job-with-symlink/config/some-symlinked-dir/some-file"), "10s")).To(gbytes.Say("domain: original-domain.pcfdev.io")) 279 | }) 280 | 281 | Context("when the distribution is not 'pcf'", func() { 282 | BeforeEach(func() { 283 | Expect(exec.Command("docker", "exec", dockerID, "go", "build", "-ldflags", "-X main.distro=oss -X main.provisionScriptPath=/go/src/provisioner/provision-script", "-o", "provision", "provisioner").Run()).To(Succeed()) 284 | }) 285 | 286 | It("should not disable HSTS in UAA", func() { 287 | provisionForVirtualBox(dockerID) 288 | 289 | session, err := gexec.Start(exec.Command("docker", "exec", dockerID, "grep", "hstsEnabled", "/var/vcap/packages/uaa/tomcat/conf/web.xml"), GinkgoWriter, GinkgoWriter) 290 | Expect(err).NotTo(HaveOccurred()) 291 | Eventually(session).Should(gexec.Exit(1)) 292 | }) 293 | }) 294 | 295 | Context("when provisioning does not have the required number of args", func() { 296 | It("should exit with an error", func() { 297 | runFailure(exec.Command("docker", "exec", dockerID, "/go/src/provisioner/provision", "local.pcfdev.io", "192.168.11.11", "", ""), "10s") 298 | }) 299 | }) 300 | 301 | Context("when provisioning fails", func() { 302 | BeforeEach(func() { 303 | Expect(exec.Command("bash", "-c", "echo \"#!/bin/bash\nexit 42\" > "+pwd+"/provision-script").Run()).To(Succeed()) 304 | }) 305 | 306 | It("should exit with the exit status of the provision script", func() { 307 | session, _ := gexec.Start(exec.Command("docker", "exec", dockerID, "/go/src/provisioner/provision", "local.pcfdev.io", "192.168.11.11", "", "", "virtualbox"), GinkgoWriter, GinkgoWriter) 308 | Eventually(session, "10s").Should(gexec.Exit(42)) 309 | }) 310 | }) 311 | 312 | Context("when provisioning takes too long", func() { 313 | BeforeEach(func() { 314 | Expect(exec.Command("bash", "-c", "echo \"#!/bin/bash\nsleep 20\" > "+pwd+"/provision-script").Run()).To(Succeed()) 315 | Expect(exec.Command("docker", "exec", dockerID, "go", "build", "-ldflags", "-X main.provisionScriptPath=/go/src/provisioner/provision-script -X main.timeoutInSeconds=2", "-o", "provision", "provisioner").Run()).To(Succeed()) 316 | }) 317 | 318 | It("exit with an exit status of 1 and tell why it is exiting...", func() { 319 | session, err := gexec.Start(exec.Command("docker", "exec", dockerID, "/go/src/provisioner/provision", "local.pcfdev.io", "192.168.11.11", "", "", "virtualbox"), GinkgoWriter, GinkgoWriter) 320 | Expect(err).NotTo(HaveOccurred()) 321 | Eventually(session, "15s").Should(gexec.Exit(1)) 322 | Expect(session).To(gbytes.Say("Timed out after 2 seconds.")) 323 | }) 324 | }) 325 | }) 326 | 327 | func provisionForVirtualBox(dockerID string) *gexec.Session { 328 | return runSuccessfully(exec.Command("docker", "exec", dockerID, "/go/src/provisioner/provision", "local.pcfdev.io", "192.168.11.11", "", "", "virtualbox"), "10s") 329 | } 330 | 331 | func provisionForAws(dockerID string) *gexec.Session { 332 | return runSuccessfully(exec.Command("docker", "exec", dockerID, "/go/src/provisioner/provision", "local.pcfdev.io", "192.168.11.11", "", "", "aws"), "10s") 333 | } 334 | 335 | func runSuccessfully(command *exec.Cmd, timeout string) *gexec.Session { 336 | session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) 337 | ExpectWithOffset(1, err).NotTo(HaveOccurred()) 338 | EventuallyWithOffset(1, session, timeout).Should(gexec.Exit(0)) 339 | return session 340 | } 341 | 342 | func runFailure(command *exec.Cmd, timeout string) *gexec.Session { 343 | session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) 344 | ExpectWithOffset(1, err).NotTo(HaveOccurred()) 345 | ConsistentlyWithOffset(1, session, timeout).ShouldNot(gexec.Exit(0)) 346 | return session 347 | } 348 | 349 | func randomOpenPort() string { 350 | conn, err := net.Listen("tcp", "127.0.0.1:0") 351 | Expect(err).NotTo(HaveOccurred()) 352 | defer conn.Close() 353 | address := strings.Split(conn.Addr().String(), ":") 354 | return address[1] 355 | } 356 | 357 | func waitForServer(host string, timeout time.Duration) { 358 | currentWait := 0 * time.Second 359 | serverOpen := false 360 | 361 | for !serverOpen && currentWait < timeout { 362 | exec.Command("curl", host) 363 | session, _ := gexec.Start(exec.Command("curl", host), GinkgoWriter, GinkgoWriter) 364 | Eventually(session, "1s").Should(gexec.Exit()) 365 | if session.ExitCode() == 0 { 366 | serverOpen = true 367 | } 368 | currentWait += time.Second * 1 369 | time.Sleep(time.Second) 370 | } 371 | Expect(serverOpen).To(BeTrue()) 372 | } 373 | 374 | func randomPortInRange(lowerPort string, higherPort string) string { 375 | 376 | higherPortNumber, err := strconv.Atoi(higherPort) 377 | Expect(err).NotTo(HaveOccurred()) 378 | 379 | lowerPortNumber, err := strconv.Atoi(lowerPort) 380 | Expect(err).NotTo(HaveOccurred()) 381 | 382 | return strconv.Itoa(rand.Intn(higherPortNumber-lowerPortNumber) + lowerPortNumber) 383 | 384 | } 385 | -------------------------------------------------------------------------------- /src/provisioner/provisioner/commands/close_all_ports.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "provisioner/provisioner" 5 | ) 6 | 7 | type CloseAllPorts struct { 8 | CmdRunner provisioner.CmdRunner 9 | } 10 | 11 | func (c *CloseAllPorts) Run() error { 12 | if err := c.dropNewConnections("eth0"); err != nil { 13 | return err 14 | } 15 | 16 | if err := c.dropNewConnections("eth1"); err != nil { 17 | return err 18 | } 19 | 20 | return c.CmdRunner.Run("iptables", "-I", "INPUT", "-i", "lo", "-j", "ACCEPT") 21 | } 22 | 23 | func (c *CloseAllPorts) dropNewConnections(interfaceName string) error { 24 | if err := c.CmdRunner.Run("iptables", "-I", "INPUT", "-i", interfaceName, "-p", "tcp", "-j", "DROP"); err != nil { 25 | return err 26 | } 27 | 28 | return c.CmdRunner.Run("iptables", "-I", "INPUT", "-i", interfaceName, "-m", "conntrack", "--ctstate", "ESTABLISHED,RELATED", "-j", "ACCEPT") 29 | } 30 | 31 | func (*CloseAllPorts) Distro() string { 32 | return provisioner.DistributionOSS 33 | } -------------------------------------------------------------------------------- /src/provisioner/provisioner/commands/commands_suite_test.go: -------------------------------------------------------------------------------- 1 | package commands_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | ) 9 | 10 | func TestCommands(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Commands Suite") 13 | } 14 | -------------------------------------------------------------------------------- /src/provisioner/provisioner/commands/configure_dnsmasq.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "fmt" 5 | "provisioner/provisioner" 6 | "regexp" 7 | "strings" 8 | "provisioner/fs" 9 | ) 10 | 11 | type ConfigureDnsmasq struct { 12 | FS provisioner.FS 13 | CmdRunner provisioner.CmdRunner 14 | Domain string 15 | ExternalIP string 16 | } 17 | 18 | func (c *ConfigureDnsmasq) Run() error { 19 | if err := c.CmdRunner.Run("resolvconf", "--disable-updates"); err != nil { 20 | return err 21 | } 22 | 23 | if err := c.CmdRunner.Run("service", "dnsmasq", "stop"); err != nil { 24 | return err 25 | } 26 | 27 | output, err := c.CmdRunner.Output("ip", "route", "get", "1") 28 | if err != nil { 29 | return err 30 | } 31 | 32 | var internalIP string 33 | regex := regexp.MustCompile(`\s{2}src\s(.*)`) 34 | if matches := regex.FindStringSubmatch(string(output)); len(matches) > 1 { 35 | internalIP = matches[1] 36 | } else { 37 | return fmt.Errorf("internal ip could not be parsed from output: %s", string(output)) 38 | } 39 | 40 | if err := c.FS.Write("/etc/dnsmasq.d/domain", strings.NewReader(fmt.Sprintf("address=/.%s/%s\naddress=/.cf.internal/127.0.0.1", c.Domain, c.ExternalIP)), fs.FileModeRootReadWrite); err != nil { 41 | return err 42 | } 43 | 44 | if err := c.FS.Write("/etc/dnsmasq.d/interface", strings.NewReader(fmt.Sprintf("listen-address=%s", internalIP)), fs.FileModeRootReadWrite); err != nil { 45 | return err 46 | } 47 | 48 | if err := c.FS.Write("/etc/dnsmasq.conf", strings.NewReader("resolv-file=/var/pcfdev/external-resolv.conf"), fs.FileModeRootReadWrite); err != nil { 49 | return err 50 | } 51 | 52 | exists, err := c.FS.Exists("/var/pcfdev/external-resolv.conf") 53 | if err != nil { 54 | return err 55 | } 56 | 57 | if !exists { 58 | data, err := c.FS.Read("/etc/resolv.conf") 59 | if err != nil { 60 | return err 61 | } 62 | 63 | nameservers := []string{} 64 | for _, nameserver := range strings.Split(string(data), "\n") { 65 | if strings.HasPrefix(nameserver, "nameserver") && !strings.Contains(nameserver, "127.0.0.1") { 66 | nameservers = append(nameservers, nameserver) 67 | } 68 | } 69 | 70 | if err := c.FS.Write("/var/pcfdev/external-resolv.conf", strings.NewReader(strings.Join(nameservers, "\n")), fs.FileModeRootReadWrite); err != nil { 71 | return err 72 | } 73 | 74 | } 75 | 76 | if err := c.CmdRunner.Run("service", "dnsmasq", "start"); err != nil { 77 | return err 78 | } 79 | 80 | return c.FS.Write("/etc/resolv.conf", strings.NewReader(fmt.Sprintf("nameserver %s", internalIP)), fs.FileModeRootReadWrite) 81 | } 82 | 83 | func (*ConfigureDnsmasq) Distro() string { 84 | return provisioner.DistributionOSS 85 | } 86 | -------------------------------------------------------------------------------- /src/provisioner/provisioner/commands/configure_dnsmasq_test.go: -------------------------------------------------------------------------------- 1 | package commands_test 2 | 3 | import ( 4 | "errors" 5 | "provisioner/provisioner" 6 | "provisioner/provisioner/commands" 7 | "provisioner/provisioner/mocks" 8 | "strings" 9 | 10 | "github.com/golang/mock/gomock" 11 | . "github.com/onsi/ginkgo" 12 | . "github.com/onsi/gomega" 13 | "os" 14 | "provisioner/fs" 15 | ) 16 | 17 | var _ = Describe("ConfigureDnsmasq", func() { 18 | var ( 19 | mockCtrl *gomock.Controller 20 | mockFS *mocks.MockFS 21 | mockCmdRunner *mocks.MockCmdRunner 22 | cDnsmasq *commands.ConfigureDnsmasq 23 | ) 24 | 25 | BeforeEach(func() { 26 | mockCtrl = gomock.NewController(GinkgoT()) 27 | mockFS = mocks.NewMockFS(mockCtrl) 28 | mockCmdRunner = mocks.NewMockCmdRunner(mockCtrl) 29 | cDnsmasq = &commands.ConfigureDnsmasq{ 30 | FS: mockFS, 31 | CmdRunner: mockCmdRunner, 32 | Domain: "some-domain", 33 | ExternalIP: "some-external-ip", 34 | } 35 | }) 36 | 37 | AfterEach(func() { 38 | mockCtrl.Finish() 39 | }) 40 | 41 | Describe("#Run", func() { 42 | Context("when there are external nameservers in /etc/resolv.conf", func() { 43 | It("should write dns resolutions for consul and domain into the dnsmasq config and save external nameservers", func() { 44 | gomock.InOrder( 45 | mockCmdRunner.EXPECT().Run("resolvconf", "--disable-updates"), 46 | mockCmdRunner.EXPECT().Run("service", "dnsmasq", "stop"), 47 | mockCmdRunner.EXPECT().Output("ip", "route", "get", "1").Return([]byte("some-ip via some-other-ip dev eth0 src some-internal-ip\n cache"), nil), 48 | mockFS.EXPECT().Write("/etc/dnsmasq.d/domain", strings.NewReader("address=/.some-domain/some-external-ip\naddress=/.cf.internal/127.0.0.1"), os.FileMode(fs.FileModeRootReadWrite)), 49 | mockFS.EXPECT().Write("/etc/dnsmasq.d/interface", strings.NewReader("listen-address=some-internal-ip"), os.FileMode(fs.FileModeRootReadWrite)), 50 | mockFS.EXPECT().Write("/etc/dnsmasq.conf", strings.NewReader("resolv-file=/var/pcfdev/external-resolv.conf"), os.FileMode(fs.FileModeRootReadWrite)), 51 | mockFS.EXPECT().Exists("/var/pcfdev/external-resolv.conf").Return(false, nil), 52 | mockFS.EXPECT().Read("/etc/resolv.conf").Return([]byte("nameserver 127.0.0.1\nnameserver some-external-nameserver\n# Generated by bosh-agent\nnameserver some-other-external-nameserver"), nil), 53 | mockFS.EXPECT().Write("/var/pcfdev/external-resolv.conf", strings.NewReader("nameserver some-external-nameserver\nnameserver some-other-external-nameserver"), os.FileMode(fs.FileModeRootReadWrite)), 54 | mockCmdRunner.EXPECT().Run("service", "dnsmasq", "start"), 55 | mockFS.EXPECT().Write("/etc/resolv.conf", strings.NewReader("nameserver some-internal-ip"), os.FileMode(fs.FileModeRootReadWrite)), 56 | ) 57 | 58 | Expect(cDnsmasq.Run()).To(Succeed()) 59 | }) 60 | }) 61 | 62 | Context("when there are no external nameservers in /etc/resolv.conf", func() { 63 | It("should write dns resolutions for consul and domain into the dnsmasq config and save external nameservers", func() { 64 | gomock.InOrder( 65 | mockCmdRunner.EXPECT().Run("resolvconf", "--disable-updates"), 66 | mockCmdRunner.EXPECT().Run("service", "dnsmasq", "stop"), 67 | mockCmdRunner.EXPECT().Output("ip", "route", "get", "1").Return([]byte("some-ip via some-other-ip dev eth0 src some-internal-ip\n cache"), nil), 68 | mockFS.EXPECT().Write("/etc/dnsmasq.d/domain", strings.NewReader("address=/.some-domain/some-external-ip\naddress=/.cf.internal/127.0.0.1"), os.FileMode(fs.FileModeRootReadWrite)), 69 | mockFS.EXPECT().Write("/etc/dnsmasq.d/interface", strings.NewReader("listen-address=some-internal-ip"), os.FileMode(fs.FileModeRootReadWrite)), 70 | mockFS.EXPECT().Write("/etc/dnsmasq.conf", strings.NewReader("resolv-file=/var/pcfdev/external-resolv.conf"), os.FileMode(fs.FileModeRootReadWrite)), 71 | mockFS.EXPECT().Exists("/var/pcfdev/external-resolv.conf").Return(false, nil), 72 | mockFS.EXPECT().Read("/etc/resolv.conf").Return([]byte("nameserver 127.0.0.1"), nil), 73 | mockFS.EXPECT().Write("/var/pcfdev/external-resolv.conf", strings.NewReader(""), os.FileMode(fs.FileModeRootReadWrite)), 74 | mockCmdRunner.EXPECT().Run("service", "dnsmasq", "start"), 75 | mockFS.EXPECT().Write("/etc/resolv.conf", strings.NewReader("nameserver some-internal-ip"), os.FileMode(fs.FileModeRootReadWrite)), 76 | ) 77 | 78 | Expect(cDnsmasq.Run()).To(Succeed()) 79 | }) 80 | }) 81 | 82 | Context("when external-resolv.conf already exists", func() { 83 | It("should not overwrite the file", func() { 84 | gomock.InOrder( 85 | mockCmdRunner.EXPECT().Run("resolvconf", "--disable-updates"), 86 | mockCmdRunner.EXPECT().Run("service", "dnsmasq", "stop"), 87 | mockCmdRunner.EXPECT().Output("ip", "route", "get", "1").Return([]byte("some-ip via some-other-ip dev eth0 src some-internal-ip\n cache"), nil), 88 | mockFS.EXPECT().Write("/etc/dnsmasq.d/domain", strings.NewReader("address=/.some-domain/some-external-ip\naddress=/.cf.internal/127.0.0.1"), os.FileMode(fs.FileModeRootReadWrite)), 89 | mockFS.EXPECT().Write("/etc/dnsmasq.d/interface", strings.NewReader("listen-address=some-internal-ip"), os.FileMode(fs.FileModeRootReadWrite)), 90 | mockFS.EXPECT().Write("/etc/dnsmasq.conf", strings.NewReader("resolv-file=/var/pcfdev/external-resolv.conf"), os.FileMode(fs.FileModeRootReadWrite)), 91 | mockFS.EXPECT().Exists("/var/pcfdev/external-resolv.conf").Return(true, nil), 92 | mockCmdRunner.EXPECT().Run("service", "dnsmasq", "start"), 93 | mockFS.EXPECT().Write("/etc/resolv.conf", strings.NewReader("nameserver some-internal-ip"), os.FileMode(fs.FileModeRootReadWrite)), 94 | ) 95 | 96 | Expect(cDnsmasq.Run()).To(Succeed()) 97 | }) 98 | }) 99 | 100 | Context("when there is an error disabling resolvconf updates", func() { 101 | It("should return the error", func() { 102 | mockCmdRunner.EXPECT().Run("resolvconf", "--disable-updates").Return(errors.New("some-error")) 103 | 104 | Expect(cDnsmasq.Run()).To(MatchError("some-error")) 105 | }) 106 | }) 107 | 108 | Context("when there is an error stopping dnsmasq", func() { 109 | It("should return the error", func() { 110 | gomock.InOrder( 111 | mockCmdRunner.EXPECT().Run("resolvconf", "--disable-updates"), 112 | mockCmdRunner.EXPECT().Run("service", "dnsmasq", "stop").Return(errors.New("some-error")), 113 | ) 114 | 115 | Expect(cDnsmasq.Run()).To(MatchError("some-error")) 116 | }) 117 | }) 118 | 119 | Context("when there is an error writing the dnsmasq conf", func() { 120 | It("should return the error", func() { 121 | gomock.InOrder( 122 | mockCmdRunner.EXPECT().Run("resolvconf", "--disable-updates"), 123 | mockCmdRunner.EXPECT().Run("service", "dnsmasq", "stop"), 124 | mockCmdRunner.EXPECT().Output("ip", "route", "get", "1").Return([]byte("some-ip via some-other-ip dev eth0 src some-internal-ip\n cache"), nil), 125 | mockFS.EXPECT().Write("/etc/dnsmasq.d/domain", strings.NewReader("address=/.some-domain/some-external-ip\naddress=/.cf.internal/127.0.0.1"), os.FileMode(fs.FileModeRootReadWrite)), 126 | mockFS.EXPECT().Write("/etc/dnsmasq.d/interface", strings.NewReader("listen-address=some-internal-ip"), os.FileMode(fs.FileModeRootReadWrite)), 127 | mockFS.EXPECT().Write("/etc/dnsmasq.conf", strings.NewReader("resolv-file=/var/pcfdev/external-resolv.conf"), os.FileMode(fs.FileModeRootReadWrite)).Return(errors.New("some-error")), 128 | ) 129 | 130 | Expect(cDnsmasq.Run()).To(MatchError("some-error")) 131 | }) 132 | }) 133 | 134 | Context("when there is an error checking the resolv conf", func() { 135 | It("should return the error", func() { 136 | gomock.InOrder( 137 | mockCmdRunner.EXPECT().Run("resolvconf", "--disable-updates"), 138 | mockCmdRunner.EXPECT().Run("service", "dnsmasq", "stop"), 139 | mockCmdRunner.EXPECT().Output("ip", "route", "get", "1").Return([]byte("some-ip via some-other-ip dev eth0 src some-internal-ip\n cache"), nil), 140 | mockFS.EXPECT().Write("/etc/dnsmasq.d/domain", strings.NewReader("address=/.some-domain/some-external-ip\naddress=/.cf.internal/127.0.0.1"), os.FileMode(fs.FileModeRootReadWrite)), 141 | mockFS.EXPECT().Write("/etc/dnsmasq.d/interface", strings.NewReader("listen-address=some-internal-ip"), os.FileMode(fs.FileModeRootReadWrite)), 142 | mockFS.EXPECT().Write("/etc/dnsmasq.conf", strings.NewReader("resolv-file=/var/pcfdev/external-resolv.conf"), os.FileMode(fs.FileModeRootReadWrite)), 143 | mockFS.EXPECT().Exists("/var/pcfdev/external-resolv.conf").Return(false, errors.New("some-error")), 144 | ) 145 | 146 | Expect(cDnsmasq.Run()).To(MatchError("some-error")) 147 | }) 148 | }) 149 | 150 | Context("when there is an error reading the resolv conf", func() { 151 | It("should return the error", func() { 152 | gomock.InOrder( 153 | mockCmdRunner.EXPECT().Run("resolvconf", "--disable-updates"), 154 | mockCmdRunner.EXPECT().Run("service", "dnsmasq", "stop"), 155 | mockCmdRunner.EXPECT().Output("ip", "route", "get", "1").Return([]byte("some-ip via some-other-ip dev eth0 src some-internal-ip\n cache"), nil), 156 | mockFS.EXPECT().Write("/etc/dnsmasq.d/domain", strings.NewReader("address=/.some-domain/some-external-ip\naddress=/.cf.internal/127.0.0.1"), os.FileMode(fs.FileModeRootReadWrite)), 157 | mockFS.EXPECT().Write("/etc/dnsmasq.d/interface", strings.NewReader("listen-address=some-internal-ip"), os.FileMode(fs.FileModeRootReadWrite)), 158 | mockFS.EXPECT().Write("/etc/dnsmasq.conf", strings.NewReader("resolv-file=/var/pcfdev/external-resolv.conf"), os.FileMode(fs.FileModeRootReadWrite)), 159 | mockFS.EXPECT().Exists("/var/pcfdev/external-resolv.conf").Return(false, nil), 160 | mockFS.EXPECT().Read("/etc/resolv.conf").Return(nil, errors.New("some-error")), 161 | ) 162 | 163 | Expect(cDnsmasq.Run()).To(MatchError("some-error")) 164 | }) 165 | }) 166 | 167 | Context("when there is an error writing the pcfdev external resolv.conf", func() { 168 | It("should return the error", func() { 169 | gomock.InOrder( 170 | mockCmdRunner.EXPECT().Run("resolvconf", "--disable-updates"), 171 | mockCmdRunner.EXPECT().Run("service", "dnsmasq", "stop"), 172 | mockCmdRunner.EXPECT().Output("ip", "route", "get", "1").Return([]byte("some-ip via some-other-ip dev eth0 src some-internal-ip\n cache"), nil), 173 | mockFS.EXPECT().Write("/etc/dnsmasq.d/domain", strings.NewReader("address=/.some-domain/some-external-ip\naddress=/.cf.internal/127.0.0.1"), os.FileMode(fs.FileModeRootReadWrite)), 174 | mockFS.EXPECT().Write("/etc/dnsmasq.d/interface", strings.NewReader("listen-address=some-internal-ip"), os.FileMode(fs.FileModeRootReadWrite)), 175 | mockFS.EXPECT().Write("/etc/dnsmasq.conf", strings.NewReader("resolv-file=/var/pcfdev/external-resolv.conf"), os.FileMode(fs.FileModeRootReadWrite)), 176 | mockFS.EXPECT().Exists("/var/pcfdev/external-resolv.conf").Return(false, nil), 177 | mockFS.EXPECT().Read("/etc/resolv.conf").Return([]byte("nameserver 127.0.0.1\nnameserver some-external-nameserver\nnameserver some-other-external-nameserver"), nil), 178 | mockFS.EXPECT().Write("/var/pcfdev/external-resolv.conf", strings.NewReader("nameserver some-external-nameserver\nnameserver some-other-external-nameserver"), os.FileMode(fs.FileModeRootReadWrite)).Return(errors.New("some-error")), 179 | ) 180 | 181 | Expect(cDnsmasq.Run()).To(MatchError("some-error")) 182 | }) 183 | }) 184 | 185 | Context("when there is an error starting dnsmasq", func() { 186 | It("should return the error", func() { 187 | gomock.InOrder( 188 | mockCmdRunner.EXPECT().Run("resolvconf", "--disable-updates"), 189 | mockCmdRunner.EXPECT().Run("service", "dnsmasq", "stop"), 190 | mockCmdRunner.EXPECT().Output("ip", "route", "get", "1").Return([]byte("some-ip via some-other-ip dev eth0 src some-internal-ip\n cache"), nil), 191 | mockFS.EXPECT().Write("/etc/dnsmasq.d/domain", strings.NewReader("address=/.some-domain/some-external-ip\naddress=/.cf.internal/127.0.0.1"), os.FileMode(fs.FileModeRootReadWrite)), 192 | mockFS.EXPECT().Write("/etc/dnsmasq.d/interface", strings.NewReader("listen-address=some-internal-ip"), os.FileMode(fs.FileModeRootReadWrite)), 193 | mockFS.EXPECT().Write("/etc/dnsmasq.conf", strings.NewReader("resolv-file=/var/pcfdev/external-resolv.conf"), os.FileMode(fs.FileModeRootReadWrite)), 194 | mockFS.EXPECT().Exists("/var/pcfdev/external-resolv.conf").Return(false, nil), 195 | mockFS.EXPECT().Read("/etc/resolv.conf").Return([]byte("nameserver 127.0.0.1\nnameserver some-external-nameserver\nnameserver some-other-external-nameserver"), nil), 196 | mockFS.EXPECT().Write("/var/pcfdev/external-resolv.conf", strings.NewReader("nameserver some-external-nameserver\nnameserver some-other-external-nameserver"), os.FileMode(fs.FileModeRootReadWrite)), 197 | mockCmdRunner.EXPECT().Run("service", "dnsmasq", "start").Return(errors.New("some-error")), 198 | ) 199 | 200 | Expect(cDnsmasq.Run()).To(MatchError("some-error")) 201 | }) 202 | }) 203 | 204 | Context("when there is an error writing to the /etc/resolv.conf", func() { 205 | It("should return the error", func() { 206 | gomock.InOrder( 207 | mockCmdRunner.EXPECT().Run("resolvconf", "--disable-updates"), 208 | mockCmdRunner.EXPECT().Run("service", "dnsmasq", "stop"), 209 | mockCmdRunner.EXPECT().Output("ip", "route", "get", "1").Return([]byte("some-ip via some-other-ip dev eth0 src some-internal-ip\n cache"), nil), 210 | mockFS.EXPECT().Write("/etc/dnsmasq.d/domain", strings.NewReader("address=/.some-domain/some-external-ip\naddress=/.cf.internal/127.0.0.1"), os.FileMode(fs.FileModeRootReadWrite)), 211 | mockFS.EXPECT().Write("/etc/dnsmasq.d/interface", strings.NewReader("listen-address=some-internal-ip"), os.FileMode(fs.FileModeRootReadWrite)), 212 | mockFS.EXPECT().Write("/etc/dnsmasq.conf", strings.NewReader("resolv-file=/var/pcfdev/external-resolv.conf"), os.FileMode(fs.FileModeRootReadWrite)), 213 | mockFS.EXPECT().Exists("/var/pcfdev/external-resolv.conf").Return(false, nil), 214 | mockFS.EXPECT().Read("/etc/resolv.conf").Return([]byte("nameserver 127.0.0.1\nnameserver some-external-nameserver\nnameserver some-other-external-nameserver"), nil), 215 | mockFS.EXPECT().Write("/var/pcfdev/external-resolv.conf", strings.NewReader("nameserver some-external-nameserver\nnameserver some-other-external-nameserver"), os.FileMode(fs.FileModeRootReadWrite)), 216 | mockCmdRunner.EXPECT().Run("service", "dnsmasq", "start"), 217 | mockFS.EXPECT().Write("/etc/resolv.conf", strings.NewReader("nameserver some-internal-ip"), os.FileMode(fs.FileModeRootReadWrite)).Return(errors.New("some-error")), 218 | ) 219 | 220 | Expect(cDnsmasq.Run()).To(MatchError("some-error")) 221 | }) 222 | }) 223 | 224 | Context("when the internal ip is not returned from the output", func() { 225 | It("should return an error", func() { 226 | gomock.InOrder( 227 | mockCmdRunner.EXPECT().Run("resolvconf", "--disable-updates"), 228 | mockCmdRunner.EXPECT().Run("service", "dnsmasq", "stop"), 229 | mockCmdRunner.EXPECT().Output("ip", "route", "get", "1").Return([]byte("some-bad-output"), nil), 230 | ) 231 | 232 | Expect(cDnsmasq.Run()).To(MatchError("internal ip could not be parsed from output: some-bad-output")) 233 | }) 234 | }) 235 | 236 | Context("when there is an error retrieving the internal ip", func() { 237 | It("should return an error", func() { 238 | gomock.InOrder( 239 | mockCmdRunner.EXPECT().Run("resolvconf", "--disable-updates"), 240 | mockCmdRunner.EXPECT().Run("service", "dnsmasq", "stop"), 241 | mockCmdRunner.EXPECT().Output("ip", "route", "get", "1").Return(nil, errors.New("some-error")), 242 | ) 243 | 244 | Expect(cDnsmasq.Run()).To(MatchError("some-error")) 245 | }) 246 | }) 247 | 248 | Context("when there is an error writing the dnsmasq configuration", func() { 249 | It("should return an error", func() { 250 | gomock.InOrder( 251 | mockCmdRunner.EXPECT().Run("resolvconf", "--disable-updates"), 252 | mockCmdRunner.EXPECT().Run("service", "dnsmasq", "stop"), 253 | mockCmdRunner.EXPECT().Output("ip", "route", "get", "1").Return([]byte("some-ip via some-other-ip dev eth0 src some-internal-ip\n cache"), nil), 254 | mockFS.EXPECT().Write("/etc/dnsmasq.d/domain", strings.NewReader("address=/.some-domain/some-external-ip\naddress=/.cf.internal/127.0.0.1"), os.FileMode(fs.FileModeRootReadWrite)).Return(errors.New("some-error")), 255 | ) 256 | 257 | Expect(cDnsmasq.Run()).To(MatchError("some-error")) 258 | }) 259 | }) 260 | 261 | Context("when there is an error writing the dnsmasq interface configuration", func() { 262 | It("should return an error", func() { 263 | gomock.InOrder( 264 | mockCmdRunner.EXPECT().Run("resolvconf", "--disable-updates"), 265 | mockCmdRunner.EXPECT().Run("service", "dnsmasq", "stop"), 266 | mockCmdRunner.EXPECT().Output("ip", "route", "get", "1").Return([]byte("some-ip via some-other-ip dev eth0 src some-internal-ip\n cache"), nil), 267 | mockFS.EXPECT().Write("/etc/dnsmasq.d/domain", strings.NewReader("address=/.some-domain/some-external-ip\naddress=/.cf.internal/127.0.0.1"), os.FileMode(fs.FileModeRootReadWrite)), 268 | mockFS.EXPECT().Write("/etc/dnsmasq.d/interface", strings.NewReader("listen-address=some-internal-ip"), os.FileMode(fs.FileModeRootReadWrite)).Return(errors.New("some-error")), 269 | ) 270 | 271 | Expect(cDnsmasq.Run()).To(MatchError("some-error")) 272 | }) 273 | }) 274 | }) 275 | 276 | Describe("#Distro", func() { 277 | It("should return 'oss'", func() { 278 | Expect(cDnsmasq.Distro()).To(Equal(provisioner.DistributionOSS)) 279 | }) 280 | }) 281 | }) 282 | -------------------------------------------------------------------------------- /src/provisioner/provisioner/commands/configure_garden_dns.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "fmt" 5 | "provisioner/provisioner" 6 | "regexp" 7 | "strings" 8 | "provisioner/fs" 9 | ) 10 | 11 | type ConfigureGardenDNS struct { 12 | FS provisioner.FS 13 | CmdRunner provisioner.CmdRunner 14 | } 15 | 16 | func (c *ConfigureGardenDNS) Run() error { 17 | output, err := c.CmdRunner.Output("ip", "route", "get", "1") 18 | if err != nil { 19 | return err 20 | } 21 | 22 | var internalIP string 23 | regex := regexp.MustCompile(`\s{2}src\s(.*)`) 24 | if matches := regex.FindStringSubmatch(string(output)); len(matches) > 1 { 25 | internalIP = matches[1] 26 | } else { 27 | return fmt.Errorf("internal ip could not be parsed from output: %s", string(output)) 28 | } 29 | 30 | gardenBytes, err := c.FS.Read("/var/vcap/jobs/garden/bin/garden_ctl") 31 | if err != nil { 32 | return err 33 | } 34 | 35 | cleanedGardenCtl := []string{} 36 | 37 | for _, line := range strings.Split(string(gardenBytes), "\n") { 38 | if !strings.Contains(line, "-dnsServer=") { 39 | cleanedGardenCtl = append(cleanedGardenCtl, line) 40 | } 41 | } 42 | 43 | dnsInsertString := strings.Replace(strings.Join(cleanedGardenCtl, "\n"), `1>>$LOG_DIR/garden.stdout.log \`, fmt.Sprintf("-dnsServer=%s \\\n 1>>$LOG_DIR/garden.stdout.log \\", internalIP), fs.FileModeRootReadWrite) 44 | return c.FS.Write("/var/vcap/jobs/garden/bin/garden_ctl", strings.NewReader(dnsInsertString), fs.FileModeRootReadWrite) 45 | } 46 | 47 | func (*ConfigureGardenDNS) Distro() string { 48 | return provisioner.DistributionOSS 49 | } 50 | -------------------------------------------------------------------------------- /src/provisioner/provisioner/commands/configure_garden_dns_test.go: -------------------------------------------------------------------------------- 1 | package commands_test 2 | 3 | import ( 4 | "provisioner/provisioner/commands" 5 | "provisioner/provisioner/mocks" 6 | "strings" 7 | 8 | "github.com/cppforlife/packer-bosh/bosh-provisioner/Godeps/_workspace/src/github.com/cloudfoundry/bosh-agent/errors" 9 | "github.com/golang/mock/gomock" 10 | . "github.com/onsi/ginkgo" 11 | . "github.com/onsi/gomega" 12 | "os" 13 | "provisioner/fs" 14 | "provisioner/provisioner" 15 | ) 16 | 17 | var _ = Describe("ConfigureGardenDNS", func() { 18 | var ( 19 | mockCtrl *gomock.Controller 20 | mockFS *mocks.MockFS 21 | mockCmdRunner *mocks.MockCmdRunner 22 | cmd *commands.ConfigureGardenDNS 23 | ) 24 | 25 | BeforeEach(func() { 26 | mockCtrl = gomock.NewController(GinkgoT()) 27 | mockFS = mocks.NewMockFS(mockCtrl) 28 | mockCmdRunner = mocks.NewMockCmdRunner(mockCtrl) 29 | cmd = &commands.ConfigureGardenDNS{ 30 | FS: mockFS, 31 | CmdRunner: mockCmdRunner, 32 | } 33 | }) 34 | 35 | AfterEach(func() { 36 | mockCtrl.Finish() 37 | }) 38 | 39 | Describe("#Run", func() { 40 | Context("when there are no dnsServers", func() { 41 | It("should use the internal IP as a DNS server in garden", func() { 42 | gomock.InOrder( 43 | mockCmdRunner.EXPECT().Output("ip", "route", "get", "1").Return([]byte("some-ip via some-other-ip dev eth0 src some-internal-ip\n cache"), nil), 44 | mockFS.EXPECT().Read("/var/vcap/jobs/garden/bin/garden_ctl").Return([]byte("some-executable \\\n -someProperty=some-value \\\n 1>>$LOG_DIR/garden.stdout.log \\"), nil), 45 | mockFS.EXPECT().Write("/var/vcap/jobs/garden/bin/garden_ctl", strings.NewReader("some-executable \\\n -someProperty=some-value \\\n -dnsServer=some-internal-ip \\\n 1>>$LOG_DIR/garden.stdout.log \\"), os.FileMode(fs.FileModeRootReadWrite)), 46 | ) 47 | 48 | Expect(cmd.Run()).To(Succeed()) 49 | }) 50 | }) 51 | 52 | Context("when there are dnsServers", func() { 53 | It("should use the internal IP as a DNS server in garden", func() { 54 | gomock.InOrder( 55 | mockCmdRunner.EXPECT().Output("ip", "route", "get", "1").Return([]byte("some-ip via some-other-ip dev eth0 src some-internal-ip\n cache"), nil), 56 | mockFS.EXPECT().Read("/var/vcap/jobs/garden/bin/garden_ctl").Return([]byte("some-executable \\\n -someProperty=some-value \\\n -dnsServer=some-ip \\\n -dnsServer=some-other-ip \\\n 1>>$LOG_DIR/garden.stdout.log \\"), nil), 57 | mockFS.EXPECT().Write("/var/vcap/jobs/garden/bin/garden_ctl", strings.NewReader("some-executable \\\n -someProperty=some-value \\\n -dnsServer=some-internal-ip \\\n 1>>$LOG_DIR/garden.stdout.log \\"), os.FileMode(fs.FileModeRootReadWrite)), 58 | ) 59 | 60 | Expect(cmd.Run()).To(Succeed()) 61 | }) 62 | }) 63 | 64 | Context("when there is an error gettting the internal ip", func() { 65 | It("return the error", func() { 66 | mockCmdRunner.EXPECT().Output("ip", "route", "get", "1").Return(nil, errors.New("some-error")) 67 | 68 | Expect(cmd.Run()).To(MatchError("some-error")) 69 | }) 70 | }) 71 | 72 | Context("when the internal ip cannot be parsed", func() { 73 | It("return an error", func() { 74 | mockCmdRunner.EXPECT().Output("ip", "route", "get", "1").Return([]byte("some-bad-output"), nil) 75 | 76 | Expect(cmd.Run()).To(MatchError("internal ip could not be parsed from output: some-bad-output")) 77 | }) 78 | }) 79 | 80 | Context("when there is an error reading the garden ctl", func() { 81 | It("return the error", func() { 82 | gomock.InOrder( 83 | mockCmdRunner.EXPECT().Output("ip", "route", "get", "1").Return([]byte("some-ip via some-other-ip dev eth0 src some-internal-ip\n cache"), nil), 84 | mockFS.EXPECT().Read("/var/vcap/jobs/garden/bin/garden_ctl").Return(nil, errors.New("some-error")), 85 | ) 86 | 87 | Expect(cmd.Run()).To(MatchError("some-error")) 88 | }) 89 | }) 90 | 91 | Context("when there is an error rewriting the garden ctl", func() { 92 | It("return the error", func() { 93 | gomock.InOrder( 94 | mockCmdRunner.EXPECT().Output("ip", "route", "get", "1").Return([]byte("some-ip via some-other-ip dev eth0 src some-internal-ip\n cache"), nil), 95 | mockFS.EXPECT().Read("/var/vcap/jobs/garden/bin/garden_ctl").Return([]byte("some-executable \\\n -someProperty=some-value \\\n -dnsServer=some-ip \\\n -dnsServer=some-other-ip \\\n 1>>$LOG_DIR/garden.stdout.log \\"), nil), 96 | mockFS.EXPECT().Write("/var/vcap/jobs/garden/bin/garden_ctl", strings.NewReader("some-executable \\\n -someProperty=some-value \\\n -dnsServer=some-internal-ip \\\n 1>>$LOG_DIR/garden.stdout.log \\"), os.FileMode(fs.FileModeRootReadWrite)).Return(errors.New("some-error")), 97 | ) 98 | 99 | Expect(cmd.Run()).To(MatchError("some-error")) 100 | }) 101 | }) 102 | }) 103 | 104 | Describe("#Distro", func() { 105 | It("should return 'oss'", func() { 106 | Expect(cmd.Distro()).To(Equal(provisioner.DistributionOSS)) 107 | }) 108 | }) 109 | }) 110 | -------------------------------------------------------------------------------- /src/provisioner/provisioner/commands/disable_uaa_hsts.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "bytes" 5 | "encoding/xml" 6 | "io/ioutil" 7 | "os" 8 | "strings" 9 | 10 | "golang.org/x/net/html/charset" 11 | "provisioner/provisioner" 12 | ) 13 | 14 | type DisableUAAHSTS struct { 15 | WebXMLPath string 16 | } 17 | 18 | func (d *DisableUAAHSTS) Run() error { 19 | var webXMLData WebApp 20 | 21 | webXMLContents, err := ioutil.ReadFile(d.WebXMLPath) 22 | if err != nil { 23 | return err 24 | } 25 | 26 | decoder := xml.NewDecoder(bytes.NewReader(webXMLContents)) 27 | decoder.CharsetReader = charset.NewReaderLabel 28 | if err := decoder.Decode(&webXMLData); err != nil { 29 | return err 30 | } 31 | 32 | hstsFilter := Filter{ 33 | FilterName: "httpHeaderSecurity", 34 | FilterClass: "org.apache.catalina.filters.HttpHeaderSecurityFilter", 35 | InitParam: InitParam{ 36 | ParamName: "hstsEnabled", 37 | ParamValue: "false", 38 | }, 39 | AsyncSupported: true, 40 | } 41 | hstsFilterExists := false 42 | for _, filter := range webXMLData.Filters { 43 | if strings.TrimSpace(filter.FilterName) == strings.TrimSpace(hstsFilter.FilterName) && 44 | strings.TrimSpace(filter.FilterClass) == strings.TrimSpace(hstsFilter.FilterClass) && 45 | strings.TrimSpace(filter.InitParam.ParamName) == strings.TrimSpace(hstsFilter.InitParam.ParamName) { 46 | hstsFilterExists = true 47 | } 48 | } 49 | 50 | if hstsFilterExists { 51 | webXMLData.Filters = nil 52 | } else { 53 | webXMLData.Filters = []Filter{hstsFilter} 54 | } 55 | 56 | webXMLFile, err := os.OpenFile(d.WebXMLPath, os.O_WRONLY|os.O_TRUNC, 0644) 57 | if err != nil { 58 | panic(err) 59 | } 60 | defer webXMLFile.Close() 61 | 62 | encoder := xml.NewEncoder(webXMLFile) 63 | encoder.Indent("", " ") 64 | if err := encoder.Encode(&webXMLData); err != nil { 65 | panic(err) 66 | } 67 | 68 | return nil 69 | } 70 | 71 | func (*DisableUAAHSTS) Distro() string { 72 | return provisioner.DistributionPCF 73 | } 74 | 75 | type WebApp struct { 76 | XMLName xml.Name `xml:"web-app"` 77 | Filters []Filter `xml:"filter"` 78 | AllXML string `xml:",innerxml"` 79 | } 80 | 81 | type Filter struct { 82 | FilterName string `xml:"filter-name"` 83 | FilterClass string `xml:"filter-class"` 84 | InitParam InitParam `xml:"init-param"` 85 | AsyncSupported bool `xml:"async-supported"` 86 | } 87 | 88 | type InitParam struct { 89 | ParamName string `xml:"param-name"` 90 | ParamValue string `xml:"param-value"` 91 | } 92 | -------------------------------------------------------------------------------- /src/provisioner/provisioner/commands/disable_uaa_hsts_test.go: -------------------------------------------------------------------------------- 1 | package commands_test 2 | 3 | import ( 4 | "encoding/xml" 5 | "io/ioutil" 6 | "os" 7 | "path/filepath" 8 | 9 | "provisioner/provisioner/commands" 10 | 11 | . "github.com/onsi/ginkgo" 12 | . "github.com/onsi/gomega" 13 | "golang.org/x/net/html/charset" 14 | "provisioner/provisioner" 15 | ) 16 | 17 | var ( 18 | tempDir string 19 | webXMLPath string 20 | ) 21 | 22 | var _ = Describe("DisableUAAHSTS", func() { 23 | 24 | var cmd *commands.DisableUAAHSTS 25 | 26 | Describe("#Run", func() { 27 | BeforeEach(func() { 28 | var err error 29 | tempDir, err = ioutil.TempDir("", "pcfdev-commands") 30 | Expect(err).NotTo(HaveOccurred()) 31 | }) 32 | AfterEach(func() { 33 | os.RemoveAll(tempDir) 34 | }) 35 | 36 | Context("when HSTS is not disabled in tomcat's web.xml", func() { 37 | BeforeEach(func() { 38 | templateXMLpath := filepath.Join("..", "..", "assets", "tomcat-web.xml") 39 | writeTomcatXML(templateXMLpath) 40 | 41 | cmd = &commands.DisableUAAHSTS{ 42 | WebXMLPath: webXMLPath, 43 | } 44 | }) 45 | 46 | It("should edit tomcat's web.xml to disable HSTS", func() { 47 | webXMLData := decodeWebXML(webXMLPath) 48 | Expect(len(webXMLData.Filters)).To(Equal(0)) 49 | 50 | Expect(cmd.Run()).To(Succeed()) 51 | 52 | webXMLData = decodeWebXML(webXMLPath) 53 | Expect(len(webXMLData.Filters)).To(Equal(1)) 54 | Expect(webXMLData.Filters[0].FilterName).To(Equal("httpHeaderSecurity")) 55 | Expect(webXMLData.Filters[0].FilterClass).To(Equal("org.apache.catalina.filters.HttpHeaderSecurityFilter")) 56 | Expect(webXMLData.Filters[0].InitParam.ParamName).To(Equal("hstsEnabled")) 57 | Expect(webXMLData.Filters[0].InitParam.ParamValue).To(Equal("false")) 58 | Expect(webXMLData.Filters[0].AsyncSupported).To(BeTrue()) 59 | }) 60 | }) 61 | 62 | Context("when HSTS is already disabled in tomcat's web.xml", func() { 63 | BeforeEach(func() { 64 | templateXMLpath := filepath.Join("..", "..", "assets", "tomcat-web-hsts-disabled.xml") 65 | writeTomcatXML(templateXMLpath) 66 | 67 | cmd = &commands.DisableUAAHSTS{ 68 | WebXMLPath: webXMLPath, 69 | } 70 | }) 71 | 72 | It("should not add another filter and keep the other filters", func() { 73 | webXMLData := decodeWebXML(webXMLPath) 74 | Expect(len(webXMLData.Filters)).To(Equal(2)) 75 | Expect(webXMLData.Filters[0].FilterName).To(Equal("httpHeaderSecurity")) 76 | Expect(webXMLData.Filters[0].FilterClass).To(Equal("org.apache.catalina.filters.HttpHeaderSecurityFilter")) 77 | Expect(webXMLData.Filters[0].InitParam.ParamName).To(Equal("hstsEnabled")) 78 | Expect(webXMLData.Filters[0].InitParam.ParamValue).To(Equal("false")) 79 | Expect(webXMLData.Filters[0].AsyncSupported).To(BeTrue()) 80 | 81 | Expect(webXMLData.Filters[1].FilterName).To(Equal("some-other-filter")) 82 | Expect(webXMLData.Filters[1].FilterClass).To(Equal("some-other-company")) 83 | Expect(webXMLData.Filters[1].InitParam.ParamName).To(Equal("some-param")) 84 | Expect(webXMLData.Filters[1].InitParam.ParamValue).To(Equal("some-value")) 85 | 86 | Expect(cmd.Run()).To(Succeed()) 87 | 88 | webXMLData = decodeWebXML(webXMLPath) 89 | Expect(len(webXMLData.Filters)).To(Equal(2)) 90 | Expect(webXMLData.Filters[0].FilterName).To(Equal("httpHeaderSecurity")) 91 | Expect(webXMLData.Filters[0].FilterClass).To(Equal("org.apache.catalina.filters.HttpHeaderSecurityFilter")) 92 | Expect(webXMLData.Filters[0].InitParam.ParamName).To(Equal("hstsEnabled")) 93 | Expect(webXMLData.Filters[0].InitParam.ParamValue).To(Equal("false")) 94 | Expect(webXMLData.Filters[0].AsyncSupported).To(BeTrue()) 95 | Expect(webXMLData.Filters[1].FilterName).To(Equal("some-other-filter")) 96 | Expect(webXMLData.Filters[1].FilterClass).To(Equal("some-other-company")) 97 | Expect(webXMLData.Filters[1].InitParam.ParamName).To(Equal("some-param")) 98 | Expect(webXMLData.Filters[1].InitParam.ParamValue).To(Equal("some-value")) 99 | }) 100 | }) 101 | 102 | Context("when the path to the web.xml does not exist", func() { 103 | BeforeEach(func() { 104 | cmd = &commands.DisableUAAHSTS{ 105 | WebXMLPath: "/some/bad/path", 106 | } 107 | }) 108 | 109 | It("should return an error", func() { 110 | Expect(cmd.Run()).To(MatchError(ContainSubstring("no such file or directory"))) 111 | }) 112 | }) 113 | 114 | Context("when the XML being parsed is invalid", func() { 115 | BeforeEach(func() { 116 | templateXMLpath := filepath.Join("..", "..", "assets", "tomcat-web-invalid.xml") 117 | writeTomcatXML(templateXMLpath) 118 | 119 | cmd = &commands.DisableUAAHSTS{ 120 | WebXMLPath: webXMLPath, 121 | } 122 | }) 123 | 124 | It("should return an error", func() { 125 | Expect(cmd.Run()).To(MatchError(ContainSubstring("EOF"))) 126 | }) 127 | }) 128 | }) 129 | 130 | Describe("#Distro", func() { 131 | It("should return 'pcf'", func() { 132 | Expect(cmd.Distro()).To(Equal(provisioner.DistributionPCF)) 133 | }) 134 | }) 135 | }) 136 | 137 | func writeTomcatXML(path string) { 138 | webXMLContents, err := ioutil.ReadFile(path) 139 | Expect(err).NotTo(HaveOccurred()) 140 | 141 | webXMLPath = filepath.Join(tempDir, "web.xml") 142 | Expect(ioutil.WriteFile(webXMLPath, webXMLContents, 0644)).To(Succeed()) 143 | 144 | } 145 | 146 | func decodeWebXML(webXMLPath string) *commands.WebApp { 147 | var webXMLData commands.WebApp 148 | webXMLReader, err := os.Open(webXMLPath) 149 | defer webXMLReader.Close() 150 | Expect(err).NotTo(HaveOccurred()) 151 | decoder := xml.NewDecoder(webXMLReader) 152 | decoder.CharsetReader = charset.NewReaderLabel 153 | Expect(decoder.Decode(&webXMLData)).To(Succeed()) 154 | return &webXMLData 155 | } 156 | -------------------------------------------------------------------------------- /src/provisioner/provisioner/commands/open_port.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "provisioner/provisioner" 5 | ) 6 | 7 | type OpenPort struct { 8 | CmdRunner provisioner.CmdRunner 9 | Port string 10 | } 11 | 12 | func (o *OpenPort) Run() error { 13 | return o.CmdRunner.Run("iptables", "-I", "INPUT", "-p", "tcp", "--dport", o.Port, "-j", "ACCEPT") 14 | } 15 | 16 | func (*OpenPort) Distro() string { 17 | return provisioner.DistributionOSS 18 | } -------------------------------------------------------------------------------- /src/provisioner/provisioner/commands/replace_domain.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "fmt" 5 | "provisioner/fs" 6 | "provisioner/provisioner" 7 | "strings" 8 | ) 9 | 10 | type ReplaceDomain struct { 11 | CmdRunner provisioner.CmdRunner 12 | FS provisioner.FS 13 | NewDomain string 14 | } 15 | 16 | func (r *ReplaceDomain) Run() error { 17 | files, err := r.CmdRunner.Output("bash", "-c", "find /var/vcap/jobs/*/ -type f") 18 | if err != nil { 19 | return err 20 | } 21 | 22 | oldDomain, err := r.FS.Read("/var/pcfdev/domain") 23 | if err != nil { 24 | return err 25 | } 26 | 27 | err = r.CmdRunner.Run("bash", "-c", fmt.Sprintf(`perl -p -i -e s/\\Q%s\\E/%s/g %s`, strings.TrimSpace(string(oldDomain)), r.NewDomain, strings.Replace(string(files), "\n", " ", -1))) 28 | if err != nil { 29 | return err 30 | } 31 | 32 | return r.FS.Write("/var/pcfdev/domain", strings.NewReader(r.NewDomain), fs.FileModeRootReadWrite) 33 | } 34 | 35 | func (r *ReplaceDomain) Distro() string { 36 | return provisioner.DistributionOSS 37 | } 38 | -------------------------------------------------------------------------------- /src/provisioner/provisioner/commands/replace_domain_test.go: -------------------------------------------------------------------------------- 1 | package commands_test 2 | 3 | import ( 4 | "github.com/golang/mock/gomock" 5 | "strings" 6 | 7 | . "github.com/onsi/ginkgo" 8 | . "github.com/onsi/gomega" 9 | 10 | "errors" 11 | "os" 12 | "provisioner/fs" 13 | "provisioner/provisioner" 14 | "provisioner/provisioner/commands" 15 | "provisioner/provisioner/mocks" 16 | ) 17 | 18 | var _ = Describe("ReplaceDomain", func() { 19 | var ( 20 | mockCtrl *gomock.Controller 21 | mockFS *mocks.MockFS 22 | mockCmdRunner *mocks.MockCmdRunner 23 | cmd *commands.ReplaceDomain 24 | ) 25 | 26 | BeforeEach(func() { 27 | mockCtrl = gomock.NewController(GinkgoT()) 28 | mockFS = mocks.NewMockFS(mockCtrl) 29 | mockCmdRunner = mocks.NewMockCmdRunner(mockCtrl) 30 | cmd = &commands.ReplaceDomain{ 31 | FS: mockFS, 32 | CmdRunner: mockCmdRunner, 33 | NewDomain: "some-new-domain", 34 | } 35 | }) 36 | 37 | AfterEach(func() { 38 | mockCtrl.Finish() 39 | }) 40 | 41 | Describe("#Run", func() { 42 | It("should replace the old domain with the new domain in all files (without following symlinks) in /var/vcap/jobs", func() { 43 | gomock.InOrder( 44 | mockCmdRunner.EXPECT().Output("bash", "-c", "find /var/vcap/jobs/*/ -type f").Return([]byte("/var/vcap/jobs/some-job/some-file\n/var/vcap/jobs/some-job/some-other-file"), nil), 45 | mockFS.EXPECT().Read("/var/pcfdev/domain").Return([]byte("some-old-domain\n"), nil), 46 | mockCmdRunner.EXPECT().Run("bash", "-c", `perl -p -i -e s/\\Qsome-old-domain\\E/some-new-domain/g /var/vcap/jobs/some-job/some-file /var/vcap/jobs/some-job/some-other-file`), 47 | mockFS.EXPECT().Write("/var/pcfdev/domain", strings.NewReader("some-new-domain"), os.FileMode(fs.FileModeRootReadWrite)), 48 | ) 49 | 50 | Expect(cmd.Run()).To(Succeed()) 51 | }) 52 | 53 | Context("when finding job files fails", func() { 54 | It("should return an error", func() { 55 | mockCmdRunner.EXPECT().Output("bash", "-c", "find /var/vcap/jobs/*/ -type f").Return(nil, errors.New("some-error")) 56 | 57 | Expect(cmd.Run()).To(MatchError("some-error")) 58 | }) 59 | }) 60 | 61 | Context("when reading domain file fails", func() { 62 | It("should return an error", func() { 63 | gomock.InOrder( 64 | mockCmdRunner.EXPECT().Output("bash", "-c", "find /var/vcap/jobs/*/ -type f").Return([]byte("/var/vcap/jobs/some-job/some-file\n/var/vcap/jobs/some-job/some-other-file"), nil), 65 | mockFS.EXPECT().Read("/var/pcfdev/domain").Return(nil, errors.New("some-error")), 66 | ) 67 | Expect(cmd.Run()).To(MatchError("some-error")) 68 | }) 69 | }) 70 | 71 | Context("when replacing the domain in the job files fails", func() { 72 | It("should return an error", func() { 73 | gomock.InOrder( 74 | mockCmdRunner.EXPECT().Output("bash", "-c", "find /var/vcap/jobs/*/ -type f").Return([]byte("/var/vcap/jobs/some-job/some-file\n/var/vcap/jobs/some-job/some-other-file"), nil), 75 | mockFS.EXPECT().Read("/var/pcfdev/domain").Return([]byte("some-old-domain\n"), nil), 76 | mockCmdRunner.EXPECT().Run("bash", "-c", `perl -p -i -e s/\\Qsome-old-domain\\E/some-new-domain/g /var/vcap/jobs/some-job/some-file /var/vcap/jobs/some-job/some-other-file`).Return(errors.New("some-error")), 77 | ) 78 | 79 | Expect(cmd.Run()).To(MatchError("some-error")) 80 | }) 81 | }) 82 | 83 | Context("when writing the new domain fails", func() { 84 | It("should return an error", func() { 85 | gomock.InOrder( 86 | mockCmdRunner.EXPECT().Output("bash", "-c", "find /var/vcap/jobs/*/ -type f").Return([]byte("/var/vcap/jobs/some-job/some-file\n/var/vcap/jobs/some-job/some-other-file"), nil), 87 | mockFS.EXPECT().Read("/var/pcfdev/domain").Return([]byte("some-old-domain\n"), nil), 88 | mockCmdRunner.EXPECT().Run("bash", "-c", `perl -p -i -e s/\\Qsome-old-domain\\E/some-new-domain/g /var/vcap/jobs/some-job/some-file /var/vcap/jobs/some-job/some-other-file`), 89 | mockFS.EXPECT().Write("/var/pcfdev/domain", strings.NewReader("some-new-domain"), os.FileMode(fs.FileModeRootReadWrite)).Return(errors.New("some-error")), 90 | ) 91 | 92 | Expect(cmd.Run()).To(MatchError("some-error")) 93 | }) 94 | }) 95 | }) 96 | 97 | Describe("#Distro", func() { 98 | It("should return 'oss'", func() { 99 | Expect(cmd.Distro()).To(Equal(provisioner.DistributionOSS)) 100 | }) 101 | }) 102 | }) 103 | -------------------------------------------------------------------------------- /src/provisioner/provisioner/commands/setup_api.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "provisioner/provisioner" 5 | "strings" 6 | "provisioner/fs" 7 | ) 8 | 9 | type SetupApi struct { 10 | CmdRunner provisioner.CmdRunner 11 | FS provisioner.FS 12 | } 13 | 14 | func (s *SetupApi) Run() error { 15 | monitrcContents := `check process pcfdev-api 16 | with pidfile /var/vcap/sys/run/pcfdev-api/api.pid 17 | start program "/var/pcfdev/api/api_ctl start" 18 | stop program "/var/pcfdev/api/api_ctl stop" 19 | group vcap 20 | mode manual` 21 | 22 | apiCtlContents := `#!/bin/bash 23 | set -ex 24 | 25 | PIDFILE=/var/vcap/sys/run/pcfdev-api/api.pid 26 | 27 | case $1 in 28 | 29 | start) 30 | mkdir -p /var/vcap/sys/run/pcfdev-api 31 | /var/pcfdev/api/api & 32 | echo $! > ${PIDFILE} 33 | 34 | ;; 35 | 36 | stop) 37 | kill $(cat $PIDFILE) 38 | 39 | ;; 40 | 41 | *) 42 | echo "Usage: pcfdev_api_ctl {start|stop}" 43 | ;; 44 | 45 | esac` 46 | 47 | err := s.FS.Write("/var/vcap/monit/job/1001_pcfdev_api.monitrc", strings.NewReader(monitrcContents), fs.FileModeRootReadWrite) 48 | if err != nil { 49 | return err 50 | } 51 | 52 | return s.FS.Write("/var/pcfdev/api/api_ctl", strings.NewReader(apiCtlContents), fs.FileModeRootReadWriteExecutable) 53 | } 54 | 55 | func(s *SetupApi) Distro() string { 56 | return provisioner.DistributionOSS 57 | } -------------------------------------------------------------------------------- /src/provisioner/provisioner/commands/setup_api_test.go: -------------------------------------------------------------------------------- 1 | package commands_test 2 | 3 | import ( 4 | "github.com/golang/mock/gomock" 5 | "strings" 6 | 7 | . "github.com/onsi/ginkgo" 8 | . "github.com/onsi/gomega" 9 | 10 | "errors" 11 | "os" 12 | "provisioner/fs" 13 | "provisioner/provisioner" 14 | "provisioner/provisioner/commands" 15 | "provisioner/provisioner/mocks" 16 | ) 17 | 18 | var _ = Describe("SetupApi", func() { 19 | var ( 20 | mockCtrl *gomock.Controller 21 | mockFS *mocks.MockFS 22 | mockCmdRunner *mocks.MockCmdRunner 23 | cmd *commands.SetupApi 24 | ) 25 | 26 | BeforeEach(func() { 27 | mockCtrl = gomock.NewController(GinkgoT()) 28 | mockFS = mocks.NewMockFS(mockCtrl) 29 | mockCmdRunner = mocks.NewMockCmdRunner(mockCtrl) 30 | cmd = &commands.SetupApi{ 31 | FS: mockFS, 32 | CmdRunner: mockCmdRunner, 33 | } 34 | }) 35 | 36 | AfterEach(func() { 37 | mockCtrl.Finish() 38 | }) 39 | 40 | Describe("#Run", func() { 41 | Context("When the file system is in a bad state", func() { 42 | It("returns the error from failing to write the monitrc", func() { 43 | mockFS.EXPECT().Write("/var/pcfdev/api/api_ctl", gomock.Any(), os.FileMode(fs.FileModeRootReadWriteExecutable)).AnyTimes() 44 | mockFS.EXPECT().Write("/var/vcap/monit/job/1001_pcfdev_api.monitrc", gomock.Any(), os.FileMode(fs.FileModeRootReadWrite)).Return(errors.New("some-error")) 45 | 46 | Expect(cmd.Run()).To(MatchError("some-error")) 47 | }) 48 | 49 | It("returns the error from failing to write the api_ctl", func() { 50 | mockFS.EXPECT().Write("/var/vcap/monit/job/1001_pcfdev_api.monitrc", gomock.Any(), os.FileMode(fs.FileModeRootReadWrite)).AnyTimes() 51 | mockFS.EXPECT().Write("/var/pcfdev/api/api_ctl", gomock.Any(), os.FileMode(fs.FileModeRootReadWriteExecutable)).Return(errors.New("some-error")) 52 | 53 | Expect(cmd.Run()).To(MatchError("some-error")) 54 | }) 55 | }) 56 | 57 | It("write a monit file to the /var/vcap/monit/job", func() { 58 | monitrc := `check process pcfdev-api 59 | with pidfile /var/vcap/sys/run/pcfdev-api/api.pid 60 | start program "/var/pcfdev/api/api_ctl start" 61 | stop program "/var/pcfdev/api/api_ctl stop" 62 | group vcap 63 | mode manual` 64 | 65 | monit_ctl := `#!/bin/bash 66 | set -ex 67 | 68 | PIDFILE=/var/vcap/sys/run/pcfdev-api/api.pid 69 | 70 | case $1 in 71 | 72 | start) 73 | mkdir -p /var/vcap/sys/run/pcfdev-api 74 | /var/pcfdev/api/api & 75 | echo $! > ${PIDFILE} 76 | 77 | ;; 78 | 79 | stop) 80 | kill $(cat $PIDFILE) 81 | 82 | ;; 83 | 84 | *) 85 | echo "Usage: pcfdev_api_ctl {start|stop}" 86 | ;; 87 | 88 | esac` 89 | 90 | gomock.InOrder( 91 | mockFS.EXPECT().Write("/var/vcap/monit/job/1001_pcfdev_api.monitrc", strings.NewReader(monitrc), os.FileMode(fs.FileModeRootReadWrite)), 92 | mockFS.EXPECT().Write("/var/pcfdev/api/api_ctl", strings.NewReader(monit_ctl), os.FileMode(fs.FileModeRootReadWriteExecutable)), 93 | ) 94 | 95 | Expect(cmd.Run()).To(Succeed()) 96 | }) 97 | }) 98 | 99 | Describe("#Distro", func() { 100 | It("should return 'oss'", func() { 101 | Expect(cmd.Distro()).To(Equal(provisioner.DistributionOSS)) 102 | }) 103 | }) 104 | }) 105 | -------------------------------------------------------------------------------- /src/provisioner/provisioner/commands/setup_cfdot.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "provisioner/provisioner" 5 | "strings" 6 | "provisioner/fs" 7 | ) 8 | 9 | type SetupCFDot struct { 10 | CmdRunner provisioner.CmdRunner 11 | FS provisioner.FS 12 | } 13 | 14 | func (s *SetupCFDot) Run() error { 15 | setupFileContentsBytes, err := s.FS.Read("/var/vcap/jobs/cfdot/bin/setup") 16 | if err != nil { 17 | return err 18 | } 19 | 20 | return s.FS.Write("/etc/profile.d/cfdot.sh", strings.NewReader(string(setupFileContentsBytes)), fs.FileModeRootReadWrite) 21 | } 22 | 23 | func (s *SetupCFDot) Distro() string { 24 | return provisioner.DistributionOSS 25 | } 26 | 27 | -------------------------------------------------------------------------------- /src/provisioner/provisioner/commands/setup_cfdot_test.go: -------------------------------------------------------------------------------- 1 | package commands_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | "github.com/golang/mock/gomock" 7 | 8 | "provisioner/fs" 9 | "provisioner/provisioner" 10 | "provisioner/provisioner/mocks" 11 | "provisioner/provisioner/commands" 12 | "strings" 13 | "os" 14 | ) 15 | 16 | var _ = Describe("SetupCFDot", func() { 17 | var ( 18 | mockCtrl *gomock.Controller 19 | mockFS *mocks.MockFS 20 | mockCmdRunner *mocks.MockCmdRunner 21 | cmd *commands.SetupCFDot 22 | ) 23 | 24 | BeforeEach(func() { 25 | mockCtrl = gomock.NewController(GinkgoT()) 26 | mockFS = mocks.NewMockFS(mockCtrl) 27 | mockCmdRunner = mocks.NewMockCmdRunner(mockCtrl) 28 | cmd = &commands.SetupCFDot{ 29 | FS: mockFS, 30 | CmdRunner: mockCmdRunner, 31 | } 32 | }) 33 | 34 | AfterEach(func() { 35 | mockCtrl.Finish() 36 | }) 37 | 38 | Describe("#Run", func() { 39 | It("should create a new file with content copied from /var/vcap/jobs/cfdot/bin/setup", func() { 40 | mockFS.EXPECT().Read("/var/vcap/jobs/cfdot/bin/setup").Return([]byte("cf-dot-setup-stuff-here\n"), nil) 41 | mockFS.EXPECT().Write("/etc/profile.d/cfdot.sh", strings.NewReader("cf-dot-setup-stuff-here\n"), os.FileMode(fs.FileModeRootReadWrite)) 42 | 43 | Expect(cmd.Run()).To(Succeed()) 44 | }) 45 | }) 46 | 47 | Describe("#Distro", func() { 48 | It("should return 'oss'", func() { 49 | Expect(cmd.Distro()).To(Equal(provisioner.DistributionOSS)) 50 | }) 51 | }) 52 | }) 53 | -------------------------------------------------------------------------------- /src/provisioner/provisioner/concrete_cmd_runner.go: -------------------------------------------------------------------------------- 1 | package provisioner 2 | 3 | import ( 4 | "io" 5 | "os/exec" 6 | "syscall" 7 | "time" 8 | ) 9 | 10 | type ConcreteCmdRunner struct { 11 | Stdout io.Writer 12 | Stderr io.Writer 13 | Timeout time.Duration 14 | } 15 | 16 | func (r *ConcreteCmdRunner) Run(command string, args ...string) error { 17 | cmd := exec.Command(command, args...) 18 | cmd.Stdout = r.Stdout 19 | cmd.Stderr = r.Stderr 20 | cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} 21 | if err := cmd.Start(); err != nil { 22 | return err 23 | } 24 | 25 | timer := time.AfterFunc(r.Timeout, func() { 26 | pgid, err := syscall.Getpgid(cmd.Process.Pid) 27 | if err == nil { 28 | syscall.Kill(-pgid, 15) 29 | } 30 | }) 31 | 32 | err := cmd.Wait() 33 | 34 | if !timer.Stop() { 35 | return &TimeoutError{} 36 | } 37 | 38 | return err 39 | } 40 | 41 | func (r *ConcreteCmdRunner) Output(command string, args ...string) ([]byte, error) { 42 | return exec.Command(command, args...).CombinedOutput() 43 | } 44 | -------------------------------------------------------------------------------- /src/provisioner/provisioner/concrete_cmd_runner_test.go: -------------------------------------------------------------------------------- 1 | package provisioner_test 2 | 3 | import ( 4 | "provisioner/provisioner" 5 | "time" 6 | 7 | . "github.com/onsi/ginkgo" 8 | . "github.com/onsi/gomega" 9 | "github.com/onsi/gomega/gbytes" 10 | ) 11 | 12 | var _ = Describe("ConcreteCmdRunner", func() { 13 | var r *provisioner.ConcreteCmdRunner 14 | Describe("#Run", func() { 15 | var ( 16 | stdout *gbytes.Buffer 17 | stderr *gbytes.Buffer 18 | ) 19 | 20 | BeforeEach(func() { 21 | stdout = gbytes.NewBuffer() 22 | stderr = gbytes.NewBuffer() 23 | 24 | r = &provisioner.ConcreteCmdRunner{ 25 | Stdout: stdout, 26 | Stderr: stderr, 27 | Timeout: 2 * time.Second, 28 | } 29 | }) 30 | 31 | It("should run commands", func() { 32 | Expect(r.Run("echo", "-n", "some output")).To(Succeed()) 33 | Eventually(stdout).Should(gbytes.Say("some output")) 34 | 35 | Expect(r.Run("bash", "-c", ">&2 echo -n some output")).To(Succeed()) 36 | Eventually(stderr).Should(gbytes.Say("some output")) 37 | }) 38 | 39 | It("should respects timeouts", func() { 40 | Expect(r.Run("bash", "-c", "sleep 5")).To(MatchError("timeout error")) 41 | }) 42 | 43 | Context("when there is an error", func() { 44 | It("should return the error and the output", func() { 45 | Expect(r.Run("/some/bad/binary")).To(MatchError(ContainSubstring("no such file or directory"))) 46 | }) 47 | }) 48 | }) 49 | 50 | Describe("#Output", func() { 51 | BeforeEach(func() { 52 | r = &provisioner.ConcreteCmdRunner{ 53 | Timeout: 2 * time.Second, 54 | } 55 | }) 56 | 57 | It("should run commands and return combined output", func() { 58 | output, err := r.Output("bash", "-c", "echo some-output; >&2 echo -n some-more-output") 59 | Expect(err).NotTo(HaveOccurred()) 60 | Expect(output).To(Equal([]byte("some-output\nsome-more-output"))) 61 | }) 62 | 63 | Context("when there is an error", func() { 64 | It("should return the error and the output", func() { 65 | _, err := r.Output("/some/bad/binary") 66 | Expect(err).To(MatchError(ContainSubstring("no such file or directory"))) 67 | }) 68 | }) 69 | }) 70 | }) 71 | -------------------------------------------------------------------------------- /src/provisioner/provisioner/errors.go: -------------------------------------------------------------------------------- 1 | package provisioner 2 | 3 | type TimeoutError struct{} 4 | 5 | func (t *TimeoutError) Error() string { 6 | return "timeout error" 7 | } 8 | -------------------------------------------------------------------------------- /src/provisioner/provisioner/mocks/cert.go: -------------------------------------------------------------------------------- 1 | // Automatically generated by MockGen. DO NOT EDIT! 2 | // Source: provisioner/provisioner (interfaces: Cert) 3 | 4 | package mocks 5 | 6 | import ( 7 | gomock "github.com/golang/mock/gomock" 8 | ) 9 | 10 | // Mock of Cert interface 11 | type MockCert struct { 12 | ctrl *gomock.Controller 13 | recorder *_MockCertRecorder 14 | } 15 | 16 | // Recorder for MockCert (not exported) 17 | type _MockCertRecorder struct { 18 | mock *MockCert 19 | } 20 | 21 | func NewMockCert(ctrl *gomock.Controller) *MockCert { 22 | mock := &MockCert{ctrl: ctrl} 23 | mock.recorder = &_MockCertRecorder{mock} 24 | return mock 25 | } 26 | 27 | func (_m *MockCert) EXPECT() *_MockCertRecorder { 28 | return _m.recorder 29 | } 30 | 31 | func (_m *MockCert) GenerateCerts(_param0 string) ([]byte, []byte, []byte, []byte, error) { 32 | ret := _m.ctrl.Call(_m, "GenerateCerts", _param0) 33 | ret0, _ := ret[0].([]byte) 34 | ret1, _ := ret[1].([]byte) 35 | ret2, _ := ret[2].([]byte) 36 | ret3, _ := ret[3].([]byte) 37 | ret4, _ := ret[4].(error) 38 | return ret0, ret1, ret2, ret3, ret4 39 | } 40 | 41 | func (_mr *_MockCertRecorder) GenerateCerts(arg0 interface{}) *gomock.Call { 42 | return _mr.mock.ctrl.RecordCall(_mr.mock, "GenerateCerts", arg0) 43 | } 44 | -------------------------------------------------------------------------------- /src/provisioner/provisioner/mocks/cmd_runner.go: -------------------------------------------------------------------------------- 1 | // Automatically generated by MockGen. DO NOT EDIT! 2 | // Source: provisioner/provisioner (interfaces: CmdRunner) 3 | 4 | package mocks 5 | 6 | import ( 7 | gomock "github.com/golang/mock/gomock" 8 | ) 9 | 10 | // Mock of CmdRunner interface 11 | type MockCmdRunner struct { 12 | ctrl *gomock.Controller 13 | recorder *_MockCmdRunnerRecorder 14 | } 15 | 16 | // Recorder for MockCmdRunner (not exported) 17 | type _MockCmdRunnerRecorder struct { 18 | mock *MockCmdRunner 19 | } 20 | 21 | func NewMockCmdRunner(ctrl *gomock.Controller) *MockCmdRunner { 22 | mock := &MockCmdRunner{ctrl: ctrl} 23 | mock.recorder = &_MockCmdRunnerRecorder{mock} 24 | return mock 25 | } 26 | 27 | func (_m *MockCmdRunner) EXPECT() *_MockCmdRunnerRecorder { 28 | return _m.recorder 29 | } 30 | 31 | func (_m *MockCmdRunner) Output(_param0 string, _param1 ...string) ([]byte, error) { 32 | _s := []interface{}{_param0} 33 | for _, _x := range _param1 { 34 | _s = append(_s, _x) 35 | } 36 | ret := _m.ctrl.Call(_m, "Output", _s...) 37 | ret0, _ := ret[0].([]byte) 38 | ret1, _ := ret[1].(error) 39 | return ret0, ret1 40 | } 41 | 42 | func (_mr *_MockCmdRunnerRecorder) Output(arg0 interface{}, arg1 ...interface{}) *gomock.Call { 43 | _s := append([]interface{}{arg0}, arg1...) 44 | return _mr.mock.ctrl.RecordCall(_mr.mock, "Output", _s...) 45 | } 46 | 47 | func (_m *MockCmdRunner) Run(_param0 string, _param1 ...string) error { 48 | _s := []interface{}{_param0} 49 | for _, _x := range _param1 { 50 | _s = append(_s, _x) 51 | } 52 | ret := _m.ctrl.Call(_m, "Run", _s...) 53 | ret0, _ := ret[0].(error) 54 | return ret0 55 | } 56 | 57 | func (_mr *_MockCmdRunnerRecorder) Run(arg0 interface{}, arg1 ...interface{}) *gomock.Call { 58 | _s := append([]interface{}{arg0}, arg1...) 59 | return _mr.mock.ctrl.RecordCall(_mr.mock, "Run", _s...) 60 | } 61 | -------------------------------------------------------------------------------- /src/provisioner/provisioner/mocks/command.go: -------------------------------------------------------------------------------- 1 | // Automatically generated by MockGen. DO NOT EDIT! 2 | // Source: provisioner/provisioner (interfaces: Command) 3 | 4 | package mocks 5 | 6 | import ( 7 | gomock "github.com/golang/mock/gomock" 8 | ) 9 | 10 | // Mock of Command interface 11 | type MockCommand struct { 12 | ctrl *gomock.Controller 13 | recorder *_MockCommandRecorder 14 | } 15 | 16 | // Recorder for MockCommand (not exported) 17 | type _MockCommandRecorder struct { 18 | mock *MockCommand 19 | } 20 | 21 | func NewMockCommand(ctrl *gomock.Controller) *MockCommand { 22 | mock := &MockCommand{ctrl: ctrl} 23 | mock.recorder = &_MockCommandRecorder{mock} 24 | return mock 25 | } 26 | 27 | func (_m *MockCommand) EXPECT() *_MockCommandRecorder { 28 | return _m.recorder 29 | } 30 | 31 | func (_m *MockCommand) Distro() string { 32 | ret := _m.ctrl.Call(_m, "Distro") 33 | ret0, _ := ret[0].(string) 34 | return ret0 35 | } 36 | 37 | func (_mr *_MockCommandRecorder) Distro() *gomock.Call { 38 | return _mr.mock.ctrl.RecordCall(_mr.mock, "Distro") 39 | } 40 | 41 | func (_m *MockCommand) Run() error { 42 | ret := _m.ctrl.Call(_m, "Run") 43 | ret0, _ := ret[0].(error) 44 | return ret0 45 | } 46 | 47 | func (_mr *_MockCommandRecorder) Run() *gomock.Call { 48 | return _mr.mock.ctrl.RecordCall(_mr.mock, "Run") 49 | } 50 | -------------------------------------------------------------------------------- /src/provisioner/provisioner/mocks/fs.go: -------------------------------------------------------------------------------- 1 | // Automatically generated by MockGen. DO NOT EDIT! 2 | // Source: provisioner/provisioner (interfaces: FS) 3 | 4 | package mocks 5 | 6 | import ( 7 | gomock "github.com/golang/mock/gomock" 8 | io "io" 9 | os "os" 10 | ) 11 | 12 | // Mock of FS interface 13 | type MockFS struct { 14 | ctrl *gomock.Controller 15 | recorder *_MockFSRecorder 16 | } 17 | 18 | // Recorder for MockFS (not exported) 19 | type _MockFSRecorder struct { 20 | mock *MockFS 21 | } 22 | 23 | func NewMockFS(ctrl *gomock.Controller) *MockFS { 24 | mock := &MockFS{ctrl: ctrl} 25 | mock.recorder = &_MockFSRecorder{mock} 26 | return mock 27 | } 28 | 29 | func (_m *MockFS) EXPECT() *_MockFSRecorder { 30 | return _m.recorder 31 | } 32 | 33 | func (_m *MockFS) Exists(_param0 string) (bool, error) { 34 | ret := _m.ctrl.Call(_m, "Exists", _param0) 35 | ret0, _ := ret[0].(bool) 36 | ret1, _ := ret[1].(error) 37 | return ret0, ret1 38 | } 39 | 40 | func (_mr *_MockFSRecorder) Exists(arg0 interface{}) *gomock.Call { 41 | return _mr.mock.ctrl.RecordCall(_mr.mock, "Exists", arg0) 42 | } 43 | 44 | func (_m *MockFS) Mkdir(_param0 string) error { 45 | ret := _m.ctrl.Call(_m, "Mkdir", _param0) 46 | ret0, _ := ret[0].(error) 47 | return ret0 48 | } 49 | 50 | func (_mr *_MockFSRecorder) Mkdir(arg0 interface{}) *gomock.Call { 51 | return _mr.mock.ctrl.RecordCall(_mr.mock, "Mkdir", arg0) 52 | } 53 | 54 | func (_m *MockFS) Read(_param0 string) ([]byte, error) { 55 | ret := _m.ctrl.Call(_m, "Read", _param0) 56 | ret0, _ := ret[0].([]byte) 57 | ret1, _ := ret[1].(error) 58 | return ret0, ret1 59 | } 60 | 61 | func (_mr *_MockFSRecorder) Read(arg0 interface{}) *gomock.Call { 62 | return _mr.mock.ctrl.RecordCall(_mr.mock, "Read", arg0) 63 | } 64 | 65 | func (_m *MockFS) Write(_param0 string, _param1 io.Reader, _param2 os.FileMode) error { 66 | ret := _m.ctrl.Call(_m, "Write", _param0, _param1, _param2) 67 | ret0, _ := ret[0].(error) 68 | return ret0 69 | } 70 | 71 | func (_mr *_MockFSRecorder) Write(arg0, arg1, arg2 interface{}) *gomock.Call { 72 | return _mr.mock.ctrl.RecordCall(_mr.mock, "Write", arg0, arg1, arg2) 73 | } 74 | -------------------------------------------------------------------------------- /src/provisioner/provisioner/mocks/ui.go: -------------------------------------------------------------------------------- 1 | // Automatically generated by MockGen. DO NOT EDIT! 2 | // Source: provisioner/provisioner (interfaces: UI) 3 | 4 | package mocks 5 | 6 | import ( 7 | gomock "github.com/golang/mock/gomock" 8 | ) 9 | 10 | // Mock of UI interface 11 | type MockUI struct { 12 | ctrl *gomock.Controller 13 | recorder *_MockUIRecorder 14 | } 15 | 16 | // Recorder for MockUI (not exported) 17 | type _MockUIRecorder struct { 18 | mock *MockUI 19 | } 20 | 21 | func NewMockUI(ctrl *gomock.Controller) *MockUI { 22 | mock := &MockUI{ctrl: ctrl} 23 | mock.recorder = &_MockUIRecorder{mock} 24 | return mock 25 | } 26 | 27 | func (_m *MockUI) EXPECT() *_MockUIRecorder { 28 | return _m.recorder 29 | } 30 | 31 | func (_m *MockUI) PrintHelpText(_param0 string) error { 32 | ret := _m.ctrl.Call(_m, "PrintHelpText", _param0) 33 | ret0, _ := ret[0].(error) 34 | return ret0 35 | } 36 | 37 | func (_mr *_MockUIRecorder) PrintHelpText(arg0 interface{}) *gomock.Call { 38 | return _mr.mock.ctrl.RecordCall(_mr.mock, "PrintHelpText", arg0) 39 | } 40 | -------------------------------------------------------------------------------- /src/provisioner/provisioner/provisioner.go: -------------------------------------------------------------------------------- 1 | package provisioner 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "os" 7 | "provisioner/fs" 8 | ) 9 | 10 | //go:generate mockgen -package mocks -destination mocks/cert.go provisioner/provisioner Cert 11 | type Cert interface { 12 | GenerateCerts(domain string) (certificate []byte, privateKey []byte, caCertificate []byte, caPrivateKey []byte, err error) 13 | } 14 | 15 | //go:generate mockgen -package mocks -destination mocks/cmd_runner.go provisioner/provisioner CmdRunner 16 | type CmdRunner interface { 17 | Run(command string, args ...string) error 18 | Output(command string, args ...string) (output []byte, err error) 19 | } 20 | 21 | //go:generate mockgen -package mocks -destination mocks/fs.go provisioner/provisioner FS 22 | type FS interface { 23 | Mkdir(directory string) error 24 | Write(path string, contents io.Reader, perm os.FileMode) error 25 | Read(path string) (contents []byte, err error) 26 | Exists(path string) (bool, error) 27 | } 28 | 29 | //go:generate mockgen -package mocks -destination mocks/ui.go provisioner/provisioner UI 30 | type UI interface { 31 | PrintHelpText(domain string) error 32 | } 33 | 34 | //go:generate mockgen -package mocks -destination mocks/command.go provisioner/provisioner Command 35 | type Command interface { 36 | Run() error 37 | Distro() string 38 | } 39 | 40 | type Provisioner struct { 41 | Cert Cert 42 | CmdRunner CmdRunner 43 | FS FS 44 | UI UI 45 | DisableUAAHSTS Command 46 | ConfigureDnsmasq Command 47 | Commands []Command 48 | 49 | Distro string 50 | } 51 | 52 | const ( 53 | DistributionOSS = "oss" 54 | DistributionPCF = "pcf" 55 | ) 56 | 57 | func (p *Provisioner) Provision(provisionScriptPath string, args ...string) error { 58 | domain := args[0] 59 | 60 | cert, key, caCert, _, err := p.Cert.GenerateCerts(domain) 61 | if err != nil { 62 | return err 63 | } 64 | 65 | if err := p.FS.Mkdir("/var/vcap/jobs/gorouter/config"); err != nil { 66 | return err 67 | } 68 | 69 | if err := p.FS.Write("/var/vcap/jobs/gorouter/config/cert.pem", bytes.NewReader(cert), fs.FileModeRootReadWrite); err != nil { 70 | return err 71 | } 72 | 73 | if err := p.FS.Write("/var/vcap/jobs/gorouter/config/key.pem", bytes.NewReader(key), fs.FileModeRootReadWrite); err != nil { 74 | return err 75 | } 76 | 77 | if err := p.FS.Mkdir("/var/pcfdev/openssl"); err != nil { 78 | return err 79 | } 80 | 81 | if err := p.FS.Write("/var/pcfdev/openssl/ca_cert.pem", bytes.NewReader(caCert), fs.FileModeRootReadWrite); err != nil { 82 | return err 83 | } 84 | 85 | for _, command := range p.Commands { 86 | if p.Distro == DistributionOSS && command.Distro() == DistributionPCF { 87 | continue 88 | } 89 | if err := command.Run(); err != nil { 90 | return err 91 | } 92 | } 93 | 94 | if err := p.CmdRunner.Run(provisionScriptPath, args...); err != nil { 95 | return err 96 | } 97 | 98 | return p.FS.Write("/run/pcfdev-healthcheck", bytes.NewReader([]byte("")), fs.FileModeRootReadWrite) 99 | } 100 | -------------------------------------------------------------------------------- /src/provisioner/provisioner/provisioner_suite_test.go: -------------------------------------------------------------------------------- 1 | package provisioner_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | ) 9 | 10 | func TestProvisioner(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Provisioner Suite") 13 | } 14 | -------------------------------------------------------------------------------- /src/provisioner/provisioner/provisioner_test.go: -------------------------------------------------------------------------------- 1 | package provisioner_test 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "provisioner/provisioner" 7 | "provisioner/provisioner/mocks" 8 | 9 | "github.com/golang/mock/gomock" 10 | 11 | . "github.com/onsi/ginkgo" 12 | . "github.com/onsi/gomega" 13 | "os" 14 | "provisioner/fs" 15 | ) 16 | 17 | var _ = Describe("Provisioner", func() { 18 | Describe("#Provision", func() { 19 | var ( 20 | p *provisioner.Provisioner 21 | mockCtrl *gomock.Controller 22 | mockCert *mocks.MockCert 23 | mockCmdRunner *mocks.MockCmdRunner 24 | mockFS *mocks.MockFS 25 | mockUI *mocks.MockUI 26 | firstCommand *mocks.MockCommand 27 | secondCommand *mocks.MockCommand 28 | ) 29 | 30 | BeforeEach(func() { 31 | mockCtrl = gomock.NewController(GinkgoT()) 32 | mockCert = mocks.NewMockCert(mockCtrl) 33 | mockCmdRunner = mocks.NewMockCmdRunner(mockCtrl) 34 | mockFS = mocks.NewMockFS(mockCtrl) 35 | mockUI = mocks.NewMockUI(mockCtrl) 36 | firstCommand = mocks.NewMockCommand(mockCtrl) 37 | secondCommand = mocks.NewMockCommand(mockCtrl) 38 | 39 | p = &provisioner.Provisioner{ 40 | Cert: mockCert, 41 | CmdRunner: mockCmdRunner, 42 | FS: mockFS, 43 | UI: mockUI, 44 | Commands: []provisioner.Command{ 45 | firstCommand, 46 | secondCommand, 47 | }, 48 | 49 | Distro: provisioner.DistributionPCF, 50 | } 51 | }) 52 | 53 | AfterEach(func() { 54 | mockCtrl.Finish() 55 | }) 56 | 57 | It("should provision a VM", func() { 58 | gomock.InOrder( 59 | mockCert.EXPECT().GenerateCerts("some-domain").Return([]byte("some-cert"), []byte("some-key"), []byte("some-ca-cert"), []byte("some-ca-key"), nil), 60 | mockFS.EXPECT().Mkdir("/var/vcap/jobs/gorouter/config"), 61 | mockFS.EXPECT().Write("/var/vcap/jobs/gorouter/config/cert.pem", bytes.NewReader([]byte("some-cert")), os.FileMode(fs.FileModeRootReadWrite)), 62 | mockFS.EXPECT().Write("/var/vcap/jobs/gorouter/config/key.pem", bytes.NewReader([]byte("some-key")), os.FileMode(fs.FileModeRootReadWrite)), 63 | mockFS.EXPECT().Mkdir("/var/pcfdev/openssl"), 64 | mockFS.EXPECT().Write("/var/pcfdev/openssl/ca_cert.pem", bytes.NewReader([]byte("some-ca-cert")), os.FileMode(fs.FileModeRootReadWrite)), 65 | firstCommand.EXPECT().Run(), 66 | secondCommand.EXPECT().Run(), 67 | mockCmdRunner.EXPECT().Run("some-provision-script-path", "some-domain"), 68 | mockFS.EXPECT().Write("/run/pcfdev-healthcheck", bytes.NewReader([]byte("")), os.FileMode(fs.FileModeRootReadWrite)), 69 | ) 70 | 71 | Expect(p.Provision("some-provision-script-path", "some-domain")).To(Succeed()) 72 | }) 73 | 74 | Context("when the distribution is oss", func() { 75 | It("should not run pcf 'Commands'", func() { 76 | p.Distro = provisioner.DistributionOSS 77 | 78 | gomock.InOrder( 79 | mockCert.EXPECT().GenerateCerts("some-domain").Return([]byte("some-cert"), []byte("some-key"), []byte("some-ca-cert"), []byte("some-ca-key"), nil), 80 | mockFS.EXPECT().Mkdir("/var/vcap/jobs/gorouter/config"), 81 | mockFS.EXPECT().Write("/var/vcap/jobs/gorouter/config/cert.pem", bytes.NewReader([]byte("some-cert")), os.FileMode(fs.FileModeRootReadWrite)), 82 | mockFS.EXPECT().Write("/var/vcap/jobs/gorouter/config/key.pem", bytes.NewReader([]byte("some-key")), os.FileMode(fs.FileModeRootReadWrite)), 83 | mockFS.EXPECT().Mkdir("/var/pcfdev/openssl"), 84 | mockFS.EXPECT().Write("/var/pcfdev/openssl/ca_cert.pem", bytes.NewReader([]byte("some-ca-cert")), os.FileMode(fs.FileModeRootReadWrite)), 85 | firstCommand.EXPECT().Distro().Return(provisioner.DistributionPCF), 86 | secondCommand.EXPECT().Distro().Return(provisioner.DistributionOSS), 87 | secondCommand.EXPECT().Run(), 88 | mockCmdRunner.EXPECT().Run("some-provision-script-path", "some-domain"), 89 | mockFS.EXPECT().Write("/run/pcfdev-healthcheck", bytes.NewReader([]byte("")), os.FileMode(fs.FileModeRootReadWrite)), 90 | ) 91 | 92 | Expect(p.Provision("some-provision-script-path", "some-domain")).To(Succeed()) 93 | }) 94 | }) 95 | 96 | Context("when there is an error generating certificate", func() { 97 | It("should return the error", func() { 98 | mockCert.EXPECT().GenerateCerts("some-domain").Return(nil, nil, nil, nil, errors.New("some-error")) 99 | 100 | Expect(p.Provision("some-provision-script-path", "some-domain")).To(MatchError("some-error")) 101 | }) 102 | }) 103 | 104 | Context("when there is an error creating the gorouter config directory", func() { 105 | It("should return the error", func() { 106 | gomock.InOrder( 107 | mockCert.EXPECT().GenerateCerts("some-domain").Return([]byte("some-cert"), []byte("some-key"), []byte("some-ca-cert"), []byte("some-ca-key"), nil), 108 | mockFS.EXPECT().Mkdir("/var/vcap/jobs/gorouter/config").Return(errors.New("some-error")), 109 | ) 110 | 111 | Expect(p.Provision("some-provision-script-path", "some-domain")).To(MatchError("some-error")) 112 | }) 113 | }) 114 | 115 | Context("when there is an error writing the certificate", func() { 116 | It("should return the error", func() { 117 | gomock.InOrder( 118 | mockCert.EXPECT().GenerateCerts("some-domain").Return([]byte("some-cert"), []byte("some-key"), []byte("some-ca-cert"), []byte("some-ca-key"), nil), 119 | mockFS.EXPECT().Mkdir("/var/vcap/jobs/gorouter/config"), 120 | mockFS.EXPECT().Write("/var/vcap/jobs/gorouter/config/cert.pem", bytes.NewReader([]byte("some-cert")), os.FileMode(fs.FileModeRootReadWrite)).Return(errors.New("some-error")), 121 | ) 122 | 123 | Expect(p.Provision("some-provision-script-path", "some-domain")).To(MatchError("some-error")) 124 | }) 125 | }) 126 | 127 | Context("when there is an error writing the private key", func() { 128 | It("should return the error", func() { 129 | gomock.InOrder( 130 | mockCert.EXPECT().GenerateCerts("some-domain").Return([]byte("some-cert"), []byte("some-key"), []byte("some-ca-cert"), []byte("some-ca-key"), nil), 131 | mockFS.EXPECT().Mkdir("/var/vcap/jobs/gorouter/config"), 132 | mockFS.EXPECT().Write("/var/vcap/jobs/gorouter/config/cert.pem", bytes.NewReader([]byte("some-cert")), os.FileMode(fs.FileModeRootReadWrite)), 133 | mockFS.EXPECT().Write("/var/vcap/jobs/gorouter/config/key.pem", bytes.NewReader([]byte("some-key")), os.FileMode(fs.FileModeRootReadWrite)).Return(errors.New("some-error")), 134 | ) 135 | 136 | Expect(p.Provision("some-provision-script-path", "some-domain")).To(MatchError("some-error")) 137 | }) 138 | }) 139 | 140 | Context("when there is an error creating the openssl directory", func() { 141 | It("should return the error", func() { 142 | gomock.InOrder( 143 | mockCert.EXPECT().GenerateCerts("some-domain").Return([]byte("some-cert"), []byte("some-key"), []byte("some-ca-cert"), []byte("some-ca-key"), nil), 144 | mockFS.EXPECT().Mkdir("/var/vcap/jobs/gorouter/config"), 145 | mockFS.EXPECT().Write("/var/vcap/jobs/gorouter/config/cert.pem", bytes.NewReader([]byte("some-cert")), os.FileMode(fs.FileModeRootReadWrite)), 146 | mockFS.EXPECT().Write("/var/vcap/jobs/gorouter/config/key.pem", bytes.NewReader([]byte("some-key")), os.FileMode(fs.FileModeRootReadWrite)), 147 | mockFS.EXPECT().Mkdir("/var/pcfdev/openssl").Return(errors.New("some-error")), 148 | ) 149 | 150 | Expect(p.Provision("some-provision-script-path", "some-domain")).To(MatchError("some-error")) 151 | }) 152 | }) 153 | 154 | Context("when there is an error writing the CA certificate", func() { 155 | It("should return the error", func() { 156 | gomock.InOrder( 157 | mockCert.EXPECT().GenerateCerts("some-domain").Return([]byte("some-cert"), []byte("some-key"), []byte("some-ca-cert"), []byte("some-ca-key"), nil), 158 | mockFS.EXPECT().Mkdir("/var/vcap/jobs/gorouter/config"), 159 | mockFS.EXPECT().Write("/var/vcap/jobs/gorouter/config/cert.pem", bytes.NewReader([]byte("some-cert")), os.FileMode(fs.FileModeRootReadWrite)), 160 | mockFS.EXPECT().Write("/var/vcap/jobs/gorouter/config/key.pem", bytes.NewReader([]byte("some-key")), os.FileMode(fs.FileModeRootReadWrite)), 161 | mockFS.EXPECT().Mkdir("/var/pcfdev/openssl"), 162 | mockFS.EXPECT().Write("/var/pcfdev/openssl/ca_cert.pem", bytes.NewReader([]byte("some-ca-cert")), os.FileMode(fs.FileModeRootReadWrite)).Return(errors.New("some-error")), 163 | ) 164 | 165 | Expect(p.Provision("some-provision-script-path", "some-domain")).To(MatchError("some-error")) 166 | }) 167 | }) 168 | 169 | Context("when a command fails", func() { 170 | It("should return an error", func() { 171 | gomock.InOrder( 172 | mockCert.EXPECT().GenerateCerts("some-domain").Return([]byte("some-cert"), []byte("some-key"), []byte("some-ca-cert"), []byte("some-ca-key"), nil), 173 | mockFS.EXPECT().Mkdir("/var/vcap/jobs/gorouter/config"), 174 | mockFS.EXPECT().Write("/var/vcap/jobs/gorouter/config/cert.pem", bytes.NewReader([]byte("some-cert")), os.FileMode(fs.FileModeRootReadWrite)), 175 | mockFS.EXPECT().Write("/var/vcap/jobs/gorouter/config/key.pem", bytes.NewReader([]byte("some-key")), os.FileMode(fs.FileModeRootReadWrite)), 176 | mockFS.EXPECT().Mkdir("/var/pcfdev/openssl"), 177 | mockFS.EXPECT().Write("/var/pcfdev/openssl/ca_cert.pem", bytes.NewReader([]byte("some-ca-cert")), os.FileMode(fs.FileModeRootReadWrite)), 178 | firstCommand.EXPECT().Run().Return(errors.New("some-error")), 179 | ) 180 | 181 | Expect(p.Provision("some-provision-script-path", "some-domain")).To(MatchError("some-error")) 182 | }) 183 | }) 184 | 185 | Context("when there is an error running the provision script", func() { 186 | It("should return the error", func() { 187 | gomock.InOrder( 188 | mockCert.EXPECT().GenerateCerts("some-domain").Return([]byte("some-cert"), []byte("some-key"), []byte("some-ca-cert"), []byte("some-ca-key"), nil), 189 | mockFS.EXPECT().Mkdir("/var/vcap/jobs/gorouter/config"), 190 | mockFS.EXPECT().Write("/var/vcap/jobs/gorouter/config/cert.pem", bytes.NewReader([]byte("some-cert")), os.FileMode(fs.FileModeRootReadWrite)), 191 | mockFS.EXPECT().Write("/var/vcap/jobs/gorouter/config/key.pem", bytes.NewReader([]byte("some-key")), os.FileMode(fs.FileModeRootReadWrite)), 192 | mockFS.EXPECT().Mkdir("/var/pcfdev/openssl"), 193 | mockFS.EXPECT().Write("/var/pcfdev/openssl/ca_cert.pem", bytes.NewReader([]byte("some-ca-cert")), os.FileMode(fs.FileModeRootReadWrite)), 194 | firstCommand.EXPECT().Run(), 195 | secondCommand.EXPECT().Run(), 196 | mockCmdRunner.EXPECT().Run("some-provision-script-path", "some-domain").Return(errors.New("some-error")), 197 | ) 198 | 199 | Expect(p.Provision("some-provision-script-path", "some-domain")).To(MatchError("some-error")) 200 | }) 201 | }) 202 | 203 | Context("when there is an error writing the healthcheck file", func() { 204 | It("should return the error", func() { 205 | gomock.InOrder( 206 | mockCert.EXPECT().GenerateCerts("some-domain").Return([]byte("some-cert"), []byte("some-key"), []byte("some-ca-cert"), []byte("some-ca-key"), nil), 207 | mockFS.EXPECT().Mkdir("/var/vcap/jobs/gorouter/config"), 208 | mockFS.EXPECT().Write("/var/vcap/jobs/gorouter/config/cert.pem", bytes.NewReader([]byte("some-cert")), os.FileMode(fs.FileModeRootReadWrite)), 209 | mockFS.EXPECT().Write("/var/vcap/jobs/gorouter/config/key.pem", bytes.NewReader([]byte("some-key")), os.FileMode(fs.FileModeRootReadWrite)), 210 | mockFS.EXPECT().Mkdir("/var/pcfdev/openssl"), 211 | mockFS.EXPECT().Write("/var/pcfdev/openssl/ca_cert.pem", bytes.NewReader([]byte("some-ca-cert")), os.FileMode(fs.FileModeRootReadWrite)), 212 | firstCommand.EXPECT().Run(), 213 | secondCommand.EXPECT().Run(), 214 | mockCmdRunner.EXPECT().Run("some-provision-script-path", "some-domain"), 215 | mockFS.EXPECT().Write("/run/pcfdev-healthcheck", bytes.NewReader([]byte("")), os.FileMode(fs.FileModeRootReadWrite)).Return(errors.New("some-error")), 216 | ) 217 | 218 | Expect(p.Provision("some-provision-script-path", "some-domain")).To(MatchError("some-error")) 219 | }) 220 | }) 221 | }) 222 | }) 223 | -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "releases": { 3 | "binary-buildpack" : { 4 | "version": "1.0.11", 5 | "sha1": "897826f41d17ddc82967e8ced549c8f8565715b7", 6 | "source_location": "https://github.com/cloudfoundry/binary-buildpack-release.git", 7 | "compiled_release_url": "https://s3.amazonaws.com/pcfdev/compiled-releases/binary-buildpack-1.0.11-1496755011.tgz" 8 | }, 9 | "capi" : { 10 | "version": "1.27.0-2-gc89eb61", 11 | "sha1": "8e03eea2c64b3baf9de092fef13f71e3f17e460e", 12 | "source_location": "https://github.com/cloudfoundry/capi-release.git", 13 | "compiled_release_url": "https://s3.amazonaws.com/pcfdev/compiled-releases/capi-1.27.0-2-gc89eb61-1501026707.tgz" 14 | }, 15 | "cf" : { 16 | "version": "v259-1-g51ea620b", 17 | "sha1": "45eee862a3f5aabe1b18b95929afd163899d3ecc", 18 | "source_location": "https://github.com/cloudfoundry/cf-release.git", 19 | "compiled_release_url": "https://s3.amazonaws.com/pcfdev/compiled-releases/cf-v259-1-g51ea620b-1500397980.tgz" 20 | }, 21 | "cf-mysql" : { 22 | "version": "v35-1-g2ff76af5", 23 | "sha1": "b2407a1dff3389ffdf18f1dd2d90ddce20e331d6", 24 | "source_location": "https://github.com/cloudfoundry/cf-mysql-release.git", 25 | "compiled_release_url": "https://s3.amazonaws.com/pcfdev/compiled-releases/cf-mysql-v35-1-g2ff76af5-1501012547.tgz" 26 | }, 27 | "cflinuxfs2-rootfs" : { 28 | "version": "v1.116.0", 29 | "sha1": "fecbd6a141cd0956d319122d05ff210a096186ac", 30 | "source_location": "https://github.com/cloudfoundry/cflinuxfs2-rootfs-release.git", 31 | "compiled_release_url": "https://s3.amazonaws.com/pcfdev/compiled-releases/cflinuxfs2-v1.116.0-1497451333.tgz" 32 | }, 33 | "cf-networking" : { 34 | "version": "v0.25.0-2-gc862e46", 35 | "sha1": "3fb5c16ceb63be4edfd81309631fe1dc89f69aab", 36 | "source_location": "https://github.com/cloudfoundry-incubator/cf-networking-release", 37 | "compiled_release_url": "https://s3.amazonaws.com/pcfdev/compiled-releases/cf-networking-v0.25.0-2-gc862e46-1501024622.tgz" 38 | }, 39 | "consul" : { 40 | "version": "v152", 41 | "sha1": "96ef889efa01087f0495f55853e12ef37e114746", 42 | "source_location": "https://github.com/cloudfoundry-incubator/consul-release.git", 43 | "compiled_release_url": "https://s3.amazonaws.com/pcfdev/compiled-releases/consul-v152-1499709321.tgz" 44 | }, 45 | "diego" : { 46 | "version": "v1.15.0-2-gf3b6fd7a", 47 | "sha1": "15bd67d178791403469ceae62329ac8b29fbe5f6", 48 | "source_location": "https://github.com/cloudfoundry/diego-release.git", 49 | "compiled_release_url": "https://s3.amazonaws.com/pcfdev/compiled-releases/diego-v1.15.0-2-gf3b6fd7a-1501016603.tgz" 50 | }, 51 | "etcd" : { 52 | "version": "v104", 53 | "sha1": "d07f31e9e12b5f9d9c224411aac013afed1cf658", 54 | "source_location": "https://github.com/cloudfoundry-incubator/etcd-release.git", 55 | "compiled_release_url": "https://s3.amazonaws.com/pcfdev/compiled-releases/etcd-v104-1499707363.tgz" 56 | }, 57 | "garden-runc" : { 58 | "version": "v1.5.0", 59 | "sha1": "2d14be094a078047bfadf443ceb2f9799d5d3024", 60 | "source_location": "https://github.com/cloudfoundry/garden-runc-release.git", 61 | "compiled_release_url": "https://s3.amazonaws.com/pcfdev/compiled-releases/garden-runc-v1.5.0-1501067825.tgz" 62 | }, 63 | "go-buildpack" : { 64 | "version": "1.8.1", 65 | "sha1": "5a1ec163a70fc2f4602c2b76ab20f61a08730b60", 66 | "source_location": "https://github.com/cloudfoundry/go-buildpack-release.git", 67 | "compiled_release_url": "https://s3.amazonaws.com/pcfdev/compiled-releases/go-buildpack-1.8.1-1498226718.tgz" 68 | }, 69 | "java-offline-buildpack" : { 70 | "version": "3.16", 71 | "sha1": "ef9469f31363d55bbe327e899288be7f37f17e66", 72 | "source_location": "https://github.com/cloudfoundry/java-offline-buildpack-release.git", 73 | "compiled_release_url": "https://s3.amazonaws.com/pcfdev/compiled-releases/java-offline-buildpack-3.16-1496353415.tgz" 74 | }, 75 | "local-volume" : { 76 | "version": "brokerapi2.9-97-g54cd13f", 77 | "sha1": "846a49f3bec78c5d53cf1f69c2cfe9b4c491f21d", 78 | "source_location": "https://github.com/cloudfoundry-incubator/local-volume-release.git", 79 | "compiled_release_url": "https://s3.amazonaws.com/pcfdev/compiled-releases/local-volume-brokerapi2.9-97-g54cd13f-1498517156.tgz" 80 | }, 81 | "loggregator" : { 82 | "version": "v85-1-g331bf230", 83 | "sha1": "819e301ee0660702d531bb3fc322188b3dfe5164", 84 | "source_location": "https://github.com/cloudfoundry/loggregator.git", 85 | "compiled_release_url": "https://s3.amazonaws.com/pcfdev/compiled-releases/loggregator-v85-1-g331bf230-1501013779.tgz" 86 | }, 87 | "nats" : { 88 | "version": "v14-1-gd95b4d6", 89 | "sha1": "d35ef533b13a0cd2c8a2304a508388ede7305d02", 90 | "source_location": "https://github.com/cloudfoundry/nats-release.git", 91 | "compiled_release_url": "https://s3.amazonaws.com/pcfdev/compiled-releases/nats-v14-1-gd95b4d6-1500502388.tgz" 92 | }, 93 | "nodejs-buildpack" : { 94 | "version": "1.5.31", 95 | "sha1": "1f6b6305f794105ceb3b0a2c5989159fb3466b34", 96 | "source_location": "https://github.com/cloudfoundry/nodejs-buildpack-release.git", 97 | "compiled_release_url": "https://s3.amazonaws.com/pcfdev/compiled-releases/nodejs-buildpack-1.5.31-1501018153.tgz" 98 | }, 99 | "php-buildpack" : { 100 | "version": "4.3.31", 101 | "sha1": "f202386505ded742bf554e6d3cfbb4daca0b68d9", 102 | "source_location": "https://github.com/cloudfoundry/php-buildpack-release.git", 103 | "compiled_release_url": "https://s3.amazonaws.com/pcfdev/compiled-releases/php-buildpack-4.3.31-1500050473.tgz" 104 | }, 105 | "python-buildpack" : { 106 | "version": "1.5.18", 107 | "sha1": "d700feeb6930e88a7f4a7e0c33f90eee0eeaa2a6", 108 | "source_location": "https://github.com/cloudfoundry/python-buildpack-release.git", 109 | "compiled_release_url": "https://s3.amazonaws.com/pcfdev/compiled-releases/python-buildpack-1.5.18-1501018679.tgz" 110 | }, 111 | "routing" : { 112 | "version": "0.152.0-4-gc4e8657", 113 | "sha1": "f42a7ef39fbcfc90d7ea4c8619d6071d51d7f911", 114 | "source_location": "https://github.com/cloudfoundry-incubator/routing-release.git", 115 | "compiled_release_url": "https://s3.amazonaws.com/pcfdev/compiled-releases/routing-0.152.0-4-gc4e8657-1501020545.tgz" 116 | }, 117 | "ruby-buildpack" : { 118 | "version": "1.6.37", 119 | "sha1": "d8f25677a29088adc737088b1a395c6bacb16e1f", 120 | "source_location": "https://github.com/cloudfoundry/ruby-buildpack-release.git", 121 | "compiled_release_url": "https://s3.amazonaws.com/pcfdev/compiled-releases/ruby-buildpack-1.6.37-1501017963.tgz" 122 | }, 123 | "staticfile-buildpack" : { 124 | "version": "1.4.5", 125 | "sha1": "44091b829ca732703002f69b93151b29c2d05c9f", 126 | "source_location": "https://github.com/cloudfoundry/staticfile-buildpack-release.git", 127 | "compiled_release_url": "https://s3.amazonaws.com/pcfdev/compiled-releases/staticfile-buildpack-1.4.5-1500050172.tgz" 128 | }, 129 | "uaa" : { 130 | "version": "v34-1-g2b93080", 131 | "sha1": "c4bb8f6b925dbc5d32a0e15c4d88f5f6ef1e7b30", 132 | "source_location": "https://github.com/cloudfoundry/uaa-release.git", 133 | "compiled_release_url": "https://s3.amazonaws.com/pcfdev/compiled-releases/uaa-v34-1-g2b93080-1501014327.tgz" 134 | } 135 | } 136 | } 137 | --------------------------------------------------------------------------------