├── terraform ├── userdata.sh ├── main.tf └── .terraform.lock.hcl ├── HashiTalks 2022 - slides.pdf ├── .gitignore ├── packer ├── packerconf.pkr.hcl └── demo.pkr.hcl ├── app └── main.go ├── .github ├── scripts │ ├── e2e_test.sh │ └── set_iteration.sh └── workflows │ └── build_and_deploy.yaml ├── README.md └── LICENSE /terraform/userdata.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | set -xeuo pipefail 3 | 4 | # Start the web server 5 | /opt/webapp/server 6 | -------------------------------------------------------------------------------- /HashiTalks 2022 - slides.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CalebAlbers/hashitalks-2022-hcp-packer-demo/HEAD/HashiTalks 2022 - slides.pdf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | 3 | # Packer 4 | packer_cache/ 5 | packer/packer_manifest.json 6 | 7 | # Terraform 8 | .terraform/ 9 | terraform.tfstate** 10 | terraform/.terraform.tfstate.lock.info 11 | 12 | # Go 13 | bin/** 14 | -------------------------------------------------------------------------------- /packer/packerconf.pkr.hcl: -------------------------------------------------------------------------------- 1 | packer { 2 | required_version = ">= 1.7.0" 3 | required_plugins { 4 | amazon = { 5 | version = ">= 1.0.3" 6 | source = "github.com/hashicorp/amazon" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /app/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | ) 8 | 9 | func main() { 10 | log.Print("Starting up!") 11 | 12 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 13 | fmt.Fprint(w, "Hello world!") 14 | }) 15 | 16 | // a truly basic health endpoint 17 | http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { 18 | fmt.Fprint(w, "looks good") 19 | }) 20 | 21 | log.Fatal(http.ListenAndServe(":8080", nil)) 22 | } 23 | -------------------------------------------------------------------------------- /.github/scripts/e2e_test.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | 3 | set -eEuo pipefail 4 | 5 | usage() { 6 | cat < 11 | 12 | Example: 13 | > $(basename "$0") https://example.com/api/v2/healthz 14 | [Check: 1/25] Service is not ready yet, retrying in 5 seconds... 15 | [Check: 2/25] Service is not ready yet, retrying in 5 seconds... 16 | 🎉 Service is up! 🎉 17 | EOF 18 | exit 1 19 | } 20 | 21 | test "$#" -eq 1 || usage 22 | 23 | host="$1" 24 | 25 | i=1 26 | interval=5 # check every 5 seconds 27 | max_attempts=25 # 5 minutes 28 | 29 | echo "Running test against target: $host" 30 | 31 | until $(curl --output /dev/null --silent --head --fail "$host"); do 32 | if [ ${i} -ge ${max_attempts} ];then 33 | echo "Service state is unhealthy after ${i} attempts... failing health check." 34 | exit 1 35 | fi 36 | 37 | echo "[Check: ${i}/${max_attempts}] Service is not ready yet, retrying in ${interval} seconds..." 38 | i=$(($i + 1)) 39 | 40 | sleep $interval 41 | done 42 | 43 | echo "🎉 Service is up! 🎉" 44 | -------------------------------------------------------------------------------- /.github/scripts/set_iteration.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | 3 | set -eEuo pipefail 4 | 5 | usage() { 6 | cat < 11 | 12 | --- 13 | 14 | Requires the following environment variables to be set: 15 | - HCP_CLIENT_ID 16 | - HCP_CLIENT_SECRET 17 | - HCP_ORGANIZATION_ID 18 | - HCP_PROJECT_ID 19 | EOF 20 | exit 1 21 | } 22 | 23 | auth() { 24 | token=$(curl --request POST --silent \ 25 | --url 'https://auth.hashicorp.com/oauth/token' \ 26 | --data grant_type=client_credentials \ 27 | --data client_id="$HCP_CLIENT_ID" \ 28 | --data client_secret="$HCP_CLIENT_SECRET" \ 29 | --data audience="https://api.hashicorp.cloud") 30 | echo "$token" | jq -r '.access_token' 31 | } 32 | 33 | 34 | # Entry point 35 | test "$#" -eq 3 || usage 36 | 37 | bucket_slug="$1" 38 | channel_name="$2" 39 | iteration_id="$3" 40 | 41 | base_url="https://api.cloud.hashicorp.com/packer/2021-04-30/organizations/$HCP_ORGANIZATION_ID/projects/$HCP_PROJECT_ID" 42 | 43 | # authenticate 44 | bearer=$(auth) 45 | 46 | curl --request PATCH \ 47 | --url "$base_url/images/$bucket_slug/channels/$channel_name" \ 48 | --data-raw '{"iteration_id":"'"$iteration_id"'"}' \ 49 | --header "authorization: Bearer $bearer" 50 | -------------------------------------------------------------------------------- /terraform/main.tf: -------------------------------------------------------------------------------- 1 | variable "iteration_id" { 2 | description = "HCP Packer Iteration ID" 3 | } 4 | 5 | data "hcp_packer_image" "ubuntu" { 6 | bucket_name = "hcp-packer-demo" 7 | cloud_provider = "aws" 8 | iteration_id = var.iteration_id 9 | region = "us-west-2" 10 | } 11 | 12 | resource "aws_security_group" "allow_8080" { 13 | description = "Allow inbound TCP traffic to port 8080" 14 | 15 | ingress { 16 | from_port = 8080 17 | to_port = 8080 18 | protocol = "tcp" 19 | cidr_blocks = ["0.0.0.0/0"] 20 | ipv6_cidr_blocks = ["::/0"] 21 | } 22 | 23 | egress { 24 | from_port = 0 25 | to_port = 0 26 | protocol = "-1" 27 | cidr_blocks = ["0.0.0.0/0"] 28 | ipv6_cidr_blocks = ["::/0"] 29 | } 30 | } 31 | 32 | module "ec2_instance" { 33 | source = "terraform-aws-modules/ec2-instance/aws" 34 | version = "~> 3.0" 35 | 36 | name = "basic-webapp" 37 | 38 | ami = data.hcp_packer_image.ubuntu.cloud_image_id 39 | instance_type = "t4g.micro" 40 | user_data = file("${path.module}/userdata.sh") 41 | 42 | vpc_security_group_ids = [aws_security_group.allow_8080.id] 43 | 44 | associate_public_ip_address = true 45 | 46 | tags = { 47 | Terraform = "true" 48 | Environment = "dev" 49 | } 50 | } 51 | 52 | output "health_endpoint" { 53 | description = "URL of the health check endpoint" 54 | value = "http://${module.ec2_instance.public_ip}:8080/healthz" 55 | } 56 | -------------------------------------------------------------------------------- /packer/demo.pkr.hcl: -------------------------------------------------------------------------------- 1 | data "amazon-ami" "base_image" { 2 | region = "us-west-2" 3 | filters = { 4 | name = "ubuntu/images/hvm-ssd/ubuntu-focal-20.04-arm64-server-*" 5 | root-device-type = "ebs" 6 | } 7 | most_recent = true 8 | owners = ["099720109477"] 9 | } 10 | 11 | source "amazon-ebs" "example" { 12 | region = "us-west-2" 13 | source_ami = data.amazon-ami.base_image.id 14 | instance_type = "t4g.micro" 15 | ssh_username = "ubuntu" 16 | ssh_agent_auth = false 17 | ami_name = "hcp_packer_demo_app_{{timestamp}}" 18 | } 19 | 20 | build { 21 | hcp_packer_registry { 22 | bucket_name = "hcp-packer-demo" 23 | description = "Super simple static website" 24 | 25 | bucket_labels = { 26 | "hashitalks" = "2022" 27 | "author" = "Caleb Albers" 28 | } 29 | 30 | build_labels = { 31 | "foo-version" = "1.4.2", 32 | } 33 | } 34 | 35 | sources = ["source.amazon-ebs.example"] 36 | 37 | // Create directories 38 | provisioner "shell" { 39 | inline = ["sudo mkdir /opt/webapp/"] 40 | } 41 | 42 | // Copy binary to tmp 43 | provisioner "file" { 44 | source = "../bin/server" 45 | destination = "/tmp/" 46 | } 47 | 48 | // move binary to desired directory 49 | provisioner "shell" { 50 | inline = ["sudo mv /tmp/server /opt/webapp/"] 51 | } 52 | 53 | post-processor "manifest" { 54 | output = "packer_manifest.json" 55 | strip_path = true 56 | custom_data = { 57 | iteration_id = packer.iterationID 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /terraform/.terraform.lock.hcl: -------------------------------------------------------------------------------- 1 | # This file is maintained automatically by "terraform init". 2 | # Manual edits may be lost in future updates. 3 | 4 | provider "registry.terraform.io/hashicorp/aws" { 5 | version = "3.74.1" 6 | constraints = ">= 3.72.0" 7 | hashes = [ 8 | "h1:J9suPUZHL+g+gQCpxtzDwM0PKfLG7q+UC8CwgE9Qi38=", 9 | "zh:2de9a8c19e07ea3b12c3fe5fe23ffa71354f90683d1f3ded41f2f318e8bad401", 10 | "zh:3f651572f9ad067e119ed083d25455627ae121d36e737823f1d89445949f8ca0", 11 | "zh:468c5954ea646e8edbf70c5a3dbce3d9591a47259f3cf3bdfb2c8728a5e3a083", 12 | "zh:5b379f4803268d3a2cde0bd8a2b6b0a3752e0a22d2cb15a9a28c6a8852d17840", 13 | "zh:5f1271620def1e199afad2377e37ab194f5d5ea51ff804c0e7d468fc4a48b741", 14 | "zh:770783d8d743f28ecaeaf7485f9d602071d610278e33347a692ebb75ae690a8f", 15 | "zh:aecfa7b52f39cbfb1ef53576935ad6cc05deebf82d0b8b6b82c10727469d1c85", 16 | "zh:c905af45fc8cb64fe566c5b35241baf5e5850e137ebbd59a3298321648d05046", 17 | "zh:d7dabb6a110073c8adaf34af288a485714b4be7185304d491f042827a77f9d5f", 18 | "zh:e8ccc2ef2465164ce467f32d58e5ffad74da92cc3733551aef5e0d839532e3d4", 19 | "zh:f1c2c9145383ab8675eab68398b53cf33edb2665d64ef2e48e0444771fa5849e", 20 | ] 21 | } 22 | 23 | provider "registry.terraform.io/hashicorp/hcp" { 24 | version = "0.22.0" 25 | hashes = [ 26 | "h1:e4fu0Hk6z9hZZy3AMcT4FoV+u42PiiowAhGPNhQrNig=", 27 | "zh:10ad9c203a7dabefef3ac8270d46c14e7817b946d0ac85b1640ecd4a91253bce", 28 | "zh:15381f53098fdda335981d2641514503c5e91874bf24ee59e576c590cd6044cd", 29 | "zh:311dfc0cb1da566c51a1f8254413e985357a2b4bc180ba99e53f1c37d94c64e9", 30 | "zh:4161958ce73070ee3fb8513ba56c57ca5a7d943a7cb4a237d8f9585e6ed5c19d", 31 | "zh:6ab2b613f1f527647b14171fd4f3566a49c851096654bede219c1124e3dcf51e", 32 | "zh:7bb39edda9076d6fced011b78c59941b14a1b4bd5cc4f73103568480d6db900f", 33 | "zh:8e3c6b73a6267bd17d2f30647106885b48150fe3b1b03202499b6acd82ad86fe", 34 | "zh:97472833dd2d055a4cd9dc54a4a5a05b24cb305f64fcf8187f1f6ca2913ee685", 35 | "zh:a9935a263b2f309c168acdb388da4df4af8913c132f3d169489a3c698abd860f", 36 | "zh:efdba1395a9ac5071b514b604a9b013ad0f7758a6f3323c7c8282512e75a0424", 37 | "zh:f45eda73b49af46e4fa262536fee77c69d3424c1265687a985b2334f27ed817e", 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hcp-packer-demo :tada: 2 | 3 | This repo holds the code for my "Automating Image Pipelines with HCP Packer" presentation at [HashiTalks 2022](https://events.hashicorp.com/hashitalks2022). It contains functional code examples for packaging an AMI with a static go binary with Packer, running an integration test against that AMI, and then promoting the validated image to dev, staging, and production release channels via HCP Packer. 4 | 5 | [![IMAGE ALT TEXT](https://user-images.githubusercontent.com/7110138/162481240-f9b5e487-dedd-47e9-aabd-6b32a17fd34e.png)](http://www.youtube.com/watch?v=C0DEQZjzYUs "Automating Image Pipelines with HCP Packer") 6 | 7 | 8 | ## Getting Started 9 | 10 | ### Go Web App 11 | 12 | The `./app` directory has a very bare-bones Go-based web server implementation that returns a `Hello World!` string for any request. 13 | 14 | If you wish to compile the app for testing, you can run the following: 15 | 16 | ```bash 17 | # compile go binary 18 | go build -o ./bin/server ./app/main.go 19 | 20 | # start server 21 | ./bin/server 22 | ``` 23 | 24 | You can now go to http://localhost:8080 in a web browser, or send a request via curl: 25 | 26 | ```bash 27 | ❯ curl http://localhost:8080 28 | Hello world! 29 | ``` 30 | 31 | ### Packer 32 | 33 | The `./packer` directory holds the Packer template for building an Ubuntu 20.10 server that hosts the aforementioned Go binary. It integrates with HCP Packer so that every time `packer build` is ran, metadata about the artifacts generated are sent to HCP Packer as an **iteration**. 34 | 35 | ```bash 36 | cd ./.packer 37 | packer init . 38 | packer build . 39 | ``` 40 | 41 | ### Terraform 42 | 43 | The `./terraform` directory holds Terraform code that spins up an EC2 instance hosting the Go web app built earlier via pulling the latest AMI information from HCP Packer. It then attaches a public IP address and outputs the health check endpoint exposed by the web server. 44 | 45 | :warning: This Terraform code is meant only to be used as a simple example of E2E testing infrastructure. It is for demo purposes and does not take into account the strict security controls that you should consider when making an application production-ready. 46 | 47 | ```bash 48 | cd ./.terraform 49 | terraform init 50 | terraform apply -var="iteration_id=$iteration_id" # replace with your desired HCP Packer iteration 51 | ``` 52 | 53 | ### Integration Test 54 | 55 | The `./.github/scripts/e2e_test.sh` script acts as a simple integration testing script. It functions by taking in a health check endpoint url (output from Terraform) and tries to connect every 5 seconds up to 25 attempts. If the health check endpoint exposed by the web app returns a 200 OK, the script succeeds, otherwise it exits with a failure. In practice, replace this with a much more thorough test suite. 56 | 57 | ```bash 58 | cd ./.github/scripts 59 | ./e2e_test.sh 60 | ``` 61 | 62 | Example: 63 | ``` 64 | ❯ ./e2e_test.sh https://example.com/api/v2/healthz 65 | [Check: 1/25] Service is not ready yet, retrying in 5 seconds... 66 | [Check: 2/25] Service is not ready yet, retrying in 5 seconds... 67 | 🎉 Service is up! 🎉 68 | ``` 69 | 70 | ### GitHub Actions Image Build Workflow 71 | 72 | All of the steps above are chained together into an automated pipeline via a GitHub Actions workflow that can be found at `./.github/workflows/build_and_deploy.yaml`. This workflow has four jobs: 73 | - `build` - Compiles the Go app and bakes it into an AMI with Packer 74 | - `test` - Spins up an EC2 instance with the newly build AMI via Terraform, runs an integration test on it, and then cleans up the infrastructure 75 | - `promote-dev-staging` - If the integration test was successful, this job promotes the HCP Packer iteration to the `dev` and `staging` release channels 76 | - `promote-prod` - After manual approval, this job promotes the HCP Packer iteration to the `prod` release channel 77 | 78 | :bulb: When implementing your own version of this pipeline, you may want to trigger a Terraform plan/apply for your workspaces to deploy the latest machine images across your infrastructure. For example, you could expand the `promote-` jobs listed above with a CLI-driven `terraform plan` command or similar. 79 | -------------------------------------------------------------------------------- /.github/workflows/build_and_deploy.yaml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | env: 7 | HCP_ORGANIZATION_ID: ${{ secrets.HCP_ORGANIZATION_ID }} 8 | HCP_PROJECT_ID: ${{ secrets.HCP_PROJECT_ID }} 9 | HCP_CLIENT_ID: ${{ secrets.HCP_CLIENT_ID }} 10 | HCP_CLIENT_SECRET: ${{ secrets.HCP_CLIENT_SECRET }} 11 | 12 | jobs: 13 | build: 14 | name: Build 15 | runs-on: ubuntu-latest 16 | outputs: 17 | iteration_id: ${{ steps.hcp.outputs.iteration_id }} 18 | steps: 19 | - name: Checkout Repository 20 | uses: actions/checkout@v2 21 | 22 | - name: Configure AWS Credentials 23 | uses: aws-actions/configure-aws-credentials@v1 24 | with: 25 | aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} 26 | aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 27 | aws-region: us-west-2 28 | 29 | - name: Compile Application 30 | env: 31 | GOOS: linux 32 | GOARCH: arm64 # target architecture is ubuntu on arm64 instances 33 | run: go build -o bin/server app/main.go 34 | 35 | - name: Packer Init 36 | working-directory: ./packer 37 | run: packer init . 38 | 39 | - name: Packer Build 40 | working-directory: ./packer 41 | run: packer build . 42 | 43 | - name: Get HCP Packer Iteration ID from Packer Manifest 44 | id: hcp 45 | working-directory: ./packer 46 | run: | 47 | last_run_uuid=$(jq -r '.last_run_uuid' "./packer_manifest.json") 48 | build=$(jq -r '.builds[] | select(.packer_run_uuid == "'"$last_run_uuid"'")' "./packer_manifest.json") 49 | iteration_id=$(echo "$build" | jq -r '.custom_data.iteration_id') 50 | echo "::set-output name=iteration_id::$iteration_id" 51 | 52 | test: 53 | name: E2E Testing 54 | needs: build 55 | runs-on: ubuntu-latest 56 | steps: 57 | - name: Checkout Repository 58 | uses: actions/checkout@v2 59 | 60 | - name: Configure AWS Credentials 61 | uses: aws-actions/configure-aws-credentials@v1 62 | with: 63 | aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} 64 | aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 65 | aws-region: us-west-2 66 | 67 | - name: Setup Terraform 68 | uses: hashicorp/setup-terraform@v1 69 | with: 70 | terraform_wrapper: false 71 | 72 | - name: Terraform Init 73 | working-directory: ./terraform 74 | run: terraform init 75 | 76 | - name: Create Ephemeral Infrastructure 77 | id: tf_apply 78 | working-directory: ./terraform 79 | run: | 80 | terraform apply -auto-approve -var="iteration_id=${{ needs.build.outputs.iteration_id }}" 81 | health_endpoint="$(terraform output -raw health_endpoint)" 82 | echo "::set-output name=health_endpoint::${health_endpoint}" 83 | 84 | - name: Invoke E2E Test 85 | working-directory: ./.github/scripts 86 | run: ./e2e_test.sh "${{ steps.tf_apply.outputs.health_endpoint }}" 87 | 88 | - name: Clean Up Testing Infrastructure 89 | if: always() # always run cleanup steps, regardless of E2E test outcomes 90 | working-directory: ./terraform 91 | run: terraform destroy -auto-approve -var="iteration_id=${{ needs.build.outputs.iteration_id }}" 92 | 93 | promote-dev-staging: 94 | name: Promote to Dev and Staging Channels 95 | needs: ['build', 'test'] # this will automatically be skipped if the test job fails 96 | runs-on: ubuntu-latest 97 | steps: 98 | - name: Checkout Repository 99 | uses: actions/checkout@v2 100 | 101 | - name: Promote Iteration to Dev 102 | working-directory: .github/scripts 103 | run: ./set_iteration.sh "hcp-packer-demo" "dev" "${{ needs.build.outputs.iteration_id }}" 104 | 105 | - name: Promote Iteration to Staging 106 | working-directory: .github/scripts 107 | run: ./set_iteration.sh "hcp-packer-demo" "staging" "${{ needs.build.outputs.iteration_id }}" 108 | 109 | promote-prod: 110 | name: Promote to Production 111 | needs: ['build', 'test', 'promote-dev-staging'] 112 | environment: production # requires approval 113 | runs-on: ubuntu-latest 114 | steps: 115 | - name: Checkout Repository 116 | uses: actions/checkout@v2 117 | 118 | - name: Promote Iteration to Prod 119 | working-directory: .github/scripts 120 | run: ./set_iteration.sh "hcp-packer-demo" "prod" "${{ needs.build.outputs.iteration_id }}" 121 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------