Terraform In Action Meap V10 Meap Scott Winkler
download
https://ebookbell.com/product/terraform-in-action-meap-v10-meap-
scott-winkler-24426952
Explore and download more ebooks at ebookbell.com
Here are some recommended products that we believe you will be
interested in. You can click the link to download.
Terraform In Depth Robert Hafner
https://ebookbell.com/product/terraform-in-depth-robert-
hafner-232391492
Devops In Sharepoint Handson Packer Terraform Ansible Vagrant
Srivastava
https://ebookbell.com/product/devops-in-sharepoint-handson-packer-
terraform-ansible-vagrant-srivastava-33975010
Terraform For Google Cloud Essential Guide Learn How To Provision
Infrastructure In Google Cloud Securely And Efficiently 1st Edition
Bernd Nordhausen
https://ebookbell.com/product/terraform-for-google-cloud-essential-
guide-learn-how-to-provision-infrastructure-in-google-cloud-securely-
and-efficiently-1st-edition-bernd-nordhausen-47589692
Getting Started With Containers In Azure Deploy Secure Cloud
Applications Using Terraform 2nd Edition Shimon Ifrah
https://ebookbell.com/product/getting-started-with-containers-in-
azure-deploy-secure-cloud-applications-using-terraform-2nd-edition-
shimon-ifrah-54542376
Aws Cloud Automation Indepth Guide To Automation Using Terraform
Infrastructure As Code Solutions James Odeyinka
https://ebookbell.com/product/aws-cloud-automation-indepth-guide-to-
automation-using-terraform-infrastructure-as-code-solutions-james-
odeyinka-55917934
Patterns And Practices For Infrastructure As Code With Examples In
Python And Terraform Meap V08 Meap V08 Rosemary Wang
https://ebookbell.com/product/patterns-and-practices-for-
infrastructure-as-code-with-examples-in-python-and-terraform-
meap-v08-meap-v08-rosemary-wang-42522802
Describing The City Describing The State Representations Of Venice And
The Venetian Terraferma In The Renaissance 1st Edition Sandra Toffolo
https://ebookbell.com/product/describing-the-city-describing-the-
state-representations-of-venice-and-the-venetian-terraferma-in-the-
renaissance-1st-edition-sandra-toffolo-51684884
Terraforming Mars Astrobiology Perspectives On Life In The Universe
1st Edition Martin Beech Editor
https://ebookbell.com/product/terraforming-mars-astrobiology-
perspectives-on-life-in-the-universe-1st-edition-martin-beech-
editor-49011774
Terraforming The Creating Of Habitable Worlds Martin Beech
https://ebookbell.com/product/terraforming-the-creating-of-habitable-
worlds-martin-beech-43621274
MEAP Edition
Manning Early Access Program
Terraform in Action
Version 9
Copyright 2020 Manning Publications
For more information on this and other Manning titles go to
manning.com
©Manning Publications Co. To comment go to liveBook
welcome
Dear Reader,
Thank you for purchasing this Terraform in Action. Personally, I love books and I think they
are the best way to broach a new subject. Now that I’ve written one of my own, I can
understand why this is the case: an incredible amount of time and effort from many different
people went into handcrafting this experience for you. If you want to jumpstart your skills with
Terraform and skip all the boring stuff, then this is the book for you.
When I started learning Terraform there were not many resources available. In many
ways, it was the Wild West of Terraform. My path to learning was long and tortuous, and so by
writing this book, I hope to save you from going what I went through. This is a culmination of
all by learnings and musings about Terraform over the years. I poured a lot of myself into this
book and didn’t hold anything back. If you can finish reading this then I promise you will know
all that I know – you too can become a Terraform guru. My teaching style doesn’t involve
memorization or rote copy-paste, because I don’t believe in that. Instead, I will teach you how
to think in Terraform and how to solve your own problems. The world is a big place and the
potential use cases of Terraform are far too many for this to merely be a cookbook.
Whether you are a beginner approaching Terraform for the first time, or already a
seasoned expert, I believe there is something for you in this book. This is my gift to you, the
reader. This is the book that I wish I had available to me when I was a beginner.
If you have any questions, or just want to share your thoughts, please send me an email
to scottjwinkler@gmail.com or post any feedback you have in liveBook discussion.
I look forward to hearing from you!
Thanks,
—Scott
©Manning Publications Co. To comment go to liveBook
©Manning Publications Co. To comment go to liveBook
brief contents
PART 1: LEARNING THE ROPES
1 Getting Started with Terraform
2 Lifecycle of a Terraform Resource
3 Functional Programming
4 Deploying a Multi-Tiered Web Application in AWS
PART 2: TERRAFORM IN THE WILD
5 Serverless Made Easy
6 Terraform with Friends
7 CI/CD Pipelines as Code
8 A Multi-Cloud MMORPG
PART 3: BECOMING A TERRAFORM GURU
9 Zero Downtime Deployments
10 Refactoring and Testing
11 Extending Terraform by Writing your own Provider
12 Terraform in Automation
13 Secrets Management
Appendix A: Creating Custom Resources with the Shell
Provider
Getting Started with Terraform
This chapter covers:
• Why Terraform is so great
• Syntax of the HashiCorp Configuration Language (HCL)
• Fundamental elements and building blocks of Terraform
• Setting up a Terraform workspace
• Configuring and deploying an Ubuntu virtual machine on AWS
“What does Terraform do?”, is a question I get asked frequently, but it’s not one that I ever find easy
to answer. At first, it seems like a no-brainer that should be obvious, but you’d be surprised how
difficult it is to describe what Terraform is to someone who’s never used it. When I was first asked
this question, I replied that “Terraform is an Infrastructure as Code provisioning tool”, because I
knew that’s what I was supposed to say, but I also didn’t necessarily understand what any of that
meant. While the phrase adequately describes what Terraform is, it’s also not helpful for
understanding what Terraform actually does. HashiCorp, the company behind Terraform, describes
the technology as: “a tool for building, changing, and versioning infrastructure safely and efficiently”,
but even that isn’t exactly clear. For starters, what does “infrastructure” mean? And what kind of
problems does Terraform help you solve?
The word infrastructure is an elusive sort of word that seems to have taken on a multitude of
different meanings in recent years. Infrastructure, at least in the tech sector, used to refer to the
literal hardware components that you’d see in private, on-premise data centers. These data centers
had racks of physical hardware components for the express purpose of running application software
and making it available to the world. Again, these were physical devices, so you could walk through
the racks of hardware components and feel the heat radiating off these machines, or just admire the
flashing lights and beeping sounds as you walk around.
Nowadays, it’s a different story. The cloud has well and truly taken over, and most companies
have either migrated to the cloud or are in the process of migrating to the cloud. Tech giants like
1
1
Google, Amazon, and Microsoft have completely dominated the cloud scene by offering attractive on-
demand compute, storage and networking related services via an Application Programming Interface
(API). The word “infrastructure” thus no longer refers exclusively to physical hardware in on-premise
data centers, as it could also refer to any virtualized hardware or managed software accessible
through an API. What used to be a concrete and easily explainable term has now become abstract
and muddied. As we shall see later, even this relaxed definition of “infrastructure” may not be
appropriate in describing the current state of affairs.
Terraform is an Infrastructure as Code technology created by HashiCorp for automating
infrastructure provisioning to the cloud. Why the need for automation? Isn’t the cloud supposed to
solve all our problems? Well, as it turns out, provisioning infrastructure to the cloud is a hugely
tedious process, especially once you factor in the extreme scale that modern companies operate at.
Companies used to have entire teams dedicated to the task of point-and-clicking their way through
the console. Some of the more astute among those eventually discovered how to do less work
increase productivity by writing custom scripts, which is really how the idea of Infrastructure as Code
really started.
Infrastructure as Code (IaC) is the process of managing and provisioning data centers through
machine-readable definition files, rather than manual point-and-click. It means writing code to define
your infrastructure, in the same manner you would write code to define your application software.
Expressing your infrastructure as code has many distinct advantages over manual provisioning, such
as being able to check code into version control systems, perform regular audit checks, and deploy
faster and more reliably.
Traditionally, IaC was accomplished using configuration management tools like Chef, Puppet,
Ansible, and SaltStack. Configuration management tools like these provide a great deal of
customization but tend to have a higher learning curve, and focus more on application delivery rather
than infrastructure provisioning (which is where Terraform excels).
Terraform is an infrastructure provisioning tool, not a configuration management tool. Being a
provisioning tool means that Terraform can deploy your entire infrastructure stack, not just new
versions of your application (although it can do that too). By writing configuration files, Terraform
can deploy just about anything. The main use case of Terraform is to deploy ephemeral, on-demand
infrastructure in a predictable and repeatable fashion.
What can Terraform deploy to? Well, just about any cloud or combination of clouds, including:
private, public, hybrid, and multi-cloud.
2
Figure 1.1 Terraform can deploy infrastructure to any cloud or combination of clouds
In this chapter we’re going to start by going over the distinguishing features of Terraform.
Terraform is just a tool after all, so why pick Terraform over another, when there are so many tools
that can do the job? We’ll also talk about the comparative advantages and disadvantages of
Terraform in relation to other popular IaC technologies, and what makes Terraform the clear winner.
Finally, we’ll look at the quintessential “Hello World!” of Terraform by deploying a single server to
AWS and improving it by incorporating some of the more dynamic features of Terraform.
1.1 What makes Terraform so great?
There’s been a lot of hype about Terraform recently, but is any of it really justified? Terraform isn’t
the only Infrastructure as Code technology on the block. There are plenty of other tools that do the
same thing. Moreover, Terraform was created and released by a relatively small tech company, and
doesn’t even have a 1.0 version out yet. How is it that Terraform, a technology in the highly lucrative
software deployment market space, able to compete against competition from Amazon, Microsoft,
and Google? The answer is, of course, that Terraform enjoys a unique set of competitive advantages.
These competitive advantages stem from six key characteristics:
1. Provisioning tool: Deploy infrastructure, not just applications
2. Easy to use: For all of us non geniuses
3. Free and Open Source: Who doesn’t like free?
4. Declarative: Say what you want, not how to do it
5. Cloud agnostic: Deploy to any cloud using the same tool.
6. Expressive and extendable: You aren’t limited by the language
A comparison of Terraform to similar IaC tools is shown in table 1.1:
Name Key Features
Provisioning
tool
Easy to
use
Free and
Open
Source
Declarative Cloud
Agnostic
Expressive
and
extendable
Public Cloud
Hybrid Cloud
Private Cloud
main.tf
Configuration
Files
User
Configures
Deploys
Writes
Deployment Targets
3
Ansible1 X X X X
Chef2 X X X X
Puppet3 X X X X
SaltStack4 X X X X X
Terraform5 X X X X X X
Pulumi6 X X X X
AWS CloudFormation7 X X X
GCP Resource Manager8 X X X
Azure Resource Manager9 X X
Table 1.1 A comparison of popular IaC tools
1.1.1 Provisioning Tool
Terraform is an infrastructure provisioning, not a configuration management, tool. The distinction is
subtle but important. Configuration management (CM) tools, like Chef, Puppet, Ansible and
SaltStack, were all designed to install and manage software on existing servers. They work by
performing push or pull updates and restoring software to a desired state if drift has occurred. This
works fine if you only care about application delivery, but it doesn’t help at all with the initial
infrastructure provisioning process. You’d still have to write custom scripts to deploy your
infrastructure before configuration management tools could be of any use.
NOTE it’s not a hard and fast rule that provisioning tools can only provision and configuration management tools can
only configure. Many CM tools offer some degree of infrastructure provisioning, and vice versa.
The biggest difference between configuration management and provisioning tools is a matter of
philosophy. CM tools favor mutable infrastructure, whereas Terraform and other provisioning tools,
favor immutable infrastructure. What’s the difference then between mutable and immutable
infrastructure?
Mutable infrastructure is performing software updates in place, especially in the context of
application delivery. It’s having pet servers. If pet servers are sick, you don’t kill them. You nurse
them back to health by rolling out new software updates.
Immutable infrastructure, on the other hand, is about treating servers like cattle, not like pets.
If a server gets into a bad or undesirable configuration state, the solution is to simply take it out
back, shoot it in the head, and replace it with a new one.
1Ansible – https://www.ansible.com
2 Chef – https://www.chef.io
3Puppet – https://www.puppet.com/
4SaltStack – https://www.saltstack.com/
5Terraform – https://www.terraform.io/
6 Pulumi – https://www.pulumi.com
7CloudFormation – https://aws.amazon.com/cloudformation/
8GCP Resource Manager – https://cloud.google.com/resource-manager/
9Azure Resource Manager – https://azure.microsoft.com/features/resource-manager/
4
Another way to imagine the differences between mutable and immutable infrastructure is to think
of an old toy that no longer does what you want. Mutable infrastructure would say that there is
inherent goodness in the old toy and would recommend fixing it up. Immutable infrastructure, being
significantly more callous and worldly, would suggest throwing out the old toy in favor of a new one
that already has the features you want.
Why is immutability better than mutability? Because it’s is easier to reason about. You don’t have
to worry about things changing or leaving behind artifacts between deployments. Everything is
commoditized, so there is no chance of having special snowflake servers. Additionally, this makes it
easier to perform repeatable deployments and provision ephemeral environments that are all exactly
uniform. Immutability is an ongoing trend in the software community. It’s why we are moving away
from virtual machines to containers, and from containers to serverless. The less you have to worry
about the previous state of software, the better.
One conclusion that a lot of people make after first hearing about provisioning and configuration
management tools is that they should be combined together. After all, why not use Terraform to
deploy your infrastructure, and a configuration management tool to ensure it is in a desired state?
See figure 1.2 for an example of how this would work.
Figure 1.2 Combining Terraform and CM tools. Terraform deploy infrastructure initially and Ansible installs and
updates software
I am of the opinion that Terraform should not be combined with configuration management
technologies, mainly because of their clashing philosophies. Terraform favors immutable
infrastructure, whereas CM favors mutable. If you combine the two, invariably, you will wind up with
something that straddles the paradigms of both, leading to something that is not much better than
what we had before.
Configuration Files
main.tf Terraform
deploys
configures
provisions
infrastructure
installs and
updates
software
Configuration Files
playbook
.yml
configures
1) Provisioning
2) Configuring
Virtual Machine
(VM)
Cloud Providers
Ansible
updates
5
There are many ways to perform continuous delivery with Terraform that do not involve CM tools.
For example, you could deploy new virtual machines with cloud-init scripts, or perform zero-downtime
Blue/Green deployments10, or use a conventional container scheduler like Kubernetes/Nomad. The
choice mainly has to do with what you are deploying, and the amount of time you are willing to spend
to set it up. We’ll talk more about these different strategies in chapters 7, 8, and 9.
1.1.2 Easy to Use
The basics of Terraform are quick and easy to learn, even for non-programmers. By the end of
chapter 4 you will already have the skills to call yourself an intermediate with the technology, which
is kind of shocking when you think about it. Achieving mastery is another story, of course, but that’s
why you’re here, and that’s why this book is as long as it is.
The main reason why Terraform is so easy is because the code is written in a domain specific
configuration language called HashiCorp Configuration Language (or just HCL for short). It’s a
language invented by HashiCorp as a substitute for more verbose configuration languages like JSON
and XML. HCL attempts to strike a balance between human and machine readability and was
influenced by earlier field attempts such as libucl and Nginx configuration. HCL is fully compatible
with JSON, which means that HCL can be converted 1:1 to JSON, and vice-versa. This makes it easy
to interoperate with systems outside of Terraform, or even generate configuration code on the fly.
1.1.3 Free and Open-Source Software
The engine that powers Terraform is called Terraform core, a free and open source software offered
under the Mozilla Public License v2.0. This license stipulates that anyone is allowed to use, distribute,
or modify the software for both private and commercial purposes. Being free is great because you
never have to worry about incurring additional costs when using Terraform, In addition, you gain full
transparency to the product and are able to make open source contributions if you so desire.
HashiCorp has a dedicated team working on the ongoing development of Terraform, and they are
very good at fixing bugs and introducing new features in a timely manner.
There’s also no premium version of Terraform that you’re missing out by using the open source
version. HashiCorp has repeatedly stated that they do not have any intentions to offer a premium
version of Terraform, now or ever. Instead they plan to make money by offering specialized tooling
that makes it easier to run Terraform in high-scale, multi-tenant environments. To this end, they
have developed and released Terraform Cloud and Terraform Enterprise. Both products utilize the
same open source Terraform core you can download for free, so it’s easy to imagine developing your
own platform if deploying Terraform workspaces. In chapter 11 we’ll explore this topic, as well as
discuss everything else you need to be aware of when running Terraform in automation.
1.1.4 Declarative Programming
Declarative programming is when you expresses the logic of a computation (the what) without
describing the control flow (the how). Instead of writing step-by-step instructions, you simply
describe what you want, and it will get done. Examples of declarative programming languages include
10 Blue/Green deployment is a technique that reduces downtime and risk by running two identical production environments called Blue and Green. At any time, only
one of the environments is live, with the live environment serving all production traffic.
6
database query languages (SQL), functional programming languages (Haskell, Clojure), configuration
languages (XML, JSON), and most IaC tools (Ansible, Chef, Puppet, Terraform).
Declarative programming is in contrast to imperative programming, which focuses on the how.
With imperative programming, you use conditional branching, loops, and expressions to control
system flow, save state, and execute commands. The hardware implementation of nearly all modern
computers is imperative in nature, so it follows that most conventional programming languages are
imperative as well (Python, Java, C, etc.),
The difference between declarative and imperative programming is the difference between getting
in a car and driving yourself from point A to point B, vs. calling an Uber and having your chauffeur
drive you. Assuming you are unfamiliar with the area, it is more than likely you will take an inefficient
route, compared to your chaffeur.
Figure 1.3 Driving yourself vs being chauffeured
NOTE Declarative programming cares about the destination, not the journey. Imperative programming cares about
the journey, not the destination
1.1.5 Cloud Agnostic
Cloud agnostic means being able to seamlessly run on any cloud platform (private or public).
Terraform is cloud agnostic because it allows you to deploy infrastructure to whichever cloud, or
7
combination of clouds using the same configuration language and deployment workflows. Being cloud
agnostic is important because it means you aren’t locked-in to a particular cloud vendor, and you
don’t have to learn a whole new technology if you do switch clouds.
The way Terraform integrates with all the different clouds is through providers. Providers are
plugins for Terraform that are designed to interface with external APIs. Each cloud vendor maintains
their own Terraform provider, enabling Terraform to interface with and manage resources in that
cloud. Providers are written in golang and handle all of the procedural logic for authenticating, making
API requests, handling timeouts and errors, and managing the lifecycle of resources. There are
hundreds of providers available in the Terraform provider registry, together allowing Terraform to
manages thousands of different kinds of resources. You can even write you own Terraform provider,
which is something we will discuss in chapter 10.
Providers can be mixed and matched however you like. If you want to deploy to the hybrid or
multi-cloud, it’s almost as easy as deploying to the single cloud. There are architecture and design
patterns to be aware of, but we will cover these in chapter 8.
Figure 1.4 Deploying to multiple clouds at the same time with Terraform
1.1.6 Richly Expressive and Highly Extensible
Terraform is richly expressive and highly extensible compared to other declarative IaC tools. With
conditionals, for expressions, directives, template files, dynamic blocks, variables, and many built-in
functions, it’s easy to write code to deploy exactly what you want. A tech comparison between
Terraform and AWS CloudFormation is shown in table 1.2
8
Name Language Features Other Features
Intrinsic
Functions
Conditional
Statements
For Loops Types Pluggable Modular Wait
Conditions
Terraform 87 Yes Yes String,
Number,
List,
Map,
Boolean,
Objects,
Complex
Types
Yes Yes No
AWS
CloudFormation
11 Yes No String,
Number,
List
Limited Yes Yes
Table 1.2 Tech comparison between the IaC tools Terraform and AWS CloudFormation
You can also create reusable code by packaging them in modules, which can be shared with
others through the public module registry (something we will discuss in chapter 6). Finally, as
mentioned in the previous section, you can also extend Terraform by writing your own provider.
1.2 Hello Terraform!
In this section we will take a look at a classical use case for Terraform - deploying a virtual machine
(EC2 instance) onto AWS. We’ll use the AWS provider for Terraform to make API calls on our behalf
and deploy an EC2 instance. When we’re done, we’ll have Terraform take down the instance, so we
don’t incur ongoing costs by keeping the server running. An architecture diagram for what we are
about to do is shown in figure 1.5.
9
Figure 1.5 Using Terraform to deploy an EC2 instance to AWS
As a prerequisite for this scenario, I except that you have Terraform 0.13.X installed11, and that
you have access credentials for AWS. The steps we’ll take for deploying the project are:
1. Writing Terraform configuration files
2. Configuring the AWS provider
3. Initializing Terraform with terraform init
4. Deploying the EC2 instance with terraform apply
5. Cleaning-up with terraform destroy
This flow can be visualized by figure 1.6.
Figure 1.6 sequence diagram of “Hello Terraform!” deployment
1.2.1 Writing Terraform Configuration
Terraform reads from configuration files to deploy infrastructure. To tell Terraform we want it to
deploy an EC2 instance, we need to declare an EC2 instance as code. Let’s do that now. Start by
creating a new file named main.tf, with the contents from Listing 1.1. The “.tf” extension signifies
11Install Terraform – https://learn.hashicorp.com/terraform/getting-started/install.html
Local Machine AWS
User
Writes
Provisions
EC2 API
Terraform
AWS Provider
main.tf
API
Calls
EC2 Instance
(VM)
Configures
10
that it’s a Terraform configuration file. When Terraform runs it will read all files in the current working
directory with a “.tf” extension and concatenate them together.
Listing 1.1 Contents of main.tf
resource "aws_instance" "helloworld" { #A
ami = "ami-09dd2e08d601bff67" #B
instance_type = "t2.micro" #B
tags = { #B
Name = "HelloWorld" #B
} #B
}
#A Declaring an aws_instance resource with name “helloworld”
#B Attributes for the EC2 instance
NOTE this AMI is only valid for the us-west-2 region
This code in Listing 1.1 declares that we want Terraform to provision a t2.micro AWS EC2
instance with an Ubuntu AMI (Amazon Machine Image), and a name tag. Compare this to the
equivalent CloudFormation code which is shown in Snippet 1.1, and you can see how much clearer
and more concise it is.
Snippet 1.1 Equivalent AWS CloudFormation
{
"Resources": {
"Example": {
"Type": "AWS::EC2::Instance",
"Properties": {
"ImageId": "ami-09dd2e08d601bff67",
"InstanceType": "t2.micro",
"Tags": [
{
"Key": "Name",
"Value": "HelloWorld"
}
]
}
}
}
}
The EC2 instance code block is a kind of Terraform resource. Resources are the most prevalent
and important element in all of Terraform. They are how Terraform provisions infrastructure, such
as virtual machines, load balancers, NAT gateways, databases, and so forth. Resources are declared
as HCL objects, with type resource and exactly two labels. The first label specifies the type of
resource you want to create, and the second is the resource name. Name has no special significance
and is only used to reference the resource within a given module scope (we will talk about modules
and module scope in chapter 4). Together, the type and name make up the resource identifier,
which is unique for each resource. Figure 1.7 shows the syntax of a resource block.
11
Figure 1.7 Syntax of a resource block
Each resource has inputs and outputs. Inputs are called arguments and outputs are called
attributes. Arguments are passed through the resource and available as resource attributes, but
there are also other attributes which you don’t set and which are only available after the resource
has been created. These other attributes are called computed attributes and are calculated
information, typically metadata about the resource itself. Figure 1.8 shows some of the arguments,
attributes and computed attributes of an aws_instance resource.
Figure 1.8 Inputs and outputs for an aws_instance resource
1.2.2 Configuring the AWS Provider
Next, we need to configure the AWS provider. The AWS provider is responsible for understanding API
interactions, making authenticated requests, and exposing resources to Terraform – including the
aws_instance resource. Let’s explicitly declare our dependency on the AWS provider by adding a
provider block to the configuration file. Update your code in main.tf to look like Listing 1.2
Listing 1.2 Contents of main.tf
provider "aws" { #A
version = "2.65.0" #B
region = "us-west-2" #B
}
resource "aws_instance" "helloworld" {
ami = "ami-09dd2e08d601bff67"
instance_type = "t2.micro"
aws_instance
ami
instance_type instance_type
tags
id
ami
Computed
Attributes
Attributes
Arguments
tags
arn
12
tags = {
Name = "HelloWorld"
}
}
#A Declaring the AWS provider
#B Configuring the provider by locking in a provider version and specifying a default region
Authenticating to AWS
As mentioned previously, I expect you to already have default credentials for AWS. You can either store them in the
~/.aws/credentials file, or as environment variables. There are also three other ways to authenticate, these being:
Non-default profile credentials – you can use profile credentials other than the default by setting an optional provider
attribute known as profile (e.g. profile = <your-profile-name>)
1) Static credentials – you can hardcode AWS credentials in the Terraform AWS provider. This is a not a recommended,
because of the risk of inadvertently exposing secret credentials through version control systems.
2) Assumed role – Terraform can use the credentials you give it to assume a role to another account and use that other role
for all future actions.
If you need to modify the configuration of the AWS provider to fit your unique situation, I suggest consulting the AWS
provider documentation12 for more information on how to do one of the above listed options.
Unlike resources, providers only have one label: name. Name is the official name of the provider
as published in the provider registry (e.g. “aws” for AWS, “google” for GCP and “azurerm” for Azure).
The syntax for a provider block is shown in figure 1.9.
Figure 1.9 Syntax of a Provider Block
NOTE The provider registry is a global store for sharing versioned provider binaries. When Terraform initializes, it
automatically looks up and downloads any required providers from the provider registry
Providers don’t have outputs, only inputs. You configure a provider by passing in inputs, or
configuration arguments, to the provider block. Configuration arguments will be things like servjce
endpoint URL, region, provider version, and any credentials needed to authenticate against the API.
Providers use the configuration arguments to configure all the resources underneath them. This can
be visualized by figure 1.10.
12 https://www.terraform.io/docs/providers/aws/index.html
13
Figure 1.10 How the configured provider injects credentials into aws_instance when making API calls
Usually you don’t want to pass secrets into the provider as plain text, especially when this code
will later be checked into version control, so many providers have the ability to read environment
variables or shared credential files to retrieve this secret information dynamically. If you are
interested more in secrets management, I recommend reading chapter 12, which is where we cover
this topic in great detail.
1.2.3 Initializing Terraform
Before we have Terraform deploy our EC2 instance, we first have to initialize the workspace. Even
though we have declared the AWS provider, Terraform still needs to download and install the binary
from the provider registry. Initialization is required for all new workspaces, as well as any new
Terraform project you download from source control and are running for the first time.
You can initialize Terraform by runningthe command: terraform init. When you do this, you
will see the following output:
NOTE you will need to have Terraform installed on your machine for this to work, if you do not have it already
$ terraform init
Initializing the backend...
Initializing provider plugins...
- Finding latest version of hashicorp/aws...
- Installing hashicorp/aws v2.65.0... #A
- Installed hashicorp/aws v2.65.0 (signed by HashiCorp)
The following providers do not have any version constraints in configuration,
so the latest version was installed.
To prevent automatic upgrades to new major versions that may contain breaking
changes, we recommend adding version constraints in a required_providers block
in your configuration, with the constraint strings suggested below.
* hashicorp/aws: version = "~> 2.65.0"
Terraform has been successfully initialized! #B
You may now begin working with Terraform. Try running "terraform plan" to see
14
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
#A Here Terraform is fetching the latest version of the AWS provider
#B The only thing that we really care about
1.2.4 Deploying the EC2 Instance
Now we’re ready to deploy the EC2 instance using Terraform. Do this now be executing the terraform
apply command.
WARNING performing this action may result in charges to your AWS account. This include potential charges for EC2
and CloudWatch Logs.
$ terraform apply
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# aws_instance.helloworld will be created
+ resource "aws_instance" "helloworld" { #A
+ ami = "ami-09dd2e08d601bff67" #B
+ arn = (known after apply)
+ associate_public_ip_address = (known after apply)
+ availability_zone = (known after apply)
+ cpu_core_count = (known after apply)
+ cpu_threads_per_core = (known after apply)
+ get_password_data = false
+ host_id = (known after apply)
+ id = (known after apply)
+ instance_state = (known after apply)
+ instance_type = "t2.micro" #C
+ ipv6_address_count = (known after apply)
+ ipv6_addresses = (known after apply)
+ key_name = (known after apply)
+ network_interface_id = (known after apply)
+ outpost_arn = (known after apply)
+ password_data = (known after apply)
+ placement_group = (known after apply)
+ primary_network_interface_id = (known after apply)
+ private_dns = (known after apply)
+ private_ip = (known after apply)
+ public_dns = (known after apply)
+ public_ip = (known after apply)
+ security_groups = (known after apply)
+ source_dest_check = true
+ subnet_id = (known after apply)
+ tags = { #D
+ "Name" = "HelloWorld"
15
}
+ tenancy = (known after apply)
+ volume_tags = (known after apply)
+ vpc_security_group_ids = (known after apply)
+ ebs_block_device {
+ delete_on_termination = (known after apply)
+ device_name = (known after apply)
+ encrypted = (known after apply)
+ iops = (known after apply)
+ kms_key_id = (known after apply)
+ snapshot_id = (known after apply)
+ volume_id = (known after apply)
+ volume_size = (known after apply)
+ volume_type = (known after apply)
}
+ ephemeral_block_device {
+ device_name = (known after apply)
+ no_device = (known after apply)
+ virtual_name = (known after apply)
}
+ metadata_options {
+ http_endpoint = (known after apply)
+ http_put_response_hop_limit = (known after apply)
+ http_tokens = (known after apply)
}
+ network_interface {
+ delete_on_termination = (known after apply)
+ device_index = (known after apply)
+ network_interface_id = (known after apply)
}
+ root_block_device {
+ delete_on_termination = (known after apply)
+ device_name = (known after apply)
+ encrypted = (known after apply)
+ iops = (known after apply)
+ kms_key_id = (known after apply)
+ volume_id = (known after apply)
+ volume_size = (known after apply)
+ volume_type = (known after apply)
}
}
Plan: 1 to add, 0 to change, 0 to destroy. #D
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: #E
#A plus sign means create
#B ami attribute
#C instance_type attribute
#D tags attribute
16
#D Totals summary
#E Manual approval step
TIP If you received an error saying “No Valid Credentials Sources Found” then you need to make sure you have either
default credentials set in ~/.aws/credentials, or AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY
set.
The output is called an execution plan and it outlines the set of actions that Terraform intends to
perform to achieve your desired state. It’s a good idea to review the plan as a sanity check before
proceeding. There shouldn’t be anything odd here, unless you made a typo, so when you are done
reviewing the execution plan, approve it by entering “yes” at the command line.
After a minute or two (the approximate time it takes to provision an EC2 instance) the apply will
complete successfully. An example output is shown below.
aws_instance.helloworld: Creating...
aws_instance.helloworld: Still creating... [10s elapsed]
aws_instance.helloworld: Still creating... [20s elapsed]
aws_instance.helloworld: Still creating... [30s elapsed]
aws_instance.helloworld: Creation complete after 34s [id=i-0c98c98679298dab8]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
You can verify your resource was created by finding it in the AWS console for EC2, as shown in
figure 1.11. Note that this instance will be in the us-west-2 region, because that’s what we set in the
provider.
Figure 1.11 Screenshot of EC2 instance in AWS Console
All of the stateful information about the resource is stored in a file called terraform.tfstate. Don’t
let the “.tfstate” prefix fool you, it’s really just a JSON file. The terraform show command can be
used to print human readable output from the state file and makes it easy to list all the data of the
resources Terraform manages. The show command and output result is shown below.
$ terraform show
# aws_instance.helloworld:
resource "aws_instance" "helloworld" {
ami = "ami-09dd2e08d601bff67"
arn = "arn:aws:ec2:us-west-2:215974853022:instance/i-
0c98c98679298dab8"
associate_public_ip_address = true
17
availability_zone = "us-west-2a"
cpu_core_count = 1
cpu_threads_per_core = 1
disable_api_termination = false
ebs_optimized = false
get_password_data = false
hibernation = false
id = "i-0c98c98679298dab8" #A
instance_state = "running"
instance_type = "t2.micro"
ipv6_address_count = 0
ipv6_addresses = []
monitoring = false
primary_network_interface_id = "eni-003e10654e38f1447"
private_dns = "ip-172-31-28-98.us-west-2.compute.internal"
private_ip = "172.31.28.98"
public_dns = "ec2-52-38-134-200.us-west-2.compute.amazonaws.com"
public_ip = "52.38.134.200"
security_groups = [
"default",
]
source_dest_check = true
subnet_id = "subnet-0d78ac285558cff78"
tags = {
"Name" = "HelloWorld"
}
tenancy = "default"
volume_tags = {}
vpc_security_group_ids = [
"sg-0d8222ef7623a02a5",
]
credit_specification {
cpu_credits = "standard"
}
metadata_options {
http_endpoint = "enabled"
http_put_response_hop_limit = 1
http_tokens = "optional"
}
root_block_device {
delete_on_termination = true
device_name = "/dev/sda1"
encrypted = false
iops = 100
volume_id = "vol-05366f9f647f56541"
volume_size = 8
volume_type = "gp2"
}
}
#A id is an important computed attribute
There’s a lot more data here than we originally set in the resource block. The reason for this is
because most of the arguments are either optional or computed after the resource has been created.
You can customize aws_instance by passing in optional arguments. If you want to know what these
18
are, I recommend looking through the AWS provider documentation, as well as the AWS API
documentation.
1.2.5 Destroying the EC2 Instance
Now it’s time to say goodbye to the EC2 instance. You always want to take down any infrastructure
you are no longer using, as it costs money to run stuff in the cloud. Terraform has a special command
to destroy all resources, called terraform destroy. When you run this command, you will be
prompted to manually confirm the destroy operation, as shown below.
$ terraform destroy
aws_instance.helloworld: Refreshing state... [id=i-0c98c98679298dab8]
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
- destroy
Terraform will perform the following actions:
# aws_instance.helloworld will be destroyed
- resource "aws_instance" "helloworld" { #A
- ami = "ami-09dd2e08d601bff67" -> null
- arn = "arn:aws:ec2:us-west-2:215974853022:instance/i-
0c98c98679298dab8" -> null
- associate_public_ip_address = true -> null
- availability_zone = "us-west-2a" -> null
- cpu_core_count = 1 -> null
- cpu_threads_per_core = 1 -> null
- disable_api_termination = false -> null
- ebs_optimized = false -> null
- get_password_data = false -> null
- hibernation = false -> null
- id = "i-0c98c98679298dab8" -> null
- instance_state = "running" -> null
- instance_type = "t2.micro" -> null
- ipv6_address_count = 0 -> null
- ipv6_addresses = [] -> null
- monitoring = false -> null
- primary_network_interface_id = "eni-003e10654e38f1447" -> null
- private_dns = "ip-172-31-28-98.us-west-2.compute.internal" -> null
- private_ip = "172.31.28.98" -> null
- public_dns = "ec2-52-38-134-200.us-west-2.compute.amazonaws.com" ->
null
- public_ip = "52.38.134.200" -> null
- security_groups = [
- "default",
] -> null
- source_dest_check = true -> null
- subnet_id = "subnet-0d78ac285558cff78" -> null
- tags = {
- "Name" = "HelloWorld"
} -> null
- tenancy = "default" -> null
- volume_tags = {} -> null
- vpc_security_group_ids = [
- "sg-0d8222ef7623a02a5",
] -> null
19
- credit_specification {
- cpu_credits = "standard" -> null
}
- metadata_options {
- http_endpoint = "enabled" -> null
- http_put_response_hop_limit = 1 -> null
- http_tokens = "optional" -> null
}
- root_block_device {
- delete_on_termination = true -> null
- device_name = "/dev/sda1" -> null
- encrypted = false -> null
- iops = 100 -> null
- volume_id = "vol-05366f9f647f56541" -> null
- volume_size = 8 -> null
- volume_type = "gp2" -> null
}
}
Plan: 0 to add, 0 to change, 1 to destroy. #B
Do you really want to destroy all resources?
Terraform will destroy all your managed infrastructure, as shown above.
There is no undo. Only 'yes' will be accepted to confirm.
Enter a value:
#A minus sign means destroy
#B Summary of all the actions Terraform intends to take
WARNING It is important not to manually edit or delete the terraform.tfstate file, or else Terraform will lose
track of all managed resources
The destroy plan is just like the previous execution plan, except it is for the delete operations
instead of create.
NOTE terraform destroy does exactly the same thing as if you were to delete all the configuration code and
run a terraform apply.
Confirming that you wish to apply the destroy plan by typing in “yes” at the prompt, wait a few
minutes for Terraform to do its thing, and you will be notified that Terraform has finished destroying
all resources,. Your output will look like the following:
aws_instance.helloworld: Destroying... [id=i-0c98c98679298dab8]
aws_instance.helloworld: Still destroying... [id=i-0c98c98679298dab8, 10s elapsed]
aws_instance.helloworld: Still destroying... [id=i-0c98c98679298dab8, 20s elapsed]
aws_instance.helloworld: Still destroying... [id=i-0c98c98679298dab8, 30s elapsed]
aws_instance.helloworld: Destruction complete after 31s
Destroy complete! Resources: 1 destroyed.
20
You can verify that the resources have indeed been destroyed either by refreshing the AWS
console, or running a terraform show and confirming that it returns nothing.
$ terraform show
1.3 Brave New Hello World!
I like the classic “Hello World!” example, and feel it is a good starter project, but I also don’t think it
does justice to the technology as a whole. Terraform can do much more than simply provision
resources from static configuration code. It’s able to provision resources dynamically based on the
results of external queries and data lookups. Let us now consider data sources, which are elements
that allow you to fetch data at runtime and perform computations as well.
In this section we will improve the classic “Hello World!” example by adding a data source to
dynamically lookup the latest value of the Ubuntu AMI. We’ll pass the output value into aws_instance
so we don’t have to statically set the AMI statically in the EC2 instance resource configuration (see
figure 1.12).
Figure 1.12 How the output of the aws_ami data source will be chained to the input of the aws_instance resource
Because we’ve already configured the AWS provided and initialized Terraform with terraform
init, we can skip some of the steps we did previously. Here, we’ll be doing the following:
1. Modifying Terraform configuration to add the data source
2. Re-deploying with. terraform apply
3. Cleaning-up with terraform destroy
This flow can also be visualized by figure 1.13.
ami
instance_type
ami
instance_type
tags
id
Resource Attributes
Data Source
Arguments
most_recent
filter
owners aws_ami
(data source)
Data Source
Attributes
aws_instance
(resource)
filter
owners
most_recent
id
Resource Arguments
tags
21
Figure 1.13 sequence diagram of deployment
1.3.1 Modifying Terraform Configuration
We need to add the code to read from the external data source, allowing us to query the most recent
Ubuntu AMI published to AWS. Edit the main.tf to look like Listing 1.3.
Listing 1.3 Contents of main.tf
provider "aws" {
version = "2.65.0"
region = "us-west-2"
}
data "aws_ami" "ubuntu" { #A
most_recent = true
filter { #B
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
}
owners = ["099720109477"] #C
}
resource "aws_instance" "helloworld" {
ami = data.aws_ami.ubuntu.id #D
instance_type = "t2.micro"
tags = {
Name = "HelloWorld"
}
}
#A Declaring an aws_ami data source with name “ubuntu”
#B Setting a filter to select all AMI’s with name matching this regex expression
#C canonical Ubuntu AWS account id
#D chaining resources together
Like resources, data sources are declared by creating an HCL object with type “data” having
exactly two labels. The first label specifies the type of data source, and the second is the name of
the data source. Together the type and name are referred to as the data source’s “identifier” and
must be unique within a module. Figure 1.14 illustrates the syntax of a data source.
22
Figure 1.14 Syntax of a data source
The contents of a data source code block are called query constraint arguments and behave
exactly the same as arguments do for resources. The query constraint arguments are used to specify
which resource(s) to fetch data from. Data sources are unmanaged resources that Terraform can
read data from but doesn’t directly control. This is in contrast to managed resources, which are
resources that Terraform directly controls the entire lifecycle of.
1.3.2 Applying Changes
Let’s go ahead and apply our changes by having Terraform deploy an EC2 instance with the Ubuntu
data source output value for AMI. Do this now by running terraform apply. Your log output will be
as follows.
$ terraform apply
data.aws_ami.ubuntu: Refreshing state... #A
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# aws_instance.helloworld will be created
+ resource "aws_instance" "helloworld" {
+ ami = "ami-09dd2e08d601bff67" #B
+ arn = (known after apply)
+ associate_public_ip_address = (known after apply)
+ availability_zone = (known after apply)
+ cpu_core_count = (known after apply)
+ cpu_threads_per_core = (known after apply)
+ get_password_data = false
+ host_id = (known after apply)
+ id = (known after apply)
+ instance_state = (known after apply)
+ instance_type = "t2.micro" #C
// skip some logs
}
Plan: 1 to add, 0 to change, 0 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
23
Only 'yes' will be accepted to approve.
Enter a value:
#A read latest Ubuntu AMI
#B set from output of data source
Apply the changes by entering “yes” into the command line. After waiting a few minutes, your
output will be:
aws_instance.helloworld: Creating...
aws_instance.helloworld: Still creating... [10s elapsed]
aws_instance.helloworld: Still creating... [20s elapsed]
aws_instance.helloworld: Still creating... [30s elapsed]
aws_instance.helloworld: Creation complete after 34s [id=i-07cf7ed8831c72324]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
As before, you can verify the changes for yourself by either navigating through the AWS console,
or invoking terraform show.
1.3.3 Destroying Infrastructure
Destroy the infrastructure that we created in the previous step by running terraform destroy. You’ll
receive another manual confirmation:
$ terraform destroy
…
- root_block_device {
- delete_on_termination = true -> null
- device_name = "/dev/sda1" -> null
- encrypted = false -> null
- iops = 100 -> null
- volume_id = "vol-05366f9f647f56541" -> null
- volume_size = 8 -> null
- volume_type = "gp2" -> null
}
}
Plan: 0 to add, 0 to change, 1 to destroy. #B
Do you really want to destroy all resources?
Terraform will destroy all your managed infrastructure, as shown above.
There is no undo. Only 'yes' will be accepted to confirm.
Enter a value:
After manually confirming and waiting a few more minutes, the EC2 instance is now gone.
aws_instance.example: Destroying... [id=i-07cf7ed8831c72324]
aws_instance.example: Still destroying... [id=i-07cf7ed8831c72324, 10s elapsed]
aws_instance.example: Still destroying... [id=i-07cf7ed8831c72324, 20s elapsed]
aws_instance.example: Still destroying... [id=i-07cf7ed8831c72324, 30s elapsed]
aws_instance.example: Destruction complete after 30s
Destroy complete! Resources: 1 destroyed.
24
1.4 Fireside Chat
In this introductory chapter, not only did we discuss what Terraform is, and how it compares to other
IaC tools, we also performed two real-world deployments. The first is the de-facto “Hello World!” of
Terraform, while the second is my personal favorite, because it throws in an additional data source
to demonstrate the dynamic capabilities of Terraform.
In the next few chapters, we will go through the fundamentals of how Terraform works, as well
as the major constructs and syntax elements of the Terraform HCL language. This builds up to chapter
4 when we deploy a complete load balanced web server and application onto AWS. Stick with it, and
I promise it will be worth it!
1.5 Summary
• Terraform is a declarative IaC provisioning tool. It can deploy resources onto any public or
private cloud.
• Terrraform is: 1) a provisioning tool 2) easy to use 3) free and open source 4) declarative 5)
cloud agnostic 6) expressive and extensible
• The major configuration elements of Terraform are resources, data sources and providers
• Code blocks can be chained together to perform dynamic deployments
• To deploy a Terraform project you first write configuration code, then configure providers and
other input variables, terraform init, and finally terraform apply. Clean-up is done with
terraform destroy.
©Manning Publications Co. To comment go to liveBook
25
Lifecycle of a Terraform Resource
This chapter covers:
• Generating and applying execution plans
• Analyzing when function hooks are triggered by Terraform
• Utilizing the Local provider to create and manage files
• Simulating, detecting, and correcting for configuration drift
• Understanding the basics of Terraform state management
When you do away with all its bells and whistles, Terraform is a surprisingly simple technology.
Fundamentally, Terraform is a glorified state management tool that performs CRUD operations
(create, read, update, delete) on managed resources. Oftentimes managed resources will be cloud-
based resources, but they don’t have to be. Anything that implements CRUD can be represented as
a Terraform resource, so long as there is sufficient desire and motivation to do so.
In this chapter we will deep-dive into the internals of Terraform by walking through the lifecycle
of a single resource. We could use any resource for this task, but to keep things simple, we’ll use a
resource that doesn’t call any remote network APIs. These special sorts of resources are called local-
only resources and only exist within the confines of Terraform, or the machine running Terraform.
Local-only resources typically serve a marginal purpose, such as to glue together “real” infrastructure
objects, but they also make a great teaching aid. Examples of local-only resources include private
keys, self-signed TLS certificates, and random ids.
We will be using the local_file resource from the Local provider for Terraform to create, read,
update, and delete a text file containing the first few passages of Sun Tzu’s, “The Art of War”.
26
2
2.1 Process Overview
local_file is a resource from the Local provider, and it allows us to create and manage text files
with Terraform. We’ll use it to create an art_of_war.txt file containing the first two stanzas of Sun
Tzu’s, “The Art of War”. Our high-level architecture diagram is illustrated in figure 2.1.
Figure 2.1 Inputs and outputs of the Sun Tzu scenario
NOTE Although a text file isn’t normally considered “infrastructure”, you can still deploy it in the same way you would
an EC2 instance. Does that mean that it’s “infrastructure”? Does the distinction even matter anymore? I’ll leave it for
you to decide.
First, we’ll create the resource. Next, we’ll simulate configuration drift and perform an update.
Finally, we’ll clean up with a terraform destroy. Out procedure is shown in figure 2.2.
Figure 2.2 First, we create the resource [1], then we read and update it [2] and [3]. Finally, we delete the resource [4].
art_of_war.txt
(modified)
Created
start end
Read
3) Update
2) Read 4) Delete
1) Create
Deleted
art_of_war.txt
art_of_war.txt
(modified)
27
2.1.1 Lifecycle Function Hooks
All Terraform resources implement the resource schema interface. The resource schema mandates,
among other things, that resources define CRUD functions hooks, one each for Create(), Read(),
Update(), and Delete(). Terraform invokes these hooks when certain conditions are met. Generally
speaking, Create() is called during resource creation, Read() during plan generation, Update()
during resource updates, and Delete() during deletes. There’s a bit more to it than that, but you
get the idea.
Because it’s a resource, local_file also implements the resource schema interface. That means
that it defines function hooks for Create(), Read(), Update(), and Delete(). This is in contrast to
the local_file data source which only implements Read() (see figure 2.3). In this scenario I will
point out when and why each of these function hooks gets called.
Figure 2.3 The two resources in the Local provider are a managed resource and an unmanaged data source. The
managed resource implements full CRUD, while the data source only implements Read()
2.2 Declaring a Local File Resource
Let’s get started by creating a new workspace for Terraform. Do this by creating a new empty
directory somewhere on your computer. Make sure the folder doesn’t contain any existing
configuration code, because Terraform concatenates all “.tf” files together. In this workspace, make
a new file called main.tf and add the following code from Listing 2.1.
Listing 2.1 Contents of main.tf
terraform { #A
required_version = "~> 0.13"
required_providers {
local = "~> 1.4"
}
}
28
resource "local_file" "literature" {
filename = "art_of_war.txt"
content = <<-EOT #B
Sun Tzu said: The art of war is of vital importance to the State.
It is a matter of life and death, a road either to safety or to
ruin. Hence it is a subject of inquiry which can on no account be
neglected.
EOT
}
#A Terraform settings blocks
#B Heredoc syntax for multi-line strings
TIP The <<- sequence indicates an indented heredoc string. Anything between the opening identifier and the closing
identifier (EOT) is interpreted literally. Leading whitespace, however, is ignored (unlike traditional heredoc syntax).
There are two configuration blocks in Listing 2.1. The first block: terraform {…} is a special
configuration block responsible for configuring Terraform itself. Its primarily use is for version locking
your code, but it can also configure where you state file is stored, and where providers are
downloaded (we will discuss this more in chapter 6). As a reminder, the Local provider has not yet
been installed yet, to do that we first need to perform terraform init.
The second configuration block is a resource block that declares a local_file resource. It
provisions a text file with a given filename and content value. In this scenario, the content will contain
the first couple stanzas of Sun Tzu’s masterpiece, “The Art of War”, and the filename will be
art_of_war.txt. We use heredoc syntax (<<-) to input a multi-line string literal.
2.3 Initializing the Workspace
At this point, Terraform isn’t aware of your workspace, let alone that it’s supposed to create or
manage anything, because it hasn’t yet been initialized. Let’s remedy that with a good ol’ terraform
init. terraform init always must be run at least once, but you’ll have to run it again each time
you add new provider or module dependency. Don’t fret about when to run terraform init, because
Terraform will always remind you. Moreover, terraform init is an idempotent command, which
means you can call it as many times as you want in a row, and it will have no side effects.
Run terraform init now:
$ terraform init
Initializing the backend...
Initializing provider plugins...
- Finding latest version of hashicorp/local...
- Installing hashicorp/local v1.4.0...
- Installed hashicorp/local v1.4.0 (signed by HashiCorp)
The following providers do not have any version constraints in configuration,
so the latest version was installed.
To prevent automatic upgrades to new major versions that may contain breaking
29
changes, we recommend adding version constraints in a required_providers block
in your configuration, with the constraint strings suggested below.
* hashicorp/local: version = "~> 1.4.0"
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
After initialization, Terraform creates a hidden .terraform directory for installing plugins and
modules. The directory structure for the current Terraform workspace is the following:
.
├── main.tf
└── .terraform
└── plugins
├── registry.terraform.io
│ └── hashicorp
│ └── local
│ └── 1.4.0
│ └── darwin_amd64
│ └── terraform-provider-local_v1.4.0_x4
└── selections.json
Because we declared a local_file resource in main.tf, Terraform is smart enough to realize
that there is an implicit dependency on the Local provider, so it looks it up and downloads it from the
provider registry. You don’t have to declare an empty provider block ( i.e. provider "local" {})
unless you want to
TIP version lock any providers you use, whether they are implicitly or explicitly defined to ensure that any deployment
you make is always repeatable.
2.4 Generating an Execution Plan
Before we create the local_file resource with a terraform apply, we can preview what Terraform
intends to do by running terraform plan. You should always run terraform plan before deploying.
I will often skip this step throughout to book for the sake of brevity, but you should still do it for
yourself even if I do not call it out. terraform plan not only informs you what Terraform intends to
do, it acts as a linter, letting you know of any syntax or dependency errors you might have. It’s a
read only action that does not alter the state of deployed infrastructure, and like terraform init,
it’s idempotent.
Generate an execution plan now by running terraform plan.
$ terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.
30
------------------------------------------------------------------------
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# local_file.literature will be created
+ resource "local_file" "literature" {
+ content = <<~EOT
Sun Tzu said: The art of war is of vital importance to the State.
It is a matter of life and death, a road either to safety or to
ruin. Hence it is a subject of inquiry which can on no account be
neglected.
EOT
+ directory_permission = "0777"
+ file_permission = "0777"
+ filename = "art_of_war.txt"
+ id = (known after apply) #A
}
Plan: 1 to add, 0 to change, 0 to destroy.
------------------------------------------------------------------------
Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.
#A computed meta-attribute
When might my plan fail?
Terraform plans can fail for many reasons, such as if your configuration code is invalid, or if there’s a versioning issue, or
even network related problems. Sometimes, albeit rarely, the plan will fail as a result of a bug in the provider’s source code.
You need to carefully read whatever error message you receive to know for sure. For more verbose logs, you can turn on trace
level logging by setting the environment variable TF_LOG to a non-zero value, e.g. export TF_LOG=1
As you can see from the output, Terraform is letting us know that it wants to create a local_file
resource. Besides the attributes that we supply, it also wants to set a computed attribute called id.
id is a meta-attribute that Terraform sets on all resources. It’s used to uniquely identify real-world
resources, as well as internal calculations.
Although this particular terraform plan should have exited quickly, some plans can take a while
to complete. It all has to do with how many resources you are deploying, as well as how how many
resources you already have in your state file.
31
TIP if terraform plan is running slow, turn off trace level logging and consider increasing parallelism (-
parallelism=n)
Although the output of the plan we performed is fairly straightforward, there’s a lot going on that
you should be aware of. The three main stages of a terraform plan are:
1. Reading Configuration and State – Terraform first reads your configuration and state files
(if they exist).
2. Determine Actions to Take – Terraform performs a calculation to determine what needs to
be done to achieve your desired state. This can be on of: Create(), Read(), Update(),
Delete() and No-op.
3. Outputting Plan – Actions need to occur in the right order, to avoid dependency problems.
If we had more than one resource, this would be a bigger deal.
Figure 2.4 is a detailed flow diagram showing what happens during terraform plan.
32
Figure 2.4 Steps that Terraform performs when generating an execution plan for a new deployment
We haven’t yet talked about the dependency graph, but it’s a big part of Terraform and every
terraform plan generates one for respecting implicit and explicit dependencies between resource
and provider nodes. Terraform has a special command for visualizing the dependency graph:
terraform graph. This command outputs a dotfile which can be converted to a digraph using a
variety of tools. You can see the resultant graph I produced for this workspace from the preceding
DOT graph1 in figure 2.5.
1DOT is a graph description language. DOT graphs are files with the filename extension dot. Various programs can process and render DOT files in graphical form
end
start
Read Configuration
Create()
Resource in State? Read()
Delete()
Read State
For Each Resource
No
Yes
main.tf
terraform.tf
state
Has Changes?
Output Plan
No-op
No
Is Destroy Plan?
Update()
No
Yes Yes
33
Figure 2.5 Dependency graph for this workspace
The dependency graph for this workspace has a few nodes, including one for the Local provider,
one for the local_file resource, and a few other meta nodes that correspond to housekeeping
actions. During an apply, Terraform will walk the dependency graph to ensure that everything is done
in the right order. We’ll examine a more complex digraph in the next chapter.
2.4.1 Inspecting the Plan
It’s possible to read the output of a terraform plan in JSON format, which could be useful when
integrating with custom tools, or enforcing Policy as Code (we will discuss Policy as Code in chapter
12).
First, save the output of the plan by setting the optional -out flag:
$ terraform plan -out plan.out
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.
------------------------------------------------------------------------
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# local_file.literature will be created
+ resource "local_file" "literature" {
+ content = <<~EOT
Sun Tzu said: The art of war is of vital importance to the State.
It is a matter of life and death, a road either to safety or to
ruin. Hence it is a subject of inquiry which can on no account be
neglected.
EOT
+ directory_permission = "0777"
34
+ file_permission = "0777"
+ filename = "art_of_war.txt"
+ id = (known after apply)
}
Plan: 1 to add, 0 to change, 0 to destroy.
------------------------------------------------------------------------
This plan was saved to: plan.out
To perform exactly these actions, run the following command to apply:
terraform apply "plan.out"
plan.out is now saved as a binary file, so the next step is to convert it to JSON format. This can
be done (rather unintuitively) by ingesting it with terraform show and piping it to an output file
$ terraform show -json plan.out > plan.json
Finally, we have the plan in human readable format:
$ cat plan.json
{"format_version":"0.1","terraform_version":"0.13.0","planned_values":{"root_module":{"resources"
:[{"address":"local_file.literature","mode":"managed","type":"local_file","name":"literatur
e","provider_name":"registry.terraform.io/hashicorp/local","schema_version":0,"values":{"co
ntent":"Sun Tzu said: The art of war is of vital importance to the State.nnIt is a matter
of life and death, a road either to safety or to nruin. Hence it is a subject of inquiry
which can on no account
benneglected.n","content_base64":null,"directory_permission":"0777","file_permission":"07
77","filename":"art_of_war.txt","sensitive_content":null}}]}},"resource_changes":[{"address
":"local_file.literature","mode":"managed","type":"local_file","name":"literature","provide
r_name":"registry.terraform.io/hashicorp/local","change":{"actions":["create"],"before":nul
l,"after":{"content":"Sun Tzu said: The art of war is of vital importance to the
State.nnIt is a matter of life and death, a road either to safety or to nruin. Hence it
is a subject of inquiry which can on no account
benneglected.n","content_base64":null,"directory_permission":"0777","file_permission":"07
77","filename":"art_of_war.txt","sensitive_content":null},"after_unknown":{"id":true}}}],"c
onfiguration":{"root_module":{"resources":[{"address":"local_file.literature","mode":"manag
ed","type":"local_file","name":"literature","provider_config_key":"local","expressions":{"c
ontent":{"constant_value":"Sun Tzu said: The art of war is of vital importance to the
State.nnIt is a matter of life and death, a road either to safety or to nruin. Hence it
is a subject of inquiry which can on no account
benneglected.n"},"filename":{"constant_value":"art_of_war.txt"}},"schema_version":0}]}}}
2.5 Creating the Local File Resource
Now let’s run terraform apply to compare the output against the generated execution plan. The
command and output are shown below.
$ terraform apply
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# local_file.literature will be created
35
+ resource "local_file" "literature" {
+ content = <<~EOT
Sun Tzu said: The art of war is of vital importance to the State.
It is a matter of life and death, a road either to safety or to
ruin. Hence it is a subject of inquiry which can on no account be
neglected.
EOT
+ directory_permission = "0777"
+ file_permission = "0777"
+ filename = "art_of_war.txt"
+ id = (known after apply)
}
Plan: 1 to add, 0 to change, 0 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value:
Do they look similar? It’s no coincidence. The execution plan generated by terraform apply is
the exactly the same as the plan generated by terraform plan. In fact, you could even apply the
results of terraform plan explicitly, i.e:
$ terraform plan -out plan.out && terraform apply "plan.out"
TIP separating the plan and apply like this could be useful when running Terraform in automation
Regardless of how you generate an execution plan, it’s always a good idea to review the contents
of the plan before applying. During an apply, Terraform creates and destroys real infrastructure,
which of course has real-world consequences. If you are not careful, then a simple mistake or typo
could wipe out all your entire infrastructure before you even have a chance to react.
For this workspace, there’s nothing to worry about because we aren’t creating “real” infrastructure
anyways.
Returning to the command line, enter “yes” at the prompt to approve the manual confirmation
step. Your output will be as follows.
$ terraform apply
…
Enter a value: yes
local_file.literature: Creating...
local_file.literature: Creation complete after 0s [id=907b35148fa2bce6c92cba32410c25b06d24e9af]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Two files were created as a result of this command, art_of_war.txt, and terraform.tfstate
file. You current directory (excluding hidden files) will now be:
.
├── art_of_war.txt
├── main.tf
36
└── terraform.tfstate
The terraform.tfstate file you see here is the state file that Terraform uses to keep track of
the resources it manages. It’s used to perform diffs during the plan and detect configuration drift.
Snippet 2.1 shows what the current state file looks like.
Snippet 2.1 Contents of terraform.tfstate
{
"version": 4, #A
"terraform_version": "0.13.0",
"serial": 1,
"lineage": "0fe3ddc2-1d7e-1116-d554-c030de42d9ea",
"outputs": {},
"resources": [
{
"mode": "managed",
"type": "local_file",
"name": "literature",
"provider": "provider["registry.terraform.io/hashicorp/local"]",
"instances": [
{
"schema_version": 0,
"attributes": { #A
"content": "Sun Tzu said: The art of war is of vital importance to the State.nnIt
is a matter of life and death, a road either to safety or to nruin. Hence it is a subject
of inquiry which can on no account benneglected.n",
"content_base64": null,
"directory_permission": "0777",
"file_permission": "0777",
"filename": "art_of_war.txt",
"id": "907b35148fa2bce6c92cba32410c25b06d24e9af", #B
"sensitive_content": null
},
"private": "bnVsbA=="
}
]
}
]
}
#A Stateful information about the art_of_war.txt resource
#B The unique id of resource
WARNING It’s important to not edit, delete or otherwise tamper with the terraform.tfstate file, or else
Terraform could potentially lose track of the resources it manages. It is possible to restore a corrupted or missing state
file, but it’s difficult and time consuming to do so.
We can verify that art_of_war.txt matches what we expect by cat-ing the file. The command
and output are shown below.
$ cat art_of_war.txt
Sun Tzu said: The art of war is of vital importance to the State.
It is a matter of life and death, a road either to safety or to
37
ruin. Hence it is a subject of inquiry which can on no account be
neglected.
Okay, a file was created, but how did Terraform actually do that? During the apply, Terraform
called Create()on local_file (see figure 2.6).
Figure 2.6 Calling Create() on local_file during terraform apply
To give you an idea of what Create() does, here is the actual source code from the provider:
NOTE relax and don’t worry about understanding the code just yet. We will come back to in in chapter 10.
Listing 2.2 Local File Create
func resourceLocalFileCreate(d *schema.ResourceData, _ interface{}) error {
content, err := resourceLocalFileContent(d)
if err != nil {
return err
}
destination := d.Get("filename").(string)
destinationDir := path.Dir(destination)
if _, err := os.Stat(destinationDir); err != nil {
dirPerm := d.Get("directory_permission").(string)
dirMode, _ := strconv.ParseInt(dirPerm, 8, 64)
if err := os.MkdirAll(destinationDir, os.FileMode(dirMode)); err != nil {
return err
}
}
filePerm := d.Get("file_permission").(string)
Terraform
Invokes
resource "local_file"
Create() Read() Update() Delete()
Local Provider
38
fileMode, _ := strconv.ParseInt(filePerm, 8, 64)
if err := ioutil.WriteFile(destination, []byte(content), os.FileMode(fileMode)); err != nil
{
return err
}
checksum := sha1.Sum([]byte(content))
d.SetId(hex.EncodeToString(checksum[:]))
return nil
}
2.6 Performing No-Op
Terraform can read existing resources to ensure they are in a desired configuration state. One way
to do this is by running terraform plan. When terraform plan is run, Terraform will call Read()
on each resource in the state file. Since our state file has only one resource, Terraform calls Read()
on just local_file. Figure 2.7 shows what this looks like.
Figure 2.7 terraform plan calls Read() on the local_file resource
Let’s run the terraform plan now:
$ terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.
local_file.literature: Refreshing state... [id=907b35148fa2bce6c92cba32410c25b06d24e9af]
------------------------------------------------------------------------
Terraform
Invokes
resource "local_file"
Create() Read() Update() Delete()
Local Provider
39
No changes. Infrastructure is up-to-date.
This means that Terraform did not detect any differences between your
configuration and real physical resources that exist. As a result, no
actions need to be performed.
There are no changes, as we would expect. The flow diagram for how a No-Op was decided is
shown in figure 2.8. You can see that Read() is called as part of the state refresh process.
Figure 2.8 Steps that Terraform performs when generating an execution plan for an existing deployment already in
the desired state
Finally, here is the code from the provider that is performing Read(). Again, don’t worry too much
about completely understanding it.
Listing 2.3 Local File Read
end
start
Read Configuration
Create()
Resource in State? Read()
Delete()
Read State
For Each Resource
No
Yes
main.tf
terraform.tf
state
Has Changes?
Output Plan
No-op
No
Is Destroy Plan?
Update()
No
Yes Yes
40
func resourceLocalFileRead(d *schema.ResourceData, _ interface{}) error {
// If the output file doesn't exist, mark the resource for creation.
outputPath := d.Get("filename").(string)
if _, err := os.Stat(outputPath); os.IsNotExist(err) {
d.SetId("")
return nil
}
// Verify that the content of the destination file matches the content we
// expect. Otherwise, the file might have been modified externally and we
// must reconcile.
outputContent, err := ioutil.ReadFile(outputPath)
if err != nil {
return err
}
outputChecksum := sha1.Sum([]byte(outputContent))
if hex.EncodeToString(outputChecksum[:]) != d.Id() {
d.SetId("")
return nil
}
return nil
}
2.7 Updating the Local File Resource
You know what’s better than having a file containing the first two stanzas of the “The Art of War”?
That’s right, having a file containing the first four stanzas of “The Art of War”! Updates are integral
to Terraform and it’s important to understand how they work. Update your main.tf code to look like
Listing 2.4.
Listing 2.4 Contents of main.tf
terraform {
required_version = "~> 0.13"
required_providers {
local = "~> 1.4"
}
}
resource "local_file" "literature" {
filename = "art_of_war.txt"
content = <<-EOT #A
Sun Tzu said: The art of war is of vital importance to the State.
It is a matter of life and death, a road either to safety or to
ruin. Hence it is a subject of inquiry which can on no account be
neglected.
The art of war, then, is governed by five constant factors, to be
taken into account in one's deliberations, when seeking to
determine the conditions obtaining in the field.
These are: (1) The Moral Law; (2) Heaven; (3) Earth; (4) The
Commander; (5) Method and discipline.
EOT
41
}
#A Adding two additional stanzas
There isn’t a special command for performing an update; all that needs happen is run a terraform
apply. Before we do that though, let’s run terraform plan to see what the generated execution
plan looks like. The command and output are shown below.
$ terraform plan
Refreshing Terraform state in-memory prior to plan... #A
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.
local_file.literature: Refreshing state... [id=907b35148fa2bce6c92cba32410c25b06d24e9af]
------------------------------------------------------------------------
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement
Terraform will perform the following actions:
# local_file.literature must be replaced
-/+ resource "local_file" "literature" {
~ content = <<~EOT # forces replacement #B
Sun Tzu said: The art of war is of vital importance to the State.
It is a matter of life and death, a road either to safety or to
ruin. Hence it is a subject of inquiry which can on no account be
neglected.
+
+ The art of war, then, is governed by five constant factors, to be
+ taken into account in one's deliberations, when seeking to
+ determine the conditions obtaining in the field.
+
+ These are: (1) The Moral Law; (2) Heaven; (3) Earth; (4) The
+ Commander; (5) Method and discipline.
EOT
directory_permission = "0777"
file_permission = "0777"
filename = "art_of_war.txt"
~ id = "907b35148fa2bce6c92cba32410c25b06d24e9af" -> (known after apply)
}
Plan: 1 to add, 0 to change, 1 to destroy.
------------------------------------------------------------------------
Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.
#A Read() happens first
#B Force new recreates resource
As you can see, Terraform has noticed that we’ve altered the content attribute, and is therefore
proposing to destroy the old resource and create a new resource in its stead. The reason why this is
done rather than update the attribute in place, is because content is marked as a force new attribute,
42
which means if you change it, the whole resource is tainted. In order to achieve the new desired
state, Terraform must recreate the resource from scratch. This is a classic example of immutable
infrastructure, although not all attributes of managed Terraform resource will behave like this. In
fact, most resources have regular in-place (i.e. mutable) updates. The difference between mutable
and immutable updates is shown in figure 2.9.
Figure 2.9 Difference between an immutable and mutable update
“Force New” updates sound terrifying!
Although destroying and recreating tainted infrastructure may sound disturbing at first, terraform plan will always
let you know what Terraform is going to do ahead of time, so it will never come as a surprise. Furthermore, Terraform is great
at creating repeatable environments, so recreating a single piece of infrastructure is not a problem. The only potential
downside is if there is downtime to your service. If you absolutely cannot tolerate anydowntime, then stick around for chapter
9 when we cover how to perform zero downtime deployments with Terraform.
The flow chart for the execution plan is illustrated by figure 2.10.
Immutable Update: Force New
Mutable Update: Normal Behavior
Terraform
Invokes
resource "local_file"
Create() Read() Update() Delete()
1) First call Delete()
2) Then call Create()
Invokes
resource "local_file"
Create() Read() Update() Delete()
Terraform
43
Figure 2.10 Steps that Terraform performs when generating an execution plan for an update
Go ahead and apply the proposed changes from the execution plan by running the command:
terraform apply -auto-approve. The optional -auto-approve flag tells Terraform to skip the manual
approval step and immediately apply changes.
WARNING -auto-approve can be dangerous if you have not already reviewed the results of the plan
$ terraform apply -auto-approve
local_file.literature: Refreshing state... [id=907b35148fa2bce6c92cba32410c25b06d24e9af]
local_file.literature: Destroying... [id=907b35148fa2bce6c92cba32410c25b06d24e9af]
local_file.literature: Destruction complete after 0s
local_file.literature: Creating...
local_file.literature: Creation complete after 0s [id=657f681ea1991bc54967362324b5cc9e07c06ba5]
Apply complete! Resources: 1 added, 0 changed, 1 destroyed.
end
start
Read Configuration
Create()
Resource in State? Read()
Delete()
Read State
For Each Resource
No
Yes
main.tf
terraform.tf
state
Has Changes?
Output Plan
No-op
No
Is Destroy Plan?
Update()
No
Yes Yes
44
You can verify that the file is now up to date by cat-ing the file once more. The command and
output are as follows:
$ cat art_of_war.txt
Sun Tzu said: The art of war is of vital importance to the State.
It is a matter of life and death, a road either to safety or to
ruin. Hence it is a subject of inquiry which can on no account be
neglected.
The art of war, then, is governed by five constant factors, to be
taken into account in one's deliberations, when seeking to
determine the conditions obtaining in the field.
These are: (1) The Moral Law; (2) Heaven; (3) Earth; (4) The
Commander; (5) Method and discipline.
2.7.1 Detecting Configuration Drift
So far, we’ve been able to create and update a text file resource. But what happens if there are ad
hoc changes to the file through means outside of Terraform? Configuration drift is a common
occurrence in situations where there are multiple privileged users on the same file system. If you
have cloud-based resources, this would be equivalent to someone making point and click changes to
deployed infrastructure in the console. How does Terraform deal with configuration drift? It does so
by calculating the difference between current state and desired state and performing an update.
We can simulate configuration drift by directly modifying art_of_war.txt. In this file, replace all
occurrences of “Sun Tzu” with “Napolean”.
The contents of our art_of_war.txt file will now be:
Napoleon said: The art of war is of vital importance to the
State.
It is a matter of life and death, a road either to safety or to
ruin. Hence it is a subject of inquiry which can on no account be
neglected.
The art of war, then, is governed by five constant factors, to be
taken into account in one's deliberations, when seeking to
determine the conditions obtaining in the field.
These are: (1) The Moral Law; (2) Heaven; (3) Earth; (4) The
Commander; (5) Method and discipline.
This misquote is patently untrue, so we’d like Terraform to detect that configuration drift has
occurred and fix it. Run a terraform plan to see what Terraform has to say for itself.
$ terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.
local_file.literature: Refreshing state... [id=657f681ea1991bc54967362324b5cc9e07c06ba5]
------------------------------------------------------------------------
45
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# local_file.literature will be created
+ resource "local_file" "literature" {
+ content = <<~EOT
Sun Tzu said: The art of war is of vital importance to the State.
It is a matter of life and death, a road either to safety or to
ruin. Hence it is a subject of inquiry which can on no account be
neglected.
The art of war, then, is governed by five constant factors, to be
taken into account in one's deliberations, when seeking to
determine the conditions obtaining in the field.
These are: (1) The Moral Law; (2) Heaven; (3) Earth; (4) The
Commander; (5) Method and discipline.
EOT
+ directory_permission = "0777"
+ file_permission = "0777"
+ filename = "art_of_war.txt"
+ id = (known after apply)
}
Plan: 1 to add, 0 to change, 0 to destroy.
------------------------------------------------------------------------
Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.
Wait, what just happened here? It would appear Terraform has forgotten the resource it manages
even exists and is therefore proposing to create a new resource. In fact, Terraform has not forgotten
that the resource it manages still exists. The resource is still present in the state file, and you can
verify by running terraform show:
$ terraform show
# local_file.literature:
resource "local_file" "literature" {
content = <<~EOT
Sun Tzu said: The art of war is of vital importance to the State.
It is a matter of life and death, a road either to safety or to
ruin. Hence it is a subject of inquiry which can on no account be
neglected.
The art of war, then, is governed by five constant factors, to be
taken into account in one's deliberations, when seeking to
determine the conditions obtaining in the field.
These are: (1) The Moral Law; (2) Heaven; (3) Earth; (4) The
Commander; (5) Method and discipline.
EOT
46
Other documents randomly have
different content
concerning "Miss Wayne." His anxiety as to her health appeared to
do great credit to his goodness of heart. Between Bill and Pete there
was always frank discussion, in private, although on the subject of
the social secretary it flowed with perhaps a trifle less freedom.
So greatly had the party furthered the innocent dreams of Aunt
Caroline that she lost no time in urging further assaults and triumphs
in the new world that had been opened to her nephew.
"My dear," she said to Mary, "I think it would be well to give a small
dinner—very soon."
Mary agreed that it would be very well, indeed.
"I confess that I have certain ambitions," said Aunt Caroline. "I
would like to have William extend his circle somewhat, and among
people whom it would be a very fine thing for him to know."
Mary carelessly approved that, too.
"It would be wonderful, my dear, if we could have Mrs. Rokeby-Jones
as a guest."
Mary glanced sharply at Aunt Caroline. She was suddenly trembling
with a premonition.
"But do we know Mrs. Rokeby-Jones?" she asked.
Aunt Caroline smiled confidently.
"You do, my dear."
To which, of course, Mary was forced to nod an assent.
"I believe it would be all right for you to speak to her about it,"
added Aunt Caroline. "She thinks so highly of you that I am sure she
would not consider it strange in the least. And besides, there is
always the Marshall name."
The Marshall name was Aunt Caroline's shield and buckler at all
times, and since Bill's party she had come to regard it as a password
of potent magic.
Mary felt suddenly weak, but she fought to avoid disclosure of the
fact. Mrs. Rokeby-Jones! What could she say? Already, in the case of
Bill's party, threads of acquaintanceship that were so tenuous as
scarcely to be threads at all had been called upon to bear the strain
of invitations, and, much to her astonishment, they had borne the
strain. Thereby emboldened, Aunt Caroline was now seeking to
bridge new gulfs. But why did she have to pick Mrs. Rokeby-Jones?
Was it because—— Mary tried to put from her mind the unworthy
suspicion that Aunt Caroline was still delving as to the facts
concerning what they said about the elder daughter. But whatever
the motive, whether it be hidden or wholly on the surface, booted
little to Mary. It was an impossible proposal.
"She will recall you, of course," Aunt Caroline was saying. "And I am
sure that she knows the Marshalls. In fact, I have an impression that
at one time William's mother——"
"But are you sure she hasn't gone to Newport?" asked Mary,
desperately.
"I saw her name in the paper only this morning, my dear. She was
entertaining last night at the theater."
Mary began wadding a handkerchief.
"And perhaps she could suggest somebody else," added Aunt
Caroline. "At any rate, suppose you get in touch with her and let me
know what she says."
Mary went up-stairs to nurse her misery. It was out of the question
to refuse, yet she dreaded to obey. She could not call upon Mrs.
Rokeby-Jones; even a blind person could tell the difference between
Nell Norcross and Mary Wayne. She could not get Nell to go, for Nell
was still overcome by her adventures at the party. She could not
send a letter, because the writing would betray her. She could
telephone, perhaps; but would Mrs. Rokeby-Jones detect a strange
voice? And even if she succeeded in imposture over the wire, how
was she to approach the matter of an invitation to the home of a
stranger?
After much anguished thought, she decided upon the telephone.
"But even if she consents," murmured Mary, "I'll never dare meet
her face to face."
A connection was made in disconcertingly short time and Mary, after
talking with a person who was evidently the butler, held the wire,
the receiver trembling in her fingers. And then a clear, cool voice——
"Well? Who is it?"
"This—this is Miss Norcross talking," and then Mary held her breath.
"Miss who?"
"Norcross. Miss Norcross."
"Do I know you? Have I met you?" said the voice on the wire.
"This is Nell Norcross." Mary was raising her voice.
"Yes; I hear the name. But I don't place you."
"Miss Norcross—formerly your secretary."
There was an instant's pause. Then the cool voice again:
"Perhaps you have the wrong number. This is Mrs. Rokeby-Jones
talking."
"Then I have the right number," said Mary, wrinkling her forehead in
perplexity. "I used to be your secretary—Miss Norcross."
"But I have never had a secretary by that name," said Mrs. Rokeby-
Jones.
Mary gasped.
"But the reference you gave me! Don't you remember?"
"I have an excellent memory," the voice said. "I have never
employed any person named Miss Norcross, I never knew anybody
by that name and I certainly never supplied a reference to any such
person. You are laboring under some mistake."
"But—but——"
"Good-by."
And Mrs. Rokeby-Jones hung up.
Mary slowly replaced the receiver and sat staring at the telephone. A
blow between the eyes could not have stunned her more effectually.
Mrs. Rokeby-Jones had repudiated her reference!
Presently she rallied. She ran to her own room and began dressing
for the street. She felt that she must escape from the house in order
to think. At all costs she must avoid Aunt Caroline until she had been
able to untangle this dismaying snarl. A few minutes later she made
certain of that by slipping down the rear staircase and leaving the
house by a side entrance.
Fifteen minutes later she was at Nell's boarding-house, impatiently
ringing the bell.
Nell was propped up in a rocker, looking very wan as Mary entered,
but brightening as she recognized her visitor. Mary drew a chair and
sat opposite.
"A most embarrassing thing has happened," she said. "I have just
had Mrs. Rokeby-Jones on the telephone."
Nell stifled an exclamation.
"And she doesn't remember me—or you, rather—or anybody named
Norcross!"
"Oh, my dear!"
"It's the truth, Nell. Oh, I never felt so queer in my life."
Nell moistened her lips and stared with incredulous eyes.
"What—what made you call her up?" she faltered.
"Because I couldn't help it. I was forced to."
And Mary explained the further ambitions of Aunt Caroline and what
they had led to.
"Oh, it was shocking, Nell! What did she mean? How dared she do
it?"
"I—I—— Oh, Mary!"
"But how could she?" persisted Mary. "That's what I don't
understand. Even if my voice sounded strange I don't see how she
could. Why did she deny that she ever wrote a reference?"
Nell Norcross pressed a hand to her lips to keep them from
quivering. In her eyes there was something that suggested she had
seen a ghost. Slowly she began to rock to and fro in her chair,
making a gurgling in her throat. Then she whimpered.
"B-because she never wrote it!" she moaned.
"Why—Nell. Oh, Heavens!"
Mary suddenly seemed to have become as frightened as Nell. She
glanced quickly over her shoulder, as though expecting to face an
eavesdropper. Then she sprang up, went to the door and locked it.
"Nell Norcross, tell me what you mean!"
"She—she didn't write it. Oh, Mary! Oh—please!"
For Mary had taken her by the shoulders and was pushing her rigidly
against the back of the chair.
"Who wrote it?" demanded Mary.
"I did."
It required several seconds for Mary to absorb this astounding
confession. Then:
"You forged it?"
"I—I wrote it. It isn't forgery, is it? I won't go to jail, will I? Oh, Mary,
don't let them——"
Mary shook her somewhat roughly.
"Tell me more about it," she commanded. "Did you lose the
reference she gave you? Or did she refuse to give you one?"
Nell shook her head miserably.
"It's worse than that," she sobbed. "I—I never set eyes on the
woman in my life."
Mary collapsed into her own chair. She seemed to hear the cool,
clear voice of Mrs. Rokeby-Jones calmly denying. Now it was taking
an accusative tone. She flushed to a deep red. The memory of that
telephone conversation appalled her.
"But the other references?" she managed to whisper.
"All the same."
"All! You wrote them yourself?"
Nell answered with a feeble nod.
"Every one of them?"
"Every one."
"And do you know any of the women who—whose names are
signed?"
"Two—one of them by sight."
"Nell Norcross!"
But Nell had reached a fine stage of tears and there was nothing to
be had out of her for several minutes. Then Mary managed to calm
her.
"Now, tell me about it," she said. "And stop crying, because it won't
do a bit of good."
Nell swallowed a sob and mopped at her eyes.
"I—I was in the same fix that you were," she said shakily. "Only I
guess I was that way longer. I didn't have any job, and I couldn't get
one—without references. You understand?"
Mary nodded. Indeed she did understand.
"I worked in a furrier's; one of the Fifth Avenue places.
Stenographer, and I helped on the books, too. And then—well, I had
to leave. It wasn't my fault; honestly, Mary. I couldn't stay there
because of the way he acted. And of course I wouldn't—I couldn't—
ask him for references."
Nell was quieting down, and Mary nodded again, to encourage her.
"Well you know how it is trying to get a job without any references.
No decent place will take you. I kept it up for weeks. Why, I couldn't
even get a trial. When I couldn't get references, or even refer them
to the last place, they'd look at me as if I were trying to steal a job."
"I know," murmured Mary. "They'd look at me, too."
"So I got desperate. You know what that is, too. I had to have a job
or starve. And I had to have references—so I wrote them!"
"Oh, Nell!"
Nell looked up defiantly.
"Well, what else could I do? And I didn't harm anybody, did I? I
didn't say anything about myself that wasn't true. All I did was to
use some good names. And not one of them would ever have known
if you hadn't called that woman up on the telephone. They were all
customers of the place where I worked. I knew their names and
addresses. I couldn't go and ask them to give me references, could
I? I couldn't even do that with the one I'd spoken to. So I got some
stationery and wrote myself references—that's all."
Mary pondered the confession.
"If it had only been one reference," she began, "but you had five or
six."
"I only intended to write one," declared Nell. "But what was the use
of being a piker, I thought. So—well I plunged."
"Yes; you plunged," agreed Mary. "And now look at the fix I'm in."
"But you've got a wonderful place!"
Mary smiled bitterly.
"Oh, yes; it's wonderful enough. I'm not only holding it under a false
name, but now it turns out that even the references were false.
And"—she looked sharply at Nell as something else occurred to her
—"perhaps it doesn't end even there. Tell me—is your name really
Nell Norcross?"
"Why, Mary Wayne! Of course it is!"
"Well, how could I be sure. I'm false; the references are false. Why
couldn't your name be false, too? That would be the finishing touch;
that would leave me—nowhere. And I'm just about there, as it is."
"But I am Nell Norcross, I tell you. I can prove that."
"Oh, I suppose so," said Mary, wearily. "So am I Nell Norcross,
according to the references. If you've committed a crime, I suppose
I have, too. They call it compounding it, don't they? Oh, we're both
in; I dare say I'm in deeper than you, because I've been taking
money for it."
"You haven't cheated them, have you? You've worked for it."
"Yes, I've worked. But—why, in Heaven's name, Nell, didn't you tell
me all this before I started?"
"I was too sick."
"You weren't too sick to give me the references and send me off to
take the job."
"But I was too sick not to have you take it," said Nell. "One of us
had to go to work. And if I'd told you, you wouldn't have done it."
"That's true enough," assented Mary. "I wouldn't have dared. It took
all the nerve I had, as it was. But now what am I going to do?"
"Why, you'll go right on sticking to your job, of course."
"And keep on being a liar, and a hypocrite, and a falsifier, and maybe
some kind of a forger—— Why, I believe I am a forger! I signed your
name to some kind of a bail bond!"
"Oh, well; you told me the case was settled, Mary. So you don't have
to worry about that."
"I can worry about my conscience if I like," declared Mary,
resentfully.
"Yes; but you can't eat your conscience, or buy clothes with it, or
hire a room—or anything."
Mary stared down at the floor for a while.
"I suppose I've got to keep on taking care of you until you're well,"
she remarked.
Nell winced.
"I—I hate to be a charity patient," she faltered. "I'll make it all up to
you some time. But if you'll only keep on for the present——"
Mary reached forward impulsively and took her hands.
"I don't mean to suggest that," she said. "You're not a charity
patient; you got my job for me. Of course I'll look out for you, Nell.
I'll see it through somehow, as long as it's necessary. There; don't
worry, dear. I'm not angry. I'm just staggered."
Nell leaned forward and kissed her.
"You're a darling!" she said. "And just as soon as I'm strong I'll get a
job for myself."
Mary looked at her thoughtfully.
"Yes," she said slowly, "I suppose you might write yourself some
more references."
"Mary Wayne!"
CHAPTER XV
To Sail the Ocean Blue
Mary Wayne was in weak, human fear. The confession of Nell
Norcross had not merely served to revive half-forgotten
apprehensions, but had overwhelmed her with new ones. She
wanted to quit. She did not dare. For where could she get another
place, and who would take care of Nell? Circumstances were driving
her toward a life of perpetual charlatanism, it seemed, but for the
present she could not even struggle against them.
Mary was neither a prude nor a Puritan, so it may as well be said
that what troubled her most was not the practice of deception. It
was the fear of discovery. She now lived with an explosive mine
under her feet. At any instant Aunt Caroline, for all her innocence
and abiding faith, might inadvertently make the contact. Then—
catastrophe! Even that queer valet might make a discovery; she was
by no means certain that he was without suspicion. Bill Marshall
himself might blunder into a revelation; but Mary feared him least of
all. She did not regard him as too dull to make a discovery, but she
had a feeling that if he made it he would in some manner safely
remove her from the arena of disturbance before the explosion
occurred.
All the way back to the Marshall house she was seized with fits of
trembling. The trembling angered her, but she was unable to control
it. Suppose Aunt Caroline had taken it into her head to seek a
personal talk with Mrs. Rokeby-Jones! Or, even if matters had not
gone that far, what would she say when Aunt Caroline asked for the
result of Mary's interview?
"The city of New York is not large enough for Mrs. Rokeby-Jones and
me," declared Mary. "I feel it in my bones. One of us must go.
Which?"
She had reached a decision when the butler opened the front door
and informed her that Mr. William would like to see her. He was the
very person that Mary wanted to see. She found him in the office.
"Say, what's this I hear about a dinner?" demanded Bill.
"Has your aunt been speaking to you?"
"Uh, huh! I don't want any dinner. Good Lord, they'll ask me to make
a speech!"
Mary smiled for the first time in hours.
"Of course," said Bill, uncomfortably, "I promised to do better and all
that sort of thing, and I don't want to break my word. But a dinner—
oh, gee!"
"I don't favor the dinner idea myself," said Mary.
"But it looks like Aunt Caroline was all set for it. What's the answer?"
Mary laid her gloves on the desk and removed her hat.
"It seems to me," she said, "that the thing to do is to go out of town
for a while."
Bill looked at her with a hopeful expression.
"You see, Mr. Marshall, the town season is really over. Most of the
worth-while people have left the city. It's summer. There will be
nothing of importance in society before the fall; nothing that would
interest you, at any rate. So I would advise doing exactly what the
other people are doing."
Bill rubbed his nose thoughtfully.
"Trouble is, we haven't got a country house," he said. "We don't own
a villa, or a camp or any of that fashionable stuff."
"I understand," said Mary. "But how about a yacht?"
"Don't even own a skiff."
"But we could hire one, couldn't we?"
Mary had unconsciously adopted the "we."
Bill regarded her with sudden interest. He stopped rubbing his nose,
which was always one of his signs of indecision.
"Say, where did you get that idea?" he demanded.
"Why, it's a perfectly obvious one to arrive at, considering the
season of the year."
"Have you spoken to my aunt about it?"
"Not yet. I wanted to consult you first, of course."
Bill liked that. It was another way of saying that she was still his
secretary.
"You've got a whole beanful of ideas, haven't you?" he exclaimed, in
admiration. "Well, I'm for this one, strong!"
Mary breathed a little more deeply. It seemed as if she had already
removed herself a step further from Mrs. Rokeby-Jones and other
perils of the city.
"I'm glad you like it," she said.
"Like it! Why, man alive—I mean little girl—well, anyhow, it's just the
stunt we're going to pull off."
"It's not really a stunt," Mary reminded him. "It's not original at all.
We do it simply because it is the right thing to do. Everybody of any
account has a yacht, and now is the time for yachting."
"Now, don't you go crabbing your own stuff," said Bill. "This thing is
a great invention, Secretary Norcross, and you get all the credit. I
wouldn't have thought of it in a billion years. Now, what's your idea
about this yacht? Do we want a little one or a whale? Where do we
go? When? And who's going along?"
"Well, I don't know much about yachts," confessed Mary. "But it
seems to me that a medium-sized one would do. We're not going
across the ocean, you know."
"We might," declared Bill, hopefully—"we might start that trip around
the world. I'm supposed to be on my way to Australia, you know,
studying crustaceans."
Mary laughed.
"Do we cart a gang along?"
Mary had a vision of a tin ear. She shook her head.
"I see no occasion for a large party, Mr. Marshall. We might ask one
or two besides the family; the bishop, for instance."
"Now you're joshing me. Into what part of the world do we sail this
yacht, if you don't happen to be under sealed orders."
He was traveling somewhat rapidly, Mary thought; and she was
right. Bill was already cleaving the high seas, perched on his own
quarter-deck and inhaling stupendous quantities of salty air.
"I think we'd better obtain your aunt's approval before we plot out a
cruise," she advised. "Also, there's the problem of getting a yacht."
"We'll get one if we steal it," Bill assured her. "I'll talk to Pete about
it. He's amphibious. He's a sort of nautical valet. He knows all about
yachts."
"I dare say. He seems to have a wide range of information. Suppose
you consult him, while I speak to your aunt."
A frown clouded Bill's face.
"Do you suppose Aunt Caroline will want to go?" he asked.
"Want to? Why, she must."
"I don't see why. I don't believe she'd enjoy it a bit. We can have a
barrel of fun if Aunt Caroline doesn't go. Let's leave her home."
Mary shook her head decisively.
"That's out of the question. Of course she'll go.
"But, listen; I don't need any chaperon."
"Well, perhaps I do," said Mary.
"Oh!" Bill was still scowling. "Why couldn't we let Pete be the
chaperon?"
Mary squashed that suggestion with a glance.
"Then don't blame me if she turns out to be a bum sailor," he
warned.
"I think I'll speak to her now," said Mary.
Aunt Caroline was frankly surprised. It had never occurred to her
that there were times when society went to sea. Yet, to Mary's great
relief, she did not prove to be an antagonist. She merely wanted to
be shown that this cruise would actually be in furtherance of Bill's
career.
"Of course it will," urged Mary. "It's the very thing. We'll take the
regular summer society cruise."
"And what is that, my dear?"
Mary bit her lip. She did not have the least idea.
"Oh, I suppose we'll stop at Newport, Narragansett, Bar Harbor, and
such places," she said, dismissing the details with a wave of her
hand. "We'll make all the regular society ports—that is, of course, if
you approve the idea, Miss Marshall."
Aunt Caroline smiled.
"Certainly I approve it, my dear. Although I admit it perplexes me.
What sort of yachting flannels does an old lady wear?"
"Oh, they dress exactly like the young ones," said Mary, hastily.
"Which reminds me that we'll both need gowns. So, please order
whatever you want."
"You're awfully generous with me," and Mary laid an impulsive hand
on Aunt Caroline's. She felt very small and mean and unworthy.
"I want you to be a credit to the family, my dear. So far, you're doing
beautifully! Have you spoken to William about buying the yacht?"
"Oh, we don't have to buy one! We just hire one—charter it, I think
they say."
"It sounds like hiring clothes," said Aunt Caroline. "Still, I leave it all
to you and William. But if it's necessary, buy one. And please get it
as large as possible. We wouldn't want to be seasick, you know."
"We'll only sail where it's nice and calm," Mary assured her.
"And where there are the proper sort of people. Very well, my dear.
And, oh, I've just remembered: have you done anything yet about
Mrs. Rokeby-Jones?"
That lady had passed completely out of Mary's head.
"Why—er—you see, this other matter came up, Miss Marshall, so I
haven't done anything about her as yet."
"Never mind the dinner, then," said Aunt Caroline.
"I'm afraid we wouldn't have time for it," agreed Mary.
"Probably not, my dear. We'll do better. We'll invite her to sail with us
on our yacht."
Mary groped her way out of the room.
The business of fleeing the city went surprisingly well,
notwithstanding Aunt Caroline's obsession on the subject of Mrs.
Rokeby-Jones. Bill consulted Pete Stearns, who numbered among his
friends a marine architect. The marine architect believed that he
knew the very boat they needed. She was not a steam-yacht; most
of the steam-yachts, he pointed out, were too large for a small party
and a lot of them were obsolete. What they wanted was a big
cruiser with Diesel engines, that ran smoothly, noiselessly and never
smokily.
So through the offices of the marine architect, who made a nice
commission, of which he said nothing at all, Bill Marshall became
charterer of the yacht Sunshine, an able yet luxurious craft,
measuring some one hundred and twenty feet on the water-line,
capable of all the speed that was required in the seven seas of
society and sufficiently commodious in saloon and stateroom
accommodations.
Mary Wayne was delighted. Any craft that would sail her away from
New York City would have been a marine palace, in her eyes. She
would have embarked on a railroad car-float, if necessary. There was
a vast amount of shopping to be done, which also pleased Mary.
Aunt Caroline insisted upon being absurdly liberal; she was in
constant apprehension that the ladies of the party would not be
properly arrayed for a nautical campaign. So Mary presently found
herself the possessor of more summer gowns than she had ever
dreamed of.
Even when it came to the business of seeing that Bill Marshall was
adequately tailored for the sea Aunt Caroline proved prolific in ideas.
Somehow, she acquired the notion that Bill would need a uniform;
she pictured him standing on the bridge, with a spy-glass under his
arm, or perhaps half-way up the shrouds, gazing out upon the far
horizon; although there were no shrouds on the Sunshine, inasmuch
as there were no masts. But Aunt Caroline did not know that. To her,
Bill would not merely be the proprietor and chief passenger of this
argosy, but the captain, as well.
Mary saved Bill from the uniform. She did it tactfully but firmly, after
explaining to Aunt Caroline that only the hired persons on board
would wear uniforms. Nevertheless, Aunt Caroline insisted on such a
plethoric wardrobe for her nephew that for a time she even
considered the advisability of an assistant valet. Pete fell in with that
idea instantly, but again there was a veto from Mary. One valet was
trouble enough, as she well knew.
When it came to the matter of Mrs. Rokeby-Jones, however, Mary
was hard put for a suitable defense. Aunt Caroline mentioned the
lady several times; she hoped that the negotiations were progressing
favorably; in fact, she at last reached the point where she decided
upon two additional evening gowns for herself, because she was
certain that Mrs. Rokeby-Jones would come arrayed like the Queen
of Sheba. Poor Aunt Caroline did not know that the Queen of Sheba,
in these times, would look like a shoddy piker beside even the
humblest manicure in New York.
Mary had consulted Bill about Mrs. Rokeby-Jones. She could not
explain as fully as she would have liked just why it was impossible
for her to transmit Aunt Caroline's invitation; but she did not need
to. Bill was flatly against his aunt's scheme. He declared that he
would back Mary to the uttermost limit of opposition.
"But opposition is exactly what we must avoid," said Mary. "We
mustn't antagonize—and yet we must stop it. Oh, dear! It seems a
shame for me to be plotting this way against your aunt; she's been
so wonderful to me. But there's no way to make her see that a
perfect stranger is hardly likely to accept an invitation to a yachting
party. Of course, your aunt is relying on the Marshall name." Bill
nodded.
"And names don't get you anywhere; except, perhaps, in society. I
knew a youngster who called himself Young John L. He kept at it for
quite a while, but the only thing he was ever any good at was lying
on his back in the middle of the ring and listening to a man count
ten. That's all his name ever got him."
"But to get back to Mrs. Rokeby-Jones," said Mary, with a slight
frown. "We've got to appear to want her, but we mustn't have her."
"We won't; don't you worry. We'll count her out or claim a foul. We'll
leave her on the string-piece, if it comes to the worst."
"It isn't quite so simple as that, Mr. Marshall. Do you know what your
aunt did to-day? She wrote her a note—personally."
"I know it," said Bill.
"She told you?"
"No; but here's the note."
He delved into a pocket and produced an envelope. Mary's eyes
became round.
"Why, how in the world——"
"You see, the letters were given to Pete, to put stamps on and mail.
And—well, he thought I might be interested in this one."
"But—that's a crime, isn't it?"
"Why do you have such unpleasant thoughts, Secretary Norcross?
Pete says it's no crime at all; not unless it's been dropped in a letter-
box. But if you feel finicky about it, why here's the letter. Mail it."
Mary shook her head.
"I'd be afraid to touch it."
"Thought so," said Bill, as he returned the letter to his pocket. "I'll
hold it for a while."
"If the boat was only sailing now!" exclaimed Mary.
"That's a good suggestion. I'll hold it till we sail."
"Why, I never suggested anything of the kind, Mr. Marshall."
She made a very fair show of indignation, but Bill simply winked at
her. Mary turned away for fear of betraying herself. Nevertheless,
she knew that it was all very discreditable and she was not in the
least proud of herself. It was a comfort, though, to have somebody
else sharing the guilt.
The day came for the sailing of Aunt Caroline's armada. The
Sunshine lay at anchor in the Hudson. From early morning a launch
had been making steady trips from wharf to yacht, carrying trunks,
boxes, grips, hampers, and packages. A superficial observer would
have been justified in assuming that the Sunshine was documented
for the Philippines, or some equally distant haven. All of Aunt
Caroline's new gowns, all of Mary's, all of Bill's wardrobe, all of
Pete's, and many other things that might prove of service in an
emergency went aboard the Sunshine.
At the last moment there was great difficulty in persuading Aunt
Caroline to leave the house. There had been no word from Mrs.
Rokeby-Jones, and the good lady who was determined to be her
hostess insisted that she would not depart without her. Bill fumed;
Mary twisted her handkerchief. Aunt Caroline was displaying
stubborn symptoms.
"Madam, I telephoned myself, only half an hour ago," said Pete.
"She was not at home."
"She's probably on her way to the yacht," said Bill, with a glance at
Mary.
"We'll wait a while and telephone again," announced Aunt Caroline.
"But if she's on her way," said Mary, "wouldn't it be better for you to
be there to receive her?"
Aunt Caroline hesitated. It was Pete who saved the day.
"If I may make bold to suggest, Miss Marshall, you could go to the
yacht at once. If Mrs. Rokeby-Jones has not arrived you could then
telephone from the boat."
Mary turned away and stuffed her handkerchief into her mouth. Bill
went out into the hall to see if the taxis had arrived.
"Peter," said Aunt Caroline, "that's a most sensible suggestion. I
never thought of the telephone on board."
CHAPTER XVI
Three Errands Ashore
If Aunt Caroline had been bred to the sea, and familiar with its
customs that have practically crystallized into an unwritten law, she
would have written in her log:
Aboard the yacht Sunshine—Latitude, 40° 43' North; Longitude,
74° 0' West. Weather, clear; wind, SSW., moderate; sea,
smooth. Barometer, 29.6.
But not being a seafaring lady, she phrased it in this way in the
course of a remark to her nephew:
"William, isn't it lovely to be sitting here aboard our own yacht in the
Hudson, and isn't the weather superb?"
The Sunshine still lay at her anchorage, with every prospect
auspicious, except for the fact that nothing had been heard from
Mrs. Rokeby-Jones. The sun had set somewhere in New Jersey and
the lights of New York were shining in its stead. There was a soft
coolness in the air, so that Aunt Caroline found comfort in a light
wrap.
Bill had decided that they would not sail until later in the evening.
This was not because of Aunt Caroline's anxiety concerning the
missing guest, but for the reason that he had an errand ashore
which he had been unable to discharge during the busy hours of the
day. It was an errand he could trust to nobody, not even to Pete
Stearns. In fact, he did not consider it wisdom to take Pete into his
confidence.
Aunt Caroline had, indeed, discovered a telephone aboard the
Sunshine. It was in the owner's stateroom, which had been set apart
for her because it was the most commodious of all the sleeping
apartments. Three times she had talked into this telephone, on each
occasion giving the correct number of the Rokeby-Jones house, of
which she had made a memorandum before leaving shore. But each
time she was answered by the voice of a man, always the same
voice. The second time he laughed and the third time he hung up
with a bang! So Aunt Caroline, after vainly trying to lodge a
complaint with "Information," made a personal investigation and
discovered that the other end of the telephone system was in the
cabin of the sailing-master.
She made an instant complaint to Bill, and Bill referred her to Pete.
The latter explained it very easily.
"You see, madam, through a mistake the telephone company was
notified that we were sailing several hours ago, so they sent a man
out in a boat to disconnect the shore wire. I'm very sorry, madam."
Aunt Caroline accepted the explanation, as she had come to accept
anything from Pete Stearns, although it did nothing to allay her
anxiety as to Mrs. Rokeby-Jones.
Dinner had been over for more than an hour and darkness had
settled upon the river when Bill Marshall announced that he was
going ashore. He said that it was expressly for the purpose of
pursuing Aunt Caroline's thwarted telephone inquiry and that he
would not come back until he had definite news. His aunt thanked
him for his thoughtfulness, settled herself for a nap in a deck-chair
and Bill ordered the launch.
He was about to embark upon his errand when it occurred to him
that perhaps his secretary would also like to go ashore. Bill had it in
the back of his head that there might be time to pay a short visit to
a roof-garden or seek some sequestered place for a chat. He had
been trying for some time to have a confidential chat with Mary
Wayne, but she had an annoying way of discovering other and prior
engagements.
"You mean the young lady, sir?" said the second officer. "She went
ashore an hour ago, sir. I sent her across in the launch."
Bill became thoughtful. Why hadn't she mentioned the matter to
him? And who was the boss of this yacht, anyhow? Could people
order up the launch just as if they owned it?
He made a search for Pete Stearns and could not find him. Again he
spoke to the second officer.
"Oh, the young man, sir? Why, he went ashore at the same time. I
believe I heard him say that he had a few purchases to make."
Bill gritted his teeth. Here was a piece of presumption that no owner
could tolerate. They had gone away together, of course; they had
been very careful not to say a word to him. What for? What sort of
an affair was in progress between his valet and his secretary? The
more he thought about it the higher rose his temper.
"I'm going ashore myself," he said shortly. "Please hurry the launch."
Ten minutes later he was hunting for a taxi along the Manhattan
waterfront, deeply disturbed in mind and with a fixed resolution to
demand explanations.
But the suspicions of Bill Marshall did injustice at least to one of the
missing persons. Mary Wayne had gone ashore on a purely private
mission, and she was not only surprised, but annoyed when her
employer's valet also stepped into the launch.
"If you don't mind, miss," said Pete, apologetically, as the launch
was headed for the wharf, "I have some purchases to make for Mr.
William."
Mary answered, of course, that she did not mind, and after that she
kept her thoughts to herself. Where the wharf entrance opened on
Twelfth Avenue, Pete lifted his hat respectfully, bid her good
evening, and went off in an opposite direction.
But he did not go far; merely far enough to conceal himself in a
shadow from which he could watch without fear of discovery. Mary
was without suspicion; she walked briskly eastward, glad to be so
easily rid of her fellow passenger. When he had permitted her to
assume a safe lead, Pete stepped out of his shadow and followed.
It was fortunate that there were two taxis at the stand which Mary
discovered after a journey of several blocks through lonely streets;
that is, Pete considered it was fortunate. He took the second one,
giving the driver the order and promise of reward that are usual in
such affairs. This nocturnal excursion on the part of Mary Wayne had
piqued his curiosity. He knew that she had not spoken to Bill
Marshall about it; he doubted if she had said anything to Aunt
Caroline. The clandestine character of Mary's shore visit impressed
him as warranting complete investigation.
The two taxis had not been in motion for many minutes when Pete
became convinced that he could name Mary's destination almost
beyond a question. They were headed down-town, with occasional
jogs toward the East Side. So certain was Pete of his conclusion and
so anxious was he, purely for reasons of self-gratification, to prove
the accuracy of his powers of deduction, that he halted his taxi, paid
off the driver and set off at a leisurely walk, quite content in mind as
he watched the vehicle that contained Mary Wayne disappear from
view.
Twenty minutes later Pete found himself vindicated. In front of the
boarding-house where Nell Norcross roomed stood a taxi. Sitting on
the top step of the porch were two figures. As he strolled slowly by
on the opposite side of the street he had no difficulty in recognizing
Mary Wayne's smart little yachting suit of white linen. Of course,
there was no doubt as to the identity of the second person, even
though the street lights were dim and there was no lamp-post within
a hundred feet of the boarding-house. Pete walked as far as the
corner and posted himself.
The conversation between Mary and Nell proceeded in low tones.
"We shall be in Larchmont to-morrow," Mary was saying. "I'll try to
send you a note from there. After that I'll keep you informed as well
as I can concerning the rest of the trip, so you can reach me, if it's
necessary. We are not traveling on any fixed time-table."
"I'll feel dreadfully lonely, Mary."
"I'd have brought you if I could, Nell; but there wasn't any legitimate
excuse. And besides, I don't think you're strong enough to attempt
it."
"If there was only somebody staying behind that I knew," Nell
sighed. "I'll be so helpless."
"Nonsense. Besides, who would stay behind?"
Nell did not answer, but if Pete Stearns could have read a fleeting
thought from his point of observation on the street corner his
waistcoat buttons would doubtless have gone flying. Mary Wayne,
however, read the thought.
"You don't mean that valet who brought you home from the party?"
she demanded suddenly.
"Oh, I didn't mean anybody particularly," answered Nell, guiltily. "But
of course even he would be better than nobody."
"Nell Norcross, don't let that young man get into your head. There's
something mysterious about him. He may be only a valet, but I'm
not certain. I'm suspicious of him. He has a habit of forgetting
himself."
"I know," assented Nell, nodding.
"Oh, you do, do you? I might have guessed it. Take my advice and
give him a wide berth."
Nell regarded her friend with a look of speculative anxiety.
"Of course, Mary, I don't want to interfere with you in any way. But
——"
"Interfere with me?" exclaimed Mary sharply. "Do you think I am
interested in valets?"
"But you thought he might be something else. At least, you hinted it.
He's a divinity student, isn't he?"
"Divinity!" Mary summoned all her scorn in that word. "Oh, very
likely. But what sort of a divinity is he studying? Perhaps you're a
candidate for the place."
"Mary Wayne, you're mean! I think that's a nasty remark."
"Oh, well; I didn't mean it. But you'd better take my advice, just the
same. I've seen much more of him than you have."
Nell sighed again.
"Now, my dear, I must be going back. They'll be sending out a
general alarm for me, I suppose. I didn't ask anybody's permission
to come, you see."
"There isn't much doubt Mr. Marshall will be alarmed," remarked
Nell, who was not above seeking a legitimate revenge.
"You're in a rather silly mood this evening," said Mary. "Well, good-
by. I'll send you some more money as soon as I'm paid again."
Nell looked gratefully at a small roll of bills that lay in her hand.
"You're awfully good to me," she murmured. "Good-by. And if you
see——"
But Mary ran down the steps, popped into the taxi and was driven
off.
Pete Stearns aroused himself, crossed the street, and walked briskly
in the direction of the boarding-house. He arrived in time to
intercept Nell, who had risen to go in. She sat down again in sheer
surprise, and Pete seated himself without invitation on the step
below.
"It's a fine night, isn't it?" he said. "Now what's your real name?"
Nell gasped and could only stare.
"Is it Wayne?" he demanded.
"Of—of course, it is!"
"I just wanted to see if I'd forgotten. Sometimes my memory walks
out on me. Amnesia, you know. It's lucky I never suffered from
aphasia. A bishop with aphasia wouldn't be able to hold his job. Let's
talk about the bishops."
And he did, for ten solid minutes, until Nell began seriously to
wonder if he was in his right mind. Suddenly he dropped the
subject.
"You said your name was Wayne, didn't you?"
"Why in the world do you keep asking that?" she parried.
"It's the amnesia. Excuse it, please. Now let's talk about ourselves."
Eventually he said good night; he would be delaying the yacht, he
explained. But he promised to write, which was something that had
not even been hinted at during the conversation. He also shook
hands with her, begged her to have faith in him, urged her to believe
nothing she might hear, reaffirmed his purpose to become a bishop
and perhaps even an archbishop, told her that she inspired him to
great things, as witness—a kiss that landed on the end of her nose.
Then he ran.
Nell Norcross was still sitting on the top step half an hour later,
trying to muster sufficient confidence for the climb up-stairs.
At about the same time Bill Marshall was taking leave of a friend in
the back room of a hostelry that had descended to the evil fortunes
of selling near-beer.
"I'm sorry I won't be able to be there, Kid," he said, "but go to it and
don't worry about any cops butting in to bust up the game."
"I'll run it strictly Q. T., Bill. Doncha worry about nothin'."
"I won't. But I owe you that much for the way they chucked you out
of the house the other night."
"'Sall right, 'sall right," said Kid Whaley with a generous wave of his
hand. "They didn't hurt me none."
Bill handed him something, and the Kid pocketed it with a wink.
"I'd like to take you with me, Kid; but you understand."
"Aw, sure. Sure—I'm wise. I ain't strong for yachtin', anyhow. That's
why I blew me roll in a buzz-wagon. Well, s'long, Bill. This here little
scrap's goin' t' be a bird. I'll tell y' all about it."
When Mary Wayne arrived at the wharf there was no sign of the
launch. She remembered that she had said nothing about the time
of her return. Out in the river she could see the riding lights of the
Sunshine and the glow from the saloon windows. But she had not
the least idea of how to make a signal, nor any notion that they
would understand a signal. The wharf was lonely. It seemed to her,
as she seated herself on the string-piece, that she was as remote
from civilization as though she were sitting at the north pole,
although she knew there were seven or eight million people within a
radius of a few miles. There was nothing to do but wait, even if it
was a creepy place for waiting.
She had been sitting there for what seemed like half the night when
a sound of footsteps startled her. Out of the murk a figure was
approaching. An instant later, to her relief, she perceived it to be the
valet.
He bowed in his mock deferential way and seated himself beside her.
"No launch?" he inquired.
"I forgot to speak to them."
"So did I. Well, the yacht's there, anyhow, miss. They won't leave
without us. Is Miss Wayne better?"
Mary experienced a shock. She leaned closer toward him and stared
through the gloom.
"You followed me!" she exclaimed.
"I'd hardly say that, miss. You see, I was quite certain where you
were going."
She had an impulse to sweep him off into the water.
"I shall speak to Mr. Marshall about this," she said hotly. "I do not
propose to be spied upon by a servant."
Pete made a gesture of deprecation.
"Why be nasty, miss? Let's talk about something pleasanter. You
know, if we both started telling all we knew there might be a great
deal of embarrassment."
"Just what do you mean by that?" she demanded.
"I leave it to your imagination," he said cryptically.
"I can tell things myself," she said savagely.
"Exactly, miss. So why shouldn't we be friends? Why can't we
establish a real democracy? I won't always be a valet; some day I'll
be a bishop."
"I believe you're nothing but a fraud!"
"Well, now," observed Pete in a mild tone, "I might remark, on the
other hand—but I think the master is coming."
Mary jumped to her feet with a sense of confusion. There was no
doubt that the large figure emerging out of the darkness was that of
Bill Marshall. How was she to explain the valet?
"Oh, hello!" said Bill as he identified her. "Waiting here all alone, eh?
Well, that's a darn shame. Hasn't the launch—oh!" He discovered the
presence of Pete Stearns. "Didn't know you had company," he
added, his tone altering. "Beg your pardon."
"I—I haven't," said Mary, defiantly.
"I'll see if there's any sign of the launch." Bill walked to the end of
the wharf, where he stood staring at the river, raging with and
almost bursting with questions that he scorned to ask.
"Why didn't you explain to him?" snapped Mary, whirling upon Pete.
"I pass the question back to you, miss." And Pete lighted a cigarette,
the glow of the match illuminating for an instant a pair of eyes that
were regarding her with unveiled amusement.
When the launch came, after an uncomfortably long interval, Bill
helped her into it, with cold courtesy. The valet scrambled aboard
and took himself off to the bow. All the way to the Sunshine the
three sat in silence—Bill smoldering with anger and curiosity, Mary
humiliated and resentful, Pete content because they were as they
were.
The social secretary hastened to her stateroom as soon as she
stepped aboard; she did not pause to speak to Aunt Caroline, who
was dozing in her chair. Pete disappeared with like alacrity. It
remained for Bill to arouse his aunt and suggest that it was time for
her to retire.
"But Mrs. Rokeby-Jones?" asked Aunt Caroline.
"Had her on the wire; she can't come," said Bill. "Says she wrote a
note, but it must have gone astray. Very sorry and all that sort of
thing."
Aunt Caroline sighed.
"At any rate, I have done my duty, William. When do we sail?"
"Soon."
Bill went forward to give an order to the sailing-master.
CHAPTER XVII
The Way of a Maid
Larchmont Harbor!
It was fair even to the eyes of Bill Marshall, as he stood under the
after awning of the Sunshine, staring out over the shining water, as
yet untouched by so much as a breath of breeze. He was in no
pleasant mood this morning, but he could not deny the serene,
luxurious charm of the harbor. At another time it might have
awakened the spirit of the muse within him; Pete always insisted
that far under the surface Bill was a poet. But now its influence was
not quite so potent as that; it merely laid a restraining spell upon
him, soothing him, mollifying him, yet not lifting him to the heights.
There were many yachts at anchor, with club ensigns and owners'
flags drooping limp in the sluggish air. Bill watched them for signs of
life, but it was still an early hour for Larchmont. Occasionally he saw
a hand scrubbing a deck or polishing a brass, but he discovered no
person who resembled an owner or a guest. A warm mist had
thinned sufficiently to show the rocky shore, and beyond it, partly
sequestered among the trees, the summer homes and cottages of
persons who still slept in innocence of the designs of Aunt Caroline.
The harbor was not even half awake; it was yet heavy with the
unspent drowsiness of a summer night.
Bill was on deck early because he had slept badly. The affair of Mary
Wayne and Pete Stearns, as he interpreted it, rankled. The yacht
had been clear of Hell Gate before he went to his stateroom, and
even then it was a long time before he closed his eyes. The fact that
Bill was jealous he did not himself attempt to blink; he admitted it.
Welcome to our website – the perfect destination for book lovers and
knowledge seekers. We believe that every book holds a new world,
offering opportunities for learning, discovery, and personal growth.
That’s why we are dedicated to bringing you a diverse collection of
books, ranging from classic literature and specialized publications to
self-development guides and children's books.
More than just a book-buying platform, we strive to be a bridge
connecting you with timeless cultural and intellectual values. With an
elegant, user-friendly interface and a smart search system, you can
quickly find the books that best suit your interests. Additionally,
our special promotions and home delivery services help you save time
and fully enjoy the joy of reading.
Join us on a journey of knowledge exploration, passion nurturing, and
personal growth every day!
ebookbell.com

Terraform In Action Meap V10 Meap Scott Winkler

  • 1.
    Terraform In ActionMeap V10 Meap Scott Winkler download https://ebookbell.com/product/terraform-in-action-meap-v10-meap- scott-winkler-24426952 Explore and download more ebooks at ebookbell.com
  • 2.
    Here are somerecommended products that we believe you will be interested in. You can click the link to download. Terraform In Depth Robert Hafner https://ebookbell.com/product/terraform-in-depth-robert- hafner-232391492 Devops In Sharepoint Handson Packer Terraform Ansible Vagrant Srivastava https://ebookbell.com/product/devops-in-sharepoint-handson-packer- terraform-ansible-vagrant-srivastava-33975010 Terraform For Google Cloud Essential Guide Learn How To Provision Infrastructure In Google Cloud Securely And Efficiently 1st Edition Bernd Nordhausen https://ebookbell.com/product/terraform-for-google-cloud-essential- guide-learn-how-to-provision-infrastructure-in-google-cloud-securely- and-efficiently-1st-edition-bernd-nordhausen-47589692 Getting Started With Containers In Azure Deploy Secure Cloud Applications Using Terraform 2nd Edition Shimon Ifrah https://ebookbell.com/product/getting-started-with-containers-in- azure-deploy-secure-cloud-applications-using-terraform-2nd-edition- shimon-ifrah-54542376
  • 3.
    Aws Cloud AutomationIndepth Guide To Automation Using Terraform Infrastructure As Code Solutions James Odeyinka https://ebookbell.com/product/aws-cloud-automation-indepth-guide-to- automation-using-terraform-infrastructure-as-code-solutions-james- odeyinka-55917934 Patterns And Practices For Infrastructure As Code With Examples In Python And Terraform Meap V08 Meap V08 Rosemary Wang https://ebookbell.com/product/patterns-and-practices-for- infrastructure-as-code-with-examples-in-python-and-terraform- meap-v08-meap-v08-rosemary-wang-42522802 Describing The City Describing The State Representations Of Venice And The Venetian Terraferma In The Renaissance 1st Edition Sandra Toffolo https://ebookbell.com/product/describing-the-city-describing-the- state-representations-of-venice-and-the-venetian-terraferma-in-the- renaissance-1st-edition-sandra-toffolo-51684884 Terraforming Mars Astrobiology Perspectives On Life In The Universe 1st Edition Martin Beech Editor https://ebookbell.com/product/terraforming-mars-astrobiology- perspectives-on-life-in-the-universe-1st-edition-martin-beech- editor-49011774 Terraforming The Creating Of Habitable Worlds Martin Beech https://ebookbell.com/product/terraforming-the-creating-of-habitable- worlds-martin-beech-43621274
  • 6.
    MEAP Edition Manning EarlyAccess Program Terraform in Action Version 9 Copyright 2020 Manning Publications For more information on this and other Manning titles go to manning.com ©Manning Publications Co. To comment go to liveBook
  • 7.
    welcome Dear Reader, Thank youfor purchasing this Terraform in Action. Personally, I love books and I think they are the best way to broach a new subject. Now that I’ve written one of my own, I can understand why this is the case: an incredible amount of time and effort from many different people went into handcrafting this experience for you. If you want to jumpstart your skills with Terraform and skip all the boring stuff, then this is the book for you. When I started learning Terraform there were not many resources available. In many ways, it was the Wild West of Terraform. My path to learning was long and tortuous, and so by writing this book, I hope to save you from going what I went through. This is a culmination of all by learnings and musings about Terraform over the years. I poured a lot of myself into this book and didn’t hold anything back. If you can finish reading this then I promise you will know all that I know – you too can become a Terraform guru. My teaching style doesn’t involve memorization or rote copy-paste, because I don’t believe in that. Instead, I will teach you how to think in Terraform and how to solve your own problems. The world is a big place and the potential use cases of Terraform are far too many for this to merely be a cookbook. Whether you are a beginner approaching Terraform for the first time, or already a seasoned expert, I believe there is something for you in this book. This is my gift to you, the reader. This is the book that I wish I had available to me when I was a beginner. If you have any questions, or just want to share your thoughts, please send me an email to scottjwinkler@gmail.com or post any feedback you have in liveBook discussion. I look forward to hearing from you! Thanks, —Scott ©Manning Publications Co. To comment go to liveBook
  • 8.
    ©Manning Publications Co.To comment go to liveBook brief contents PART 1: LEARNING THE ROPES 1 Getting Started with Terraform 2 Lifecycle of a Terraform Resource 3 Functional Programming 4 Deploying a Multi-Tiered Web Application in AWS PART 2: TERRAFORM IN THE WILD 5 Serverless Made Easy 6 Terraform with Friends 7 CI/CD Pipelines as Code 8 A Multi-Cloud MMORPG PART 3: BECOMING A TERRAFORM GURU 9 Zero Downtime Deployments 10 Refactoring and Testing 11 Extending Terraform by Writing your own Provider 12 Terraform in Automation 13 Secrets Management Appendix A: Creating Custom Resources with the Shell Provider
  • 9.
    Getting Started withTerraform This chapter covers: • Why Terraform is so great • Syntax of the HashiCorp Configuration Language (HCL) • Fundamental elements and building blocks of Terraform • Setting up a Terraform workspace • Configuring and deploying an Ubuntu virtual machine on AWS “What does Terraform do?”, is a question I get asked frequently, but it’s not one that I ever find easy to answer. At first, it seems like a no-brainer that should be obvious, but you’d be surprised how difficult it is to describe what Terraform is to someone who’s never used it. When I was first asked this question, I replied that “Terraform is an Infrastructure as Code provisioning tool”, because I knew that’s what I was supposed to say, but I also didn’t necessarily understand what any of that meant. While the phrase adequately describes what Terraform is, it’s also not helpful for understanding what Terraform actually does. HashiCorp, the company behind Terraform, describes the technology as: “a tool for building, changing, and versioning infrastructure safely and efficiently”, but even that isn’t exactly clear. For starters, what does “infrastructure” mean? And what kind of problems does Terraform help you solve? The word infrastructure is an elusive sort of word that seems to have taken on a multitude of different meanings in recent years. Infrastructure, at least in the tech sector, used to refer to the literal hardware components that you’d see in private, on-premise data centers. These data centers had racks of physical hardware components for the express purpose of running application software and making it available to the world. Again, these were physical devices, so you could walk through the racks of hardware components and feel the heat radiating off these machines, or just admire the flashing lights and beeping sounds as you walk around. Nowadays, it’s a different story. The cloud has well and truly taken over, and most companies have either migrated to the cloud or are in the process of migrating to the cloud. Tech giants like 1 1
  • 10.
    Google, Amazon, andMicrosoft have completely dominated the cloud scene by offering attractive on- demand compute, storage and networking related services via an Application Programming Interface (API). The word “infrastructure” thus no longer refers exclusively to physical hardware in on-premise data centers, as it could also refer to any virtualized hardware or managed software accessible through an API. What used to be a concrete and easily explainable term has now become abstract and muddied. As we shall see later, even this relaxed definition of “infrastructure” may not be appropriate in describing the current state of affairs. Terraform is an Infrastructure as Code technology created by HashiCorp for automating infrastructure provisioning to the cloud. Why the need for automation? Isn’t the cloud supposed to solve all our problems? Well, as it turns out, provisioning infrastructure to the cloud is a hugely tedious process, especially once you factor in the extreme scale that modern companies operate at. Companies used to have entire teams dedicated to the task of point-and-clicking their way through the console. Some of the more astute among those eventually discovered how to do less work increase productivity by writing custom scripts, which is really how the idea of Infrastructure as Code really started. Infrastructure as Code (IaC) is the process of managing and provisioning data centers through machine-readable definition files, rather than manual point-and-click. It means writing code to define your infrastructure, in the same manner you would write code to define your application software. Expressing your infrastructure as code has many distinct advantages over manual provisioning, such as being able to check code into version control systems, perform regular audit checks, and deploy faster and more reliably. Traditionally, IaC was accomplished using configuration management tools like Chef, Puppet, Ansible, and SaltStack. Configuration management tools like these provide a great deal of customization but tend to have a higher learning curve, and focus more on application delivery rather than infrastructure provisioning (which is where Terraform excels). Terraform is an infrastructure provisioning tool, not a configuration management tool. Being a provisioning tool means that Terraform can deploy your entire infrastructure stack, not just new versions of your application (although it can do that too). By writing configuration files, Terraform can deploy just about anything. The main use case of Terraform is to deploy ephemeral, on-demand infrastructure in a predictable and repeatable fashion. What can Terraform deploy to? Well, just about any cloud or combination of clouds, including: private, public, hybrid, and multi-cloud. 2
  • 11.
    Figure 1.1 Terraformcan deploy infrastructure to any cloud or combination of clouds In this chapter we’re going to start by going over the distinguishing features of Terraform. Terraform is just a tool after all, so why pick Terraform over another, when there are so many tools that can do the job? We’ll also talk about the comparative advantages and disadvantages of Terraform in relation to other popular IaC technologies, and what makes Terraform the clear winner. Finally, we’ll look at the quintessential “Hello World!” of Terraform by deploying a single server to AWS and improving it by incorporating some of the more dynamic features of Terraform. 1.1 What makes Terraform so great? There’s been a lot of hype about Terraform recently, but is any of it really justified? Terraform isn’t the only Infrastructure as Code technology on the block. There are plenty of other tools that do the same thing. Moreover, Terraform was created and released by a relatively small tech company, and doesn’t even have a 1.0 version out yet. How is it that Terraform, a technology in the highly lucrative software deployment market space, able to compete against competition from Amazon, Microsoft, and Google? The answer is, of course, that Terraform enjoys a unique set of competitive advantages. These competitive advantages stem from six key characteristics: 1. Provisioning tool: Deploy infrastructure, not just applications 2. Easy to use: For all of us non geniuses 3. Free and Open Source: Who doesn’t like free? 4. Declarative: Say what you want, not how to do it 5. Cloud agnostic: Deploy to any cloud using the same tool. 6. Expressive and extendable: You aren’t limited by the language A comparison of Terraform to similar IaC tools is shown in table 1.1: Name Key Features Provisioning tool Easy to use Free and Open Source Declarative Cloud Agnostic Expressive and extendable Public Cloud Hybrid Cloud Private Cloud main.tf Configuration Files User Configures Deploys Writes Deployment Targets 3
  • 12.
    Ansible1 X XX X Chef2 X X X X Puppet3 X X X X SaltStack4 X X X X X Terraform5 X X X X X X Pulumi6 X X X X AWS CloudFormation7 X X X GCP Resource Manager8 X X X Azure Resource Manager9 X X Table 1.1 A comparison of popular IaC tools 1.1.1 Provisioning Tool Terraform is an infrastructure provisioning, not a configuration management, tool. The distinction is subtle but important. Configuration management (CM) tools, like Chef, Puppet, Ansible and SaltStack, were all designed to install and manage software on existing servers. They work by performing push or pull updates and restoring software to a desired state if drift has occurred. This works fine if you only care about application delivery, but it doesn’t help at all with the initial infrastructure provisioning process. You’d still have to write custom scripts to deploy your infrastructure before configuration management tools could be of any use. NOTE it’s not a hard and fast rule that provisioning tools can only provision and configuration management tools can only configure. Many CM tools offer some degree of infrastructure provisioning, and vice versa. The biggest difference between configuration management and provisioning tools is a matter of philosophy. CM tools favor mutable infrastructure, whereas Terraform and other provisioning tools, favor immutable infrastructure. What’s the difference then between mutable and immutable infrastructure? Mutable infrastructure is performing software updates in place, especially in the context of application delivery. It’s having pet servers. If pet servers are sick, you don’t kill them. You nurse them back to health by rolling out new software updates. Immutable infrastructure, on the other hand, is about treating servers like cattle, not like pets. If a server gets into a bad or undesirable configuration state, the solution is to simply take it out back, shoot it in the head, and replace it with a new one. 1Ansible – https://www.ansible.com 2 Chef – https://www.chef.io 3Puppet – https://www.puppet.com/ 4SaltStack – https://www.saltstack.com/ 5Terraform – https://www.terraform.io/ 6 Pulumi – https://www.pulumi.com 7CloudFormation – https://aws.amazon.com/cloudformation/ 8GCP Resource Manager – https://cloud.google.com/resource-manager/ 9Azure Resource Manager – https://azure.microsoft.com/features/resource-manager/ 4
  • 13.
    Another way toimagine the differences between mutable and immutable infrastructure is to think of an old toy that no longer does what you want. Mutable infrastructure would say that there is inherent goodness in the old toy and would recommend fixing it up. Immutable infrastructure, being significantly more callous and worldly, would suggest throwing out the old toy in favor of a new one that already has the features you want. Why is immutability better than mutability? Because it’s is easier to reason about. You don’t have to worry about things changing or leaving behind artifacts between deployments. Everything is commoditized, so there is no chance of having special snowflake servers. Additionally, this makes it easier to perform repeatable deployments and provision ephemeral environments that are all exactly uniform. Immutability is an ongoing trend in the software community. It’s why we are moving away from virtual machines to containers, and from containers to serverless. The less you have to worry about the previous state of software, the better. One conclusion that a lot of people make after first hearing about provisioning and configuration management tools is that they should be combined together. After all, why not use Terraform to deploy your infrastructure, and a configuration management tool to ensure it is in a desired state? See figure 1.2 for an example of how this would work. Figure 1.2 Combining Terraform and CM tools. Terraform deploy infrastructure initially and Ansible installs and updates software I am of the opinion that Terraform should not be combined with configuration management technologies, mainly because of their clashing philosophies. Terraform favors immutable infrastructure, whereas CM favors mutable. If you combine the two, invariably, you will wind up with something that straddles the paradigms of both, leading to something that is not much better than what we had before. Configuration Files main.tf Terraform deploys configures provisions infrastructure installs and updates software Configuration Files playbook .yml configures 1) Provisioning 2) Configuring Virtual Machine (VM) Cloud Providers Ansible updates 5
  • 14.
    There are manyways to perform continuous delivery with Terraform that do not involve CM tools. For example, you could deploy new virtual machines with cloud-init scripts, or perform zero-downtime Blue/Green deployments10, or use a conventional container scheduler like Kubernetes/Nomad. The choice mainly has to do with what you are deploying, and the amount of time you are willing to spend to set it up. We’ll talk more about these different strategies in chapters 7, 8, and 9. 1.1.2 Easy to Use The basics of Terraform are quick and easy to learn, even for non-programmers. By the end of chapter 4 you will already have the skills to call yourself an intermediate with the technology, which is kind of shocking when you think about it. Achieving mastery is another story, of course, but that’s why you’re here, and that’s why this book is as long as it is. The main reason why Terraform is so easy is because the code is written in a domain specific configuration language called HashiCorp Configuration Language (or just HCL for short). It’s a language invented by HashiCorp as a substitute for more verbose configuration languages like JSON and XML. HCL attempts to strike a balance between human and machine readability and was influenced by earlier field attempts such as libucl and Nginx configuration. HCL is fully compatible with JSON, which means that HCL can be converted 1:1 to JSON, and vice-versa. This makes it easy to interoperate with systems outside of Terraform, or even generate configuration code on the fly. 1.1.3 Free and Open-Source Software The engine that powers Terraform is called Terraform core, a free and open source software offered under the Mozilla Public License v2.0. This license stipulates that anyone is allowed to use, distribute, or modify the software for both private and commercial purposes. Being free is great because you never have to worry about incurring additional costs when using Terraform, In addition, you gain full transparency to the product and are able to make open source contributions if you so desire. HashiCorp has a dedicated team working on the ongoing development of Terraform, and they are very good at fixing bugs and introducing new features in a timely manner. There’s also no premium version of Terraform that you’re missing out by using the open source version. HashiCorp has repeatedly stated that they do not have any intentions to offer a premium version of Terraform, now or ever. Instead they plan to make money by offering specialized tooling that makes it easier to run Terraform in high-scale, multi-tenant environments. To this end, they have developed and released Terraform Cloud and Terraform Enterprise. Both products utilize the same open source Terraform core you can download for free, so it’s easy to imagine developing your own platform if deploying Terraform workspaces. In chapter 11 we’ll explore this topic, as well as discuss everything else you need to be aware of when running Terraform in automation. 1.1.4 Declarative Programming Declarative programming is when you expresses the logic of a computation (the what) without describing the control flow (the how). Instead of writing step-by-step instructions, you simply describe what you want, and it will get done. Examples of declarative programming languages include 10 Blue/Green deployment is a technique that reduces downtime and risk by running two identical production environments called Blue and Green. At any time, only one of the environments is live, with the live environment serving all production traffic. 6
  • 15.
    database query languages(SQL), functional programming languages (Haskell, Clojure), configuration languages (XML, JSON), and most IaC tools (Ansible, Chef, Puppet, Terraform). Declarative programming is in contrast to imperative programming, which focuses on the how. With imperative programming, you use conditional branching, loops, and expressions to control system flow, save state, and execute commands. The hardware implementation of nearly all modern computers is imperative in nature, so it follows that most conventional programming languages are imperative as well (Python, Java, C, etc.), The difference between declarative and imperative programming is the difference between getting in a car and driving yourself from point A to point B, vs. calling an Uber and having your chauffeur drive you. Assuming you are unfamiliar with the area, it is more than likely you will take an inefficient route, compared to your chaffeur. Figure 1.3 Driving yourself vs being chauffeured NOTE Declarative programming cares about the destination, not the journey. Imperative programming cares about the journey, not the destination 1.1.5 Cloud Agnostic Cloud agnostic means being able to seamlessly run on any cloud platform (private or public). Terraform is cloud agnostic because it allows you to deploy infrastructure to whichever cloud, or 7
  • 16.
    combination of cloudsusing the same configuration language and deployment workflows. Being cloud agnostic is important because it means you aren’t locked-in to a particular cloud vendor, and you don’t have to learn a whole new technology if you do switch clouds. The way Terraform integrates with all the different clouds is through providers. Providers are plugins for Terraform that are designed to interface with external APIs. Each cloud vendor maintains their own Terraform provider, enabling Terraform to interface with and manage resources in that cloud. Providers are written in golang and handle all of the procedural logic for authenticating, making API requests, handling timeouts and errors, and managing the lifecycle of resources. There are hundreds of providers available in the Terraform provider registry, together allowing Terraform to manages thousands of different kinds of resources. You can even write you own Terraform provider, which is something we will discuss in chapter 10. Providers can be mixed and matched however you like. If you want to deploy to the hybrid or multi-cloud, it’s almost as easy as deploying to the single cloud. There are architecture and design patterns to be aware of, but we will cover these in chapter 8. Figure 1.4 Deploying to multiple clouds at the same time with Terraform 1.1.6 Richly Expressive and Highly Extensible Terraform is richly expressive and highly extensible compared to other declarative IaC tools. With conditionals, for expressions, directives, template files, dynamic blocks, variables, and many built-in functions, it’s easy to write code to deploy exactly what you want. A tech comparison between Terraform and AWS CloudFormation is shown in table 1.2 8
  • 17.
    Name Language FeaturesOther Features Intrinsic Functions Conditional Statements For Loops Types Pluggable Modular Wait Conditions Terraform 87 Yes Yes String, Number, List, Map, Boolean, Objects, Complex Types Yes Yes No AWS CloudFormation 11 Yes No String, Number, List Limited Yes Yes Table 1.2 Tech comparison between the IaC tools Terraform and AWS CloudFormation You can also create reusable code by packaging them in modules, which can be shared with others through the public module registry (something we will discuss in chapter 6). Finally, as mentioned in the previous section, you can also extend Terraform by writing your own provider. 1.2 Hello Terraform! In this section we will take a look at a classical use case for Terraform - deploying a virtual machine (EC2 instance) onto AWS. We’ll use the AWS provider for Terraform to make API calls on our behalf and deploy an EC2 instance. When we’re done, we’ll have Terraform take down the instance, so we don’t incur ongoing costs by keeping the server running. An architecture diagram for what we are about to do is shown in figure 1.5. 9
  • 18.
    Figure 1.5 UsingTerraform to deploy an EC2 instance to AWS As a prerequisite for this scenario, I except that you have Terraform 0.13.X installed11, and that you have access credentials for AWS. The steps we’ll take for deploying the project are: 1. Writing Terraform configuration files 2. Configuring the AWS provider 3. Initializing Terraform with terraform init 4. Deploying the EC2 instance with terraform apply 5. Cleaning-up with terraform destroy This flow can be visualized by figure 1.6. Figure 1.6 sequence diagram of “Hello Terraform!” deployment 1.2.1 Writing Terraform Configuration Terraform reads from configuration files to deploy infrastructure. To tell Terraform we want it to deploy an EC2 instance, we need to declare an EC2 instance as code. Let’s do that now. Start by creating a new file named main.tf, with the contents from Listing 1.1. The “.tf” extension signifies 11Install Terraform – https://learn.hashicorp.com/terraform/getting-started/install.html Local Machine AWS User Writes Provisions EC2 API Terraform AWS Provider main.tf API Calls EC2 Instance (VM) Configures 10
  • 19.
    that it’s aTerraform configuration file. When Terraform runs it will read all files in the current working directory with a “.tf” extension and concatenate them together. Listing 1.1 Contents of main.tf resource "aws_instance" "helloworld" { #A ami = "ami-09dd2e08d601bff67" #B instance_type = "t2.micro" #B tags = { #B Name = "HelloWorld" #B } #B } #A Declaring an aws_instance resource with name “helloworld” #B Attributes for the EC2 instance NOTE this AMI is only valid for the us-west-2 region This code in Listing 1.1 declares that we want Terraform to provision a t2.micro AWS EC2 instance with an Ubuntu AMI (Amazon Machine Image), and a name tag. Compare this to the equivalent CloudFormation code which is shown in Snippet 1.1, and you can see how much clearer and more concise it is. Snippet 1.1 Equivalent AWS CloudFormation { "Resources": { "Example": { "Type": "AWS::EC2::Instance", "Properties": { "ImageId": "ami-09dd2e08d601bff67", "InstanceType": "t2.micro", "Tags": [ { "Key": "Name", "Value": "HelloWorld" } ] } } } } The EC2 instance code block is a kind of Terraform resource. Resources are the most prevalent and important element in all of Terraform. They are how Terraform provisions infrastructure, such as virtual machines, load balancers, NAT gateways, databases, and so forth. Resources are declared as HCL objects, with type resource and exactly two labels. The first label specifies the type of resource you want to create, and the second is the resource name. Name has no special significance and is only used to reference the resource within a given module scope (we will talk about modules and module scope in chapter 4). Together, the type and name make up the resource identifier, which is unique for each resource. Figure 1.7 shows the syntax of a resource block. 11
  • 20.
    Figure 1.7 Syntaxof a resource block Each resource has inputs and outputs. Inputs are called arguments and outputs are called attributes. Arguments are passed through the resource and available as resource attributes, but there are also other attributes which you don’t set and which are only available after the resource has been created. These other attributes are called computed attributes and are calculated information, typically metadata about the resource itself. Figure 1.8 shows some of the arguments, attributes and computed attributes of an aws_instance resource. Figure 1.8 Inputs and outputs for an aws_instance resource 1.2.2 Configuring the AWS Provider Next, we need to configure the AWS provider. The AWS provider is responsible for understanding API interactions, making authenticated requests, and exposing resources to Terraform – including the aws_instance resource. Let’s explicitly declare our dependency on the AWS provider by adding a provider block to the configuration file. Update your code in main.tf to look like Listing 1.2 Listing 1.2 Contents of main.tf provider "aws" { #A version = "2.65.0" #B region = "us-west-2" #B } resource "aws_instance" "helloworld" { ami = "ami-09dd2e08d601bff67" instance_type = "t2.micro" aws_instance ami instance_type instance_type tags id ami Computed Attributes Attributes Arguments tags arn 12
  • 21.
    tags = { Name= "HelloWorld" } } #A Declaring the AWS provider #B Configuring the provider by locking in a provider version and specifying a default region Authenticating to AWS As mentioned previously, I expect you to already have default credentials for AWS. You can either store them in the ~/.aws/credentials file, or as environment variables. There are also three other ways to authenticate, these being: Non-default profile credentials – you can use profile credentials other than the default by setting an optional provider attribute known as profile (e.g. profile = <your-profile-name>) 1) Static credentials – you can hardcode AWS credentials in the Terraform AWS provider. This is a not a recommended, because of the risk of inadvertently exposing secret credentials through version control systems. 2) Assumed role – Terraform can use the credentials you give it to assume a role to another account and use that other role for all future actions. If you need to modify the configuration of the AWS provider to fit your unique situation, I suggest consulting the AWS provider documentation12 for more information on how to do one of the above listed options. Unlike resources, providers only have one label: name. Name is the official name of the provider as published in the provider registry (e.g. “aws” for AWS, “google” for GCP and “azurerm” for Azure). The syntax for a provider block is shown in figure 1.9. Figure 1.9 Syntax of a Provider Block NOTE The provider registry is a global store for sharing versioned provider binaries. When Terraform initializes, it automatically looks up and downloads any required providers from the provider registry Providers don’t have outputs, only inputs. You configure a provider by passing in inputs, or configuration arguments, to the provider block. Configuration arguments will be things like servjce endpoint URL, region, provider version, and any credentials needed to authenticate against the API. Providers use the configuration arguments to configure all the resources underneath them. This can be visualized by figure 1.10. 12 https://www.terraform.io/docs/providers/aws/index.html 13
  • 22.
    Figure 1.10 Howthe configured provider injects credentials into aws_instance when making API calls Usually you don’t want to pass secrets into the provider as plain text, especially when this code will later be checked into version control, so many providers have the ability to read environment variables or shared credential files to retrieve this secret information dynamically. If you are interested more in secrets management, I recommend reading chapter 12, which is where we cover this topic in great detail. 1.2.3 Initializing Terraform Before we have Terraform deploy our EC2 instance, we first have to initialize the workspace. Even though we have declared the AWS provider, Terraform still needs to download and install the binary from the provider registry. Initialization is required for all new workspaces, as well as any new Terraform project you download from source control and are running for the first time. You can initialize Terraform by runningthe command: terraform init. When you do this, you will see the following output: NOTE you will need to have Terraform installed on your machine for this to work, if you do not have it already $ terraform init Initializing the backend... Initializing provider plugins... - Finding latest version of hashicorp/aws... - Installing hashicorp/aws v2.65.0... #A - Installed hashicorp/aws v2.65.0 (signed by HashiCorp) The following providers do not have any version constraints in configuration, so the latest version was installed. To prevent automatic upgrades to new major versions that may contain breaking changes, we recommend adding version constraints in a required_providers block in your configuration, with the constraint strings suggested below. * hashicorp/aws: version = "~> 2.65.0" Terraform has been successfully initialized! #B You may now begin working with Terraform. Try running "terraform plan" to see 14
  • 23.
    any changes thatare required for your infrastructure. All Terraform commands should now work. If you ever set or change modules or backend configuration for Terraform, rerun this command to reinitialize your working directory. If you forget, other commands will detect it and remind you to do so if necessary. #A Here Terraform is fetching the latest version of the AWS provider #B The only thing that we really care about 1.2.4 Deploying the EC2 Instance Now we’re ready to deploy the EC2 instance using Terraform. Do this now be executing the terraform apply command. WARNING performing this action may result in charges to your AWS account. This include potential charges for EC2 and CloudWatch Logs. $ terraform apply An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # aws_instance.helloworld will be created + resource "aws_instance" "helloworld" { #A + ami = "ami-09dd2e08d601bff67" #B + arn = (known after apply) + associate_public_ip_address = (known after apply) + availability_zone = (known after apply) + cpu_core_count = (known after apply) + cpu_threads_per_core = (known after apply) + get_password_data = false + host_id = (known after apply) + id = (known after apply) + instance_state = (known after apply) + instance_type = "t2.micro" #C + ipv6_address_count = (known after apply) + ipv6_addresses = (known after apply) + key_name = (known after apply) + network_interface_id = (known after apply) + outpost_arn = (known after apply) + password_data = (known after apply) + placement_group = (known after apply) + primary_network_interface_id = (known after apply) + private_dns = (known after apply) + private_ip = (known after apply) + public_dns = (known after apply) + public_ip = (known after apply) + security_groups = (known after apply) + source_dest_check = true + subnet_id = (known after apply) + tags = { #D + "Name" = "HelloWorld" 15
  • 24.
    } + tenancy =(known after apply) + volume_tags = (known after apply) + vpc_security_group_ids = (known after apply) + ebs_block_device { + delete_on_termination = (known after apply) + device_name = (known after apply) + encrypted = (known after apply) + iops = (known after apply) + kms_key_id = (known after apply) + snapshot_id = (known after apply) + volume_id = (known after apply) + volume_size = (known after apply) + volume_type = (known after apply) } + ephemeral_block_device { + device_name = (known after apply) + no_device = (known after apply) + virtual_name = (known after apply) } + metadata_options { + http_endpoint = (known after apply) + http_put_response_hop_limit = (known after apply) + http_tokens = (known after apply) } + network_interface { + delete_on_termination = (known after apply) + device_index = (known after apply) + network_interface_id = (known after apply) } + root_block_device { + delete_on_termination = (known after apply) + device_name = (known after apply) + encrypted = (known after apply) + iops = (known after apply) + kms_key_id = (known after apply) + volume_id = (known after apply) + volume_size = (known after apply) + volume_type = (known after apply) } } Plan: 1 to add, 0 to change, 0 to destroy. #D Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: #E #A plus sign means create #B ami attribute #C instance_type attribute #D tags attribute 16
  • 25.
    #D Totals summary #EManual approval step TIP If you received an error saying “No Valid Credentials Sources Found” then you need to make sure you have either default credentials set in ~/.aws/credentials, or AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY set. The output is called an execution plan and it outlines the set of actions that Terraform intends to perform to achieve your desired state. It’s a good idea to review the plan as a sanity check before proceeding. There shouldn’t be anything odd here, unless you made a typo, so when you are done reviewing the execution plan, approve it by entering “yes” at the command line. After a minute or two (the approximate time it takes to provision an EC2 instance) the apply will complete successfully. An example output is shown below. aws_instance.helloworld: Creating... aws_instance.helloworld: Still creating... [10s elapsed] aws_instance.helloworld: Still creating... [20s elapsed] aws_instance.helloworld: Still creating... [30s elapsed] aws_instance.helloworld: Creation complete after 34s [id=i-0c98c98679298dab8] Apply complete! Resources: 1 added, 0 changed, 0 destroyed. You can verify your resource was created by finding it in the AWS console for EC2, as shown in figure 1.11. Note that this instance will be in the us-west-2 region, because that’s what we set in the provider. Figure 1.11 Screenshot of EC2 instance in AWS Console All of the stateful information about the resource is stored in a file called terraform.tfstate. Don’t let the “.tfstate” prefix fool you, it’s really just a JSON file. The terraform show command can be used to print human readable output from the state file and makes it easy to list all the data of the resources Terraform manages. The show command and output result is shown below. $ terraform show # aws_instance.helloworld: resource "aws_instance" "helloworld" { ami = "ami-09dd2e08d601bff67" arn = "arn:aws:ec2:us-west-2:215974853022:instance/i- 0c98c98679298dab8" associate_public_ip_address = true 17
  • 26.
    availability_zone = "us-west-2a" cpu_core_count= 1 cpu_threads_per_core = 1 disable_api_termination = false ebs_optimized = false get_password_data = false hibernation = false id = "i-0c98c98679298dab8" #A instance_state = "running" instance_type = "t2.micro" ipv6_address_count = 0 ipv6_addresses = [] monitoring = false primary_network_interface_id = "eni-003e10654e38f1447" private_dns = "ip-172-31-28-98.us-west-2.compute.internal" private_ip = "172.31.28.98" public_dns = "ec2-52-38-134-200.us-west-2.compute.amazonaws.com" public_ip = "52.38.134.200" security_groups = [ "default", ] source_dest_check = true subnet_id = "subnet-0d78ac285558cff78" tags = { "Name" = "HelloWorld" } tenancy = "default" volume_tags = {} vpc_security_group_ids = [ "sg-0d8222ef7623a02a5", ] credit_specification { cpu_credits = "standard" } metadata_options { http_endpoint = "enabled" http_put_response_hop_limit = 1 http_tokens = "optional" } root_block_device { delete_on_termination = true device_name = "/dev/sda1" encrypted = false iops = 100 volume_id = "vol-05366f9f647f56541" volume_size = 8 volume_type = "gp2" } } #A id is an important computed attribute There’s a lot more data here than we originally set in the resource block. The reason for this is because most of the arguments are either optional or computed after the resource has been created. You can customize aws_instance by passing in optional arguments. If you want to know what these 18
  • 27.
    are, I recommendlooking through the AWS provider documentation, as well as the AWS API documentation. 1.2.5 Destroying the EC2 Instance Now it’s time to say goodbye to the EC2 instance. You always want to take down any infrastructure you are no longer using, as it costs money to run stuff in the cloud. Terraform has a special command to destroy all resources, called terraform destroy. When you run this command, you will be prompted to manually confirm the destroy operation, as shown below. $ terraform destroy aws_instance.helloworld: Refreshing state... [id=i-0c98c98679298dab8] An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: - destroy Terraform will perform the following actions: # aws_instance.helloworld will be destroyed - resource "aws_instance" "helloworld" { #A - ami = "ami-09dd2e08d601bff67" -> null - arn = "arn:aws:ec2:us-west-2:215974853022:instance/i- 0c98c98679298dab8" -> null - associate_public_ip_address = true -> null - availability_zone = "us-west-2a" -> null - cpu_core_count = 1 -> null - cpu_threads_per_core = 1 -> null - disable_api_termination = false -> null - ebs_optimized = false -> null - get_password_data = false -> null - hibernation = false -> null - id = "i-0c98c98679298dab8" -> null - instance_state = "running" -> null - instance_type = "t2.micro" -> null - ipv6_address_count = 0 -> null - ipv6_addresses = [] -> null - monitoring = false -> null - primary_network_interface_id = "eni-003e10654e38f1447" -> null - private_dns = "ip-172-31-28-98.us-west-2.compute.internal" -> null - private_ip = "172.31.28.98" -> null - public_dns = "ec2-52-38-134-200.us-west-2.compute.amazonaws.com" -> null - public_ip = "52.38.134.200" -> null - security_groups = [ - "default", ] -> null - source_dest_check = true -> null - subnet_id = "subnet-0d78ac285558cff78" -> null - tags = { - "Name" = "HelloWorld" } -> null - tenancy = "default" -> null - volume_tags = {} -> null - vpc_security_group_ids = [ - "sg-0d8222ef7623a02a5", ] -> null 19
  • 28.
    - credit_specification { -cpu_credits = "standard" -> null } - metadata_options { - http_endpoint = "enabled" -> null - http_put_response_hop_limit = 1 -> null - http_tokens = "optional" -> null } - root_block_device { - delete_on_termination = true -> null - device_name = "/dev/sda1" -> null - encrypted = false -> null - iops = 100 -> null - volume_id = "vol-05366f9f647f56541" -> null - volume_size = 8 -> null - volume_type = "gp2" -> null } } Plan: 0 to add, 0 to change, 1 to destroy. #B Do you really want to destroy all resources? Terraform will destroy all your managed infrastructure, as shown above. There is no undo. Only 'yes' will be accepted to confirm. Enter a value: #A minus sign means destroy #B Summary of all the actions Terraform intends to take WARNING It is important not to manually edit or delete the terraform.tfstate file, or else Terraform will lose track of all managed resources The destroy plan is just like the previous execution plan, except it is for the delete operations instead of create. NOTE terraform destroy does exactly the same thing as if you were to delete all the configuration code and run a terraform apply. Confirming that you wish to apply the destroy plan by typing in “yes” at the prompt, wait a few minutes for Terraform to do its thing, and you will be notified that Terraform has finished destroying all resources,. Your output will look like the following: aws_instance.helloworld: Destroying... [id=i-0c98c98679298dab8] aws_instance.helloworld: Still destroying... [id=i-0c98c98679298dab8, 10s elapsed] aws_instance.helloworld: Still destroying... [id=i-0c98c98679298dab8, 20s elapsed] aws_instance.helloworld: Still destroying... [id=i-0c98c98679298dab8, 30s elapsed] aws_instance.helloworld: Destruction complete after 31s Destroy complete! Resources: 1 destroyed. 20
  • 29.
    You can verifythat the resources have indeed been destroyed either by refreshing the AWS console, or running a terraform show and confirming that it returns nothing. $ terraform show 1.3 Brave New Hello World! I like the classic “Hello World!” example, and feel it is a good starter project, but I also don’t think it does justice to the technology as a whole. Terraform can do much more than simply provision resources from static configuration code. It’s able to provision resources dynamically based on the results of external queries and data lookups. Let us now consider data sources, which are elements that allow you to fetch data at runtime and perform computations as well. In this section we will improve the classic “Hello World!” example by adding a data source to dynamically lookup the latest value of the Ubuntu AMI. We’ll pass the output value into aws_instance so we don’t have to statically set the AMI statically in the EC2 instance resource configuration (see figure 1.12). Figure 1.12 How the output of the aws_ami data source will be chained to the input of the aws_instance resource Because we’ve already configured the AWS provided and initialized Terraform with terraform init, we can skip some of the steps we did previously. Here, we’ll be doing the following: 1. Modifying Terraform configuration to add the data source 2. Re-deploying with. terraform apply 3. Cleaning-up with terraform destroy This flow can also be visualized by figure 1.13. ami instance_type ami instance_type tags id Resource Attributes Data Source Arguments most_recent filter owners aws_ami (data source) Data Source Attributes aws_instance (resource) filter owners most_recent id Resource Arguments tags 21
  • 30.
    Figure 1.13 sequencediagram of deployment 1.3.1 Modifying Terraform Configuration We need to add the code to read from the external data source, allowing us to query the most recent Ubuntu AMI published to AWS. Edit the main.tf to look like Listing 1.3. Listing 1.3 Contents of main.tf provider "aws" { version = "2.65.0" region = "us-west-2" } data "aws_ami" "ubuntu" { #A most_recent = true filter { #B name = "name" values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"] } owners = ["099720109477"] #C } resource "aws_instance" "helloworld" { ami = data.aws_ami.ubuntu.id #D instance_type = "t2.micro" tags = { Name = "HelloWorld" } } #A Declaring an aws_ami data source with name “ubuntu” #B Setting a filter to select all AMI’s with name matching this regex expression #C canonical Ubuntu AWS account id #D chaining resources together Like resources, data sources are declared by creating an HCL object with type “data” having exactly two labels. The first label specifies the type of data source, and the second is the name of the data source. Together the type and name are referred to as the data source’s “identifier” and must be unique within a module. Figure 1.14 illustrates the syntax of a data source. 22
  • 31.
    Figure 1.14 Syntaxof a data source The contents of a data source code block are called query constraint arguments and behave exactly the same as arguments do for resources. The query constraint arguments are used to specify which resource(s) to fetch data from. Data sources are unmanaged resources that Terraform can read data from but doesn’t directly control. This is in contrast to managed resources, which are resources that Terraform directly controls the entire lifecycle of. 1.3.2 Applying Changes Let’s go ahead and apply our changes by having Terraform deploy an EC2 instance with the Ubuntu data source output value for AMI. Do this now by running terraform apply. Your log output will be as follows. $ terraform apply data.aws_ami.ubuntu: Refreshing state... #A An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # aws_instance.helloworld will be created + resource "aws_instance" "helloworld" { + ami = "ami-09dd2e08d601bff67" #B + arn = (known after apply) + associate_public_ip_address = (known after apply) + availability_zone = (known after apply) + cpu_core_count = (known after apply) + cpu_threads_per_core = (known after apply) + get_password_data = false + host_id = (known after apply) + id = (known after apply) + instance_state = (known after apply) + instance_type = "t2.micro" #C // skip some logs } Plan: 1 to add, 0 to change, 0 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. 23
  • 32.
    Only 'yes' willbe accepted to approve. Enter a value: #A read latest Ubuntu AMI #B set from output of data source Apply the changes by entering “yes” into the command line. After waiting a few minutes, your output will be: aws_instance.helloworld: Creating... aws_instance.helloworld: Still creating... [10s elapsed] aws_instance.helloworld: Still creating... [20s elapsed] aws_instance.helloworld: Still creating... [30s elapsed] aws_instance.helloworld: Creation complete after 34s [id=i-07cf7ed8831c72324] Apply complete! Resources: 1 added, 0 changed, 0 destroyed. As before, you can verify the changes for yourself by either navigating through the AWS console, or invoking terraform show. 1.3.3 Destroying Infrastructure Destroy the infrastructure that we created in the previous step by running terraform destroy. You’ll receive another manual confirmation: $ terraform destroy … - root_block_device { - delete_on_termination = true -> null - device_name = "/dev/sda1" -> null - encrypted = false -> null - iops = 100 -> null - volume_id = "vol-05366f9f647f56541" -> null - volume_size = 8 -> null - volume_type = "gp2" -> null } } Plan: 0 to add, 0 to change, 1 to destroy. #B Do you really want to destroy all resources? Terraform will destroy all your managed infrastructure, as shown above. There is no undo. Only 'yes' will be accepted to confirm. Enter a value: After manually confirming and waiting a few more minutes, the EC2 instance is now gone. aws_instance.example: Destroying... [id=i-07cf7ed8831c72324] aws_instance.example: Still destroying... [id=i-07cf7ed8831c72324, 10s elapsed] aws_instance.example: Still destroying... [id=i-07cf7ed8831c72324, 20s elapsed] aws_instance.example: Still destroying... [id=i-07cf7ed8831c72324, 30s elapsed] aws_instance.example: Destruction complete after 30s Destroy complete! Resources: 1 destroyed. 24
  • 33.
    1.4 Fireside Chat Inthis introductory chapter, not only did we discuss what Terraform is, and how it compares to other IaC tools, we also performed two real-world deployments. The first is the de-facto “Hello World!” of Terraform, while the second is my personal favorite, because it throws in an additional data source to demonstrate the dynamic capabilities of Terraform. In the next few chapters, we will go through the fundamentals of how Terraform works, as well as the major constructs and syntax elements of the Terraform HCL language. This builds up to chapter 4 when we deploy a complete load balanced web server and application onto AWS. Stick with it, and I promise it will be worth it! 1.5 Summary • Terraform is a declarative IaC provisioning tool. It can deploy resources onto any public or private cloud. • Terrraform is: 1) a provisioning tool 2) easy to use 3) free and open source 4) declarative 5) cloud agnostic 6) expressive and extensible • The major configuration elements of Terraform are resources, data sources and providers • Code blocks can be chained together to perform dynamic deployments • To deploy a Terraform project you first write configuration code, then configure providers and other input variables, terraform init, and finally terraform apply. Clean-up is done with terraform destroy. ©Manning Publications Co. To comment go to liveBook 25
  • 34.
    Lifecycle of aTerraform Resource This chapter covers: • Generating and applying execution plans • Analyzing when function hooks are triggered by Terraform • Utilizing the Local provider to create and manage files • Simulating, detecting, and correcting for configuration drift • Understanding the basics of Terraform state management When you do away with all its bells and whistles, Terraform is a surprisingly simple technology. Fundamentally, Terraform is a glorified state management tool that performs CRUD operations (create, read, update, delete) on managed resources. Oftentimes managed resources will be cloud- based resources, but they don’t have to be. Anything that implements CRUD can be represented as a Terraform resource, so long as there is sufficient desire and motivation to do so. In this chapter we will deep-dive into the internals of Terraform by walking through the lifecycle of a single resource. We could use any resource for this task, but to keep things simple, we’ll use a resource that doesn’t call any remote network APIs. These special sorts of resources are called local- only resources and only exist within the confines of Terraform, or the machine running Terraform. Local-only resources typically serve a marginal purpose, such as to glue together “real” infrastructure objects, but they also make a great teaching aid. Examples of local-only resources include private keys, self-signed TLS certificates, and random ids. We will be using the local_file resource from the Local provider for Terraform to create, read, update, and delete a text file containing the first few passages of Sun Tzu’s, “The Art of War”. 26 2
  • 35.
    2.1 Process Overview local_fileis a resource from the Local provider, and it allows us to create and manage text files with Terraform. We’ll use it to create an art_of_war.txt file containing the first two stanzas of Sun Tzu’s, “The Art of War”. Our high-level architecture diagram is illustrated in figure 2.1. Figure 2.1 Inputs and outputs of the Sun Tzu scenario NOTE Although a text file isn’t normally considered “infrastructure”, you can still deploy it in the same way you would an EC2 instance. Does that mean that it’s “infrastructure”? Does the distinction even matter anymore? I’ll leave it for you to decide. First, we’ll create the resource. Next, we’ll simulate configuration drift and perform an update. Finally, we’ll clean up with a terraform destroy. Out procedure is shown in figure 2.2. Figure 2.2 First, we create the resource [1], then we read and update it [2] and [3]. Finally, we delete the resource [4]. art_of_war.txt (modified) Created start end Read 3) Update 2) Read 4) Delete 1) Create Deleted art_of_war.txt art_of_war.txt (modified) 27
  • 36.
    2.1.1 Lifecycle FunctionHooks All Terraform resources implement the resource schema interface. The resource schema mandates, among other things, that resources define CRUD functions hooks, one each for Create(), Read(), Update(), and Delete(). Terraform invokes these hooks when certain conditions are met. Generally speaking, Create() is called during resource creation, Read() during plan generation, Update() during resource updates, and Delete() during deletes. There’s a bit more to it than that, but you get the idea. Because it’s a resource, local_file also implements the resource schema interface. That means that it defines function hooks for Create(), Read(), Update(), and Delete(). This is in contrast to the local_file data source which only implements Read() (see figure 2.3). In this scenario I will point out when and why each of these function hooks gets called. Figure 2.3 The two resources in the Local provider are a managed resource and an unmanaged data source. The managed resource implements full CRUD, while the data source only implements Read() 2.2 Declaring a Local File Resource Let’s get started by creating a new workspace for Terraform. Do this by creating a new empty directory somewhere on your computer. Make sure the folder doesn’t contain any existing configuration code, because Terraform concatenates all “.tf” files together. In this workspace, make a new file called main.tf and add the following code from Listing 2.1. Listing 2.1 Contents of main.tf terraform { #A required_version = "~> 0.13" required_providers { local = "~> 1.4" } } 28
  • 37.
    resource "local_file" "literature"{ filename = "art_of_war.txt" content = <<-EOT #B Sun Tzu said: The art of war is of vital importance to the State. It is a matter of life and death, a road either to safety or to ruin. Hence it is a subject of inquiry which can on no account be neglected. EOT } #A Terraform settings blocks #B Heredoc syntax for multi-line strings TIP The <<- sequence indicates an indented heredoc string. Anything between the opening identifier and the closing identifier (EOT) is interpreted literally. Leading whitespace, however, is ignored (unlike traditional heredoc syntax). There are two configuration blocks in Listing 2.1. The first block: terraform {…} is a special configuration block responsible for configuring Terraform itself. Its primarily use is for version locking your code, but it can also configure where you state file is stored, and where providers are downloaded (we will discuss this more in chapter 6). As a reminder, the Local provider has not yet been installed yet, to do that we first need to perform terraform init. The second configuration block is a resource block that declares a local_file resource. It provisions a text file with a given filename and content value. In this scenario, the content will contain the first couple stanzas of Sun Tzu’s masterpiece, “The Art of War”, and the filename will be art_of_war.txt. We use heredoc syntax (<<-) to input a multi-line string literal. 2.3 Initializing the Workspace At this point, Terraform isn’t aware of your workspace, let alone that it’s supposed to create or manage anything, because it hasn’t yet been initialized. Let’s remedy that with a good ol’ terraform init. terraform init always must be run at least once, but you’ll have to run it again each time you add new provider or module dependency. Don’t fret about when to run terraform init, because Terraform will always remind you. Moreover, terraform init is an idempotent command, which means you can call it as many times as you want in a row, and it will have no side effects. Run terraform init now: $ terraform init Initializing the backend... Initializing provider plugins... - Finding latest version of hashicorp/local... - Installing hashicorp/local v1.4.0... - Installed hashicorp/local v1.4.0 (signed by HashiCorp) The following providers do not have any version constraints in configuration, so the latest version was installed. To prevent automatic upgrades to new major versions that may contain breaking 29
  • 38.
    changes, we recommendadding version constraints in a required_providers block in your configuration, with the constraint strings suggested below. * hashicorp/local: version = "~> 1.4.0" Terraform has been successfully initialized! You may now begin working with Terraform. Try running "terraform plan" to see any changes that are required for your infrastructure. All Terraform commands should now work. If you ever set or change modules or backend configuration for Terraform, rerun this command to reinitialize your working directory. If you forget, other commands will detect it and remind you to do so if necessary. After initialization, Terraform creates a hidden .terraform directory for installing plugins and modules. The directory structure for the current Terraform workspace is the following: . ├── main.tf └── .terraform └── plugins ├── registry.terraform.io │ └── hashicorp │ └── local │ └── 1.4.0 │ └── darwin_amd64 │ └── terraform-provider-local_v1.4.0_x4 └── selections.json Because we declared a local_file resource in main.tf, Terraform is smart enough to realize that there is an implicit dependency on the Local provider, so it looks it up and downloads it from the provider registry. You don’t have to declare an empty provider block ( i.e. provider "local" {}) unless you want to TIP version lock any providers you use, whether they are implicitly or explicitly defined to ensure that any deployment you make is always repeatable. 2.4 Generating an Execution Plan Before we create the local_file resource with a terraform apply, we can preview what Terraform intends to do by running terraform plan. You should always run terraform plan before deploying. I will often skip this step throughout to book for the sake of brevity, but you should still do it for yourself even if I do not call it out. terraform plan not only informs you what Terraform intends to do, it acts as a linter, letting you know of any syntax or dependency errors you might have. It’s a read only action that does not alter the state of deployed infrastructure, and like terraform init, it’s idempotent. Generate an execution plan now by running terraform plan. $ terraform plan Refreshing Terraform state in-memory prior to plan... The refreshed state will be used to calculate this plan, but will not be persisted to local or remote state storage. 30
  • 39.
    ------------------------------------------------------------------------ An execution planhas been generated and is shown below. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # local_file.literature will be created + resource "local_file" "literature" { + content = <<~EOT Sun Tzu said: The art of war is of vital importance to the State. It is a matter of life and death, a road either to safety or to ruin. Hence it is a subject of inquiry which can on no account be neglected. EOT + directory_permission = "0777" + file_permission = "0777" + filename = "art_of_war.txt" + id = (known after apply) #A } Plan: 1 to add, 0 to change, 0 to destroy. ------------------------------------------------------------------------ Note: You didn't specify an "-out" parameter to save this plan, so Terraform can't guarantee that exactly these actions will be performed if "terraform apply" is subsequently run. #A computed meta-attribute When might my plan fail? Terraform plans can fail for many reasons, such as if your configuration code is invalid, or if there’s a versioning issue, or even network related problems. Sometimes, albeit rarely, the plan will fail as a result of a bug in the provider’s source code. You need to carefully read whatever error message you receive to know for sure. For more verbose logs, you can turn on trace level logging by setting the environment variable TF_LOG to a non-zero value, e.g. export TF_LOG=1 As you can see from the output, Terraform is letting us know that it wants to create a local_file resource. Besides the attributes that we supply, it also wants to set a computed attribute called id. id is a meta-attribute that Terraform sets on all resources. It’s used to uniquely identify real-world resources, as well as internal calculations. Although this particular terraform plan should have exited quickly, some plans can take a while to complete. It all has to do with how many resources you are deploying, as well as how how many resources you already have in your state file. 31
  • 40.
    TIP if terraformplan is running slow, turn off trace level logging and consider increasing parallelism (- parallelism=n) Although the output of the plan we performed is fairly straightforward, there’s a lot going on that you should be aware of. The three main stages of a terraform plan are: 1. Reading Configuration and State – Terraform first reads your configuration and state files (if they exist). 2. Determine Actions to Take – Terraform performs a calculation to determine what needs to be done to achieve your desired state. This can be on of: Create(), Read(), Update(), Delete() and No-op. 3. Outputting Plan – Actions need to occur in the right order, to avoid dependency problems. If we had more than one resource, this would be a bigger deal. Figure 2.4 is a detailed flow diagram showing what happens during terraform plan. 32
  • 41.
    Figure 2.4 Stepsthat Terraform performs when generating an execution plan for a new deployment We haven’t yet talked about the dependency graph, but it’s a big part of Terraform and every terraform plan generates one for respecting implicit and explicit dependencies between resource and provider nodes. Terraform has a special command for visualizing the dependency graph: terraform graph. This command outputs a dotfile which can be converted to a digraph using a variety of tools. You can see the resultant graph I produced for this workspace from the preceding DOT graph1 in figure 2.5. 1DOT is a graph description language. DOT graphs are files with the filename extension dot. Various programs can process and render DOT files in graphical form end start Read Configuration Create() Resource in State? Read() Delete() Read State For Each Resource No Yes main.tf terraform.tf state Has Changes? Output Plan No-op No Is Destroy Plan? Update() No Yes Yes 33
  • 42.
    Figure 2.5 Dependencygraph for this workspace The dependency graph for this workspace has a few nodes, including one for the Local provider, one for the local_file resource, and a few other meta nodes that correspond to housekeeping actions. During an apply, Terraform will walk the dependency graph to ensure that everything is done in the right order. We’ll examine a more complex digraph in the next chapter. 2.4.1 Inspecting the Plan It’s possible to read the output of a terraform plan in JSON format, which could be useful when integrating with custom tools, or enforcing Policy as Code (we will discuss Policy as Code in chapter 12). First, save the output of the plan by setting the optional -out flag: $ terraform plan -out plan.out Refreshing Terraform state in-memory prior to plan... The refreshed state will be used to calculate this plan, but will not be persisted to local or remote state storage. ------------------------------------------------------------------------ An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # local_file.literature will be created + resource "local_file" "literature" { + content = <<~EOT Sun Tzu said: The art of war is of vital importance to the State. It is a matter of life and death, a road either to safety or to ruin. Hence it is a subject of inquiry which can on no account be neglected. EOT + directory_permission = "0777" 34
  • 43.
    + file_permission ="0777" + filename = "art_of_war.txt" + id = (known after apply) } Plan: 1 to add, 0 to change, 0 to destroy. ------------------------------------------------------------------------ This plan was saved to: plan.out To perform exactly these actions, run the following command to apply: terraform apply "plan.out" plan.out is now saved as a binary file, so the next step is to convert it to JSON format. This can be done (rather unintuitively) by ingesting it with terraform show and piping it to an output file $ terraform show -json plan.out > plan.json Finally, we have the plan in human readable format: $ cat plan.json {"format_version":"0.1","terraform_version":"0.13.0","planned_values":{"root_module":{"resources" :[{"address":"local_file.literature","mode":"managed","type":"local_file","name":"literatur e","provider_name":"registry.terraform.io/hashicorp/local","schema_version":0,"values":{"co ntent":"Sun Tzu said: The art of war is of vital importance to the State.nnIt is a matter of life and death, a road either to safety or to nruin. Hence it is a subject of inquiry which can on no account benneglected.n","content_base64":null,"directory_permission":"0777","file_permission":"07 77","filename":"art_of_war.txt","sensitive_content":null}}]}},"resource_changes":[{"address ":"local_file.literature","mode":"managed","type":"local_file","name":"literature","provide r_name":"registry.terraform.io/hashicorp/local","change":{"actions":["create"],"before":nul l,"after":{"content":"Sun Tzu said: The art of war is of vital importance to the State.nnIt is a matter of life and death, a road either to safety or to nruin. Hence it is a subject of inquiry which can on no account benneglected.n","content_base64":null,"directory_permission":"0777","file_permission":"07 77","filename":"art_of_war.txt","sensitive_content":null},"after_unknown":{"id":true}}}],"c onfiguration":{"root_module":{"resources":[{"address":"local_file.literature","mode":"manag ed","type":"local_file","name":"literature","provider_config_key":"local","expressions":{"c ontent":{"constant_value":"Sun Tzu said: The art of war is of vital importance to the State.nnIt is a matter of life and death, a road either to safety or to nruin. Hence it is a subject of inquiry which can on no account benneglected.n"},"filename":{"constant_value":"art_of_war.txt"}},"schema_version":0}]}}} 2.5 Creating the Local File Resource Now let’s run terraform apply to compare the output against the generated execution plan. The command and output are shown below. $ terraform apply An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # local_file.literature will be created 35
  • 44.
    + resource "local_file""literature" { + content = <<~EOT Sun Tzu said: The art of war is of vital importance to the State. It is a matter of life and death, a road either to safety or to ruin. Hence it is a subject of inquiry which can on no account be neglected. EOT + directory_permission = "0777" + file_permission = "0777" + filename = "art_of_war.txt" + id = (known after apply) } Plan: 1 to add, 0 to change, 0 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: Do they look similar? It’s no coincidence. The execution plan generated by terraform apply is the exactly the same as the plan generated by terraform plan. In fact, you could even apply the results of terraform plan explicitly, i.e: $ terraform plan -out plan.out && terraform apply "plan.out" TIP separating the plan and apply like this could be useful when running Terraform in automation Regardless of how you generate an execution plan, it’s always a good idea to review the contents of the plan before applying. During an apply, Terraform creates and destroys real infrastructure, which of course has real-world consequences. If you are not careful, then a simple mistake or typo could wipe out all your entire infrastructure before you even have a chance to react. For this workspace, there’s nothing to worry about because we aren’t creating “real” infrastructure anyways. Returning to the command line, enter “yes” at the prompt to approve the manual confirmation step. Your output will be as follows. $ terraform apply … Enter a value: yes local_file.literature: Creating... local_file.literature: Creation complete after 0s [id=907b35148fa2bce6c92cba32410c25b06d24e9af] Apply complete! Resources: 1 added, 0 changed, 0 destroyed. Two files were created as a result of this command, art_of_war.txt, and terraform.tfstate file. You current directory (excluding hidden files) will now be: . ├── art_of_war.txt ├── main.tf 36
  • 45.
    └── terraform.tfstate The terraform.tfstatefile you see here is the state file that Terraform uses to keep track of the resources it manages. It’s used to perform diffs during the plan and detect configuration drift. Snippet 2.1 shows what the current state file looks like. Snippet 2.1 Contents of terraform.tfstate { "version": 4, #A "terraform_version": "0.13.0", "serial": 1, "lineage": "0fe3ddc2-1d7e-1116-d554-c030de42d9ea", "outputs": {}, "resources": [ { "mode": "managed", "type": "local_file", "name": "literature", "provider": "provider["registry.terraform.io/hashicorp/local"]", "instances": [ { "schema_version": 0, "attributes": { #A "content": "Sun Tzu said: The art of war is of vital importance to the State.nnIt is a matter of life and death, a road either to safety or to nruin. Hence it is a subject of inquiry which can on no account benneglected.n", "content_base64": null, "directory_permission": "0777", "file_permission": "0777", "filename": "art_of_war.txt", "id": "907b35148fa2bce6c92cba32410c25b06d24e9af", #B "sensitive_content": null }, "private": "bnVsbA==" } ] } ] } #A Stateful information about the art_of_war.txt resource #B The unique id of resource WARNING It’s important to not edit, delete or otherwise tamper with the terraform.tfstate file, or else Terraform could potentially lose track of the resources it manages. It is possible to restore a corrupted or missing state file, but it’s difficult and time consuming to do so. We can verify that art_of_war.txt matches what we expect by cat-ing the file. The command and output are shown below. $ cat art_of_war.txt Sun Tzu said: The art of war is of vital importance to the State. It is a matter of life and death, a road either to safety or to 37
  • 46.
    ruin. Hence itis a subject of inquiry which can on no account be neglected. Okay, a file was created, but how did Terraform actually do that? During the apply, Terraform called Create()on local_file (see figure 2.6). Figure 2.6 Calling Create() on local_file during terraform apply To give you an idea of what Create() does, here is the actual source code from the provider: NOTE relax and don’t worry about understanding the code just yet. We will come back to in in chapter 10. Listing 2.2 Local File Create func resourceLocalFileCreate(d *schema.ResourceData, _ interface{}) error { content, err := resourceLocalFileContent(d) if err != nil { return err } destination := d.Get("filename").(string) destinationDir := path.Dir(destination) if _, err := os.Stat(destinationDir); err != nil { dirPerm := d.Get("directory_permission").(string) dirMode, _ := strconv.ParseInt(dirPerm, 8, 64) if err := os.MkdirAll(destinationDir, os.FileMode(dirMode)); err != nil { return err } } filePerm := d.Get("file_permission").(string) Terraform Invokes resource "local_file" Create() Read() Update() Delete() Local Provider 38
  • 47.
    fileMode, _ :=strconv.ParseInt(filePerm, 8, 64) if err := ioutil.WriteFile(destination, []byte(content), os.FileMode(fileMode)); err != nil { return err } checksum := sha1.Sum([]byte(content)) d.SetId(hex.EncodeToString(checksum[:])) return nil } 2.6 Performing No-Op Terraform can read existing resources to ensure they are in a desired configuration state. One way to do this is by running terraform plan. When terraform plan is run, Terraform will call Read() on each resource in the state file. Since our state file has only one resource, Terraform calls Read() on just local_file. Figure 2.7 shows what this looks like. Figure 2.7 terraform plan calls Read() on the local_file resource Let’s run the terraform plan now: $ terraform plan Refreshing Terraform state in-memory prior to plan... The refreshed state will be used to calculate this plan, but will not be persisted to local or remote state storage. local_file.literature: Refreshing state... [id=907b35148fa2bce6c92cba32410c25b06d24e9af] ------------------------------------------------------------------------ Terraform Invokes resource "local_file" Create() Read() Update() Delete() Local Provider 39
  • 48.
    No changes. Infrastructureis up-to-date. This means that Terraform did not detect any differences between your configuration and real physical resources that exist. As a result, no actions need to be performed. There are no changes, as we would expect. The flow diagram for how a No-Op was decided is shown in figure 2.8. You can see that Read() is called as part of the state refresh process. Figure 2.8 Steps that Terraform performs when generating an execution plan for an existing deployment already in the desired state Finally, here is the code from the provider that is performing Read(). Again, don’t worry too much about completely understanding it. Listing 2.3 Local File Read end start Read Configuration Create() Resource in State? Read() Delete() Read State For Each Resource No Yes main.tf terraform.tf state Has Changes? Output Plan No-op No Is Destroy Plan? Update() No Yes Yes 40
  • 49.
    func resourceLocalFileRead(d *schema.ResourceData,_ interface{}) error { // If the output file doesn't exist, mark the resource for creation. outputPath := d.Get("filename").(string) if _, err := os.Stat(outputPath); os.IsNotExist(err) { d.SetId("") return nil } // Verify that the content of the destination file matches the content we // expect. Otherwise, the file might have been modified externally and we // must reconcile. outputContent, err := ioutil.ReadFile(outputPath) if err != nil { return err } outputChecksum := sha1.Sum([]byte(outputContent)) if hex.EncodeToString(outputChecksum[:]) != d.Id() { d.SetId("") return nil } return nil } 2.7 Updating the Local File Resource You know what’s better than having a file containing the first two stanzas of the “The Art of War”? That’s right, having a file containing the first four stanzas of “The Art of War”! Updates are integral to Terraform and it’s important to understand how they work. Update your main.tf code to look like Listing 2.4. Listing 2.4 Contents of main.tf terraform { required_version = "~> 0.13" required_providers { local = "~> 1.4" } } resource "local_file" "literature" { filename = "art_of_war.txt" content = <<-EOT #A Sun Tzu said: The art of war is of vital importance to the State. It is a matter of life and death, a road either to safety or to ruin. Hence it is a subject of inquiry which can on no account be neglected. The art of war, then, is governed by five constant factors, to be taken into account in one's deliberations, when seeking to determine the conditions obtaining in the field. These are: (1) The Moral Law; (2) Heaven; (3) Earth; (4) The Commander; (5) Method and discipline. EOT 41
  • 50.
    } #A Adding twoadditional stanzas There isn’t a special command for performing an update; all that needs happen is run a terraform apply. Before we do that though, let’s run terraform plan to see what the generated execution plan looks like. The command and output are shown below. $ terraform plan Refreshing Terraform state in-memory prior to plan... #A The refreshed state will be used to calculate this plan, but will not be persisted to local or remote state storage. local_file.literature: Refreshing state... [id=907b35148fa2bce6c92cba32410c25b06d24e9af] ------------------------------------------------------------------------ An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: -/+ destroy and then create replacement Terraform will perform the following actions: # local_file.literature must be replaced -/+ resource "local_file" "literature" { ~ content = <<~EOT # forces replacement #B Sun Tzu said: The art of war is of vital importance to the State. It is a matter of life and death, a road either to safety or to ruin. Hence it is a subject of inquiry which can on no account be neglected. + + The art of war, then, is governed by five constant factors, to be + taken into account in one's deliberations, when seeking to + determine the conditions obtaining in the field. + + These are: (1) The Moral Law; (2) Heaven; (3) Earth; (4) The + Commander; (5) Method and discipline. EOT directory_permission = "0777" file_permission = "0777" filename = "art_of_war.txt" ~ id = "907b35148fa2bce6c92cba32410c25b06d24e9af" -> (known after apply) } Plan: 1 to add, 0 to change, 1 to destroy. ------------------------------------------------------------------------ Note: You didn't specify an "-out" parameter to save this plan, so Terraform can't guarantee that exactly these actions will be performed if "terraform apply" is subsequently run. #A Read() happens first #B Force new recreates resource As you can see, Terraform has noticed that we’ve altered the content attribute, and is therefore proposing to destroy the old resource and create a new resource in its stead. The reason why this is done rather than update the attribute in place, is because content is marked as a force new attribute, 42
  • 51.
    which means ifyou change it, the whole resource is tainted. In order to achieve the new desired state, Terraform must recreate the resource from scratch. This is a classic example of immutable infrastructure, although not all attributes of managed Terraform resource will behave like this. In fact, most resources have regular in-place (i.e. mutable) updates. The difference between mutable and immutable updates is shown in figure 2.9. Figure 2.9 Difference between an immutable and mutable update “Force New” updates sound terrifying! Although destroying and recreating tainted infrastructure may sound disturbing at first, terraform plan will always let you know what Terraform is going to do ahead of time, so it will never come as a surprise. Furthermore, Terraform is great at creating repeatable environments, so recreating a single piece of infrastructure is not a problem. The only potential downside is if there is downtime to your service. If you absolutely cannot tolerate anydowntime, then stick around for chapter 9 when we cover how to perform zero downtime deployments with Terraform. The flow chart for the execution plan is illustrated by figure 2.10. Immutable Update: Force New Mutable Update: Normal Behavior Terraform Invokes resource "local_file" Create() Read() Update() Delete() 1) First call Delete() 2) Then call Create() Invokes resource "local_file" Create() Read() Update() Delete() Terraform 43
  • 52.
    Figure 2.10 Stepsthat Terraform performs when generating an execution plan for an update Go ahead and apply the proposed changes from the execution plan by running the command: terraform apply -auto-approve. The optional -auto-approve flag tells Terraform to skip the manual approval step and immediately apply changes. WARNING -auto-approve can be dangerous if you have not already reviewed the results of the plan $ terraform apply -auto-approve local_file.literature: Refreshing state... [id=907b35148fa2bce6c92cba32410c25b06d24e9af] local_file.literature: Destroying... [id=907b35148fa2bce6c92cba32410c25b06d24e9af] local_file.literature: Destruction complete after 0s local_file.literature: Creating... local_file.literature: Creation complete after 0s [id=657f681ea1991bc54967362324b5cc9e07c06ba5] Apply complete! Resources: 1 added, 0 changed, 1 destroyed. end start Read Configuration Create() Resource in State? Read() Delete() Read State For Each Resource No Yes main.tf terraform.tf state Has Changes? Output Plan No-op No Is Destroy Plan? Update() No Yes Yes 44
  • 53.
    You can verifythat the file is now up to date by cat-ing the file once more. The command and output are as follows: $ cat art_of_war.txt Sun Tzu said: The art of war is of vital importance to the State. It is a matter of life and death, a road either to safety or to ruin. Hence it is a subject of inquiry which can on no account be neglected. The art of war, then, is governed by five constant factors, to be taken into account in one's deliberations, when seeking to determine the conditions obtaining in the field. These are: (1) The Moral Law; (2) Heaven; (3) Earth; (4) The Commander; (5) Method and discipline. 2.7.1 Detecting Configuration Drift So far, we’ve been able to create and update a text file resource. But what happens if there are ad hoc changes to the file through means outside of Terraform? Configuration drift is a common occurrence in situations where there are multiple privileged users on the same file system. If you have cloud-based resources, this would be equivalent to someone making point and click changes to deployed infrastructure in the console. How does Terraform deal with configuration drift? It does so by calculating the difference between current state and desired state and performing an update. We can simulate configuration drift by directly modifying art_of_war.txt. In this file, replace all occurrences of “Sun Tzu” with “Napolean”. The contents of our art_of_war.txt file will now be: Napoleon said: The art of war is of vital importance to the State. It is a matter of life and death, a road either to safety or to ruin. Hence it is a subject of inquiry which can on no account be neglected. The art of war, then, is governed by five constant factors, to be taken into account in one's deliberations, when seeking to determine the conditions obtaining in the field. These are: (1) The Moral Law; (2) Heaven; (3) Earth; (4) The Commander; (5) Method and discipline. This misquote is patently untrue, so we’d like Terraform to detect that configuration drift has occurred and fix it. Run a terraform plan to see what Terraform has to say for itself. $ terraform plan Refreshing Terraform state in-memory prior to plan... The refreshed state will be used to calculate this plan, but will not be persisted to local or remote state storage. local_file.literature: Refreshing state... [id=657f681ea1991bc54967362324b5cc9e07c06ba5] ------------------------------------------------------------------------ 45
  • 54.
    An execution planhas been generated and is shown below. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # local_file.literature will be created + resource "local_file" "literature" { + content = <<~EOT Sun Tzu said: The art of war is of vital importance to the State. It is a matter of life and death, a road either to safety or to ruin. Hence it is a subject of inquiry which can on no account be neglected. The art of war, then, is governed by five constant factors, to be taken into account in one's deliberations, when seeking to determine the conditions obtaining in the field. These are: (1) The Moral Law; (2) Heaven; (3) Earth; (4) The Commander; (5) Method and discipline. EOT + directory_permission = "0777" + file_permission = "0777" + filename = "art_of_war.txt" + id = (known after apply) } Plan: 1 to add, 0 to change, 0 to destroy. ------------------------------------------------------------------------ Note: You didn't specify an "-out" parameter to save this plan, so Terraform can't guarantee that exactly these actions will be performed if "terraform apply" is subsequently run. Wait, what just happened here? It would appear Terraform has forgotten the resource it manages even exists and is therefore proposing to create a new resource. In fact, Terraform has not forgotten that the resource it manages still exists. The resource is still present in the state file, and you can verify by running terraform show: $ terraform show # local_file.literature: resource "local_file" "literature" { content = <<~EOT Sun Tzu said: The art of war is of vital importance to the State. It is a matter of life and death, a road either to safety or to ruin. Hence it is a subject of inquiry which can on no account be neglected. The art of war, then, is governed by five constant factors, to be taken into account in one's deliberations, when seeking to determine the conditions obtaining in the field. These are: (1) The Moral Law; (2) Heaven; (3) Earth; (4) The Commander; (5) Method and discipline. EOT 46
  • 55.
    Other documents randomlyhave different content
  • 56.
    concerning "Miss Wayne."His anxiety as to her health appeared to do great credit to his goodness of heart. Between Bill and Pete there was always frank discussion, in private, although on the subject of the social secretary it flowed with perhaps a trifle less freedom. So greatly had the party furthered the innocent dreams of Aunt Caroline that she lost no time in urging further assaults and triumphs in the new world that had been opened to her nephew. "My dear," she said to Mary, "I think it would be well to give a small dinner—very soon." Mary agreed that it would be very well, indeed. "I confess that I have certain ambitions," said Aunt Caroline. "I would like to have William extend his circle somewhat, and among people whom it would be a very fine thing for him to know." Mary carelessly approved that, too. "It would be wonderful, my dear, if we could have Mrs. Rokeby-Jones as a guest." Mary glanced sharply at Aunt Caroline. She was suddenly trembling with a premonition. "But do we know Mrs. Rokeby-Jones?" she asked. Aunt Caroline smiled confidently. "You do, my dear." To which, of course, Mary was forced to nod an assent. "I believe it would be all right for you to speak to her about it," added Aunt Caroline. "She thinks so highly of you that I am sure she would not consider it strange in the least. And besides, there is always the Marshall name." The Marshall name was Aunt Caroline's shield and buckler at all times, and since Bill's party she had come to regard it as a password of potent magic.
  • 57.
    Mary felt suddenlyweak, but she fought to avoid disclosure of the fact. Mrs. Rokeby-Jones! What could she say? Already, in the case of Bill's party, threads of acquaintanceship that were so tenuous as scarcely to be threads at all had been called upon to bear the strain of invitations, and, much to her astonishment, they had borne the strain. Thereby emboldened, Aunt Caroline was now seeking to bridge new gulfs. But why did she have to pick Mrs. Rokeby-Jones? Was it because—— Mary tried to put from her mind the unworthy suspicion that Aunt Caroline was still delving as to the facts concerning what they said about the elder daughter. But whatever the motive, whether it be hidden or wholly on the surface, booted little to Mary. It was an impossible proposal. "She will recall you, of course," Aunt Caroline was saying. "And I am sure that she knows the Marshalls. In fact, I have an impression that at one time William's mother——" "But are you sure she hasn't gone to Newport?" asked Mary, desperately. "I saw her name in the paper only this morning, my dear. She was entertaining last night at the theater." Mary began wadding a handkerchief. "And perhaps she could suggest somebody else," added Aunt Caroline. "At any rate, suppose you get in touch with her and let me know what she says." Mary went up-stairs to nurse her misery. It was out of the question to refuse, yet she dreaded to obey. She could not call upon Mrs. Rokeby-Jones; even a blind person could tell the difference between Nell Norcross and Mary Wayne. She could not get Nell to go, for Nell was still overcome by her adventures at the party. She could not send a letter, because the writing would betray her. She could telephone, perhaps; but would Mrs. Rokeby-Jones detect a strange voice? And even if she succeeded in imposture over the wire, how was she to approach the matter of an invitation to the home of a stranger?
  • 58.
    After much anguishedthought, she decided upon the telephone. "But even if she consents," murmured Mary, "I'll never dare meet her face to face." A connection was made in disconcertingly short time and Mary, after talking with a person who was evidently the butler, held the wire, the receiver trembling in her fingers. And then a clear, cool voice—— "Well? Who is it?" "This—this is Miss Norcross talking," and then Mary held her breath. "Miss who?" "Norcross. Miss Norcross." "Do I know you? Have I met you?" said the voice on the wire. "This is Nell Norcross." Mary was raising her voice. "Yes; I hear the name. But I don't place you." "Miss Norcross—formerly your secretary." There was an instant's pause. Then the cool voice again: "Perhaps you have the wrong number. This is Mrs. Rokeby-Jones talking." "Then I have the right number," said Mary, wrinkling her forehead in perplexity. "I used to be your secretary—Miss Norcross." "But I have never had a secretary by that name," said Mrs. Rokeby- Jones. Mary gasped. "But the reference you gave me! Don't you remember?" "I have an excellent memory," the voice said. "I have never employed any person named Miss Norcross, I never knew anybody by that name and I certainly never supplied a reference to any such person. You are laboring under some mistake." "But—but——"
  • 59.
    "Good-by." And Mrs. Rokeby-Joneshung up. Mary slowly replaced the receiver and sat staring at the telephone. A blow between the eyes could not have stunned her more effectually. Mrs. Rokeby-Jones had repudiated her reference! Presently she rallied. She ran to her own room and began dressing for the street. She felt that she must escape from the house in order to think. At all costs she must avoid Aunt Caroline until she had been able to untangle this dismaying snarl. A few minutes later she made certain of that by slipping down the rear staircase and leaving the house by a side entrance. Fifteen minutes later she was at Nell's boarding-house, impatiently ringing the bell. Nell was propped up in a rocker, looking very wan as Mary entered, but brightening as she recognized her visitor. Mary drew a chair and sat opposite. "A most embarrassing thing has happened," she said. "I have just had Mrs. Rokeby-Jones on the telephone." Nell stifled an exclamation. "And she doesn't remember me—or you, rather—or anybody named Norcross!" "Oh, my dear!" "It's the truth, Nell. Oh, I never felt so queer in my life." Nell moistened her lips and stared with incredulous eyes. "What—what made you call her up?" she faltered. "Because I couldn't help it. I was forced to." And Mary explained the further ambitions of Aunt Caroline and what they had led to.
  • 60.
    "Oh, it wasshocking, Nell! What did she mean? How dared she do it?" "I—I—— Oh, Mary!" "But how could she?" persisted Mary. "That's what I don't understand. Even if my voice sounded strange I don't see how she could. Why did she deny that she ever wrote a reference?" Nell Norcross pressed a hand to her lips to keep them from quivering. In her eyes there was something that suggested she had seen a ghost. Slowly she began to rock to and fro in her chair, making a gurgling in her throat. Then she whimpered. "B-because she never wrote it!" she moaned. "Why—Nell. Oh, Heavens!" Mary suddenly seemed to have become as frightened as Nell. She glanced quickly over her shoulder, as though expecting to face an eavesdropper. Then she sprang up, went to the door and locked it. "Nell Norcross, tell me what you mean!" "She—she didn't write it. Oh, Mary! Oh—please!" For Mary had taken her by the shoulders and was pushing her rigidly against the back of the chair. "Who wrote it?" demanded Mary. "I did." It required several seconds for Mary to absorb this astounding confession. Then: "You forged it?" "I—I wrote it. It isn't forgery, is it? I won't go to jail, will I? Oh, Mary, don't let them——" Mary shook her somewhat roughly. "Tell me more about it," she commanded. "Did you lose the reference she gave you? Or did she refuse to give you one?"
  • 61.
    Nell shook herhead miserably. "It's worse than that," she sobbed. "I—I never set eyes on the woman in my life." Mary collapsed into her own chair. She seemed to hear the cool, clear voice of Mrs. Rokeby-Jones calmly denying. Now it was taking an accusative tone. She flushed to a deep red. The memory of that telephone conversation appalled her. "But the other references?" she managed to whisper. "All the same." "All! You wrote them yourself?" Nell answered with a feeble nod. "Every one of them?" "Every one." "And do you know any of the women who—whose names are signed?" "Two—one of them by sight." "Nell Norcross!" But Nell had reached a fine stage of tears and there was nothing to be had out of her for several minutes. Then Mary managed to calm her. "Now, tell me about it," she said. "And stop crying, because it won't do a bit of good." Nell swallowed a sob and mopped at her eyes. "I—I was in the same fix that you were," she said shakily. "Only I guess I was that way longer. I didn't have any job, and I couldn't get one—without references. You understand?" Mary nodded. Indeed she did understand.
  • 62.
    "I worked ina furrier's; one of the Fifth Avenue places. Stenographer, and I helped on the books, too. And then—well, I had to leave. It wasn't my fault; honestly, Mary. I couldn't stay there because of the way he acted. And of course I wouldn't—I couldn't— ask him for references." Nell was quieting down, and Mary nodded again, to encourage her. "Well you know how it is trying to get a job without any references. No decent place will take you. I kept it up for weeks. Why, I couldn't even get a trial. When I couldn't get references, or even refer them to the last place, they'd look at me as if I were trying to steal a job." "I know," murmured Mary. "They'd look at me, too." "So I got desperate. You know what that is, too. I had to have a job or starve. And I had to have references—so I wrote them!" "Oh, Nell!" Nell looked up defiantly. "Well, what else could I do? And I didn't harm anybody, did I? I didn't say anything about myself that wasn't true. All I did was to use some good names. And not one of them would ever have known if you hadn't called that woman up on the telephone. They were all customers of the place where I worked. I knew their names and addresses. I couldn't go and ask them to give me references, could I? I couldn't even do that with the one I'd spoken to. So I got some stationery and wrote myself references—that's all." Mary pondered the confession. "If it had only been one reference," she began, "but you had five or six." "I only intended to write one," declared Nell. "But what was the use of being a piker, I thought. So—well I plunged." "Yes; you plunged," agreed Mary. "And now look at the fix I'm in." "But you've got a wonderful place!"
  • 63.
    Mary smiled bitterly. "Oh,yes; it's wonderful enough. I'm not only holding it under a false name, but now it turns out that even the references were false. And"—she looked sharply at Nell as something else occurred to her —"perhaps it doesn't end even there. Tell me—is your name really Nell Norcross?" "Why, Mary Wayne! Of course it is!" "Well, how could I be sure. I'm false; the references are false. Why couldn't your name be false, too? That would be the finishing touch; that would leave me—nowhere. And I'm just about there, as it is." "But I am Nell Norcross, I tell you. I can prove that." "Oh, I suppose so," said Mary, wearily. "So am I Nell Norcross, according to the references. If you've committed a crime, I suppose I have, too. They call it compounding it, don't they? Oh, we're both in; I dare say I'm in deeper than you, because I've been taking money for it." "You haven't cheated them, have you? You've worked for it." "Yes, I've worked. But—why, in Heaven's name, Nell, didn't you tell me all this before I started?" "I was too sick." "You weren't too sick to give me the references and send me off to take the job." "But I was too sick not to have you take it," said Nell. "One of us had to go to work. And if I'd told you, you wouldn't have done it." "That's true enough," assented Mary. "I wouldn't have dared. It took all the nerve I had, as it was. But now what am I going to do?" "Why, you'll go right on sticking to your job, of course." "And keep on being a liar, and a hypocrite, and a falsifier, and maybe some kind of a forger—— Why, I believe I am a forger! I signed your name to some kind of a bail bond!"
  • 64.
    "Oh, well; youtold me the case was settled, Mary. So you don't have to worry about that." "I can worry about my conscience if I like," declared Mary, resentfully. "Yes; but you can't eat your conscience, or buy clothes with it, or hire a room—or anything." Mary stared down at the floor for a while. "I suppose I've got to keep on taking care of you until you're well," she remarked. Nell winced. "I—I hate to be a charity patient," she faltered. "I'll make it all up to you some time. But if you'll only keep on for the present——" Mary reached forward impulsively and took her hands. "I don't mean to suggest that," she said. "You're not a charity patient; you got my job for me. Of course I'll look out for you, Nell. I'll see it through somehow, as long as it's necessary. There; don't worry, dear. I'm not angry. I'm just staggered." Nell leaned forward and kissed her. "You're a darling!" she said. "And just as soon as I'm strong I'll get a job for myself." Mary looked at her thoughtfully. "Yes," she said slowly, "I suppose you might write yourself some more references." "Mary Wayne!"
  • 65.
    CHAPTER XV To Sailthe Ocean Blue Mary Wayne was in weak, human fear. The confession of Nell Norcross had not merely served to revive half-forgotten apprehensions, but had overwhelmed her with new ones. She wanted to quit. She did not dare. For where could she get another place, and who would take care of Nell? Circumstances were driving her toward a life of perpetual charlatanism, it seemed, but for the present she could not even struggle against them. Mary was neither a prude nor a Puritan, so it may as well be said that what troubled her most was not the practice of deception. It was the fear of discovery. She now lived with an explosive mine under her feet. At any instant Aunt Caroline, for all her innocence and abiding faith, might inadvertently make the contact. Then— catastrophe! Even that queer valet might make a discovery; she was by no means certain that he was without suspicion. Bill Marshall himself might blunder into a revelation; but Mary feared him least of all. She did not regard him as too dull to make a discovery, but she had a feeling that if he made it he would in some manner safely remove her from the arena of disturbance before the explosion occurred. All the way back to the Marshall house she was seized with fits of trembling. The trembling angered her, but she was unable to control it. Suppose Aunt Caroline had taken it into her head to seek a personal talk with Mrs. Rokeby-Jones! Or, even if matters had not gone that far, what would she say when Aunt Caroline asked for the result of Mary's interview? "The city of New York is not large enough for Mrs. Rokeby-Jones and me," declared Mary. "I feel it in my bones. One of us must go.
  • 66.
    Which?" She had reacheda decision when the butler opened the front door and informed her that Mr. William would like to see her. He was the very person that Mary wanted to see. She found him in the office. "Say, what's this I hear about a dinner?" demanded Bill. "Has your aunt been speaking to you?" "Uh, huh! I don't want any dinner. Good Lord, they'll ask me to make a speech!" Mary smiled for the first time in hours. "Of course," said Bill, uncomfortably, "I promised to do better and all that sort of thing, and I don't want to break my word. But a dinner— oh, gee!" "I don't favor the dinner idea myself," said Mary. "But it looks like Aunt Caroline was all set for it. What's the answer?" Mary laid her gloves on the desk and removed her hat. "It seems to me," she said, "that the thing to do is to go out of town for a while." Bill looked at her with a hopeful expression. "You see, Mr. Marshall, the town season is really over. Most of the worth-while people have left the city. It's summer. There will be nothing of importance in society before the fall; nothing that would interest you, at any rate. So I would advise doing exactly what the other people are doing." Bill rubbed his nose thoughtfully. "Trouble is, we haven't got a country house," he said. "We don't own a villa, or a camp or any of that fashionable stuff." "I understand," said Mary. "But how about a yacht?" "Don't even own a skiff."
  • 67.
    "But we couldhire one, couldn't we?" Mary had unconsciously adopted the "we." Bill regarded her with sudden interest. He stopped rubbing his nose, which was always one of his signs of indecision. "Say, where did you get that idea?" he demanded. "Why, it's a perfectly obvious one to arrive at, considering the season of the year." "Have you spoken to my aunt about it?" "Not yet. I wanted to consult you first, of course." Bill liked that. It was another way of saying that she was still his secretary. "You've got a whole beanful of ideas, haven't you?" he exclaimed, in admiration. "Well, I'm for this one, strong!" Mary breathed a little more deeply. It seemed as if she had already removed herself a step further from Mrs. Rokeby-Jones and other perils of the city. "I'm glad you like it," she said. "Like it! Why, man alive—I mean little girl—well, anyhow, it's just the stunt we're going to pull off." "It's not really a stunt," Mary reminded him. "It's not original at all. We do it simply because it is the right thing to do. Everybody of any account has a yacht, and now is the time for yachting." "Now, don't you go crabbing your own stuff," said Bill. "This thing is a great invention, Secretary Norcross, and you get all the credit. I wouldn't have thought of it in a billion years. Now, what's your idea about this yacht? Do we want a little one or a whale? Where do we go? When? And who's going along?" "Well, I don't know much about yachts," confessed Mary. "But it seems to me that a medium-sized one would do. We're not going across the ocean, you know."
  • 68.
    "We might," declaredBill, hopefully—"we might start that trip around the world. I'm supposed to be on my way to Australia, you know, studying crustaceans." Mary laughed. "Do we cart a gang along?" Mary had a vision of a tin ear. She shook her head. "I see no occasion for a large party, Mr. Marshall. We might ask one or two besides the family; the bishop, for instance." "Now you're joshing me. Into what part of the world do we sail this yacht, if you don't happen to be under sealed orders." He was traveling somewhat rapidly, Mary thought; and she was right. Bill was already cleaving the high seas, perched on his own quarter-deck and inhaling stupendous quantities of salty air. "I think we'd better obtain your aunt's approval before we plot out a cruise," she advised. "Also, there's the problem of getting a yacht." "We'll get one if we steal it," Bill assured her. "I'll talk to Pete about it. He's amphibious. He's a sort of nautical valet. He knows all about yachts." "I dare say. He seems to have a wide range of information. Suppose you consult him, while I speak to your aunt." A frown clouded Bill's face. "Do you suppose Aunt Caroline will want to go?" he asked. "Want to? Why, she must." "I don't see why. I don't believe she'd enjoy it a bit. We can have a barrel of fun if Aunt Caroline doesn't go. Let's leave her home." Mary shook her head decisively. "That's out of the question. Of course she'll go. "But, listen; I don't need any chaperon."
  • 69.
    "Well, perhaps Ido," said Mary. "Oh!" Bill was still scowling. "Why couldn't we let Pete be the chaperon?" Mary squashed that suggestion with a glance. "Then don't blame me if she turns out to be a bum sailor," he warned. "I think I'll speak to her now," said Mary. Aunt Caroline was frankly surprised. It had never occurred to her that there were times when society went to sea. Yet, to Mary's great relief, she did not prove to be an antagonist. She merely wanted to be shown that this cruise would actually be in furtherance of Bill's career. "Of course it will," urged Mary. "It's the very thing. We'll take the regular summer society cruise." "And what is that, my dear?" Mary bit her lip. She did not have the least idea. "Oh, I suppose we'll stop at Newport, Narragansett, Bar Harbor, and such places," she said, dismissing the details with a wave of her hand. "We'll make all the regular society ports—that is, of course, if you approve the idea, Miss Marshall." Aunt Caroline smiled. "Certainly I approve it, my dear. Although I admit it perplexes me. What sort of yachting flannels does an old lady wear?" "Oh, they dress exactly like the young ones," said Mary, hastily. "Which reminds me that we'll both need gowns. So, please order whatever you want." "You're awfully generous with me," and Mary laid an impulsive hand on Aunt Caroline's. She felt very small and mean and unworthy.
  • 70.
    "I want youto be a credit to the family, my dear. So far, you're doing beautifully! Have you spoken to William about buying the yacht?" "Oh, we don't have to buy one! We just hire one—charter it, I think they say." "It sounds like hiring clothes," said Aunt Caroline. "Still, I leave it all to you and William. But if it's necessary, buy one. And please get it as large as possible. We wouldn't want to be seasick, you know." "We'll only sail where it's nice and calm," Mary assured her. "And where there are the proper sort of people. Very well, my dear. And, oh, I've just remembered: have you done anything yet about Mrs. Rokeby-Jones?" That lady had passed completely out of Mary's head. "Why—er—you see, this other matter came up, Miss Marshall, so I haven't done anything about her as yet." "Never mind the dinner, then," said Aunt Caroline. "I'm afraid we wouldn't have time for it," agreed Mary. "Probably not, my dear. We'll do better. We'll invite her to sail with us on our yacht." Mary groped her way out of the room. The business of fleeing the city went surprisingly well, notwithstanding Aunt Caroline's obsession on the subject of Mrs. Rokeby-Jones. Bill consulted Pete Stearns, who numbered among his friends a marine architect. The marine architect believed that he knew the very boat they needed. She was not a steam-yacht; most of the steam-yachts, he pointed out, were too large for a small party and a lot of them were obsolete. What they wanted was a big cruiser with Diesel engines, that ran smoothly, noiselessly and never smokily. So through the offices of the marine architect, who made a nice commission, of which he said nothing at all, Bill Marshall became
  • 71.
    charterer of theyacht Sunshine, an able yet luxurious craft, measuring some one hundred and twenty feet on the water-line, capable of all the speed that was required in the seven seas of society and sufficiently commodious in saloon and stateroom accommodations. Mary Wayne was delighted. Any craft that would sail her away from New York City would have been a marine palace, in her eyes. She would have embarked on a railroad car-float, if necessary. There was a vast amount of shopping to be done, which also pleased Mary. Aunt Caroline insisted upon being absurdly liberal; she was in constant apprehension that the ladies of the party would not be properly arrayed for a nautical campaign. So Mary presently found herself the possessor of more summer gowns than she had ever dreamed of. Even when it came to the business of seeing that Bill Marshall was adequately tailored for the sea Aunt Caroline proved prolific in ideas. Somehow, she acquired the notion that Bill would need a uniform; she pictured him standing on the bridge, with a spy-glass under his arm, or perhaps half-way up the shrouds, gazing out upon the far horizon; although there were no shrouds on the Sunshine, inasmuch as there were no masts. But Aunt Caroline did not know that. To her, Bill would not merely be the proprietor and chief passenger of this argosy, but the captain, as well. Mary saved Bill from the uniform. She did it tactfully but firmly, after explaining to Aunt Caroline that only the hired persons on board would wear uniforms. Nevertheless, Aunt Caroline insisted on such a plethoric wardrobe for her nephew that for a time she even considered the advisability of an assistant valet. Pete fell in with that idea instantly, but again there was a veto from Mary. One valet was trouble enough, as she well knew. When it came to the matter of Mrs. Rokeby-Jones, however, Mary was hard put for a suitable defense. Aunt Caroline mentioned the lady several times; she hoped that the negotiations were progressing favorably; in fact, she at last reached the point where she decided
  • 72.
    upon two additionalevening gowns for herself, because she was certain that Mrs. Rokeby-Jones would come arrayed like the Queen of Sheba. Poor Aunt Caroline did not know that the Queen of Sheba, in these times, would look like a shoddy piker beside even the humblest manicure in New York. Mary had consulted Bill about Mrs. Rokeby-Jones. She could not explain as fully as she would have liked just why it was impossible for her to transmit Aunt Caroline's invitation; but she did not need to. Bill was flatly against his aunt's scheme. He declared that he would back Mary to the uttermost limit of opposition. "But opposition is exactly what we must avoid," said Mary. "We mustn't antagonize—and yet we must stop it. Oh, dear! It seems a shame for me to be plotting this way against your aunt; she's been so wonderful to me. But there's no way to make her see that a perfect stranger is hardly likely to accept an invitation to a yachting party. Of course, your aunt is relying on the Marshall name." Bill nodded. "And names don't get you anywhere; except, perhaps, in society. I knew a youngster who called himself Young John L. He kept at it for quite a while, but the only thing he was ever any good at was lying on his back in the middle of the ring and listening to a man count ten. That's all his name ever got him." "But to get back to Mrs. Rokeby-Jones," said Mary, with a slight frown. "We've got to appear to want her, but we mustn't have her." "We won't; don't you worry. We'll count her out or claim a foul. We'll leave her on the string-piece, if it comes to the worst." "It isn't quite so simple as that, Mr. Marshall. Do you know what your aunt did to-day? She wrote her a note—personally." "I know it," said Bill. "She told you?" "No; but here's the note."
  • 73.
    He delved intoa pocket and produced an envelope. Mary's eyes became round. "Why, how in the world——" "You see, the letters were given to Pete, to put stamps on and mail. And—well, he thought I might be interested in this one." "But—that's a crime, isn't it?" "Why do you have such unpleasant thoughts, Secretary Norcross? Pete says it's no crime at all; not unless it's been dropped in a letter- box. But if you feel finicky about it, why here's the letter. Mail it." Mary shook her head. "I'd be afraid to touch it." "Thought so," said Bill, as he returned the letter to his pocket. "I'll hold it for a while." "If the boat was only sailing now!" exclaimed Mary. "That's a good suggestion. I'll hold it till we sail." "Why, I never suggested anything of the kind, Mr. Marshall." She made a very fair show of indignation, but Bill simply winked at her. Mary turned away for fear of betraying herself. Nevertheless, she knew that it was all very discreditable and she was not in the least proud of herself. It was a comfort, though, to have somebody else sharing the guilt. The day came for the sailing of Aunt Caroline's armada. The Sunshine lay at anchor in the Hudson. From early morning a launch had been making steady trips from wharf to yacht, carrying trunks, boxes, grips, hampers, and packages. A superficial observer would have been justified in assuming that the Sunshine was documented for the Philippines, or some equally distant haven. All of Aunt Caroline's new gowns, all of Mary's, all of Bill's wardrobe, all of Pete's, and many other things that might prove of service in an emergency went aboard the Sunshine.
  • 74.
    At the lastmoment there was great difficulty in persuading Aunt Caroline to leave the house. There had been no word from Mrs. Rokeby-Jones, and the good lady who was determined to be her hostess insisted that she would not depart without her. Bill fumed; Mary twisted her handkerchief. Aunt Caroline was displaying stubborn symptoms. "Madam, I telephoned myself, only half an hour ago," said Pete. "She was not at home." "She's probably on her way to the yacht," said Bill, with a glance at Mary. "We'll wait a while and telephone again," announced Aunt Caroline. "But if she's on her way," said Mary, "wouldn't it be better for you to be there to receive her?" Aunt Caroline hesitated. It was Pete who saved the day. "If I may make bold to suggest, Miss Marshall, you could go to the yacht at once. If Mrs. Rokeby-Jones has not arrived you could then telephone from the boat." Mary turned away and stuffed her handkerchief into her mouth. Bill went out into the hall to see if the taxis had arrived. "Peter," said Aunt Caroline, "that's a most sensible suggestion. I never thought of the telephone on board."
  • 75.
    CHAPTER XVI Three ErrandsAshore If Aunt Caroline had been bred to the sea, and familiar with its customs that have practically crystallized into an unwritten law, she would have written in her log: Aboard the yacht Sunshine—Latitude, 40° 43' North; Longitude, 74° 0' West. Weather, clear; wind, SSW., moderate; sea, smooth. Barometer, 29.6. But not being a seafaring lady, she phrased it in this way in the course of a remark to her nephew: "William, isn't it lovely to be sitting here aboard our own yacht in the Hudson, and isn't the weather superb?" The Sunshine still lay at her anchorage, with every prospect auspicious, except for the fact that nothing had been heard from Mrs. Rokeby-Jones. The sun had set somewhere in New Jersey and the lights of New York were shining in its stead. There was a soft coolness in the air, so that Aunt Caroline found comfort in a light wrap. Bill had decided that they would not sail until later in the evening. This was not because of Aunt Caroline's anxiety concerning the missing guest, but for the reason that he had an errand ashore which he had been unable to discharge during the busy hours of the day. It was an errand he could trust to nobody, not even to Pete Stearns. In fact, he did not consider it wisdom to take Pete into his confidence.
  • 76.
    Aunt Caroline had,indeed, discovered a telephone aboard the Sunshine. It was in the owner's stateroom, which had been set apart for her because it was the most commodious of all the sleeping apartments. Three times she had talked into this telephone, on each occasion giving the correct number of the Rokeby-Jones house, of which she had made a memorandum before leaving shore. But each time she was answered by the voice of a man, always the same voice. The second time he laughed and the third time he hung up with a bang! So Aunt Caroline, after vainly trying to lodge a complaint with "Information," made a personal investigation and discovered that the other end of the telephone system was in the cabin of the sailing-master. She made an instant complaint to Bill, and Bill referred her to Pete. The latter explained it very easily. "You see, madam, through a mistake the telephone company was notified that we were sailing several hours ago, so they sent a man out in a boat to disconnect the shore wire. I'm very sorry, madam." Aunt Caroline accepted the explanation, as she had come to accept anything from Pete Stearns, although it did nothing to allay her anxiety as to Mrs. Rokeby-Jones. Dinner had been over for more than an hour and darkness had settled upon the river when Bill Marshall announced that he was going ashore. He said that it was expressly for the purpose of pursuing Aunt Caroline's thwarted telephone inquiry and that he would not come back until he had definite news. His aunt thanked him for his thoughtfulness, settled herself for a nap in a deck-chair and Bill ordered the launch. He was about to embark upon his errand when it occurred to him that perhaps his secretary would also like to go ashore. Bill had it in the back of his head that there might be time to pay a short visit to a roof-garden or seek some sequestered place for a chat. He had been trying for some time to have a confidential chat with Mary
  • 77.
    Wayne, but shehad an annoying way of discovering other and prior engagements. "You mean the young lady, sir?" said the second officer. "She went ashore an hour ago, sir. I sent her across in the launch." Bill became thoughtful. Why hadn't she mentioned the matter to him? And who was the boss of this yacht, anyhow? Could people order up the launch just as if they owned it? He made a search for Pete Stearns and could not find him. Again he spoke to the second officer. "Oh, the young man, sir? Why, he went ashore at the same time. I believe I heard him say that he had a few purchases to make." Bill gritted his teeth. Here was a piece of presumption that no owner could tolerate. They had gone away together, of course; they had been very careful not to say a word to him. What for? What sort of an affair was in progress between his valet and his secretary? The more he thought about it the higher rose his temper. "I'm going ashore myself," he said shortly. "Please hurry the launch." Ten minutes later he was hunting for a taxi along the Manhattan waterfront, deeply disturbed in mind and with a fixed resolution to demand explanations. But the suspicions of Bill Marshall did injustice at least to one of the missing persons. Mary Wayne had gone ashore on a purely private mission, and she was not only surprised, but annoyed when her employer's valet also stepped into the launch. "If you don't mind, miss," said Pete, apologetically, as the launch was headed for the wharf, "I have some purchases to make for Mr. William." Mary answered, of course, that she did not mind, and after that she kept her thoughts to herself. Where the wharf entrance opened on Twelfth Avenue, Pete lifted his hat respectfully, bid her good evening, and went off in an opposite direction.
  • 78.
    But he didnot go far; merely far enough to conceal himself in a shadow from which he could watch without fear of discovery. Mary was without suspicion; she walked briskly eastward, glad to be so easily rid of her fellow passenger. When he had permitted her to assume a safe lead, Pete stepped out of his shadow and followed. It was fortunate that there were two taxis at the stand which Mary discovered after a journey of several blocks through lonely streets; that is, Pete considered it was fortunate. He took the second one, giving the driver the order and promise of reward that are usual in such affairs. This nocturnal excursion on the part of Mary Wayne had piqued his curiosity. He knew that she had not spoken to Bill Marshall about it; he doubted if she had said anything to Aunt Caroline. The clandestine character of Mary's shore visit impressed him as warranting complete investigation. The two taxis had not been in motion for many minutes when Pete became convinced that he could name Mary's destination almost beyond a question. They were headed down-town, with occasional jogs toward the East Side. So certain was Pete of his conclusion and so anxious was he, purely for reasons of self-gratification, to prove the accuracy of his powers of deduction, that he halted his taxi, paid off the driver and set off at a leisurely walk, quite content in mind as he watched the vehicle that contained Mary Wayne disappear from view. Twenty minutes later Pete found himself vindicated. In front of the boarding-house where Nell Norcross roomed stood a taxi. Sitting on the top step of the porch were two figures. As he strolled slowly by on the opposite side of the street he had no difficulty in recognizing Mary Wayne's smart little yachting suit of white linen. Of course, there was no doubt as to the identity of the second person, even though the street lights were dim and there was no lamp-post within a hundred feet of the boarding-house. Pete walked as far as the corner and posted himself. The conversation between Mary and Nell proceeded in low tones.
  • 79.
    "We shall bein Larchmont to-morrow," Mary was saying. "I'll try to send you a note from there. After that I'll keep you informed as well as I can concerning the rest of the trip, so you can reach me, if it's necessary. We are not traveling on any fixed time-table." "I'll feel dreadfully lonely, Mary." "I'd have brought you if I could, Nell; but there wasn't any legitimate excuse. And besides, I don't think you're strong enough to attempt it." "If there was only somebody staying behind that I knew," Nell sighed. "I'll be so helpless." "Nonsense. Besides, who would stay behind?" Nell did not answer, but if Pete Stearns could have read a fleeting thought from his point of observation on the street corner his waistcoat buttons would doubtless have gone flying. Mary Wayne, however, read the thought. "You don't mean that valet who brought you home from the party?" she demanded suddenly. "Oh, I didn't mean anybody particularly," answered Nell, guiltily. "But of course even he would be better than nobody." "Nell Norcross, don't let that young man get into your head. There's something mysterious about him. He may be only a valet, but I'm not certain. I'm suspicious of him. He has a habit of forgetting himself." "I know," assented Nell, nodding. "Oh, you do, do you? I might have guessed it. Take my advice and give him a wide berth." Nell regarded her friend with a look of speculative anxiety. "Of course, Mary, I don't want to interfere with you in any way. But ——"
  • 80.
    "Interfere with me?"exclaimed Mary sharply. "Do you think I am interested in valets?" "But you thought he might be something else. At least, you hinted it. He's a divinity student, isn't he?" "Divinity!" Mary summoned all her scorn in that word. "Oh, very likely. But what sort of a divinity is he studying? Perhaps you're a candidate for the place." "Mary Wayne, you're mean! I think that's a nasty remark." "Oh, well; I didn't mean it. But you'd better take my advice, just the same. I've seen much more of him than you have." Nell sighed again. "Now, my dear, I must be going back. They'll be sending out a general alarm for me, I suppose. I didn't ask anybody's permission to come, you see." "There isn't much doubt Mr. Marshall will be alarmed," remarked Nell, who was not above seeking a legitimate revenge. "You're in a rather silly mood this evening," said Mary. "Well, good- by. I'll send you some more money as soon as I'm paid again." Nell looked gratefully at a small roll of bills that lay in her hand. "You're awfully good to me," she murmured. "Good-by. And if you see——" But Mary ran down the steps, popped into the taxi and was driven off. Pete Stearns aroused himself, crossed the street, and walked briskly in the direction of the boarding-house. He arrived in time to intercept Nell, who had risen to go in. She sat down again in sheer surprise, and Pete seated himself without invitation on the step below. "It's a fine night, isn't it?" he said. "Now what's your real name?" Nell gasped and could only stare.
  • 81.
    "Is it Wayne?"he demanded. "Of—of course, it is!" "I just wanted to see if I'd forgotten. Sometimes my memory walks out on me. Amnesia, you know. It's lucky I never suffered from aphasia. A bishop with aphasia wouldn't be able to hold his job. Let's talk about the bishops." And he did, for ten solid minutes, until Nell began seriously to wonder if he was in his right mind. Suddenly he dropped the subject. "You said your name was Wayne, didn't you?" "Why in the world do you keep asking that?" she parried. "It's the amnesia. Excuse it, please. Now let's talk about ourselves." Eventually he said good night; he would be delaying the yacht, he explained. But he promised to write, which was something that had not even been hinted at during the conversation. He also shook hands with her, begged her to have faith in him, urged her to believe nothing she might hear, reaffirmed his purpose to become a bishop and perhaps even an archbishop, told her that she inspired him to great things, as witness—a kiss that landed on the end of her nose. Then he ran. Nell Norcross was still sitting on the top step half an hour later, trying to muster sufficient confidence for the climb up-stairs. At about the same time Bill Marshall was taking leave of a friend in the back room of a hostelry that had descended to the evil fortunes of selling near-beer. "I'm sorry I won't be able to be there, Kid," he said, "but go to it and don't worry about any cops butting in to bust up the game." "I'll run it strictly Q. T., Bill. Doncha worry about nothin'." "I won't. But I owe you that much for the way they chucked you out of the house the other night."
  • 82.
    "'Sall right, 'sallright," said Kid Whaley with a generous wave of his hand. "They didn't hurt me none." Bill handed him something, and the Kid pocketed it with a wink. "I'd like to take you with me, Kid; but you understand." "Aw, sure. Sure—I'm wise. I ain't strong for yachtin', anyhow. That's why I blew me roll in a buzz-wagon. Well, s'long, Bill. This here little scrap's goin' t' be a bird. I'll tell y' all about it." When Mary Wayne arrived at the wharf there was no sign of the launch. She remembered that she had said nothing about the time of her return. Out in the river she could see the riding lights of the Sunshine and the glow from the saloon windows. But she had not the least idea of how to make a signal, nor any notion that they would understand a signal. The wharf was lonely. It seemed to her, as she seated herself on the string-piece, that she was as remote from civilization as though she were sitting at the north pole, although she knew there were seven or eight million people within a radius of a few miles. There was nothing to do but wait, even if it was a creepy place for waiting. She had been sitting there for what seemed like half the night when a sound of footsteps startled her. Out of the murk a figure was approaching. An instant later, to her relief, she perceived it to be the valet. He bowed in his mock deferential way and seated himself beside her. "No launch?" he inquired. "I forgot to speak to them." "So did I. Well, the yacht's there, anyhow, miss. They won't leave without us. Is Miss Wayne better?" Mary experienced a shock. She leaned closer toward him and stared through the gloom. "You followed me!" she exclaimed.
  • 83.
    "I'd hardly saythat, miss. You see, I was quite certain where you were going." She had an impulse to sweep him off into the water. "I shall speak to Mr. Marshall about this," she said hotly. "I do not propose to be spied upon by a servant." Pete made a gesture of deprecation. "Why be nasty, miss? Let's talk about something pleasanter. You know, if we both started telling all we knew there might be a great deal of embarrassment." "Just what do you mean by that?" she demanded. "I leave it to your imagination," he said cryptically. "I can tell things myself," she said savagely. "Exactly, miss. So why shouldn't we be friends? Why can't we establish a real democracy? I won't always be a valet; some day I'll be a bishop." "I believe you're nothing but a fraud!" "Well, now," observed Pete in a mild tone, "I might remark, on the other hand—but I think the master is coming." Mary jumped to her feet with a sense of confusion. There was no doubt that the large figure emerging out of the darkness was that of Bill Marshall. How was she to explain the valet? "Oh, hello!" said Bill as he identified her. "Waiting here all alone, eh? Well, that's a darn shame. Hasn't the launch—oh!" He discovered the presence of Pete Stearns. "Didn't know you had company," he added, his tone altering. "Beg your pardon." "I—I haven't," said Mary, defiantly. "I'll see if there's any sign of the launch." Bill walked to the end of the wharf, where he stood staring at the river, raging with and almost bursting with questions that he scorned to ask.
  • 84.
    "Why didn't youexplain to him?" snapped Mary, whirling upon Pete. "I pass the question back to you, miss." And Pete lighted a cigarette, the glow of the match illuminating for an instant a pair of eyes that were regarding her with unveiled amusement. When the launch came, after an uncomfortably long interval, Bill helped her into it, with cold courtesy. The valet scrambled aboard and took himself off to the bow. All the way to the Sunshine the three sat in silence—Bill smoldering with anger and curiosity, Mary humiliated and resentful, Pete content because they were as they were. The social secretary hastened to her stateroom as soon as she stepped aboard; she did not pause to speak to Aunt Caroline, who was dozing in her chair. Pete disappeared with like alacrity. It remained for Bill to arouse his aunt and suggest that it was time for her to retire. "But Mrs. Rokeby-Jones?" asked Aunt Caroline. "Had her on the wire; she can't come," said Bill. "Says she wrote a note, but it must have gone astray. Very sorry and all that sort of thing." Aunt Caroline sighed. "At any rate, I have done my duty, William. When do we sail?" "Soon." Bill went forward to give an order to the sailing-master.
  • 85.
    CHAPTER XVII The Wayof a Maid Larchmont Harbor! It was fair even to the eyes of Bill Marshall, as he stood under the after awning of the Sunshine, staring out over the shining water, as yet untouched by so much as a breath of breeze. He was in no pleasant mood this morning, but he could not deny the serene, luxurious charm of the harbor. At another time it might have awakened the spirit of the muse within him; Pete always insisted that far under the surface Bill was a poet. But now its influence was not quite so potent as that; it merely laid a restraining spell upon him, soothing him, mollifying him, yet not lifting him to the heights. There were many yachts at anchor, with club ensigns and owners' flags drooping limp in the sluggish air. Bill watched them for signs of life, but it was still an early hour for Larchmont. Occasionally he saw a hand scrubbing a deck or polishing a brass, but he discovered no person who resembled an owner or a guest. A warm mist had thinned sufficiently to show the rocky shore, and beyond it, partly sequestered among the trees, the summer homes and cottages of persons who still slept in innocence of the designs of Aunt Caroline. The harbor was not even half awake; it was yet heavy with the unspent drowsiness of a summer night. Bill was on deck early because he had slept badly. The affair of Mary Wayne and Pete Stearns, as he interpreted it, rankled. The yacht had been clear of Hell Gate before he went to his stateroom, and even then it was a long time before he closed his eyes. The fact that Bill was jealous he did not himself attempt to blink; he admitted it.
  • 86.
    Welcome to ourwebsite – the perfect destination for book lovers and knowledge seekers. We believe that every book holds a new world, offering opportunities for learning, discovery, and personal growth. That’s why we are dedicated to bringing you a diverse collection of books, ranging from classic literature and specialized publications to self-development guides and children's books. More than just a book-buying platform, we strive to be a bridge connecting you with timeless cultural and intellectual values. With an elegant, user-friendly interface and a smart search system, you can quickly find the books that best suit your interests. Additionally, our special promotions and home delivery services help you save time and fully enjoy the joy of reading. Join us on a journey of knowledge exploration, passion nurturing, and personal growth every day! ebookbell.com