Contributors1 hourHabitat can build almost any kind of software. For example:
You can also use Habitat to package legacy applications. Packaging with Habitat enables you to more easily move your apps to modern architectures, including the cloud and container-based services.
In this module you'll package a basic web application that runs a CGI script through Apache. Here's what you'll see:
Hab Studio
[1][default:/src:0]# | curl http://127.0.0.1/cgi-bin/hello-world
|
Hello World!!
|
Time is: Fri Oct 6 18:06:14 GMT 2017
|
This application is similar to the webapp package you built in Try Habitat.
Prerequisites
Install Habitat
For this module, you'll need the Habitat command-line interface (CLI) on your workstation. You'll also need basic familiarity with how Habitat works.
The best way to get set up is to go through the Try Habitat module before you start this module.
Start the Try Habitat module
1. Create the application
Here you'll create a basic web application from scratch.
Start by moving to a working directory, for example, your home directory.
Create the webapp directory.
Move to the webapp directory.
Create a file named hello-world and add these contents.
Editor: ~/webapp/hello-world
1
2
3
4
5
6
7
| #!/bin/bash
echo "Content-type: text/plain; charset=iso-8859-1"
echo ""
echo "Hello World!!"
echo "Time is: $(date)" |
This is a basic CGI script that prints the content type, a greeting, and the current time. (The content type is included in the HTTP response header; the greeting and time go into the response body.)
2. Create the plan
Everything in Habitat starts with a plan. Run hab plan init to create one.
Habitat examines your application but is unable to find scaffolding that would assist you in building your package. It instead creates:
- a
habitat directory with a plan file, plan.sh. - a configuration directory,
habitat/config. - a file to store default configuration values,
habitat/default.toml. - an application lifecycle hooks directory,
habitat/hooks.
Scaffolding simplifies the packaging process for many application types. Packaging a legacy application often requires you to specify more specifically how to build and run your package.
The plan file, plan.sh forms the basis of the Habitat package and defines how the package gets built and deployed. The config directory along with the default.toml file provide additional details.
Habitat can package everything your application needs to run. This packaging can also include scripts (known as hooks) to control how your application behaves throughout its lifecycle, for example, how it is initialized, updated, and shut down. For example, during initialization you might need to move files into the svc directory. Or perhaps at shutdown you might remove temporary files that you no longer need.
Examine your plan file, ~/webapp/habitat/plan.sh. You see that it includes the name, origin, version, maintainer, license, and source location. Other fields are provided but left commented. You can remove the commented lines if you prefer.
Editor: ~/webapp/habitat/plan.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| pkg_name=webapp
pkg_origin=learn-chef
pkg_version="0.1.0"
pkg_maintainer="The Habitat Maintainers <humans@habitat.sh>"
pkg_license=('Apache-2.0')
pkg_source="http://some_source_url/releases/${pkg_name}-${pkg_version}.tar.gz"
# pkg_filename="${pkg_name}-${pkg_version}.tar.gz"
# pkg_shasum="TODO"
# pkg_deps=(core/glibc)
# pkg_build_deps=(core/make core/gcc)
# pkg_lib_dirs=(lib)
# pkg_include_dirs=(include)
# pkg_bin_dirs=(bin)
# pkg_pconfig_dirs=(lib/pconfig)
# pkg_svc_run="bin/haproxy -f $pkg_svc_config_path/haproxy.conf"
# pkg_exports=(
# [host]=srv.address
# [port]=srv.port
# [ssl-port]=srv.ssl.port
# )
# pkg_exposes=(port ssl-port)
# pkg_binds=(
# [database]="port host"
# )
# pkg_binds_optional=(
# [storage]="port host"
# )
# pkg_interpreters=(bin/bash)
# pkg_svc_user="hab"
# pkg_svc_group="$pkg_svc_user"
# pkg_description="Some description."
# pkg_upstream_url="http://example.com/project-name" |
This list describes the core build phases that happen when you build a Habitat package:
- Download source
- Verify source
- Unpack source
- Build source
- Install source
- Copy configuration
- Add build hooks
- Sign and seal package
There are other build phases, each with its own callback, that are not shown in this list.
Each of these phases has a default behavior. When you build a legacy application you will often need to modify the operations performed by these phases. Habitat plans enable you to augment and override these build phases by defining build phase callbacks within the plan.
2.1. Download source
This phase downloads the software or source code described by pkg_source. For example, in Build a Habitat package from source, the plan for ImageMagick downloads the source code as a .tar file.
Here, there's nothing to download, as the source code is defined in the hello-world script. Override this phase by defining the do_download callback to take no action.
Editor: ~/webapp/habitat/plan.sh
1
2
3
4
5
6
7
8
9
10
| pkg_name=webapp
pkg_origin=learn-chef
pkg_version="0.1.0"
pkg_maintainer="The Habitat Maintainers <humans@habitat.sh>"
pkg_license=('Apache-2.0')
pkg_source="http://some_source_url/releases/${pkg_name}-${pkg_version}.tar.gz"
do_download() {
return 0
} |
A callback that returns 0 means that function completed successfully.
2.2. Verify source
If a plan defines pkg_source, then this phase ensures that the downloaded archive matches the provided checksum in pkg_shasum. This application does not need to be verified. Remove the pkg_source from the plan.
Editor: ~/webapp/habitat/plan.sh
1
2
3
4
5
6
7
8
9
| pkg_name=webapp
pkg_origin=learn-chef
pkg_version="0.1.0"
pkg_maintainer="The Habitat Maintainers <humans@habitat.sh>"
pkg_license=('Apache-2.0')
do_download() {
return 0
} |
2.3. Unpack source
If a plan defines a pkg_source then this phase extracts the downloaded and verified archive. This application does not need to be unpacked and pkg_source has already been removed from the plan.
2.4. Build source
This phase configures and builds the application. This legacy application relies on httpd, which is provided by the core/httpd package. Override the default build phase by defining the do_build callback to take no action.
Editor: ~/webapp/habitat/plan.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
| pkg_name=webapp
pkg_origin=learn-chef
pkg_version="0.1.0"
pkg_maintainer="The Habitat Maintainers <humans@habitat.sh>"
pkg_license=('Apache-2.0')
do_download() {
return 0
}
do_build() {
return 0
} |
2.5. Install source
After the source has been built, this phase installs the results into the package. The ~/webapp/hello-world script is the complete application. Override the do_install callback to copy the script file into the package.
Editor: ~/webapp/habitat/plan.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| pkg_name=webapp
pkg_origin=learn-chef
pkg_version="0.1.0"
pkg_maintainer="The Habitat Maintainers <humans@habitat.sh>"
pkg_license=('Apache-2.0')
do_download() {
return 0
}
do_build() {
return 0
}
do_install() {
cp hello-world $pkg_prefix/
} |
The current working directory inside of the do_install function is /src. This is the same directory that is mounted when you enter the Studio. The hello-world script is copied into the package. The package's path is constructed when it is built and then stored in the plan variable$pkg_prefix.
| The current working directory can vary among each callback, depending on the task that is being performed. |
Now update the plan to describe its dependency on Apache. Add the following to your plan settings.
Editor: Untitled
1
2
3
4
| pkg_deps=(core/httpd)
pkg_svc_user="root"
pkg_svc_group="root"
pkg_svc_run="httpd -DFOREGROUND -f $pkg_svc_config_path/httpd.conf" |
pkg_deps defines your run-time dependencies. The parenthesis () means it's an array. The core/httpd package installs Apache HTTP Server.pkg_svc_user and pkg_svc_group define that the service needs to run as root. This is because the web app serves content on port 80; any service that uses port 1024 or less requires root access.pkg_svc_run describes how to run the application when the Supervisor starts.
| Habitat's ability to define dependencies can help keep your software up-to-date. For example, if a security vulnerability is discovered in a package you depend on, for example, core/openssl, you can easily rebuild your package once the core package includes the security fix. |
Your entire ~/webapp/habitat/plan.sh file should look like this.
Editor: Untitled
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| pkg_name=webapp
pkg_origin=learn-chef
pkg_version="0.1.0"
pkg_maintainer="The Habitat Maintainers <humans@habitat.sh>"
pkg_license=('Apache-2.0')
pkg_deps=(core/httpd)
pkg_svc_user="root"
pkg_svc_group="root"
pkg_svc_run="httpd -DFOREGROUND -f $pkg_svc_config_path/httpd.conf"
do_download() {
return 0
}
do_build() {
return 0
}
do_install() {
cp hello-world $pkg_prefix/
} |
2.6. Copy configuration
This phase copies configuration files. Specifically, this phase copies the contents of the habitat/config directory into the package's config directory, $pkg_prefix/config.
Habitat enables you to configure your application when it starts up. You can also update the configuration while your application runs.
Habitat provides a templating mechanism that enables you to provide placeholder values that are filled in when your application starts up or is restarted. By default, configuration values are read from a file named default.toml.
Here you create the Apache configuration file, httpd.conf, and the default values file, default.toml.
These files are lengthy. Run curl to download them from GitHub.
Linux and macOS
Terminal: ~/webapp
$ | curl -o habitat/config/httpd.conf https://raw.githubusercontent.com/learn-chef/habitat-build-plan-legacy/master/habitat/config/httpd.conf
|
Terminal: ~/webapp
$ | curl -o habitat/default.toml https://raw.githubusercontent.com/learn-chef/habitat-build-plan-legacy/master/habitat/default.toml
|
Windows
Windows PowerShell: ~/webapp
PS > | curl -useb -o habitat\config\httpd.conf https://raw.githubusercontent.com/learn-chef/habitat-build-plan-legacy/master/habitat/config/httpd.conf
|
Windows PowerShell: ~/webapp
PS > | curl -useb -o habitat\default.toml https://raw.githubusercontent.com/learn-chef/habitat-build-plan-legacy/master/habitat/default.toml
|
To see how the Apache configuration file relates to the default.toml file, first open habitat/config/httpd.conf and locate the User and Group directives.
Editor: ~/webapp/habitat/config/httpd.conf
1
2
3
4
5
6
7
8
9
10
| #
# If you wish httpd to run as a different user or group, you must run
# httpd as root initially and it will switch.
#
# User/Group: The name (or #number) of the user/group to run httpd as.
# It is usually good practice to create a dedicated user and group for
# running httpd, as with most system services.
#
User {{cfg.user}}
Group {{cfg.group}} |
The double curly braces {{ }} show Handlebars syntax. When the Supervisor starts your service, it reads the values for {{cfg.user}} and {{cfg.group}} from default.toml. Open your default.toml file and you'll see that the values for user and group are both "hab".
Editor: ~/webapp/habitat/default.toml
1
2
3
4
5
6
7
| #httpd.conf settings
serveradmin = "you@example.com"
servername = "localhost"
serverport = "80"
listen = ["80"]
user = "hab"
group = "hab" |
When the Supervisor starts, it fills in the template with its values. For example:
Editor: ~/webapp/habitat/config/httpd.conf
1
2
3
4
5
6
7
8
9
10
| #
# If you wish httpd to run as a different user or group, you must run
# httpd as root initially and it will switch.
#
# User/Group: The name (or #number) of the user/group to run httpd as.
# It is usually good practice to create a dedicated user and group for
# running httpd, as with most system services.
#
User hab
Group hab |
You can also use the hab config apply command to alter your application's configuration at run time. You'll do that in the following module.
As an optional exercise, find a few templated settings in the Apache configuration file and map them to their corresponding values in default.toml.
2.7. Add build hooks
This phase sets up lifecycle event handlers, or hooks, to perform certain actions during a service's runtime.
Specifically, this phase copies the contents of the habitat/hooks directory into the package's config directory, $pkg_prefix/hooks. A hook is essentially a shell script. For this application you create the init hook and rely on the run hook generated automatically.
The init hook is executed when the service is started but before the service runs.
Consider the hab user and the hab group directives within the Apache configuration. The group needs to be created, the user needs to be created, and the user needs to be added to the group. Also consider these two directives from the Apache configuration file and the directories they map to.
| Directive | Value | Meaning |
|---|
DocumentRoot | "{{pkg.svc_data_path}}/htdocs" | The directory from which Apache will serve files. |
ScriptAlias | "{{pkg.svc_data_path}}/cgi-bin/" | The directory that contains CGI scripts. |
These two directories need to be created. The hello-world CGI script must also be copied into the scripts directory. The CGI script needs the appropriate execute permissions.
Create ~/webapp/habitat/hooks/init and add this content.
Editor: ~/webapp/habitat/hooks/init
1
2
3
4
5
6
7
8
9
10
11
12
| #!/bin/bash
addgroup {{cfg.group}}
adduser --disabled-password --ingroup {{cfg.user}} {{cfg.group}}
chmod 755 {{pkg.svc_data_path}}
mkdir -p {{pkg.svc_data_path}}/htdocs
mkdir -p {{pkg.svc_data_path}}/cgi-bin
cp {{pkg.path}}/hello-world {{pkg.svc_data_path}}/cgi-bin/
chmod 755 {{pkg.svc_data_path}}/cgi-bin/hello-world |
The run hook for this application describes how the service is run and is automatically generated from the plan file's pkg_svc_run value: httpd -DFOREGROUND -f $pkg_svc_config_path/httpd.conf.
2.8. Sign and seal package
This phase signs the package. For this application, no specific action is required here.
No more plan settings need to be removed, added, or changed. No more build phase callbacks need to be overridden.
3. Build the package
Your plan includes a plan.sh file, an Apache configuration file, a default.toml file, and an init hook. Let's build your package from the Studio.
Run hab studio enter to enter the Studio.
Run the build to build the package.
Hab Studio
[1][default:/src:0]# | build
|
List the resulting .hart file from the results directory.
Hab Studio
[2][default:/src:0]# | ls results/*.hart
|
results/learn-chef-webapp-0.1.0-20171009020034-x86_64-linux.hart
|
4. Run the package
Run the following hab sup start command to start the application, replacing learn-chef with your origin name.
Hab Studio
[3][default:/src:0]# | hab svc load learn-chef/webapp
|
The learn-chef/webapp service was successfully loaded
|
The Supervisor runs the application as a service in the background. Run sup-log to view the application's output.
Hab Studio
[4][default:/src:0]# | sup-log
|
--> Tailing the Habitat Supervisor's output (use 'Ctrl+c' to stop)
|
webapp.default(HK): run, compiled to /hab/svc/webapp/hooks/run
|
webapp.default(HK): Hooks compiled
|
webapp.default(SR): Hooks recompiled
|
default(CF): Updated httpd.conf ab3ed2dfa656859b0208a2052294cbf2801601244d37d6ab0845583498c7782e
|
webapp.default(SR): Configuration recompiled
|
webapp.default(SR): Initializing
|
webapp.default(SV): Starting service as user=root, group=root
|
webapp.default(O): [Thu Feb 22 22:25:57.745202 2018] [ssl:warn] [pid 432] AH01873: Init: Session Cache is not configured [hint: SSLSessionCache]
|
webapp.default(O): [Thu Feb 22 22:25:57.748495 2018] [mpm_prefork:notice] [pid 432] AH00163: Apache/2.4.27 (Unix) OpenSSL/1.0.2j configured -- resuming normal operations
|
webapp.default(O): [Thu Feb 22 22:25:57.748578 2018] [core:notice] [pid 432] AH00094: Command line: 'httpd -D FOREGROUND -f /hab/svc/webapp/config/httpd.conf'
|
Press Ctrl+C to stop viewing the application's output.
Let's verify the application is running and functional. Recall from the Try Habitat module that you need to install curl. Do so like this.
Hab Studio
[5][default:/src:0]# | hab pkg install core/curl -b
|
Finally, run curl to access your site.
Hab Studio
[6][default:/src:0]# | curl http://127.0.0.1/cgi-bin/hello-world
|
Hello World!!
|
Time is: Mon Oct 9 02:15:28 GMT 2017
|
To see the Content-type header in action, run curl like this.
Hab Studio
[7][default:/src:0]# | curl -D - http://127.0.0.1/cgi-bin/hello-world
|
HTTP/1.1 200 OK
|
Date: Mon, 09 Oct 2017 02:32:24 GMT
|
Server: Apache/2.4.27 (Unix) OpenSSL/1.0.2j
|
Transfer-Encoding: chunked
|
Content-Type: text/plain; charset=iso-8859-1
|
|
Hello World!!
|
Time is: Mon Oct 9 02:32:24 GMT 2017
|
Summary
Great work! Although the web application is basic, you learned how to:
- define plan settings.
- override build phase callbacks.
- provide a configuration file for your service in the form of a template.
- define lifecycle hooks to perform certain actions during a service's runtime.