diff --git a/Gopkg.lock b/Gopkg.lock index 66763ac64..14fe30ca4 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -70,6 +70,14 @@ packages = ["pkg/event"] revision = "2b0383b8e4d67ffac446b17a7922bf7e5d9f5362" +[[projects]] + branch = "master" + digest = "1:d6415e6b744ec877c21fe734067636b9ee149af77276b08a3d33dd8698abf947" + name = "github.com/knative/test-infra" + packages = ["."] + pruneopts = "T" + revision = "4a4a682ee1fd31f33e450406393c3553b9ec5c2a" + [[projects]] name = "github.com/matttproud/golang_protobuf_extensions" packages = ["pbutil"] diff --git a/Gopkg.toml b/Gopkg.toml index a80bbf0ac..74b37931f 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -1,6 +1,10 @@ # Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md # for detailed Gopkg.toml documentation. +required = [ + "github.com/knative/test-infra", +] + ignored = [ "github.com/knative/docs/serving/samples/grpc-ping-go*", ] @@ -9,3 +13,8 @@ ignored = [ go-tests = true unused-packages = true non-go = true + +[[prune.project]] + name = "github.com/knative/test-infra" + unused-packages = false + non-go = false diff --git a/build/builds.md b/build/builds.md index 7fbf2eb3b..91829be9b 100644 --- a/build/builds.md +++ b/build/builds.md @@ -17,6 +17,7 @@ A build runs until all `steps` have completed or until a failure occurs. * [Source](#source) * [Service Account](#service-account) * [Volumes](#volumes) + * [Timeout](#timeout) * [Examples](#examples) --- @@ -47,6 +48,7 @@ following fields: authentication information. * [`volumes`](#volumes) - Specifies one or more volumes that you want to make available to your build. + * [`timeout`](#timeout) - Specifies timeout after which the build will fail. [kubernetes-overview]: https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/#required-fields @@ -156,7 +158,7 @@ complement the volumes that are implicitly For example, use volumes to accomplish one of the following common tasks: - * [Mount a Kubernetes secrets(./auth.md). + * [Mount a Kubernetes secret](./auth.md). * Creat an `emptyDir` volume to act as a cache for use across multiple build steps. Consider using a persistent volume for inter-build caching. @@ -164,6 +166,12 @@ For example, use volumes to accomplish one of the following common tasks: * Mount a host's Docker socket to use a `Dockerfile` for container image builds. +#### Timeout + +Optional. Specifies timeout for the build. Includes time required for allocating resources and execution of build. + +* Defaults to 10 minutes. +* Refer to [Go's ParseDuration documentation](https://golang.org/pkg/time/#ParseDuration) for expected format. ### Examples @@ -179,6 +187,7 @@ additional code samples, including working copies of the following snippets: * [Mounting extra volumes](#using-an-extra-volume) * [Pushing an image](#using-steps-to-push-images) * [Authenticating with `ServiceAccount`](#using-a-serviceaccount) +* [Timeout](#using-timeout) #### Using `git` @@ -331,6 +340,22 @@ Note: For a working copy of this `ServiceAccount` example, see the [build/test/git-ssh](https://github.com/knative/build/tree/master/test/git-ssh) code sample. +#### Using `timeout` + +Specifying `timeout` for your `build`: + +```yaml +spec: + timeout: 20m + source: + git: + url: https://github.com/knative/build.git + revision: master + steps: + - image: ubuntu + args: ["cat", "README.md"] +``` + --- Except as otherwise noted, the content of this page is licensed under the diff --git a/community/REVIEWING.md b/community/REVIEWING.md index ec2118aa7..7915a1c8c 100644 --- a/community/REVIEWING.md +++ b/community/REVIEWING.md @@ -92,34 +92,13 @@ A PR can be merged only after the following criteria are met: This project uses [Prow](https://github.com/kubernetes/test-infra/tree/master/prow) to -automatically run tests for every PR. PRs with failing tests might not b -e merged. If necessary, you can rerun the tests by simply adding the comment +automatically run tests for every PR. PRs with failing tests might not be +merged. If necessary, you can rerun the tests by simply adding the comment `/retest` to your PR. Prow has several other features that make PR management easier, like running the go linter or assigning labels. For a full list of commands understood by Prow, -see the [command help -page](https://prow-internal.gcpnode.com/command-help?repo=knative%2Fknative). - -### Viewing test logs - -The Prow instance is internal to Google, which means that only Google -employees can access the "Details" link of the test job (provided by -Prow in the PR thread). However, if you're a Knative team member outside -Google, and you are a member of the -[knative-dev@](https://groups.google.com/forum/#!forum/knative-dev) -Google group, you can see the test logs by following these instructions: - -1. Wait for Prow to finish the test execution. Note the PR number. - -2. Open the URL http://gcsweb.k8s.io/gcs/knative-prow/pr-logs/pull/knative_serving/###/pull-knative-serving-@@@-tests/ -where ### is the PR number and @@@ is the test type (_build_, _unit_ or _integration_). - -3. You'll see one or more numbered directories. The highest number is the latest -test execution (called "build" by Prow). - -4. The raw test log is the text file named `build-log.txt` inside each numbered -directory. +see the [command help page](https://prow.knative.dev/command-help). --- diff --git a/community/ROLES.md b/community/ROLES.md index 7059fb3c5..e34c31ee7 100644 --- a/community/ROLES.md +++ b/community/ROLES.md @@ -46,7 +46,7 @@ table describes: Member Regular active contributor in the community -

Sponsored by 2 reviewers

+

Sponsored by two members

Has made multiple contributions to the project

diff --git a/community/SLACK-GUIDELINES.md b/community/SLACK-GUIDELINES.md index 84b6744e4..637e7f350 100644 --- a/community/SLACK-GUIDELINES.md +++ b/community/SLACK-GUIDELINES.md @@ -12,6 +12,9 @@ video recording or in another public space. Please be courteous to others. from these commands and we are a global project - please be kind. Note: `@all` is only to be used by admins. +You can join the [Knative Slack](https://slack.knative.dev) instance at +https://slack.knative.dev. + ## Code of Conduct The Knative [Code of Conduct](./CODE-OF-CONDUCT.md) applies throughout the project, and includes all communication mediums. diff --git a/community/WORKING-GROUPS.md b/community/WORKING-GROUPS.md index aa1c349e9..04d06438e 100644 --- a/community/WORKING-GROUPS.md +++ b/community/WORKING-GROUPS.md @@ -23,8 +23,10 @@ The current working groups are: * [API Core](#api-core) * [Build](#build) * [Events](#events) +* [Networking](#networking) +* [Observability](#observability) +* [Productivity](#productivity) * [Scaling](#scaling) -* [Serving](#serving) ## API Core @@ -51,8 +53,8 @@ Slack Channel | [#api](https://knative.slack.com) Artifact | Link -------------------------- | ---- Forum | [knative-dev@](https://groups.google.com/forum/#!forum/knative-dev) -Community Meeting VC | [build-crd](https://hangouts.google.com/hangouts/_/google.com/build-crd) -Community Meeting Calendar | Wednesdays 10:00a-10:30a PST
[Calendar Invitation](https://calendar.google.com/event?action=TEMPLATE&tmeid=MTBkb3MwYnVrbDd0djE0a2kzcmpmbjZndm9fMjAxODA3MTFUMTcwMDAwWiBtYXR0bW9vckBnb29nbGUuY29t&tmsrc=mattmoor%40google.com) +Community Meeting VC | [meet.google.com/hau-nwak-tgm](https://meet.google.com/hau-nwak-tgm)
Or dial in:
(US) +1 219-778-6103‬ PIN: ‪573 000‬# +Community Meeting Calendar | Wednesdays 10:00a-10:30a PST
[Calendar Invitation](https://calendar.google.com/event?action=TEMPLATE&tmeid=MTBkb3MwYnVrbDd0djE0a2kzcmpmbjZndm9fMjAxODA4MTVUMTcwMDAwWiBqYXNvbmhhbGxAZ29vZ2xlLmNvbQ&tmsrc=jasonhall%40google.com&scp=ALL) Meeting Notes | [Notes](https://docs.google.com/document/d/1e7gMVFlJfkFdTcaWj2qETeRD9kSBG2Vh8mASPmQMYC0/edit) Document Folder | [Folder](https://drive.google.com/corp/drive/folders/1ov16HvPam-v_FXAGEaUdHok6_hUAoIoe) Slack Channel | [#build-crd](https://knative.slack.com) @@ -79,6 +81,42 @@ Slack Channel | [#eventing](https://knative.slack.com/messages/C9JP ------------------------------------------------------------- | ----------- | ------- | ------- | Ville Aikas | Google | [vaikas-google](https://github.com/vaikas-google) +## Networking + +Inbound and outbound network connectivity for [serving](https://github.com/knative/serving) workloads. +Specific areas of interest include: load balancing, routing, DNS configuration and TLS support. + +Artifact | Link +-------------------------- | ---- +Forum | [knative-dev@](https://groups.google.com/forum/#!forum/knative-dev) +Community Meeting VC | Coming soon +Community Meeting Calendar | Coming soon +Meeting Notes | [Notes](https://drive.google.com/open?id=1EE1t5mTfnTir2lEasdTMRNtuPEYuPqQCZbU3NC9mHOI) +Document Folder | [Folder](https://drive.google.com/corp/drive/folders/1oVDYbcEDdQ9EpUmkK6gE4C7aZ8u6ujsN) +Slack Channel | [#networking](https://knative.slack.com) + +  | Leads | Company | Profile +--------------------------------------------------------- | ---------------- | ------- | ------- + | Nghia Tran | Google | [tcnghia](https://github.com/tcnghia) + | Mustafa Demirhan | Google | [mdemirhan](https://github.com/mdemirhan) + +## Observability + +Logging, monitoring & tracing infrastructure + +Artifact | Link +-------------------------- | ---- +Forum | [knative-dev@](https://groups.google.com/forum/#!forum/knative-dev) +Community Meeting VC | https://meet.google.com/kik-btis-sqz
Or dial in:
(US) +1 515-705-3725
PIN: 704 774# +Community Meeting Calendar | [Calendar Invitation](https://calendar.google.com/event?action=TEMPLATE&tmeid=MDc4ZnRkZjFtbzZhZzBmdDMxYXBrM3B1YTVfMjAxODA4MDJUMTczMDAwWiBtZGVtaXJoYW5AZ29vZ2xlLmNvbQ&tmsrc=mdemirhan%40google.com&scp=ALL) +Meeting Notes | [Notes](https://drive.google.com/open?id=1vWEpjf093Jsih3mKkpIvmWWbUQPxFkcyDxzNH15rQgE) +Document Folder | [Folder](https://drive.google.com/corp/drive/folders/10HcpZlI1PbFyzinO6HjfHbzCkBXrqXMy) +Slack Channel | [#observability](https://knative.slack.com) + +  | Leads | Company | Profile +--------------------------------------------------------- | ---------------- | ------- | ------- + | Mustafa Demirhan | Google | [mdemirhan](https://github.com/mdemirhan) + ## Scaling Autoscaling @@ -96,23 +134,23 @@ Slack Channel | [#autoscaling](https://knative.slack.com) ------------------------------------------------------------- | -------------- | ------- | ------- | Joseph Burnett | Google | [josephburnett](https://github.com/josephburnett) -## Serving +## Productivity -Logging infra, Monitoring infra, Trace, Load balancing/Istio, Domain names, -Routing, Observability +Project health, test framework, continuous integration & deployment, release, performance/scale/load testing infrastructure Artifact | Link -------------------------- | ---- Forum | [knative-dev@](https://groups.google.com/forum/#!forum/knative-dev) -Community Meeting VC | [TODO](TODO) -Community Meeting Calendar | [Calendar Invitation](TODO) -Meeting Notes | [Notes](TODO) -Document Folder | [Folder](https://drive.google.com/corp/drive/folders/1pfcc6z8oQl6S7bOl1MnfZJ2o32FtgvRB) -Slack Channel | [#metrics](https://knative.slack.com) +Community Meeting VC | [Hangouts](https://meet.google.com/sps-vbhg-rfx) +Community Meeting Calendar | [Calendar Invitation](https://calendar.google.com/event?action=TEMPLATE&tmeid=NW5zM21rbHVwZWgyNHFoMGpyY2JhMjB2bHRfMjAxODA4MzBUMjEwMDAwWiBnb29nbGUuY29tXzE4dW40ZnVoNnJva3FmOGhtZmZ0bTVvcXE0QGc&tmsrc=google.com_18un4fuh6rokqf8hmfftm5oqq4%40group.calendar.google.com&scp=ALL) +Meeting Notes | [Notes](https://docs.google.com/document/d/1aPRwYGD4XscRIqlBzbNsSB886PJ0G-vZYUAAUjoydko) +Document Folder | [Folder](https://drive.google.com/corp/drive/folders/1oMYB4LQHjySuMChmcWYCyhH7-CSkz2r_) +Slack Channel | [#productivity](https://knative.slack.com) -  | Leads | Company | Profile ---------------------------------------------------------- | ---------------- | ------- | ------- - | Mustafa Demirhan | Google | [mdemirhan](https://github.com/mdemirhan) +  | Leads | Company | Profile +--------------------------------------------------------- | -------------- | ------- | ------- + | Jessie Zhu | Google | [jessiezcc](https://github.com/jessiezcc) + | Adriano Cunha | Google | [adrcunhua](https://github.com/adrcunha) --- diff --git a/eventing/samples/event-flow/README.md b/eventing/samples/event-flow/README.md index ea32ada3a..cc7f1a304 100644 --- a/eventing/samples/event-flow/README.md +++ b/eventing/samples/event-flow/README.md @@ -88,8 +88,8 @@ is a random number 1-10. Now we want to consume these IoT events, so let's create the function to handle the events: ```shell -kubectl apply -f event-flow/route.yaml -kubectl apply -f event-flow/configuration.yaml +kubectl apply -f route.yaml +kubectl apply -f configuration.yaml ``` ## Create an event source @@ -103,10 +103,10 @@ in Pull mode to poll for the events from this topic. Then let's create a GCP PubSub as an event source that we can bind to. ```shell -kubectl apply -f event-flow/serviceaccount.yaml -kubectl apply -f event-flow/serviceaccountbinding.yaml -kubectl apply -f event-flow/eventsource.yaml -kubectl apply -f event-flow/eventtype.yaml +kubectl apply -f serviceaccount.yaml +kubectl apply -f serviceaccountbinding.yaml +kubectl apply -f eventsource.yaml +kubectl apply -f eventtype.yaml ``` ## Bind IoT events to our function @@ -115,5 +115,5 @@ We have now created a function that we want to consume our IoT events, and we ha source that's sending events via GCP PubSub, so let's wire the two together: ```shell -kubectl apply -f event-flow/flow.yaml +kubectl apply -f flow.yaml ``` diff --git a/eventing/samples/github-events/Dockerfile b/eventing/samples/github-events/Dockerfile new file mode 100644 index 000000000..9f71b8506 --- /dev/null +++ b/eventing/samples/github-events/Dockerfile @@ -0,0 +1,27 @@ +# Copyright 2018 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FROM golang AS builder + +WORKDIR /go/src/github.com/knative/docs/ +ADD . /go/src/github.com/knative/docs/ + +RUN CGO_ENABLED=0 go build ./eventing/samples/github-events + +FROM gcr.io/distroless/base + +COPY --from=builder /go/src/github.com/knative/docs/github-events /sample + +ENTRYPOINT ["/sample"] +EXPOSE 8080 diff --git a/eventing/samples/github-events/README.md b/eventing/samples/github-events/README.md new file mode 100644 index 000000000..49f5f1e4a --- /dev/null +++ b/eventing/samples/github-events/README.md @@ -0,0 +1,180 @@ +# Reacting to GitHub Events + +In response to a pull request event, the sample app _legit_ Service will add +`(looks pretty legit)` to the PR title. + +A GitHub webhook will be created on a repository and a Knative `Service` will be +deployed to receive the webhook's event deliveries and forward them into a +`Channel`, through a `Bus`, and out to the consumer via a `Subscription`. The +`Flow` resource takes care of provisioning the webhook, the `Service`, the +`Channel`, and the `Subscription`. + +## Prerequisites + +You will need: + +- A Kubernetes cluster with Knative serving installed. Follow the + [installation instructions](https://github.com/knative/docs/blob/master/install/README.md) + if you need to create one. +- [Docker](https://www.docker.com/) installed and running on your local machine, + and a Docker Hub account configured (you'll use it for a container registry). +- Knative eventing core installed on your Kubernetes cluster. You can install + with: + ```shell + kubectl apply -f https://storage.googleapis.com/knative-releases/eventing/latest/release.yaml + ``` +- A domain name that allows GitHub to call into the cluster: Follow the + [assign a static IP address](https://github.com/knative/docs/blob/master/serving/gke-assigning-static-ip-address.md) + and + [configure a custom domain](https://github.com/knative/docs/blob/master/serving/using-a-custom-domain.md) + instructions. + +## Configuring Knative + +To use this sample, you'll need to install the `stub` ClusterBus and the +`github` EventSource: + +```shell +# Installs ClusterBus +kubectl apply -f https://storage.googleapis.com/knative-releases/eventing/latest/release-clusterbus-stub.yaml +# Installs EventSource +kubectl apply -f https://storage.googleapis.com/knative-releases/eventing/latest/release-source-github.yaml +``` + +## Granting permissions + +Because the `github` EventSource needs to create a Knative Service, you'll need +to provision a special ServiceAccount with the necessary permissions. + +The `auth.yaml` file provisions a service +account, and creates a role which can create a Knative Service in the `default` +namespace. In a production environment, you might want to limit the access of +this service account to only specific namespaces. + +```shell +kubectl apply -f auth.yaml +``` + +## Building and deploying the sample + +1. Use Docker to build the sample code into a container. To build and push with + Docker Hub, run the following commands, replacing `{username}` with your + Docker Hub username: + + ```shell + # Build the container on your local machine + # Note: The relative path points to the _root_ of the `knative/docs` repo + docker build -t {username}/github-events --file=Dockerfile ../../../ + + # Push the container to docker registry + docker push {username}/github-events + ``` + +1. After the build has completed and the container is pushed to Docker Hub, you + can deploy the function into your cluster. **Ensure that the container image + value in `function.yaml` matches the container you built in the previous + step.** Apply the configuration using `kubectl`: + + ```shell + kubectl apply -f function.yaml + ``` + +1. Check that your service is running using: + + ```shell + kubectl get ksvc -o "custom-columns=NAME:.metadata.name,READY:.status.conditions[2].status,REASON:.status.conditions[2].message" + NAME READY REASON + legit True + ``` + + > Note: `ksvc` is an alias for `services.serving.knative.dev`. If you have + an older version (version 0.1.0) of Knative installed, you'll need to use + the long name until you upgrade to version 0.1.1 or higher. See + [Checking Knative Installation Version](../../../install/check-install-version.md) + to learn how to see what version you have installed. + +1. Create a [personal access token](https://github.com/settings/tokens) to + GitHub repo that the GitHub source can use to register webhooks with the + GitHub API. Also decide on a token that your code will use to authenticate + the incoming webhooks from GitHub (*accessToken*). + + The token can be named anything you find convenient. This sample requires + full `repo` control to be able update the title of the _Pull Request_. + The Source requires `admin:repo_hook`, this allows it to create webhooks + into repos that your account is allowed to do so. Copy and save this token; + GitHub will force you to generate it again if misplaced. + + Here I named my token "EventingSample" and have selected the recommended + scopes: + + ![GitHub UI](personal_access_token.png "GitHub personal access token screenshot") + + Update `githubsecret.yaml` with those + values. If your generated access token is `'asdfasfdsaf'` and you choose + your *secretToken* as `'personal_access_token_value'`, you'd modify + `githubsecret.yaml` like so: + + ```yaml + apiVersion: v1 + kind: Secret + metadata: + name: githubsecret + type: Opaque + stringData: + githubCredentials: > + { + "accessToken": "asdfasfdsaf", + "secretToken": "personal_access_token_value" + } + ``` + + Hint: you can makeup a random *accessToken* with: + + ```shell + head -c 8 /dev/urandom | base64 + ``` + + Then, apply the githubsecret using `kubectl`: + + ```shell + kubectl apply -f githubsecret.yaml + ``` + +1. Update the resource inside `flow.yaml` to the + org/repo of your choosing. Note that the personal access token must be valid + for the chosen org/repo. + + Then create the flow sending GitHub Events to the service: + + ```shell + kubectl apply -f flow.yaml + ``` + +1. Create a PR for the repo you configured the webhook for, and you'll see that + the Title will be modified with the suffix `(looks pretty legit)` + + +## Understanding what happened + +`TODO: similar to k8s-events.` + + + +## Cleaning up + +To clean up the function, `Flow`, auth, and secret: + +```shell +kubectl delete -f function.yaml +kubectl delete -f flow.yaml +kubectl delete -f auth.yaml +kubectl delete -f githubsecret.yaml +``` + +And then delete the [personal access token](https://github.com/settings/tokens) +created from GitHub. diff --git a/eventing/samples/github-events/auth.yaml b/eventing/samples/github-events/auth.yaml new file mode 100644 index 000000000..d4ed3e811 --- /dev/null +++ b/eventing/samples/github-events/auth.yaml @@ -0,0 +1,43 @@ +# Copyright 2018 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +apiVersion: v1 +kind: ServiceAccount +metadata: + name: feed-sa + namespace: default +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: create-knative-service + namespace: default +rules: +- apiGroups: ["serving.knative.dev"] + resources: ["services"] + verbs: ["get", "list", "watch", "create", "update", "delete", "patch"] +--- +# This enables the feed-sa to deploy the receive adapter. +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: feed-sa-deploy + namespace: default +subjects: + - kind: ServiceAccount + name: feed-sa + namespace: default +roleRef: + kind: Role + name: create-knative-service + apiGroup: rbac.authorization.k8s.io \ No newline at end of file diff --git a/eventing/samples/github-events/flow.yaml b/eventing/samples/github-events/flow.yaml new file mode 100644 index 000000000..cdf114386 --- /dev/null +++ b/eventing/samples/github-events/flow.yaml @@ -0,0 +1,37 @@ +# Copyright 2018 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: flows.knative.dev/v1alpha1 +kind: Flow +metadata: + name: github-flow + namespace: default +spec: + serviceAccountName: feed-sa + trigger: + eventType: dev.knative.github.pullrequest + resource: / # TODO: update this + service: github + parameters: + secretName: githubsecret + secretKey: githubCredentials + parametersFrom: + - secretKeyRef: + name: githubsecret + key: githubCredentials + action: + target: + kind: Service + apiVersion: serving.knative.dev/v1alpha1 + name: legit diff --git a/eventing/samples/github-events/function.go b/eventing/samples/github-events/function.go new file mode 100644 index 000000000..831f48f33 --- /dev/null +++ b/eventing/samples/github-events/function.go @@ -0,0 +1,106 @@ +/* +Copyright 2018 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "context" + "encoding/json" + "flag" + "fmt" + ghclient "github.com/google/go-github/github" + "github.com/knative/eventing/pkg/event" + "golang.org/x/oauth2" + "gopkg.in/go-playground/webhooks.v3/github" + "log" + "net/http" + "os" + "strings" +) + +const ( + // Environment variable containing json credentials + envSecret = "GITHUB_SECRET" + // this is what we tack onto each PR title if not there already + titleSuffix = "looks pretty legit" +) + +// GithubHandler holds necessary objects for communicating with the Github. +type GithubHandler struct { + client *ghclient.Client + ctx context.Context +} + +type GithubSecrets struct { + AccessToken string `json:"accessToken"` + SecretToken string `json:"secretToken"` +} + +func (h *GithubHandler) newPullRequestPayload(ctx context.Context, pl *github.PullRequestPayload) { + + title := pl.PullRequest.Title + log.Printf("GOT PR with Title: %q", title) + + // Check the title and if it contains 'looks pretty legit' leave it alone + if strings.Contains(title, titleSuffix) { + // already modified, leave it alone. + return + } + + newTitle := fmt.Sprintf("%s (%s)", title, titleSuffix) + updatedPR := ghclient.PullRequest{ + Title: &newTitle, + } + newPR, response, err := h.client.PullRequests.Edit(h.ctx, + pl.Repository.Owner.Login, pl.Repository.Name, int(pl.Number), &updatedPR) + if err != nil { + log.Printf("Failed to update PR: %s\n%s", err, response) + return + } + if newPR.Title != nil { + log.Printf("New PR Title: %q", *newPR.Title) + } else { + log.Printf("New PR title is nil") + } +} + +func main() { + flag.Parse() + githubSecrets := os.Getenv(envSecret) + + var credentials GithubSecrets + err := json.Unmarshal([]byte(githubSecrets), &credentials) + if err != nil { + log.Fatalf("Failed to unmarshal credentials: %s", err) + return + } + + // Set up the auth for being able to talk to Github. + ctx := context.Background() + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: credentials.AccessToken}, + ) + tc := oauth2.NewClient(ctx, ts) + + client := ghclient.NewClient(tc) + + h := &GithubHandler{ + client: client, + ctx: ctx, + } + + log.Fatal(http.ListenAndServe(":8080", event.Handler(h.newPullRequestPayload))) +} diff --git a/eventing/samples/github-events/function.yaml b/eventing/samples/github-events/function.yaml new file mode 100644 index 000000000..b553f2708 --- /dev/null +++ b/eventing/samples/github-events/function.yaml @@ -0,0 +1,34 @@ +# Copyright 2018 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: serving.knative.dev/v1alpha1 +kind: Service +metadata: + name: legit +spec: + runLatest: + configuration: + revisionTemplate: + metadata: + labels: + knative.dev/type: function + spec: + container: + image: docker.io/{username}/github-events # TODO: fill username out + env: + - name: GITHUB_SECRET + valueFrom: + secretKeyRef: + key: githubCredentials + name: githubsecret diff --git a/eventing/samples/github-events/githubsecret.yaml b/eventing/samples/github-events/githubsecret.yaml new file mode 100644 index 000000000..0a7f3da5e --- /dev/null +++ b/eventing/samples/github-events/githubsecret.yaml @@ -0,0 +1,25 @@ +# Copyright 2018 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: v1 +kind: Secret +metadata: + name: githubsecret +type: Opaque +stringData: + githubCredentials: > + { + "accessToken": "", + "secretToken": "" + } diff --git a/eventing/samples/github-events/personal_access_token.png b/eventing/samples/github-events/personal_access_token.png new file mode 100644 index 000000000..8f03ecc1d Binary files /dev/null and b/eventing/samples/github-events/personal_access_token.png differ diff --git a/eventing/samples/k8s-events/README.md b/eventing/samples/k8s-events/README.md index d033646dd..6e56ecb4a 100644 --- a/eventing/samples/k8s-events/README.md +++ b/eventing/samples/k8s-events/README.md @@ -46,11 +46,12 @@ kubectl apply -f serviceaccount.yaml 1. Use Docker to build the sample code into a container. To build and push with Docker Hub, run these commands replacing `{username}` with your Docker Hub - username. Run the following from the _root_ of the `knative/docs` repo: + username: ```shell # Build the container on your local machine - docker build -t {username}/k8s-events --file=eventing/samples/k8s-events/Dockerfile . + # Note: The relative path points to the _root_ of the `knative/docs` repo + docker build -t {username}/k8s-events --file Dockerfile ../../../ # Push the container to docker registry docker push {username}/k8s-events @@ -62,21 +63,26 @@ kubectl apply -f serviceaccount.yaml step.** Apply the configuration using `kubectl`: ```shell - kubectl apply -f eventing/samples/k8s-events/function.yaml + kubectl apply -f function.yaml ``` 1. Check that your service is running using: ```shell - kubectl get services.serving.knative.dev -o "custom-columns=NAME:.metadata.name,READY:.status.conditions[2].status,REASON:.status.conditions[2].message" + kubectl get ksvc -o "custom-columns=NAME:.metadata.name,READY:.status.conditions[2].status,REASON:.status.conditions[2].message" NAME READY REASON read-k8s-events True ``` + > Note: `ksvc` is an alias for `services.serving.knative.dev`. If you have + an older version (version 0.1.0) of Knative installed, you'll need to use + the long name until you upgrade to version 0.1.1 or higher. See + [Checking Knative Installation Version](../../../install/check-install-version.md) + to learn how to see what version you have installed. 1. Create the flow sending Kubernetes Events to the service: ```shell - kubectl apply -f eventing/samples/k8s-events/flow.yaml + kubectl apply -f flow.yaml ``` 1. If you have the full knative install, you can read the function logs using diff --git a/eventing/samples/k8s-events/function.yaml b/eventing/samples/k8s-events/function.yaml index 46383e64f..59c966b90 100644 --- a/eventing/samples/k8s-events/function.yaml +++ b/eventing/samples/k8s-events/function.yaml @@ -24,4 +24,4 @@ spec: spec: container: # Replace this image with your {username}/k8s-events container - image: github.com/knative/docs/eventing/sample/k8s-events + image: github.com/knative/docs/eventing/samples/k8s-events diff --git a/hack/update-deps.sh b/hack/update-deps.sh new file mode 100644 index 000000000..987503fed --- /dev/null +++ b/hack/update-deps.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +# Copyright 2018 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -o errexit +set -o nounset +set -o pipefail + +source $(dirname $0)/../vendor/github.com/knative/test-infra/scripts/library.sh + +cd ${REPO_ROOT_DIR} + +# Ensure we have everything we need under vendor/ +dep ensure + +# Keep the only dir in knative/test-infra we're interested in +find vendor/github.com/knative/test-infra -mindepth 1 -maxdepth 1 ! -name scripts -exec rm -fr {} \; diff --git a/images/knative-version.png b/images/knative-version.png new file mode 100644 index 000000000..f3b95ea8e Binary files /dev/null and b/images/knative-version.png differ diff --git a/install/Knative-with-AKS.md b/install/Knative-with-AKS.md index 5c72d22d8..42e6fb392 100644 --- a/install/Knative-with-AKS.md +++ b/install/Knative-with-AKS.md @@ -69,7 +69,7 @@ environment variables. First determine which region you'd like to run AKS in, al 1. Set `RESOURCE_GROUP` and `LOCATION` variables: ```bash - export LOCATION=east-us + export LOCATION=eastus export RESOURCE_GROUP=knative-group export CLUSTER_NAME=knative-cluster ``` @@ -123,7 +123,7 @@ Knative depends on Istio. 1. Install Istio: ```bash - kubectl apply -f https://raw.githubusercontent.com/knative/serving/v0.1.0/third_party/istio-0.8.0/istio.yaml + kubectl apply -f https://raw.githubusercontent.com/knative/serving/v0.1.1/third_party/istio-0.8.0/istio.yaml ``` 1. Label the default namespace with `istio-injection=enabled`: ```bash @@ -142,26 +142,45 @@ rerun the command to see the current status. > Note: Instead of rerunning the command, you can add `--watch` to the above command to view the component's status updates in real time. Use CTRL + C to exit watch mode. -## Installing Knative Serving +## Installing Knative components -1. Next, we will install [Knative Serving](https://github.com/knative/serving) -and its dependencies: +You can install the Knative Serving and Build components together, or Build on its own. + +### Installing Knative Serving and Build components + +1. Run the `kubectl apply` command to install Knative and its dependencies: ```bash - kubectl apply -f https://github.com/knative/serving/releases/download/v0.1.0/release.yaml + kubectl apply -f https://github.com/knative/serving/releases/download/v0.1.1/release.yaml ``` -1. Monitor the Knative components, until all of the components show a `STATUS` of -`Running`: +1. Monitor the Knative components until all of the components show a + `STATUS` of `Running`: ```bash kubectl get pods -n knative-serving + kubectl get pods -n knative-build ``` +### Installing Knative Build only + +1. Run the `kubectl apply` command to install + [Knative Build](https://github.com/knative/build) and its dependencies: + ```bash + kubectl apply -f https://raw.githubusercontent.com/knative/serving/v0.1.1/third_party/config/build/release.yaml + ``` +1. Monitor the Knative Build components until all of the components show a + `STATUS` of `Running`: + ```bash + kubectl get pods -n knative-build + Just as with the Istio components, it will take a few seconds for the Knative -components to be up and running; you can rerun the command to see the current status. +components to be up and running; you can rerun the `kubectl get` command to see +the current status. > Note: Instead of rerunning the command, you can add `--watch` to the above - command to view the component's status updates in real time. Use CTRL + C to exit watch mode. + command to view the component's status updates in real time. Use CTRL + C to + exit watch mode. -You are now ready to deploy an app to your new Knative cluster. +You are now ready to deploy an app or create a build in your new Knative +cluster. ## Deploying an app diff --git a/install/Knative-with-GKE.md b/install/Knative-with-GKE.md index 25ffbb6ae..24abd4656 100644 --- a/install/Knative-with-GKE.md +++ b/install/Knative-with-GKE.md @@ -14,12 +14,14 @@ specifications for Knative on Google Cloud Platform. This guide assumes you are using bash in a Mac or Linux environment; some commands will need to be adjusted for use in a Windows environment. -### Installing the Google Cloud SDK +### Installing the Google Cloud SDK and `kubectl` -1. If you already have `kubectl`, run `kubectl version` to check your client version. - -1. If you already have `gcloud` installed with the `kubectl` component later than - v1.10, you can skip these steps. +1. If you already have `gcloud` installed with `kubectl` version 1.10 or newer, + you can skip these steps. + > Tip: To check which version of `kubectl` you have installed, enter: + ``` + kubectl version + ``` 1. Download and install the `gcloud` command line tool: https://cloud.google.com/sdk/install @@ -117,7 +119,7 @@ Knative depends on Istio. 1. Install Istio: ```bash - kubectl apply -f https://raw.githubusercontent.com/knative/serving/v0.1.0/third_party/istio-0.8.0/istio.yaml + kubectl apply -f https://raw.githubusercontent.com/knative/serving/v0.1.1/third_party/istio-0.8.0/istio.yaml ``` 1. Label the default namespace with `istio-injection=enabled`: ```bash @@ -137,30 +139,27 @@ rerun the command to see the current status. ## Installing Knative components -You have the option to install and use only the Knative components that you -want. You can install only the component of Knative if you need that -functionality, for example Knative serving is not required to create and run -builds. +You can install the Knative Serving and Build components together, or Build on its own. -### Installing Knative Serving +### Installing Knative Serving and Build components -1. Run the `kubectl apply` command to install - [Knative Serving](https://github.com/knative/serving) and its dependencies: +1. Run the `kubectl apply` command to install Knative and its dependencies: ```bash - kubectl apply -f https://github.com/knative/serving/releases/download/v0.1.0/release.yaml + kubectl apply -f https://github.com/knative/serving/releases/download/v0.1.1/release.yaml ``` -1. Monitor the Knative serving components until all of the components show a +1. Monitor the Knative components until all of the components show a `STATUS` of `Running`: ```bash kubectl get pods -n knative-serving + kubectl get pods -n knative-build ``` -### Installing Knative Build +### Installing Knative Build only 1. Run the `kubectl apply` command to install [Knative Build](https://github.com/knative/build) and its dependencies: ```bash - kubectl apply -f https://raw.githubusercontent.com/knative/serving/v0.1.0/third_party/config/build/release.yaml + kubectl apply -f https://raw.githubusercontent.com/knative/serving/v0.1.1/third_party/config/build/release.yaml ``` 1. Monitor the Knative Build components until all of the components show a `STATUS` of `Running`: @@ -178,7 +177,7 @@ the current status. You are now ready to deploy an app or create a build in your new Knative cluster. -## Deploying apps or builds +## What's next Now that your cluster has Knative installed, you're ready to deploy an app or create a build. @@ -193,8 +192,8 @@ for getting started: * You can view the available [sample apps](../serving/samples/README.md) and deploy one of your choosing. -* To get started by creating a build, see - [Creating a simple Knative Build](../build/creating-builds.md) +* You can follow the step-by-step + [Creating a simple Knative Build](../build/creating-builds.md) guide. ## Cleaning up diff --git a/install/Knative-with-Gardener.md b/install/Knative-with-Gardener.md index 68790b3ea..84d76852a 100644 --- a/install/Knative-with-Gardener.md +++ b/install/Knative-with-Gardener.md @@ -71,7 +71,7 @@ Knative depends on Istio. 1. Install Istio: ```bash - kubectl apply -f https://raw.githubusercontent.com/knative/serving/v0.1.0/third_party/istio-0.8.0/istio.yaml + kubectl apply -f https://raw.githubusercontent.com/knative/serving/v0.1.1/third_party/istio-0.8.0/istio.yaml ``` 2. Label the default namespace with `istio-injection=enabled`: ```bash @@ -87,23 +87,45 @@ rerun the command to see the current status. > command to view the component's status updates in real time. Use CTRL + C to > exit watch mode. -## Installing Knative Serving +## Installing Knative components -1. Next, we will install [Knative Serving](https://github.com/knative/serving) - and its dependencies: - `bash kubectl apply -f https://github.com/knative/serving/releases/download/v0.1.0/release.yaml` -1. Monitor the Knative components, until all of the components show a `STATUS` - of `Running`: `bash kubectl get pods -n knative-serving` +You can install the Knative Serving and Build components together, or Build on its own. + +### Installing Knative Serving and Build components + +1. Run the `kubectl apply` command to install Knative and its dependencies: + ```bash + kubectl apply -f https://github.com/knative/serving/releases/download/v0.1.1/release.yaml + ``` +1. Monitor the Knative components until all of the components show a + `STATUS` of `Running`: + ```bash + kubectl get pods -n knative-serving + kubectl get pods -n knative-build + ``` + +### Installing Knative Build only + +1. Run the `kubectl apply` command to install + [Knative Build](https://github.com/knative/build) and its dependencies: + ```bash + kubectl apply -f https://raw.githubusercontent.com/knative/serving/v0.1.1/third_party/config/build/release.yaml + ``` +1. Monitor the Knative Build components until all of the components show a + `STATUS` of `Running`: + ```bash + kubectl get pods -n knative-build Just as with the Istio components, it will take a few seconds for the Knative -components to be up and running; you can rerun the command to see the current -status. +components to be up and running; you can rerun the `kubectl get` command to see +the current status. > Note: Instead of rerunning the command, you can add `--watch` to the above -> command to view the component's status updates in real time. Use CTRL + C to -> exit watch mode. + command to view the component's status updates in real time. Use CTRL + C to + exit watch mode. -You are now ready to deploy an app to your new Knative cluster. +You are now ready to deploy an app or create a build in your new Knative +cluster. ## Alternative way to enable Knative with Gardener @@ -137,10 +159,10 @@ spec: And of course create the respectve `ConfigMaps`: ``` -curl https://raw.githubusercontent.com/knative/serving/v0.1.0/third_party/istio-0.8.0/istio.yaml +curl https://raw.githubusercontent.com/knative/serving/v0.1.1/third_party/istio-0.8.0/istio.yaml kubectl create configmap istio-chart-080 --from-file=istio.yaml -curl https://github.com/knative/serving/releases/download/v0.1.0/release.yaml +curl https://github.com/knative/serving/releases/download/v0.1.1/release.yaml kubectl create configmap knative-chart-001 --from-file=release.yaml ``` diff --git a/install/Knative-with-IKS.md b/install/Knative-with-IKS.md index fe76404ea..3d16c75ff 100644 --- a/install/Knative-with-IKS.md +++ b/install/Knative-with-IKS.md @@ -126,7 +126,7 @@ Knative depends on Istio. 1. Install Istio: ```bash - kubectl apply -f https://raw.githubusercontent.com/knative/serving/v0.1.0/third_party/istio-0.8.0/istio.yaml + kubectl apply -f https://raw.githubusercontent.com/knative/serving/v0.1.1/third_party/istio-0.8.0/istio.yaml ``` 1. Label the default namespace with `istio-injection=enabled`: ```bash @@ -145,28 +145,45 @@ rerun the command to see the current status. > command to view the component's status updates in real time. Use CTRL+C to > exit watch mode. -## Installing Knative Serving +## Installing Knative components -1. Next, we will install [Knative Serving](https://github.com/knative/serving) - and its dependencies: +You can install the Knative Serving and Build components together, or Build on its own. + +### Installing Knative Serving and Build components + +1. Run the `kubectl apply` command to install Knative and its dependencies: ```bash - kubectl apply -f https://github.com/knative/serving/releases/download/v0.1.0/release.yaml + kubectl apply -f https://github.com/knative/serving/releases/download/v0.1.1/release.yaml ``` -1. Monitor the Knative components until all of the components show a `STATUS` - of `Running`: +1. Monitor the Knative components until all of the components show a + `STATUS` of `Running`: ```bash kubectl get pods -n knative-serving + kubectl get pods -n knative-build ``` +### Installing Knative Build only + +1. Run the `kubectl apply` command to install + [Knative Build](https://github.com/knative/build) and its dependencies: + ```bash + kubectl apply -f https://raw.githubusercontent.com/knative/serving/v0.1.1/third_party/config/build/release.yaml + ``` +1. Monitor the Knative Build components until all of the components show a + `STATUS` of `Running`: + ```bash + kubectl get pods -n knative-build + Just as with the Istio components, it will take a few seconds for the Knative -components to be up and running; you can rerun the command to see the current -status. +components to be up and running; you can rerun the `kubectl get` command to see +the current status. -> Note: Instead of re-running the command, you can add `--watch` to the above -> command to view the component's status updates in real time. Use CTRL+C to -> exit watch mode. +> Note: Instead of rerunning the command, you can add `--watch` to the above + command to view the component's status updates in real time. Use CTRL + C to + exit watch mode. -You are now ready to deploy an app to your new Knative cluster. +You are now ready to deploy an app or create a build in your new Knative +cluster. ## Deploying an app diff --git a/install/Knative-with-Minikube.md b/install/Knative-with-Minikube.md index 57a7b822c..45a5ee807 100644 --- a/install/Knative-with-Minikube.md +++ b/install/Knative-with-Minikube.md @@ -59,7 +59,7 @@ Knative depends on Istio. Run the following to install Istio. (We are changing `LoadBalancer` to `NodePort` for the `istio-ingress` service). ```shell -curl -L https://raw.githubusercontent.com/knative/serving/v0.1.0/third_party/istio-0.8.0/istio.yaml \ +curl -L https://raw.githubusercontent.com/knative/serving/v0.1.1/third_party/istio-0.8.0/istio.yaml \ | sed 's/LoadBalancer/NodePort/' \ | kubectl apply -f - @@ -85,12 +85,12 @@ rerun the command to see the current status. Next, install [Knative Serving](https://github.com/knative/serving): Because you have limited resources available, use the -`https://github.com/knative/serving/releases/download/v0.1.0/release-lite.yaml` +`https://github.com/knative/serving/releases/download/v0.1.1/release-lite.yaml` file, which omits some of the monitoring components to reduce the memory used by the Knative components. To use the provided `release-lite.yaml` release, run: ```shell -curl -L https://github.com/knative/serving/releases/download/v0.1.0/release-lite.yaml \ +curl -L https://github.com/knative/serving/releases/download/v0.1.1/release-lite.yaml \ | sed 's/LoadBalancer/NodePort/' \ | kubectl apply -f - ``` diff --git a/install/Knative-with-OpenShift.md b/install/Knative-with-OpenShift.md new file mode 100644 index 000000000..da45eb504 --- /dev/null +++ b/install/Knative-with-OpenShift.md @@ -0,0 +1,212 @@ +# Knative Install on OpenShift + +This guide walks you through the installation of the latest version of [Knative +Serving](https://github.com/knative/serving) on an +[OpenShift](https://github.com/openshift/origin) using pre-built images and +demonstrates creating and deploying an image of a sample "hello world" app onto +the newly created Knative cluster. + +You can find [guides for other platforms here](README.md). + +## Before you begin + +These instructions will run an OpenShift 3.10 (Kubernetes 1.10) cluster on your +local machine using [`oc cluster up`](https://docs.openshift.org/latest/getting_started/administrators.html#running-in-a-docker-container) +to test-drive knative. + +## Install `oc` (openshift cli) + +You can install the latest version of `oc`, the OpenShift CLI, into your local +directory by downloading the right release tarball for your OS from the +[releases page](https://github.com/openshift/origin/releases/tag/v3.10.0). + +```shell +export OS= +curl https://github.com/openshift/origin/releases/download/v3.10.0/openshift-origin-client-tools-v3.10.0-dd10d17-$OS-64bit.tar.gz -o oc.tar.gz +tar zvf oc.tar.gz -x openshift-origin-client-tools-v3.10.0-dd10d17-$OS-64bit/oc --strip=1 + +# You will now have the oc binary in your local directory +``` + +## Scripted cluster setup and installation + +For Linux and Mac, you can optionally run a +[script](scripts/knative-with-openshift.sh) that automates the steps on this +page. + +Once you have `oc` present on your machine and in your `PATH`, you can simply +run [this script](scripts/knative-with-openshift.sh); it will: + +- Create a new OpenShift cluster on your local machine with `oc cluster up` +- Install Istio and Knative serving +- Log you in as the cluster administrator +- Set up the default namespace for istio autoinjection + +Once the script completes, you'll be ready to test out Knative! + +## Creating a new OpenShift cluster + +Create a new OpenShift cluster on your local machine using `oc cluster up`: + +```shell +oc cluster up --write-config + +# Enable admission webhooks +sed -i -e 's/"admissionConfig":{"pluginConfig":null}/"admissionConfig": {\ + "pluginConfig": {\ + "ValidatingAdmissionWebhook": {\ + "configuration": {\ + "apiVersion": "v1",\ + "kind": "DefaultAdmissionConfig",\ + "disable": false\ + }\ + },\ + "MutatingAdmissionWebhook": {\ + "configuration": {\ + "apiVersion": "v1",\ + "kind": "DefaultAdmissionConfig",\ + "disable": false\ + }\ + }\ + }\ +}/' openshift.local.clusterup/kube-apiserver/master-config.yaml + +oc cluster up --server-loglevel=5 +``` + +Once the cluster is up, login as the cluster administrator: + +```shell +oc login -u system:admin +``` + +Now, we'll set up the default project for use with Knative. + +```shell +oc project default + +# SCCs (Security Context Constraints) are the precursor to the PSP (Pod +# Security Policy) mechanism in Kubernetes. +oc adm policy add-scc-to-user privileged -z default -n default + +oc label namespace default istio-injection=enabled +``` + +## Installing Istio + +Knative depends on Istio. First, run the following to grant the necessary +privileges to the service accounts istio will use: + +```shell +oc adm policy add-scc-to-user anyuid -z istio-ingress-service-account -n istio-system +oc adm policy add-scc-to-user anyuid -z default -n istio-system +oc adm policy add-scc-to-user anyuid -z prometheus -n istio-system +oc adm policy add-scc-to-user anyuid -z istio-egressgateway-service-account -n istio-system +oc adm policy add-scc-to-user anyuid -z istio-citadel-service-account -n istio-system +oc adm policy add-scc-to-user anyuid -z istio-ingressgateway-service-account -n istio-system +oc adm policy add-scc-to-user anyuid -z istio-cleanup-old-ca-service-account -n istio-system +oc adm policy add-scc-to-user anyuid -z istio-mixer-post-install-account -n istio-system +oc adm policy add-scc-to-user anyuid -z istio-mixer-service-account -n istio-system +oc adm policy add-scc-to-user anyuid -z istio-pilot-service-account -n istio-system +oc adm policy add-scc-to-user anyuid -z istio-sidecar-injector-service-account -n istio-system +oc adm policy add-cluster-role-to-user cluster-admin -z istio-galley-service-account -n istio-system +``` + +Run the following to install Istio: + +```shell +curl -L https://storage.googleapis.com/knative-releases/serving/latest/istio.yaml \ + | sed 's/LoadBalancer/NodePort/' \ + | oc apply -f - +``` + +Monitor the Istio components until all of the components show a `STATUS` of +`Running` or `Completed`: + +```shell +oc get pods -n istio-system +``` + +It will take a few minutes for all the components to be up and running; you can +rerun the command to see the current status. + +> Note: Instead of rerunning the command, you can add `--watch` to the above + command to view the component's status updates in real time. Use CTRL+C to exit watch mode. + +## Installing Knative Serving + +Next, we'll install [Knative Serving](https://github.com/knative/serving). + +First, run the following to grant the necessary privileges to the service +accounts istio will use: + +```shell +oc adm policy add-scc-to-user anyuid -z build-controller -n knative-build +oc adm policy add-scc-to-user anyuid -z controller -n knative-serving +oc adm policy add-scc-to-user anyuid -z autoscaler -n knative-serving +oc adm policy add-scc-to-user anyuid -z kube-state-metrics -n monitoring +oc adm policy add-scc-to-user anyuid -z node-exporter -n monitoring +oc adm policy add-scc-to-user anyuid -z prometheus-system -n monitoring +oc adm policy add-cluster-role-to-user cluster-admin -z build-controller -n knative-build +oc adm policy add-cluster-role-to-user cluster-admin -z controller -n knative-serving +``` + +Next, install Knative: + +```shell +curl -L https://storage.googleapis.com/knative-releases/serving/latest/release-lite.yaml \ + | sed 's/LoadBalancer/NodePort/' \ + | oc apply -f - +``` + +Monitor the Knative components until all of the components show a `STATUS` of +`Running`: + +```shell +oc get pods -n knative-serving +``` + +Just as with the Istio components, it will take a few seconds for the Knative +components to be up and running; you can rerun the command to see the current status. + +> Note: Instead of rerunning the command, you can add `--watch` to the above + command to view the component's status updates in real time. Use CTRL+C to exit watch mode. + +Now you can deploy an app to your newly created Knative cluster. + +## Deploying an app + +Now that your cluster has Knative installed, you're ready to deploy an app. + +If you'd like to follow a step-by-step guide for deploying your first app on +Knative, check out the +[Getting Started with Knative App Deployment](getting-started-knative-app.md) +guide. + +If you'd like to view the available sample apps and deploy one of your choosing, +head to the [sample apps](../serving/samples/README.md) repo. + +> Note: When looking up the IP address to use for accessing your app, you need to look up + the NodePort for the `knative-ingressgateway` as well as the IP address used for OpenShift. + You can use the following command to look up the value to use for the {IP_ADDRESS} placeholder + used in the samples: + + ```shell + export IP_ADDRESS=$(oc get node -o 'jsonpath={.items[0].status.addresses[0].address}'):$(oc get svc knative-ingressgateway -n istio-system -o 'jsonpath={.spec.ports[?(@.port==80)].nodePort}') + ``` + +## Cleaning up + +Delete your test cluster by running: + +```shell +oc cluster down +rm -rf openshift.local.clusterup +``` + +--- + +Except as otherwise noted, the content of this page is licensed under the +[Creative Commons Attribution 4.0 License](https://creativecommons.org/licenses/by/4.0/), +and code samples are licensed under the +[Apache 2.0 License](https://www.apache.org/licenses/LICENSE-2.0). diff --git a/install/Knative-with-PKS.md b/install/Knative-with-PKS.md index 11eb219eb..a78189710 100644 --- a/install/Knative-with-PKS.md +++ b/install/Knative-with-PKS.md @@ -32,7 +32,7 @@ Knative depends on Istio. Istio workloads require privileged mode for Init Conta 1. Install Istio: ```bash - kubectl apply -f https://raw.githubusercontent.com/knative/serving/v0.1.0/third_party/istio-0.8.0/istio.yaml + kubectl apply -f https://raw.githubusercontent.com/knative/serving/v0.1.1/third_party/istio-0.8.0/istio.yaml ``` 1. Label the default namespace with `istio-injection=enabled`: ```bash @@ -50,26 +50,45 @@ rerun the command to see the current status. > Note: Instead of rerunning the command, you can add `--watch` to the above command to view the component's status updates in real time. Use CTRL + C to exit watch mode. -## Installing Knative Serving +## Installing Knative components -1. Next, we will install [Knative Serving](https://github.com/knative/serving) -and its dependencies: +You can install the Knative Serving and Build components together, or Build on its own. + +### Installing Knative Serving and Build components + +1. Run the `kubectl apply` command to install Knative and its dependencies: ```bash - kubectl apply -f https://github.com/knative/serving/releases/download/v0.1.0/release.yaml + kubectl apply -f https://github.com/knative/serving/releases/download/v0.1.1/release.yaml ``` -1. Monitor the Knative components, until all of the components show a `STATUS` of -`Running`: +1. Monitor the Knative components until all of the components show a + `STATUS` of `Running`: ```bash kubectl get pods -n knative-serving + kubectl get pods -n knative-build ``` +### Installing Knative Build only + +1. Run the `kubectl apply` command to install + [Knative Build](https://github.com/knative/build) and its dependencies: + ```bash + kubectl apply -f https://raw.githubusercontent.com/knative/serving/v0.1.1/third_party/config/build/release.yaml + ``` +1. Monitor the Knative Build components until all of the components show a + `STATUS` of `Running`: + ```bash + kubectl get pods -n knative-build + Just as with the Istio components, it will take a few seconds for the Knative -components to be up and running; you can rerun the command to see the current status. +components to be up and running; you can rerun the `kubectl get` command to see +the current status. > Note: Instead of rerunning the command, you can add `--watch` to the above - command to view the component's status updates in real time. Use CTRL + C to exit watch mode. + command to view the component's status updates in real time. Use CTRL + C to + exit watch mode. -You are now ready to deploy an app to your new Knative cluster. +You are now ready to deploy an app or create a build in your new Knative +cluster. ## Deploying an app diff --git a/install/Knative-with-any-k8s.md b/install/Knative-with-any-k8s.md index fbcf20678..5c2ea6467 100644 --- a/install/Knative-with-any-k8s.md +++ b/install/Knative-with-any-k8s.md @@ -19,7 +19,7 @@ Containers 1. Install Istio: ```bash - kubectl apply -f https://raw.githubusercontent.com/knative/serving/v0.1.0/third_party/istio-0.8.0/istio.yaml + kubectl apply -f https://raw.githubusercontent.com/knative/serving/v0.1.1/third_party/istio-0.8.0/istio.yaml ``` 1. Label the default namespace with `istio-injection=enabled`: ```bash @@ -35,28 +35,45 @@ rerun the command to see the current status. > command to view the component's status updates in real time. Use CTRL + C to > exit watch mode. -## Installing Knative Serving +## Installing Knative components -1. Next, we will install [Knative Serving](https://github.com/knative/serving) - and its dependencies: +You can install the Knative Serving and Build components together, or Build on its own. + +### Installing Knative Serving and Build components + +1. Run the `kubectl apply` command to install Knative and its dependencies: ```bash - kubectl apply -f https://github.com/knative/serving/releases/download/v0.1.0/release.yaml + kubectl apply -f https://github.com/knative/serving/releases/download/v0.1.1/release.yaml ``` -1. Monitor the Knative components, until all of the components show a `STATUS` - of `Running`: +1. Monitor the Knative components until all of the components show a + `STATUS` of `Running`: ```bash kubectl get pods -n knative-serving + kubectl get pods -n knative-build ``` +### Installing Knative Build only + +1. Run the `kubectl apply` command to install + [Knative Build](https://github.com/knative/build) and its dependencies: + ```bash + kubectl apply -f https://raw.githubusercontent.com/knative/serving/v0.1.1/third_party/config/build/release.yaml + ``` +1. Monitor the Knative Build components until all of the components show a + `STATUS` of `Running`: + ```bash + kubectl get pods -n knative-build + Just as with the Istio components, it will take a few seconds for the Knative -components to be up and running; you can rerun the command to see the current -status. +components to be up and running; you can rerun the `kubectl get` command to see +the current status. > Note: Instead of rerunning the command, you can add `--watch` to the above -> command to view the component's status updates in real time. Use CTRL + C to -> exit watch mode. + command to view the component's status updates in real time. Use CTRL + C to + exit watch mode. -You are now ready to deploy an app to your new Knative cluster. +You are now ready to deploy an app or create a build in your new Knative +cluster. ## Deploying an app diff --git a/install/README.md b/install/README.md index 8b4218bdd..da3d29337 100644 --- a/install/README.md +++ b/install/README.md @@ -9,7 +9,7 @@ sure which Kubernetes platform is right for you, see [Picking the Right Solution](https://kubernetes.io/docs/setup/pick-right-solution/). We provide information for installing Knative on -[Google Kubernetes Engine](https://cloud.google.com/kubernetes-engine/docs/), [IBM Cloud Kubernetes Service](https://www.ibm.com/cloud/container-service), [Azure Kubernetes Service](https://docs.microsoft.com/en-us/azure/aks/), [Minikube](https://kubernetes.io/docs/setup/minikube/) and [Pivotal Container Service](https://pivotal.io/platform/pivotal-container-service) clusters. +[Google Kubernetes Engine](https://cloud.google.com/kubernetes-engine/docs/), [IBM Cloud Kubernetes Service](https://www.ibm.com/cloud/container-service), [Azure Kubernetes Service](https://docs.microsoft.com/en-us/azure/aks/), [Minikube](https://kubernetes.io/docs/setup/minikube/), [OpenShift](https://github.com/openshift/origin) and [Pivotal Container Service](https://pivotal.io/platform/pivotal-container-service) clusters. ## Installing Knative @@ -21,6 +21,7 @@ Knative components on the following platforms: * [Knative Install on Google Kubernetes Engine](Knative-with-GKE.md) * [Knative Install on IBM Cloud Kubernetes Service](Knative-with-IKS.md) * [Knative Install on Minikube](Knative-with-Minikube.md) +* [Knative Install on OpenShift](Knative-with-OpenShift.md) * [Knative Install on Pivotal Container Service](Knative-with-PKS.md) If you already have a Kubernetes cluster you're comfortable installing @@ -50,6 +51,10 @@ and set up an Istio IP range for outbound network access: * [Configure outbound network access](../serving/outbound-network-access.md) * [Configuring HTTPS with a custom certificate](../serving/using-an-ssl-cert.md) +## Checking the Version of Your Knative Serving Installation + +* [Checking the version of your Knative Serving installation](check-install-version.md) + --- Except as otherwise noted, the content of this page is licensed under the diff --git a/install/check-install-version.md b/install/check-install-version.md new file mode 100644 index 000000000..bb15e7952 --- /dev/null +++ b/install/check-install-version.md @@ -0,0 +1,37 @@ +# Checking the Version of Your Knative Serving Installation + +If you want to check what version of Knative serving you have installed, +enter the following command: + +```bash +kubectl describe deploy controller -n knative-serving +``` + +This will return the description for the `knative-serving` controller; this +information contains the link to the container that was used to install Knative: + +```yaml +... +Pod Template: + Labels: app=controller + Annotations: sidecar.istio.io/inject=false + Service Account: controller + Containers: + controller: + # Link to container used for Knative install + Image: gcr.io/knative-releases/github.com/knative/serving/cmd/controller@sha256:59abc8765d4396a3fc7cac27a932a9cc151ee66343fa5338fb7146b607c6e306 +... +``` + +Copy the full `gcr.io` link to the container and paste it into your browser. +If you are already signed in to a Google account, you'll be taken to the Google +Container Registry page for that container in the Google Cloud Platform console. +If you aren't already signed in, you'll need to sign in a to a Google account +before you can view the container details. + +On the container details page, you'll see a section titled +"Container classification," and in that section is a list of tags. The versions +of Knative you have installed will appear in the list as `v0.1.1`, or whatever +verion you have installed: + +![Shows list of tags on container details page; v0.1.1 is the Knative version and is the first tag.](../images/knative-version.png) \ No newline at end of file diff --git a/install/getting-started-knative-app.md b/install/getting-started-knative-app.md index dd91fb7cd..56f4b1ce7 100644 --- a/install/getting-started-knative-app.md +++ b/install/getting-started-knative-app.md @@ -77,16 +77,26 @@ Now that your service is created, Knative will perform the following steps: To see if your app has been deployed succesfully, you need the host URL and IP address created by Knative. -1. To find the IP address for your service, enter - `kubectl get svc knative-ingressgateway -n istio-system`. If your cluster is - new, it can take sometime for the service to get asssigned an external IP address. +Note: If your cluster is new, it can take some time before the service is +asssigned an external IP address. + +1. To find the IP address for your service, enter: + + ```shell + kubectl get svc knative-ingressgateway -n istio-system + + NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE + knative-ingressgateway LoadBalancer 10.23.247.74 35.203.155.229 80:32380/TCP,443:32390/TCP,32400:32400/TCP 2d + + ``` + Take note of the `EXTERNAL-IP` address. + + You can also export the IP address as a variable with the following command: ```shell export IP_ADDRESS=$(kubectl get svc knative-ingressgateway -n istio-system -o 'jsonpath={.status.loadBalancer.ingress[0].ip}') ``` - - > Note: if you use minikube or a baremetal cluster that has no external load balancer, the `EXTERNAL-IP` field is shown as ``. You need to use `NodeIP` and `NodePort` to interact your app instead. To get your app's `NodeIP` and `NodePort`, enter the following command: @@ -97,25 +107,47 @@ IP address created by Knative. 1. To find the host URL for your service, enter: ```shell - export HOST_URL=$(kubectl get services.serving.knative.dev helloworld-go -o jsonpath='{.status.domain}') + kubectl get ksvc helloworld-go -o=custom-columns=NAME:.metadata.name,DOMAIN:.status.domain + NAME DOMAIN + helloworld-go helloworld-go.default.example.com + ``` + + > Note: `ksvc` is an alias for `services.serving.knative.dev`. If you have + an older version (version 0.1.0) of Knative installed, you'll need to use + the long name until you upgrade to version 0.1.1 or higher. See + [Checking Knative Installation Version](check-install-version.md) + to learn how to see what version you have installed. + + You can also export the host URL as a variable using the following command: + + ```shell + export HOST_URL=$(kubectl get ksvc helloworld-go -o jsonpath='{.status.domain}') ``` If you changed the name from `helloworld-go` to something else when creating - the the `.yaml` file, replace `helloworld-go` in the above command with the + the `.yaml` file, replace `helloworld-go` in the above commands with the name you entered. -1. Now you can make a request to your app to see the results. Replace +1. Now you can make a request to your app and see the results. Replace `IP_ADDRESS` with the `EXTERNAL-IP` you wrote down, and replace `helloworld-go.default.example.com` with the domain returned in the previous step. - - If you deployed your own app, you may want to customize this cURL - request to interact with your application. + + ```shell + curl -H "Host: helloworld-go.default.example.com" http://IP_ADDRESS + Hello World: Go Sample v1! + ``` + + If you exported the host URL And IP address as variables in the previous steps, you + can use those variables to simplify your cURL request: ```shell curl -H "Host: ${HOST_URL}" http://${IP_ADDRESS} Hello World: Go Sample v1! ``` + + If you deployed your own app, you might want to customize this cURL + request to interact with your application. It can take a few seconds for Knative to scale up your application and return a response. diff --git a/install/scripts/knative-with-openshift.sh b/install/scripts/knative-with-openshift.sh new file mode 100755 index 000000000..7bacbad70 --- /dev/null +++ b/install/scripts/knative-with-openshift.sh @@ -0,0 +1,94 @@ +#!/usr/bin/env bash + +# Turn colors in this script off by setting the NO_COLOR variable in your +# environment to any value: +# +# $ NO_COLOR=1 test.sh +NO_COLOR=${NO_COLOR:-""} +if [ -z "$NO_COLOR" ]; then + header=$'\e[1;33m' + reset=$'\e[0m' +else + header='' + reset='' +fi + +function header_text { + echo "$header$*$reset" +} + +header_text "Starting Knative test-drive on OpenShift!" + +echo "Using oc version:" +oc version + +header_text "Writing config" +oc cluster up --write-config +sed -i -e 's/"admissionConfig":{"pluginConfig":null}/"admissionConfig": {\ + "pluginConfig": {\ + "ValidatingAdmissionWebhook": {\ + "configuration": {\ + "apiVersion": "v1",\ + "kind": "DefaultAdmissionConfig",\ + "disable": false\ + }\ + },\ + "MutatingAdmissionWebhook": {\ + "configuration": {\ + "apiVersion": "v1",\ + "kind": "DefaultAdmissionConfig",\ + "disable": false\ + }\ + }\ + }\ +}/' openshift.local.clusterup/kube-apiserver/master-config.yaml + +header_text "Starting OpenShift with 'oc cluster up'" +oc cluster up --server-loglevel=5 + +header_text "Logging in as system:admin and setting up default namespace" +oc login -u system:admin +oc project default +oc adm policy add-scc-to-user privileged -z default -n default +oc label namespace default istio-injection=enabled + +header_text "Setting up security policy for istio" +oc adm policy add-scc-to-user anyuid -z istio-ingress-service-account -n istio-system +oc adm policy add-scc-to-user anyuid -z default -n istio-system +oc adm policy add-scc-to-user anyuid -z prometheus -n istio-system +oc adm policy add-scc-to-user anyuid -z istio-egressgateway-service-account -n istio-system +oc adm policy add-scc-to-user anyuid -z istio-citadel-service-account -n istio-system +oc adm policy add-scc-to-user anyuid -z istio-ingressgateway-service-account -n istio-system +oc adm policy add-scc-to-user anyuid -z istio-cleanup-old-ca-service-account -n istio-system +oc adm policy add-scc-to-user anyuid -z istio-mixer-post-install-account -n istio-system +oc adm policy add-scc-to-user anyuid -z istio-mixer-service-account -n istio-system +oc adm policy add-scc-to-user anyuid -z istio-pilot-service-account -n istio-system +oc adm policy add-scc-to-user anyuid -z istio-sidecar-injector-service-account -n istio-system +oc adm policy add-cluster-role-to-user cluster-admin -z istio-galley-service-account -n istio-system + +header_text "Installing istio" +curl -L https://storage.googleapis.com/knative-releases/serving/latest/istio.yaml \ + | sed 's/LoadBalancer/NodePort/' \ + | oc apply -f - + +header_text "Waiting for istio to become ready" +sleep 5; while echo && oc get pods -n istio-system | grep -v -E "(Running|Completed|STATUS)"; do sleep 5; done + +header_text "Setting up security policy for knative" +oc adm policy add-scc-to-user anyuid -z build-controller -n knative-build +oc adm policy add-scc-to-user anyuid -z controller -n knative-serving +oc adm policy add-scc-to-user anyuid -z autoscaler -n knative-serving +oc adm policy add-scc-to-user anyuid -z kube-state-metrics -n monitoring +oc adm policy add-scc-to-user anyuid -z node-exporter -n monitoring +oc adm policy add-scc-to-user anyuid -z prometheus-system -n monitoring +oc adm policy add-cluster-role-to-user cluster-admin -z build-controller -n knative-build +oc adm policy add-cluster-role-to-user cluster-admin -z controller -n knative-serving + +header_text "Installing Knative" +curl -L https://storage.googleapis.com/knative-releases/serving/latest/release-lite.yaml \ + | sed 's/LoadBalancer/NodePort/' \ + | oc apply -f - + +header_text "Waiting for Knative to become ready" +sleep 5; while echo && oc get pods -n knative-serving | grep -v -E "(Running|Completed|STATUS)"; do sleep 5; done + diff --git a/resources.md b/resources.md new file mode 100644 index 000000000..ff0955fa9 --- /dev/null +++ b/resources.md @@ -0,0 +1,131 @@ +# Resources + +This page contains information about various tools and technologies +that are useful to anyone developing on Knative. + +## Community Resources + +This section contains tools and technologies developed by members of the +Knative community specifically for use with Knative. + +### [`knctl`](https://github.com/cppforlife/knctl) + +`knctl` is an under-development CLI for working with Knative. + +## Other Resources + +This section contains other tools and technologies that are useful when +working with Knative. + +### [`go-containerregistry`](https://github.com/google/go-containerregistry/) + +`go-containerregistry` is a Go library used by `ko`, `kaniko`, `skaffold` and +others, which enables support for pushing, pulling and managing images in a +container image registry, without requiring Docker to be installed. + +It also provides packages to interact with images in a local Docker daemon, +which does require that Docker be installed. + +This library also provides a CLI tool called +[`crane`](https://github.com/google/go-containerregistry/blob/master/cmd/crane/doc/crane.md), +which can be used to interact with and inspect images in a registry. + +### [`jib`](https://github.com/GoogleContainerTools/jib) + +`jib` is a tool, packaged as a Maven plugin and a Gradle plugin, that +efficiently builds container images from Java source, without a Dockerfile, +without requiring access to the Docker daemon. + +Like `ko`, when `jib` is invoked, it builds your Java source and pushes an +image with that built source atop a +[distroless](https://github.com/GoogleContainerTools/distroless) base image to +produce small images that support fast incremental image builds. + +There are `BuildTemplate`s that wraps `jib` for use with Maven and Gradle, at +https://github.com/knative/build-templates/blob/master/jib/. It expects that +your `pom.xml` or `build.gradle` describes to `jib` where to push your image. +The build templates take no parameters. + +### [`kaniko`](https://github.com/GoogleContainerTools/kaniko) + +`kaniko` is a tool that enables building a container image from source using +the Dockerfile format, without requiring access to a Docker daemon. Removing +this requirement means that `kaniko` is [safe to run on a Kubernetes +cluster](https://github.com/kubernetes/kubernetes/issues/1806). + +By contrast, building an image using `docker build` necessarily requires the +Docker daemon, which would give the build complete access to your entire +cluster. So that's a very bad idea. + +`kaniko` expects to run inside a container, so it's a natural fit for the Build +CRD [builder contract](...). `kaniko` is available as a builder at +`gcr.io/kaniko-project/executor:latest`, and there's a `BuildTemplate` that +wraps it at +https://github.com/knative/build-templates/blob/master/kaniko/kaniko.yaml. It +exposes one required parameter, `IMAGE`, which describes the name of the image +to push to. + +More information here: +https://github.com/knative/build-templates/tree/master/kaniko + +`kaniko` is unrelated to `ko`. + +### [`ko`](https://github.com/google/go-containerregistry/tree/master/cmd/ko) + +`ko` is a tool designed to make development of Go apps on Kubernetes easier, by +abstracting away the container image being used, and instead referring to Go +packages by their [import paths](https://golang.org/doc/code.html#ImportPaths) +(e.g., `github.com/kaniko/serving/cmd/controller`) + +The typical usage is `ko apply -f config.yaml`, which reads in the config YAML, +and looks for Go import paths representing runnable commands (i.e., `package +main`). When it finds a matching import path, `ko` builds the package using `go +build` then pushes a container image containing that binary on top of a base +image (by default, `gcr.io/distroless/base`) to +`$KO_DOCKER_REPO/unique-string`. After pushing those images, `ko` replaces +instances of matched import paths with fully-qualified references to the images +it pushed. + +So if `ko apply` was passed this config: + +```yaml +... +image: github.com/my/repo/cmd/foo +... +``` + +...it would produce YAML like: + +```yaml +... +image: gcr.io/my-docker-repo/foo-zyxwvut@sha256:abcdef # image by digest +... +``` + +(This assumes that you have set the environment variable +`KO_DOCKER_REPO=gcr.io/my-docker-repo`) + +`ko apply` then passes this generated YAML config to `kubectl apply`. + +`ko` also supports: +* `ko publish` to simply push images and not produce configs. +* `ko resolve` to push images and output the generated configs, but not +`kubectl apply` them. +* `ko delete` to simply passthrough to `kubectl delete` for convenience. + +`ko` is used during development and release of Knative components, but is not +intended to be required for _users_ of Knative -- they should only need to +`kubectl apply` released configs generated by `ko`. + +### [`skaffold`](https://github.com/GoogleContainerTools/skaffold) + +`skaffold` is a CLI tool to aid in iterative development for Kubernetes. +Typically, you would write a [YAML +config](https://github.com/GoogleContainerTools/skaffold/blob/master/examples/annotated-skaffold.yaml) +describing to Skaffold how to build and deploy your app, then run `skaffold +dev`, which will watch your local source tree for changes and continuously +builds and deploys based on your config when changes are detected. + +Skaffold supports many pluggable implementations for building and deploying. +Skaffold contributors are working on support for Knative Build as a build +plugin, and could support Knative Serving as a deployment plugin. diff --git a/serving/accessing-logs.md b/serving/accessing-logs.md index 2575ee10e..9a7f4473e 100644 --- a/serving/accessing-logs.md +++ b/serving/accessing-logs.md @@ -1,110 +1,116 @@ # Accessing logs -If you have not yet installed the logging and monitoring components, go through the -[installation instructions](./installing-logging-metrics-traces.md) to set up the +If you have not yet installed the logging and monitoring components, go through the +[installation instructions](./installing-logging-metrics-traces.md) to set up the necessary components first. ## Kibana and Elasticsearch -To open the Kibana UI (the visualization tool for [Elasticsearch](https://info.elastic.co), -enter the following command: +* To open the Kibana UI (the visualization tool for [Elasticsearch](https://info.elastic.co)), +start a local proxy with the following command: + ```shell + kubectl proxy + ``` -```shell -kubectl proxy -``` + This command starts a local proxy of Kibana on port 8001. For security reasons, + the Kibana UI is exposed only within the cluster. -This command starts a local proxy of Kibana on port 8001. For security reasons, the -Kibana UI is exposed only within the cluster. +* Navigate to the +[Kibana UI](http://localhost:8001/api/v1/namespaces/monitoring/services/kibana-logging/proxy/app/kibana). +*It might take a couple of minutes for the proxy to work*. -Navigate to the -[Kibana UI](http://localhost:8001/api/v1/namespaces/monitoring/services/kibana-logging/proxy/app/kibana) - (*It might take a couple of minutes for the proxy to work*). + The Discover tab of the Kibana UI looks like this: -The Discover tab of the Kibana UI looks like this: + ![Kibana UI Discover tab](./images/kibana-discover-tab-annotated.png) -![Kibana UI Discover tab](./images/kibana-discover-tab-annotated.png) + You can change the time frame of logs Kibana displays in the upper right corner + of the screen. The main search bar is across the top of the Discover page. -You can change the time frame of logs Kibana displays in the upper right corner -of the screen. The main search bar is across the top of the Discover page. - -As more logs are ingested, new fields will be discovered. To have them indexed, -go to Management > Index Patterns > Refresh button (on top right) > Refresh -fields. +* As more logs are ingested, new fields will be discovered. To have them indexed, +go to "Management" > "Index Patterns" > Refresh button (on top right) > "Refresh +fields". ### Accessing configuration and revision logs -To access the logs for a configuration, enter the following search query in Kibana: +To access the logs for a configuration: -```text -kubernetes.labels.serving_knative_dev\/configuration: "configuration-example" +* Find the configuration's name with the following command: ``` - -Replace `configuration-example` with your configuration's name. Enter the following -command to get your configuration's name: - -```shell kubectl get configurations ``` -To access logs for a revision, enter the following search query in Kibana: - -```text -kubernetes.labels.serving_knative_dev\/revision: "configuration-example-00001" +* Replace `` and enter the following search query in Kibana: +``` +kubernetes.labels.serving_knative_dev\/configuration: ``` -Replace `configuration-example-00001` with your revision's name. +To access logs for a revision: +* Find the revision's name with the following command: +``` +kubectl get revisions +``` + +* Replace `` and enter the following search query in Kibana: +``` +kubernetes.labels.serving_knative_dev\/revision: +``` ### Accessing build logs -To access the logs for a build, enter the following search query in Kibana: +To access logs for a [Knative Build](../build/README.md): -```text -kubernetes.labels.build\-name: "test-build" +* Find the build's name in the specified in the `.yaml` file: + ```yaml + apiVersion: build.knative.dev/v1alpha1 + kind: Build + metadata: + name: + ``` + Or find build names with the following command: + ``` + kubectl get builds + ``` + +* Replace `` and enter the following search query in Kibana: ``` - -Replace `test-build` with your build's name. The build name is specified in the `.yaml` file as follows: - -```yaml -apiVersion: build.knative.dev/v1alpha1 -kind: Build -metadata: - name: test-build +kubernetes.labels.build\-name: ``` ### Accessing request logs -To access to request logs, enter the following search in Kibana: +To access the request logs, enter the following search in Kibana: ```text tag: "requestlog.logentry.istio-system" ``` -Request logs contain details about requests served by the revision. Below is a sample request log: + Request logs contain details about requests served by the revision. Below is + a sample request log: -```text -@timestamp July 10th 2018, 10:09:28.000 -destinationConfiguration configuration-example -destinationNamespace default -destinationRevision configuration-example-00001 -destinationService configuration-example-00001-service.default.svc.cluster.local -latency 1.232902ms -method GET -protocol http -referer unknown -requestHost route-example.default.example.com -requestSize 0 -responseCode 200 -responseSize 36 -severity Info -sourceNamespace istio-system -sourceService unknown -tag requestlog.logentry.istio-system -traceId 986d6faa02d49533 -url / -userAgent curl/7.60.0 -``` + ```text + @timestamp July 10th 2018, 10:09:28.000 + destinationConfiguration configuration-example + destinationNamespace default + destinationRevision configuration-example-00001 + destinationService configuration-example-00001-service.default.svc.cluster.local + latency 1.232902ms + method GET + protocol http + referer unknown + requestHost route-example.default.example.com + requestSize 0 + responseCode 200 + responseSize 36 + severity Info + sourceNamespace istio-system + sourceService unknown + tag requestlog.logentry.istio-system + traceId 986d6faa02d49533 + url / + userAgent curl/7.60.0 + ``` ### Accessing end to end request traces diff --git a/serving/accessing-metrics.md b/serving/accessing-metrics.md index f1a1f86aa..374977e24 100644 --- a/serving/accessing-metrics.md +++ b/serving/accessing-metrics.md @@ -1,30 +1,31 @@ # Accessing metrics -You access metrics through the [Grafana](https://grafana.com/) UI. Grafana is the visualization tool -for [Prometheus](https://prometheus.io/). To open Grafana, enter the following command: +You access metrics through the [Grafana](https://grafana.com/) UI. Grafana is +the visualization tool for [Prometheus](https://prometheus.io/). -```shell +1. To open Grafana, enter the following command: +``` kubectl port-forward -n monitoring $(kubectl get pods -n monitoring --selector=app=grafana --output=jsonpath="{.items..metadata.name}") 3000 ``` -This starts a local proxy of Grafana on port 3000. For security reasons, the Grafana UI is exposed only within -the cluster. + * This starts a local proxy of Grafana on port 3000. For security reasons, the Grafana UI is exposed only within the cluster. -Navigate to the Grafana UI at [http://localhost:3000](http://localhost:3000). -Select the **Home** button on the top of the page to see the list of pre-installed dashboards (screenshot below): +2. Navigate to the Grafana UI at [http://localhost:3000](http://localhost:3000). + +3. Select the **Home** button on the top of the page to see the list of pre-installed dashboards (screenshot below): ![Knative Dashboards](./images/grafana1.png) -The following dashboards are pre-installed with Knative Serving: + The following dashboards are pre-installed with Knative Serving: -* **Revision HTTP Requests:** HTTP request count, latency, and size metrics per revision and per configuration -* **Nodes:** CPU, memory, network, and disk metrics at node level -* **Pods:** CPU, memory, and network metrics at pod level -* **Deployment:** CPU, memory, and network metrics aggregated at deployment level -* **Istio, Mixer and Pilot:** Detailed Istio mesh, Mixer, and Pilot metrics -* **Kubernetes:** Dashboards giving insights into cluster health, deployments, and capacity usage + * **Revision HTTP Requests:** HTTP request count, latency, and size metrics per revision and per configuration + * **Nodes:** CPU, memory, network, and disk metrics at node level + * **Pods:** CPU, memory, and network metrics at pod level + * **Deployment:** CPU, memory, and network metrics aggregated at deployment level + * **Istio, Mixer and Pilot:** Detailed Istio mesh, Mixer, and Pilot metrics + * **Kubernetes:** Dashboards giving insights into cluster health, deployments, and capacity usage -To sign in as an administrator and modify or add dashboards, sign in with username `admin` and password `admin`. -Before you expose the Grafana UI outside the cluster, make sure to change the password. +4. Set up an administrator account to modify or add dashboards by signing in with username: `admin` and password: `admin`. + * Before you expose the Grafana UI outside the cluster, make sure to change the password. --- diff --git a/serving/debugging-application-issues.md b/serving/debugging-application-issues.md index 66d9e1724..51aa1ffc6 100644 --- a/serving/debugging-application-issues.md +++ b/serving/debugging-application-issues.md @@ -38,7 +38,7 @@ kubectl get route -o yaml The `conditions` in `status` provide the reason if there is any failure. For details, see Knative -[Error Conditions and Reporting](../spec/errors.md)(currently some of them +[Error Conditions and Reporting](https://github.com/knative/serving/blob/master/docs/spec/errors.md)(currently some of them are not implemented yet). ## Check Revision status @@ -77,7 +77,7 @@ If you see this condition, check the following to continue debugging: If you see other conditions, to debug further: * Look up the meaning of the conditions in Knative - [Error Conditions and Reporting](../spec/errors.md). Note: some of them + [Error Conditions and Reporting](https://github.com/knative/serving/blob/master/docs/spec/errors.md). Note: some of them are not implemented yet. An alternative is to [check Pod status](#check-pod-status). * If you are using `BUILD` to deploy and the `BuidComplete` condition is not diff --git a/serving/installing-logging-metrics-traces.md b/serving/installing-logging-metrics-traces.md index 18f22c0b3..1568c3936 100644 --- a/serving/installing-logging-metrics-traces.md +++ b/serving/installing-logging-metrics-traces.md @@ -1,72 +1,112 @@ # Monitoring, Logging and Tracing Installation -Knative Serving offers two different monitoring setups: -One that uses Elasticsearch, Kibana, Prometheus and Grafana and -another that uses Stackdriver, Prometheus and Grafana. See below -for installation instructions for these two setups. You can install -only one of these two setups and side-by-side installation of these two are not supported. +Knative Serving offers two different monitoring setups: +[Elasticsearch, Kibana, Prometheus and Grafana](#Elasticsearch,-Kibana,-Prometheus-&-Grafana-Setup) +or +[Stackdriver, Prometheus and Grafana](#Stackdriver,-Prometheus-&-Grafana-Setup). +You can install only one of these two setups and side-by-side installation of +these two are not supported. ## Elasticsearch, Kibana, Prometheus & Grafana Setup -*If you installed Knative Serving using [Easy Install](../install/README.md#Installing-Knative) guide, -skip this step and continue to [Create Elasticsearch Indices](#Create-Elasticsearch-Indices)* +If you installed the +[full Knative release](../install/README.md#Installing-Knative), +skip this step and continue to +[Create Elasticsearch Indices](#Create-Elasticsearch-Indices) +- Install Knative monitoring components from the root of the [Serving repository](https://github.com/knative/serving): -Run: + ```shell + kubectl apply -R -f config/monitoring/100-common \ + -f config/monitoring/150-elasticsearch \ + -f third_party/config/monitoring/common \ + -f third_party/config/monitoring/elasticsearch \ + -f config/monitoring/200-common \ + -f config/monitoring/200-common/100-istio.yaml + ``` -```shell -kubectl apply -R -f config/monitoring/100-common \ - -f config/monitoring/150-elasticsearch \ - -f third_party/config/monitoring/common \ - -f third_party/config/monitoring/elasticsearch \ - -f config/monitoring/200-common \ - -f config/monitoring/200-common/100-istio.yaml -``` +- The installation is complete when logging & monitoring components are all + reported `Running` or `Completed`: -Monitor logging & monitoring components, until all of the components report Running or Completed: + ```shell + kubectl get pods -n monitoring --watch + ``` -```shell -kubectl get pods -n monitoring --watch -``` + ``` + NAME READY STATUS RESTARTS AGE + elasticsearch-logging-0 1/1 Running 0 2d + elasticsearch-logging-1 1/1 Running 0 2d + fluentd-ds-5kc85 1/1 Running 0 2d + fluentd-ds-vhrcq 1/1 Running 0 2d + fluentd-ds-xghk9 1/1 Running 0 2d + grafana-798cf569ff-v4q74 1/1 Running 0 2d + kibana-logging-7d474fbb45-6qb8x 1/1 Running 0 2d + kube-state-metrics-75bd4f5b8b-8t2h2 4/4 Running 0 2d + node-exporter-cr6bh 2/2 Running 0 2d + node-exporter-mf6k7 2/2 Running 0 2d + node-exporter-rhzr7 2/2 Running 0 2d + prometheus-system-0 1/1 Running 0 2d + prometheus-system-1 1/1 Running 0 2d + ``` -CTRL+C when it's done. + CTRL+C to exit watch. ### Create Elasticsearch Indices -We will create two indexes in ElasticSearch - one for application logs and one for request traces. -To create the indexes, open Kibana Index Management UI at this [link](http://localhost:8001/api/v1/namespaces/monitoring/services/kibana-logging/proxy/app/kibana#/management/kibana/index) -(*it might take a couple of minutes for the proxy to work the first time after the installation*). -Within the "Configure an index pattern" page, enter `logstash-*` to `Index pattern` and select `@timestamp` -from `Time Filter field name` and click on `Create` button. See below for a screenshot: +To visualize logs with Kibana, you need to set which Elasticsearch indices to explore. We will create two indices in Elasticsearch using `Logstash` for application logs and `Zipkin` +for request traces. + +- To open the Kibana UI (the visualization tool for + [Elasticsearch](https://info.elastic.co)), start a local proxy with the + following command: + + ```shell + kubectl proxy + ``` + + This command starts a local proxy of Kibana on port 8001. For security + reasons, the Kibana UI is exposed only within the cluster. + +- Navigate to the + [Kibana UI](http://localhost:8001/api/v1/namespaces/monitoring/services/kibana-logging/proxy/app/kibana). + _It might take a couple of minutes for the proxy to work_. + +- Within the "Configure an index pattern" page, enter `logstash-*` to + `Index pattern` and select `@timestamp` from `Time Filter field name` and + click on `Create` button. ![Create logstash-* index](images/kibana-landing-page-configure-index.png) -To create the second index, select `Create Index Pattern` button on top left of the page. -Enter `zipkin*` to `Index pattern` and select `timestamp_millis` from `Time Filter field name` -and click on `Create` button. +- To create the second index, select `Create Index Pattern` button on top left + of the page. Enter `zipkin*` to `Index pattern` and select `timestamp_millis` + from `Time Filter field name` and click on `Create` button. -Next, visit instructions below to access to logs, metrics and traces: -* [Accessing Logs](./accessing-logs.md) -* [Accessing Metrics](./accessing-metrics.md) -* [Accessing Traces](./accessing-traces.md) +## Stackdriver, Prometheus & Grafana Setup -## Stackdriver(logs), Prometheus & Grafana Setup +If your Knative Serving is not built on a Google Cloud Platform (GCP) based +cluster or you want to send logs to another GCP project, you need to build your +own Fluentd image and modify the configuration first. See -If your Knative Serving is not built on a GCP based cluster or you want to send logs to -another GCP project, you need to build your own Fluentd image and modify the -configuration first. See +1. Install + [Fluentd image on Knative Serving](https://github.com/knative/serving/blob/master/image/fluentd/README.md). +2. [Set up a logging plugin](setting-up-a-logging-plugin.md). +3. Install Knative monitoring components: -1. [Fluentd image on Knative Serving](/image/fluentd/README.md) -2. [Setting up a logging plugin](setting-up-a-logging-plugin.md) + ```shell + kubectl apply -R -f config/monitoring/100-common \ + -f config/monitoring/150-stackdriver-prod \ + -f third_party/config/monitoring/common \ + -f config/monitoring/200-common \ + -f config/monitoring/200-common/100-istio.yaml + ``` -```shell -kubectl apply -R -f config/monitoring/100-common \ - -f config/monitoring/150-stackdriver \ - -f third_party/config/monitoring/common \ - -f config/monitoring/200-common \ - -f config/monitoring/200-common/100-istio.yaml -``` +## Learn More + +- Learn more about accessing logs, metrics, and traces: + - [Accessing Logs](./accessing-logs.md) + - [Accessing Metrics](./accessing-metrics.md) + - [Accessing Traces](./accessing-traces.md) --- diff --git a/serving/outbound-network-access.md b/serving/outbound-network-access.md index f7588d974..ff8b197a0 100644 --- a/serving/outbound-network-access.md +++ b/serving/outbound-network-access.md @@ -14,7 +14,7 @@ depending on your platform: * For Google Container Engine (GKE) run the following command to determine the scope. Make sure to replace the variables or export these values first. ```shell - gcloud container clusters describe ${CLUSTER_ID} \ + gcloud container clusters describe ${CLUSTER_ID} \ --zone=${GCP_ZONE} | grep -e clusterIpv4Cidr -e servicesIpv4Cidr ``` * For IBM Cloud Private run the following command: diff --git a/serving/samples/gitwebhook-go/README.md b/serving/samples/gitwebhook-go/README.md index 458015103..178fd04c0 100644 --- a/serving/samples/gitwebhook-go/README.md +++ b/serving/samples/gitwebhook-go/README.md @@ -102,13 +102,19 @@ service "gitwebhook" created 1. Retrieve the hostname for this service, using the following command: ```shell - $ kubectl get services.serving.knative.dev gitwebhook \ + $ kubectl get ksvc gitwebhook \ -o=custom-columns=NAME:.metadata.name,DOMAIN:.status.domain NAME DOMAIN gitwebhook gitwebhook.default.example.com ``` + > Note: `ksvc` is an alias for `services.serving.knative.dev`. If you have + an older version (version 0.1.0) of Knative installed, you'll need to use + the long name until you upgrade to version 0.1.1 or higher. See + [Checking Knative Installation Version](../../../install/check-install-version.md) + to learn how to see what version you have installed. + 1. Browse on GitHub to the repository where you want to create a webhook. 1. Click **Settings**, then **Webhooks**, then **Add webhook**. 1. Enter the **Payload URL** as `http://{DOMAIN}`, with the value of DOMAIN listed above. diff --git a/serving/samples/helloworld-csharp/README.md b/serving/samples/helloworld-csharp/README.md index a804bf17c..13725f995 100644 --- a/serving/samples/helloworld-csharp/README.md +++ b/serving/samples/helloworld-csharp/README.md @@ -130,11 +130,17 @@ folder) you're ready to build and deploy the sample app. 1. To find the URL for your service, use ``` - kubectl get services.serving.knative.dev helloworld-csharp -o=custom-columns=NAME:.metadata.name,DOMAIN:.status.domain + kubectl get ksvc helloworld-csharp -o=custom-columns=NAME:.metadata.name,DOMAIN:.status.domain NAME DOMAIN helloworld-csharp helloworld-csharp.default.example.com ``` + > Note: `ksvc` is an alias for `services.serving.knative.dev`. If you have + an older version (version 0.1.0) of Knative installed, you'll need to use + the long name until you upgrade to version 0.1.1 or higher. See + [Checking Knative Installation Version](../../../install/check-install-version.md) + to learn how to see what version you have installed. + 1. Now you can make a request to your app to see the result. Replace `{IP_ADDRESS}` with the address you see returned in the previous step. diff --git a/serving/samples/helloworld-elixir/.gitignore b/serving/samples/helloworld-elixir/.gitignore new file mode 100644 index 000000000..3064370c9 --- /dev/null +++ b/serving/samples/helloworld-elixir/.gitignore @@ -0,0 +1,28 @@ +# App artifacts +/_build +/db +/deps +/*.ez + +# Generated on crash by the VM +erl_crash.dump + +# Generated on crash by NPM +npm-debug.log +/assets/package-lock.json + +# Static artifacts +/assets/node_modules + +# Since we are building assets from assets/, +# we ignore priv/static. You may want to comment +# this depending on your deployment strategy. +/priv/static/ + +# Files matching config/*.secret.exs pattern contain sensitive +# data and you should not commit them into version control. +# +# Alternatively, you may comment the line below and commit the +# secrets files as long as you replace their contents by environment +# variables. +/config/*.secret.exs diff --git a/serving/samples/helloworld-elixir/Dockerfile b/serving/samples/helloworld-elixir/Dockerfile new file mode 100644 index 000000000..20f684162 --- /dev/null +++ b/serving/samples/helloworld-elixir/Dockerfile @@ -0,0 +1,27 @@ +FROM elixir:alpine +ARG APP_NAME=hello +ARG PHOENIX_SUBDIR=. +ENV MIX_ENV=prod REPLACE_OS_VARS=true TERM=xterm +WORKDIR /opt/app +RUN apk update \ + && apk --no-cache --update add nodejs nodejs-npm \ + && mix local.rebar --force \ + && mix local.hex --force +COPY . . +RUN mix do deps.get, deps.compile, compile +RUN cd ${PHOENIX_SUBDIR}/assets \ + && npm install \ + && ./node_modules/brunch/bin/brunch build -p \ + && cd .. \ + && mix phx.digest +RUN mix release --env=prod --verbose \ + && mv _build/prod/rel/${APP_NAME} /opt/release \ + && mv /opt/release/bin/${APP_NAME} /opt/release/bin/start_server +FROM alpine:latest +RUN apk update && apk --no-cache --update add bash openssl-dev +ENV PORT=8080 MIX_ENV=prod REPLACE_OS_VARS=true +WORKDIR /opt/app +EXPOSE 8080 +COPY --from=0 /opt/release . +ENV RUNNER_LOG_DIR /var/log +CMD ["/opt/app/bin/start_server", "foreground", "boot_var=/tmp"] diff --git a/serving/samples/helloworld-elixir/README.md b/serving/samples/helloworld-elixir/README.md new file mode 100644 index 000000000..477ae7d3e --- /dev/null +++ b/serving/samples/helloworld-elixir/README.md @@ -0,0 +1,300 @@ +# Hello World - Elixir Sample + +A simple web application written in [Elixir](https://elixir-lang.org/) using the +[Phoenix Framework](https://phoenixframework.org/). +The application prints all environment variables to the main page. + +# Set up Elixir and Phoenix Locally + +Following the [Phoenix Installation Guide](https://hexdocs.pm/phoenix/installation.html) +is the best way to get your computer set up for developing, +building, running, and packaging Elixir Web applications. + +# Running Locally + +To start your Phoenix server: + + * Install dependencies with `mix deps.get` + * Install Node.js dependencies with `cd assets && npm install` + * Start Phoenix endpoint with `mix phx.server` + +Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. + +# Recreating the sample code + +1. Generate a new project. + + ```shell + mix phoenix.new helloelixir + ``` + + When asked, if you want to `Fetch and install dependencies? [Yn]` select `y` + +1. Follow the direction in the output to change directories into + start your local server with `mix phoenix.server` + +1. In the new directory, create a new Dockerfile for packaging + your application for deployment + + ```docker + # Start from a base image for elixir + FROM elixir:alpine + + # Set up Elixir and Phoenix + ARG APP_NAME=hello + ARG PHOENIX_SUBDIR=. + ENV MIX_ENV=prod REPLACE_OS_VARS=true TERM=xterm + WORKDIR /opt/app + + # Compile assets. + RUN apk update \ + && apk --no-cache --update add nodejs nodejs-npm \ + && mix local.rebar --force \ + && mix local.hex --force + COPY . . + + # Download and compile dependencies, then compile Web app. + RUN mix do deps.get, deps.compile, compile + RUN cd ${PHOENIX_SUBDIR}/assets \ + && npm install \ + && ./node_modules/brunch/bin/brunch build -p \ + && cd .. \ + && mix phx.digest + + # Create a release version of the application + RUN mix release --env=prod --verbose \ + && mv _build/prod/rel/${APP_NAME} /opt/release \ + && mv /opt/release/bin/${APP_NAME} /opt/release/bin/start_server + + # Prepare final layer + FROM alpine:latest + RUN apk update && apk --no-cache --update add bash openssl-dev + ENV PORT=8080 MIX_ENV=prod REPLACE_OS_VARS=true + WORKDIR /opt/app + + # Document that the service listens on port 8080. + EXPOSE 8080 + COPY --from=0 /opt/release . + ENV RUNNER_LOG_DIR /var/log + + # Command to execute the application. + CMD ["/opt/app/bin/start_server", "foreground", "boot_var=/tmp"] + ``` + +1. Create a new file, `service.yaml` and copy the following Service + definition into the file. Make sure to replace `{username}` with + your Docker Hub username. + + ```yaml + apiVersion: serving.knative.dev/v1alpha1 + kind: Service + metadata: + name: helloworld-elixir + namespace: default + spec: + runLatest: + configuration: + revisionTemplate: + spec: + container: + image: docker.io/{username}/helloworld-elixir + env: + - name: TARGET + value: "elixir Sample v1" + ``` + +# Building and deploying the sample + +The sample in this directory is ready to build and deploy without changes. +You can deploy the sample as is, or use you created version following the +directions above. + +1. Generate a new `secret_key_base` in the `config/prod.secret.exs` file. + Phoenix applications use a secrets file on production deployments and, by + default, that file is not checked into source control. We have provides + shell of an example on `config/prod.secret.exs.sample` and you can use the + following command to generate a new prod secrets file. + + ```shell + SECRET_KEY_BASE=$(elixir -e ":crypto.strong_rand_bytes(48) |> Base.encode64 |> IO.puts") + sed "s|SECRET+KEY+BASE|$SECRET_KEY_BASE|" config/prod.secret.exs.sample >config/prod.secret.exs + ``` + +1. Use Docker to build the sample code into a container. To build and push + with Docker Hub, run these commands replacing `{username}` with your Docker + Hub username: + + ```shell + # Build the container on your local machine + docker build -t {username}/helloworld-elixir . + + # Push the container to docker registry + docker push {username}/helloworld-elixir + ``` + +1. After the build has completed and the container is pushed to docker hub, you + can deploy the app into your cluster. Ensure that the container image value + in `service.yaml` matches the container you built in + the previous step. Apply the configuration using `kubectl`: + + ```shell + kubectl apply -f service.yaml + ``` + +1. Now that your service is created, Knative will perform the following steps: + * Create a new immutable revision for this version of the app. + * Network programming to create a route, ingress, service, and load balance for your app. + * Automatically scale your pods up and down (including to zero active pods). + +1. To find the IP address for your service, use + `kubectl get svc knative-ingressgateway -n istio-system` to get the ingress IP for your + cluster. If your cluster is new, it may take sometime for the service to get asssigned + an external IP address. + + ``` + kubectl get svc knative-ingressgateway -n istio-system + + NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +knative-ingressgateway LoadBalancer 10.35.254.218 35.225.171.32 80:32380/TCP,443:32390/TCP,32400:32400/TCP 1h + ``` + +1. To find the URL for your service, use + + ``` + kubectl get ksvc helloworld-elixir -o=custom-columns=NAME:.metadata.name,DOMAIN:.status.domain + + NAME DOMAIN + helloworld-elixir helloworld-elixir.default.example.com + ``` + + > Note: `ksvc` is an alias for `services.serving.knative.dev`. If you have + an older version (version 0.1.0) of Knative installed, you'll need to use + the long name until you upgrade to version 0.1.1 or higher. See + [Checking Knative Installation Version](../../../install/check-install-version.md) + to learn how to see what version you have installed. + +1. Now you can make a request to your app to see the results. Replace + `{IP_ADDRESS}` with the address you see returned in the previous step. + + ```shell + curl -H "Host: helloworld-elixir.default.example.com" http://{IP_ADDRESS} + + ... + # HTML from your application is returned. + ``` + + Here is the HTML returned from our deployed sample application: + + ```HTML + + + + + + + + + + Hello Knative + + + + +
+
+ +
+ + + + +
+
+

Welcome to Knative and Elixir

+ +

$TARGET = elixir Sample v1

+
+ +

Environment

+
    +
  • BINDIR = /opt/app/erts-9.3.2/bin
  • +
  • DEST_SYS_CONFIG_PATH = /opt/app/var/sys.config
  • +
  • DEST_VMARGS_PATH = /opt/app/var/vm.args
  • +
  • DISTILLERY_TASK = foreground
  • +
  • EMU = beam
  • +
  • ERL_LIBS = /opt/app/lib
  • +
  • ERL_OPTS =
  • +
  • ERTS_DIR = /opt/app/erts-9.3.2
  • +
  • ERTS_LIB_DIR = /opt/app/erts-9.3.2/../lib
  • +
  • ERTS_VSN = 9.3.2
  • +
  • HELLOWORLD_ELIXIR_00001_SERVICE_PORT = tcp://10.35.241.50:80
  • +
  • HELLOWORLD_ELIXIR_00001_SERVICE_PORT_80_TCP = tcp://10.35.241.50:80
  • +
  • HELLOWORLD_ELIXIR_00001_SERVICE_PORT_80_TCP_ADDR = 10.35.241.50
  • +
  • HELLOWORLD_ELIXIR_00001_SERVICE_PORT_80_TCP_PORT = 80
  • +
  • HELLOWORLD_ELIXIR_00001_SERVICE_PORT_80_TCP_PROTO = tcp
  • +
  • HELLOWORLD_ELIXIR_00001_SERVICE_SERVICE_HOST = 10.35.241.50
  • +
  • HELLOWORLD_ELIXIR_00001_SERVICE_SERVICE_PORT = 80
  • +
  • HELLOWORLD_ELIXIR_00001_SERVICE_SERVICE_PORT_HTTP = 80
  • +
  • HELLOWORLD_ELIXIR_PORT = tcp://10.35.253.90:80
  • +
  • HELLOWORLD_ELIXIR_PORT_80_TCP = tcp://10.35.253.90:80
  • +
  • HELLOWORLD_ELIXIR_PORT_80_TCP_ADDR = 10.35.253.90
  • +
  • HELLOWORLD_ELIXIR_PORT_80_TCP_PORT = 80
  • +
  • HELLOWORLD_ELIXIR_PORT_80_TCP_PROTO = tcp
  • +
  • HELLOWORLD_ELIXIR_SERVICE_HOST = 10.35.253.90
  • +
  • HELLOWORLD_ELIXIR_SERVICE_PORT = 80
  • +
  • HELLOWORLD_ELIXIR_SERVICE_PORT_HTTP = 80
  • +
  • HOME = /root
  • +
  • HOSTNAME = helloworld-elixir-00001-deployment-84f68946b4-76hcv
  • +
  • KUBERNETES_PORT = tcp://10.35.240.1:443
  • +
  • KUBERNETES_PORT_443_TCP = tcp://10.35.240.1:443
  • +
  • KUBERNETES_PORT_443_TCP_ADDR = 10.35.240.1
  • +
  • KUBERNETES_PORT_443_TCP_PORT = 443
  • +
  • KUBERNETES_PORT_443_TCP_PROTO = tcp
  • +
  • KUBERNETES_SERVICE_HOST = 10.35.240.1
  • +
  • KUBERNETES_SERVICE_PORT = 443
  • +
  • KUBERNETES_SERVICE_PORT_HTTPS = 443
  • +
  • LD_LIBRARY_PATH = /opt/app/erts-9.3.2/lib:
  • +
  • MIX_ENV = prod
  • +
  • NAME = hello@127.0.0.1
  • +
  • NAME_ARG = -name hello@127.0.0.1
  • +
  • NAME_TYPE = -name
  • +
  • OLDPWD = /opt/app
  • +
  • OTP_VER = 20
  • +
  • PATH = /opt/app/erts-9.3.2/bin:/opt/app/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
  • +
  • PORT = 8080
  • +
  • PROGNAME = opt/app/releases/0.0.1/hello.sh
  • +
  • PWD = /opt/app
  • +
  • RELEASES_DIR = /opt/app/releases
  • +
  • RELEASE_CONFIG_DIR = /opt/app
  • +
  • RELEASE_ROOT_DIR = /opt/app
  • +
  • REL_NAME = hello
  • +
  • REL_VSN = 0.0.1
  • +
  • REPLACE_OS_VARS = true
  • +
  • ROOTDIR = /opt/app
  • +
  • RUNNER_LOG_DIR = /var/log
  • +
  • RUN_ERL_ENV =
  • +
  • SHLVL = 1
  • +
  • SRC_SYS_CONFIG_PATH = /opt/app/releases/0.0.1/sys.config
  • +
  • SRC_VMARGS_PATH = /opt/app/releases/0.0.1/vm.args
  • +
  • SYS_CONFIG_PATH = /opt/app/var/sys.config
  • +
  • TARGET = elixir Sample v1
  • +
  • TERM = xterm
  • +
  • VMARGS_PATH = /opt/app/var/vm.args
  • +
+
+ +
+ + + + ``` + +## Removing the sample app deployment + +To remove the sample app from your cluster, delete the service record: + +```shell +kubectl delete -f service.yaml +``` diff --git a/serving/samples/helloworld-elixir/assets/brunch-config.js b/serving/samples/helloworld-elixir/assets/brunch-config.js new file mode 100644 index 000000000..b15df4600 --- /dev/null +++ b/serving/samples/helloworld-elixir/assets/brunch-config.js @@ -0,0 +1,62 @@ +exports.config = { + // See http://brunch.io/#documentation for docs. + files: { + javascripts: { + joinTo: "js/app.js" + + // To use a separate vendor.js bundle, specify two files path + // http://brunch.io/docs/config#-files- + // joinTo: { + // "js/app.js": /^js/, + // "js/vendor.js": /^(?!js)/ + // } + // + // To change the order of concatenation of files, explicitly mention here + // order: { + // before: [ + // "vendor/js/jquery-2.1.1.js", + // "vendor/js/bootstrap.min.js" + // ] + // } + }, + stylesheets: { + joinTo: "css/app.css" + }, + templates: { + joinTo: "js/app.js" + } + }, + + conventions: { + // This option sets where we should place non-css and non-js assets in. + // By default, we set this to "/assets/static". Files in this directory + // will be copied to `paths.public`, which is "priv/static" by default. + assets: /^(static)/ + }, + + // Phoenix paths configuration + paths: { + // Dependencies and current project directories to watch + watched: ["static", "css", "js", "vendor"], + // Where to compile files to + public: "../priv/static" + }, + + // Configure your plugins + plugins: { + babel: { + // Do not use ES6 compiler in vendor code + ignore: [/vendor/] + } + }, + + modules: { + autoRequire: { + "js/app.js": ["js/app"] + } + }, + + npm: { + enabled: true + } +}; diff --git a/serving/samples/helloworld-elixir/assets/css/app.css b/serving/samples/helloworld-elixir/assets/css/app.css new file mode 100644 index 000000000..5314c34d2 --- /dev/null +++ b/serving/samples/helloworld-elixir/assets/css/app.css @@ -0,0 +1 @@ +/* This file is for your main application css. */ \ No newline at end of file diff --git a/serving/samples/helloworld-elixir/assets/css/phoenix.css b/serving/samples/helloworld-elixir/assets/css/phoenix.css new file mode 100644 index 000000000..0b406d712 --- /dev/null +++ b/serving/samples/helloworld-elixir/assets/css/phoenix.css @@ -0,0 +1,77 @@ +/* Includes Bootstrap as well as some default style for the starter + * application. This can be safely deleted to start fresh. + */ + +/*! + * Bootstrap v3.3.5 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:focus,a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:focus,a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:focus,a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:focus,a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:focus,a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:focus,a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:focus,a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:focus,a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:focus,a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:focus,a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px\9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:14.33px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.focus,.btn-default:focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.btn-default:hover{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#286090;border-color:#122b40}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#204d74;border-color:#122b40}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#398439;border-color:#255625}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid\9}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:3;color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:2;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{min-height:16.43px;padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;filter:alpha(opacity=0);opacity:0;line-break:auto}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);line-break:auto}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000\9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-15px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-15px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-15px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} + +/* Space out content a bit */ +body, form, ul, table { + margin-top: 20px; + margin-bottom: 20px; +} + +/* Phoenix flash messages */ +.alert:empty { display: none; } + +/* Custom page header */ +.header { + border-bottom: 1px solid #e5e5e5; +} +.logo { + width: 519px; + height: 71px; + display: inline-block; + margin-bottom: 1em; + background-image: url("/images/phoenix.png"); + background-size: 519px 71px; +} + +/* Everything but the jumbotron gets side spacing for mobile first views */ +.header, +.marketing { + padding-right: 15px; + padding-left: 15px; +} + +/* Customize container */ +@media (min-width: 768px) { + .container { + max-width: 730px; + } +} +.container-narrow > hr { + margin: 30px 0; +} + +/* Main marketing message */ +.jumbotron { + text-align: center; + border-bottom: 1px solid #e5e5e5; +} + +/* Supporting marketing content */ +.marketing { + margin: 35px 0; +} + +/* Responsive: Portrait tablets and up */ +@media screen and (min-width: 768px) { + /* Remove the padding we set earlier */ + .header, + .marketing { + padding-right: 0; + padding-left: 0; + } + /* Space out the masthead */ + .header { + margin-bottom: 30px; + } + /* Remove the bottom border on the jumbotron for visual effect */ + .jumbotron { + border-bottom: 0; + } +} \ No newline at end of file diff --git a/serving/samples/helloworld-elixir/assets/js/app.js b/serving/samples/helloworld-elixir/assets/js/app.js new file mode 100644 index 000000000..e7549b9db --- /dev/null +++ b/serving/samples/helloworld-elixir/assets/js/app.js @@ -0,0 +1,21 @@ +// Brunch automatically concatenates all files in your +// watched paths. Those paths can be configured at +// config.paths.watched in "brunch-config.js". +// +// However, those files will only be executed if +// explicitly imported. The only exception are files +// in vendor, which are never wrapped in imports and +// therefore are always executed. + +// Import dependencies +// +// If you no longer want to use a dependency, remember +// to also remove its path from "config.paths.watched". +import "phoenix_html" + +// Import local files +// +// Local files can be imported directly using relative +// paths "./socket" or full ones "web/static/js/socket". + +// import socket from "./socket" diff --git a/serving/samples/helloworld-elixir/assets/js/socket.js b/serving/samples/helloworld-elixir/assets/js/socket.js new file mode 100644 index 000000000..5c23a67f7 --- /dev/null +++ b/serving/samples/helloworld-elixir/assets/js/socket.js @@ -0,0 +1,62 @@ +// NOTE: The contents of this file will only be executed if +// you uncomment its entry in "assets/js/app.js". + +// To use Phoenix channels, the first step is to import Socket +// and connect at the socket path in "lib/web/endpoint.ex": +import {Socket} from "phoenix" + +let socket = new Socket("/socket", {params: {token: window.userToken}}) + +// When you connect, you'll often need to authenticate the client. +// For example, imagine you have an authentication plug, `MyAuth`, +// which authenticates the session and assigns a `:current_user`. +// If the current user exists you can assign the user's token in +// the connection for use in the layout. +// +// In your "lib/web/router.ex": +// +// pipeline :browser do +// ... +// plug MyAuth +// plug :put_user_token +// end +// +// defp put_user_token(conn, _) do +// if current_user = conn.assigns[:current_user] do +// token = Phoenix.Token.sign(conn, "user socket", current_user.id) +// assign(conn, :user_token, token) +// else +// conn +// end +// end +// +// Now you need to pass this token to JavaScript. You can do so +// inside a script tag in "lib/web/templates/layout/app.html.eex": +// +// +// +// You will need to verify the user token in the "connect/2" function +// in "lib/web/channels/user_socket.ex": +// +// def connect(%{"token" => token}, socket) do +// # max_age: 1209600 is equivalent to two weeks in seconds +// case Phoenix.Token.verify(socket, "user socket", token, max_age: 1209600) do +// {:ok, user_id} -> +// {:ok, assign(socket, :user, user_id)} +// {:error, reason} -> +// :error +// end +// end +// +// Finally, pass the token on connect as below. Or remove it +// from connect if you don't care about authentication. + +socket.connect() + +// Now that you are connected, you can join channels with a topic: +let channel = socket.channel("topic:subtopic", {}) +channel.join() + .receive("ok", resp => { console.log("Joined successfully", resp) }) + .receive("error", resp => { console.log("Unable to join", resp) }) + +export default socket diff --git a/serving/samples/helloworld-elixir/assets/package.json b/serving/samples/helloworld-elixir/assets/package.json new file mode 100644 index 000000000..4cc398697 --- /dev/null +++ b/serving/samples/helloworld-elixir/assets/package.json @@ -0,0 +1,18 @@ +{ + "repository": {}, + "license": "MIT", + "scripts": { + "deploy": "brunch build --production", + "watch": "brunch watch --stdin" + }, + "dependencies": { + "phoenix": "file:../deps/phoenix", + "phoenix_html": "file:../deps/phoenix_html" + }, + "devDependencies": { + "babel-brunch": "6.1.1", + "brunch": "2.10.9", + "clean-css-brunch": "2.10.0", + "uglify-js-brunch": "2.10.0" + } +} diff --git a/serving/samples/helloworld-elixir/assets/static/favicon.ico b/serving/samples/helloworld-elixir/assets/static/favicon.ico new file mode 100644 index 000000000..73de524aa Binary files /dev/null and b/serving/samples/helloworld-elixir/assets/static/favicon.ico differ diff --git a/serving/samples/helloworld-elixir/assets/static/images/phoenix.png b/serving/samples/helloworld-elixir/assets/static/images/phoenix.png new file mode 100644 index 000000000..9c81075f6 Binary files /dev/null and b/serving/samples/helloworld-elixir/assets/static/images/phoenix.png differ diff --git a/serving/samples/helloworld-elixir/assets/static/robots.txt b/serving/samples/helloworld-elixir/assets/static/robots.txt new file mode 100644 index 000000000..3c9c7c01f --- /dev/null +++ b/serving/samples/helloworld-elixir/assets/static/robots.txt @@ -0,0 +1,5 @@ +# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file +# +# To ban all spiders from the entire site uncomment the next two lines: +# User-agent: * +# Disallow: / diff --git a/serving/samples/helloworld-elixir/config/config.exs b/serving/samples/helloworld-elixir/config/config.exs new file mode 100644 index 000000000..ab1e7af70 --- /dev/null +++ b/serving/samples/helloworld-elixir/config/config.exs @@ -0,0 +1,23 @@ +# This file is responsible for configuring your application +# and its dependencies with the aid of the Mix.Config module. +# +# This configuration file is loaded before any dependency and +# is restricted to this project. +use Mix.Config + +# Configures the endpoint +config :hello, HelloWeb.Endpoint, + url: [host: "localhost"], + secret_key_base: "P0fv0U47j6mdL40sn3f3BIuvEyIdbxUq+yKc8Mcbwig5105brf2Dzio5ANjTVRUo", + render_errors: [view: HelloWeb.ErrorView, accepts: ~w(html json)] + #pubsub: [name: Hello.PubSub, + # adapter: Phoenix.PubSub.PG2] + +# Configures Elixir's Logger +config :logger, :console, + format: "$time $metadata[$level] $message\n", + metadata: [:user_id] + +# Import environment specific config. This must remain at the bottom +# of this file so it overrides the configuration defined above. +import_config "#{Mix.env}.exs" diff --git a/serving/samples/helloworld-elixir/config/dev.exs b/serving/samples/helloworld-elixir/config/dev.exs new file mode 100644 index 000000000..4e166ca92 --- /dev/null +++ b/serving/samples/helloworld-elixir/config/dev.exs @@ -0,0 +1,49 @@ +use Mix.Config + +# For development, we disable any cache and enable +# debugging and code reloading. +# +# The watchers configuration can be used to run external +# watchers to your application. For example, we use it +# with brunch.io to recompile .js and .css sources. +config :hello, HelloWeb.Endpoint, + http: [port: 4000], + debug_errors: true, + code_reloader: true, + check_origin: false, + watchers: [node: ["node_modules/brunch/bin/brunch", "watch", "--stdin", + cd: Path.expand("../assets", __DIR__)]] + +# ## SSL Support +# +# In order to use HTTPS in development, a self-signed +# certificate can be generated by running the following +# command from your terminal: +# +# openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 -subj "/C=US/ST=Denial/L=Springfield/O=Dis/CN=www.example.com" -keyout priv/server.key -out priv/server.pem +# +# The `http:` config above can be replaced with: +# +# https: [port: 4000, keyfile: "priv/server.key", certfile: "priv/server.pem"], +# +# If desired, both `http:` and `https:` keys can be +# configured to run both http and https servers on +# different ports. + +# Watch static and templates for browser reloading. +config :hello, HelloWeb.Endpoint, + live_reload: [ + patterns: [ + ~r{priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$}, + ~r{priv/gettext/.*(po)$}, + ~r{lib/hello_web/views/.*(ex)$}, + ~r{lib/hello_web/templates/.*(eex)$} + ] + ] + +# Do not include metadata nor timestamps in development logs +config :logger, :console, format: "[$level] $message\n" + +# Set a higher stacktrace during development. Avoid configuring such +# in production as building large stacktraces may be expensive. +config :phoenix, :stacktrace_depth, 20 diff --git a/serving/samples/helloworld-elixir/config/prod.exs b/serving/samples/helloworld-elixir/config/prod.exs new file mode 100644 index 000000000..3ee2c7d5c --- /dev/null +++ b/serving/samples/helloworld-elixir/config/prod.exs @@ -0,0 +1,67 @@ +use Mix.Config + +# For production, we often load configuration from external +# sources, such as your system environment. For this reason, +# you won't find the :http configuration below, but set inside +# HelloWeb.Endpoint.init/2 when load_from_system_env is +# true. Any dynamic configuration should be done there. +# +# Don't forget to configure the url host to something meaningful, +# Phoenix uses this information when generating URLs. +# +# Finally, we also include the path to a cache manifest +# containing the digested version of static files. This +# manifest is generated by the mix phx.digest task +# which you typically run after static files are built. +config :hello, HelloWeb.Endpoint, + load_from_system_env: false, + http: [port: 8080], + check_origin: false, + server: true, + root: ".", + cache_static_manifest: "priv/static/cache_manifest.json" + +# Do not print debug messages in production +config :logger, level: :info + +# ## SSL Support +# +# To get SSL working, you will need to add the `https` key +# to the previous section and set your `:url` port to 443: +# +# config :hello, HelloWeb.Endpoint, +# ... +# url: [host: "example.com", port: 443], +# https: [:inet6, +# port: 443, +# keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"), +# certfile: System.get_env("SOME_APP_SSL_CERT_PATH")] +# +# Where those two env variables return an absolute path to +# the key and cert in disk or a relative path inside priv, +# for example "priv/ssl/server.key". +# +# We also recommend setting `force_ssl`, ensuring no data is +# ever sent via http, always redirecting to https: +# +# config :hello, HelloWeb.Endpoint, +# force_ssl: [hsts: true] +# +# Check `Plug.SSL` for all available options in `force_ssl`. + +# ## Using releases +# +# If you are doing OTP releases, you need to instruct Phoenix +# to start the server for all endpoints: +# +# config :phoenix, :serve_endpoints, true +# +# Alternatively, you can configure exactly which server to +# start per endpoint: +# +# config :hello, HelloWeb.Endpoint, server: true +# + +# Finally import the config/prod.secret.exs +# which should be versioned separately. +import_config "prod.secret.exs" diff --git a/serving/samples/helloworld-elixir/config/prod.secret.exs.sample b/serving/samples/helloworld-elixir/config/prod.secret.exs.sample new file mode 100644 index 000000000..1092ddd7d --- /dev/null +++ b/serving/samples/helloworld-elixir/config/prod.secret.exs.sample @@ -0,0 +1,12 @@ +use Mix.Config + +# In this file, we keep production configuration that +# you'll likely want to automate and keep away from +# your version control system. +# +# You should document the content of this +# file or create a script for recreating it, since it's +# kept out of version control and might be hard to recover +# or recreate for your teammates (or yourself later on). +config :hello, HelloWeb.Endpoint, + secret_key_base: "SECRET+KEY+BASE" diff --git a/serving/samples/helloworld-elixir/config/test.exs b/serving/samples/helloworld-elixir/config/test.exs new file mode 100644 index 000000000..5ca3a7e46 --- /dev/null +++ b/serving/samples/helloworld-elixir/config/test.exs @@ -0,0 +1,10 @@ +use Mix.Config + +# We don't run a server during test. If one is required, +# you can enable the server option below. +config :hello, HelloWeb.Endpoint, + http: [port: 4001], + server: false + +# Print only warnings and errors during test +config :logger, level: :warn diff --git a/serving/samples/helloworld-elixir/lib/hello.ex b/serving/samples/helloworld-elixir/lib/hello.ex new file mode 100644 index 000000000..44d3ddecc --- /dev/null +++ b/serving/samples/helloworld-elixir/lib/hello.ex @@ -0,0 +1,9 @@ +defmodule Hello do + @moduledoc """ + Hello keeps the contexts that define your domain + and business logic. + + Contexts are also responsible for managing your data, regardless + if it comes from the database, an external API or others. + """ +end diff --git a/serving/samples/helloworld-elixir/lib/hello/application.ex b/serving/samples/helloworld-elixir/lib/hello/application.ex new file mode 100644 index 000000000..736b48637 --- /dev/null +++ b/serving/samples/helloworld-elixir/lib/hello/application.ex @@ -0,0 +1,31 @@ +defmodule Hello.Application do + use Application + + # See https://hexdocs.pm/elixir/Application.html + # for more information on OTP Applications + def start(_type, _args) do + IO.puts :stderr, "Application starting up" + import Supervisor.Spec + + # Define workers and child supervisors to be supervised + children = [ + # Start the endpoint when the application starts + supervisor(HelloWeb.Endpoint, []), + # Start your own worker by calling: Hello.Worker.start_link(arg1, arg2, arg3) + # worker(Hello.Worker, [arg1, arg2, arg3]), + ] + + # See https://hexdocs.pm/elixir/Supervisor.html + # for other strategies and supported options + opts = [strategy: :one_for_one, name: Hello.Supervisor] + Supervisor.start_link(children, opts) + end + + # Tell Phoenix to update the endpoint configuration + # whenever the application is updated. + def config_change(changed, _new, removed) do + IO.puts :stderr, "Config changed" + HelloWeb.Endpoint.config_change(changed, removed) + :ok + end +end diff --git a/serving/samples/helloworld-elixir/lib/hello_web.ex b/serving/samples/helloworld-elixir/lib/hello_web.ex new file mode 100644 index 000000000..4573268fb --- /dev/null +++ b/serving/samples/helloworld-elixir/lib/hello_web.ex @@ -0,0 +1,67 @@ +defmodule HelloWeb do + @moduledoc """ + The entrypoint for defining your web interface, such + as controllers, views, channels and so on. + + This can be used in your application as: + + use HelloWeb, :controller + use HelloWeb, :view + + The definitions below will be executed for every view, + controller, etc, so keep them short and clean, focused + on imports, uses and aliases. + + Do NOT define functions inside the quoted expressions + below. Instead, define any helper function in modules + and import those modules here. + """ + + def controller do + quote do + use Phoenix.Controller, namespace: HelloWeb + import Plug.Conn + import HelloWeb.Router.Helpers + import HelloWeb.Gettext + end + end + + def view do + quote do + use Phoenix.View, root: "lib/hello_web/templates", + namespace: HelloWeb + + # Import convenience functions from controllers + import Phoenix.Controller, only: [get_flash: 2, view_module: 1] + + # Use all HTML functionality (forms, tags, etc) + use Phoenix.HTML + + import HelloWeb.Router.Helpers + import HelloWeb.ErrorHelpers + import HelloWeb.Gettext + end + end + + def router do + quote do + use Phoenix.Router + import Plug.Conn + import Phoenix.Controller + end + end + + def channel do + quote do + use Phoenix.Channel + import HelloWeb.Gettext + end + end + + @doc """ + When used, dispatch to the appropriate controller/view/etc. + """ + defmacro __using__(which) when is_atom(which) do + apply(__MODULE__, which, []) + end +end diff --git a/serving/samples/helloworld-elixir/lib/hello_web/channels/user_socket.ex b/serving/samples/helloworld-elixir/lib/hello_web/channels/user_socket.ex new file mode 100644 index 000000000..4e7f20813 --- /dev/null +++ b/serving/samples/helloworld-elixir/lib/hello_web/channels/user_socket.ex @@ -0,0 +1,38 @@ +defmodule HelloWeb.UserSocket do + use Phoenix.Socket + + ## Channels + # channel "room:*", HelloWeb.RoomChannel + + ## Transports + transport :longpoll, Phoenix.Transports.LongPoll + # transport :longpoll, Phoenix.Transports.LongPoll + + # Socket params are passed from the client and can + # be used to verify and authenticate a user. After + # verification, you can put default assigns into + # the socket that will be set for all channels, ie + # + # {:ok, assign(socket, :user_id, verified_user_id)} + # + # To deny connection, return `:error`. + # + # See `Phoenix.Token` documentation for examples in + # performing token verification on connect. + def connect(_params, socket) do + IO.puts :stderr, "UserSocket.connect called" + {:ok, socket} + end + + # Socket id's are topics that allow you to identify all sockets for a given user: + # + # def id(socket), do: "user_socket:#{socket.assigns.user_id}" + # + # Would allow you to broadcast a "disconnect" event and terminate + # all active sockets and channels for a given user: + # + # HelloWeb.Endpoint.broadcast("user_socket:#{user.id}", "disconnect", %{}) + # + # Returning `nil` makes this socket anonymous. + def id(_socket), do: nil +end diff --git a/serving/samples/helloworld-elixir/lib/hello_web/controllers/page_controller.ex b/serving/samples/helloworld-elixir/lib/hello_web/controllers/page_controller.ex new file mode 100644 index 000000000..7eda062a9 --- /dev/null +++ b/serving/samples/helloworld-elixir/lib/hello_web/controllers/page_controller.ex @@ -0,0 +1,14 @@ +defmodule HelloWeb.PageController do + use HelloWeb, :controller + + def index(conn, params) do + env = System.get_env() + target = Map.get(env, "TARGET") + + render conn, "index.html", + title: "Hello Knative", + greeting: "Welcome to Knative and Elixir", + target: target, + env: env + end +end diff --git a/serving/samples/helloworld-elixir/lib/hello_web/endpoint.ex b/serving/samples/helloworld-elixir/lib/hello_web/endpoint.ex new file mode 100644 index 000000000..a3cc10adb --- /dev/null +++ b/serving/samples/helloworld-elixir/lib/hello_web/endpoint.ex @@ -0,0 +1,57 @@ +defmodule HelloWeb.Endpoint do + use Phoenix.Endpoint, otp_app: :hello + + socket "/socket", HelloWeb.UserSocket + + # Serve at "/" the static files from "priv/static" directory. + # + # You should set gzip to true if you are running phoenix.digest + # when deploying your static files in production. + plug Plug.Static, + at: "/", from: :hello, gzip: false, + only: ~w(css fonts images js favicon.ico robots.txt) + + # Code reloading can be explicitly enabled under the + # :code_reloader configuration of your endpoint. + if code_reloading? do + socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket + plug Phoenix.LiveReloader + plug Phoenix.CodeReloader + end + + plug Plug.Logger + + plug Plug.Parsers, + parsers: [:urlencoded, :multipart, :json], + pass: ["*/*"], + json_decoder: Poison + + plug Plug.MethodOverride + plug Plug.Head + + # The session will be stored in the cookie and signed, + # this means its contents can be read but not tampered with. + # Set :encryption_salt if you would also like to encrypt it. + plug Plug.Session, + store: :cookie, + key: "_hello_key", + signing_salt: "M0cGJtXU" + + plug HelloWeb.Router + + @doc """ + Callback invoked for dynamically configuring the endpoint. + + It receives the endpoint configuration and checks if + configuration should be loaded from the system environment. + """ + def init(_key, config) do + IO.puts :stderr, "called HelloWeb.Endpoint.init" + if config[:load_from_system_env] do + port = System.get_env("PORT") || raise "expected the PORT environment variable to be set" + {:ok, Keyword.put(config, :http, [:inet6, port: port])} + else + {:ok, config} + end + end +end diff --git a/serving/samples/helloworld-elixir/lib/hello_web/gettext.ex b/serving/samples/helloworld-elixir/lib/hello_web/gettext.ex new file mode 100644 index 000000000..bf7a9325d --- /dev/null +++ b/serving/samples/helloworld-elixir/lib/hello_web/gettext.ex @@ -0,0 +1,24 @@ +defmodule HelloWeb.Gettext do + @moduledoc """ + A module providing Internationalization with a gettext-based API. + + By using [Gettext](https://hexdocs.pm/gettext), + your module gains a set of macros for translations, for example: + + import HelloWeb.Gettext + + # Simple translation + gettext "Here is the string to translate" + + # Plural translation + ngettext "Here is the string to translate", + "Here are the strings to translate", + 3 + + # Domain-based translation + dgettext "errors", "Here is the error message to translate" + + See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage. + """ + use Gettext, otp_app: :hello +end diff --git a/serving/samples/helloworld-elixir/lib/hello_web/router.ex b/serving/samples/helloworld-elixir/lib/hello_web/router.ex new file mode 100644 index 000000000..2b074823c --- /dev/null +++ b/serving/samples/helloworld-elixir/lib/hello_web/router.ex @@ -0,0 +1,27 @@ +defmodule HelloWeb.Router do + use HelloWeb, :router + + pipeline :browser do + plug :accepts, ["html"] + plug :fetch_session + plug :fetch_flash + plug :protect_from_forgery + plug :put_secure_browser_headers + end + + pipeline :api do + plug :accepts, ["json"] + end + + scope "/", HelloWeb do + pipe_through :browser # Use the default browser stack + + get "/", PageController, :index + get "/:name", PageController, :show + end + + # Other scopes may use custom stacks. + # scope "/api", HelloWeb do + # pipe_through :api + # end +end diff --git a/serving/samples/helloworld-elixir/lib/hello_web/templates/layout/app.html.eex b/serving/samples/helloworld-elixir/lib/hello_web/templates/layout/app.html.eex new file mode 100644 index 000000000..a3592f383 --- /dev/null +++ b/serving/samples/helloworld-elixir/lib/hello_web/templates/layout/app.html.eex @@ -0,0 +1,32 @@ + + + + + + + + + + <%= @title %> + "> + + + +
+
+ +
+ + + + +
+ <%= render @view_module, @view_template, assigns %> +
+ +
+ + + diff --git a/serving/samples/helloworld-elixir/lib/hello_web/templates/page/index.html.eex b/serving/samples/helloworld-elixir/lib/hello_web/templates/page/index.html.eex new file mode 100644 index 000000000..2f5ad5ae4 --- /dev/null +++ b/serving/samples/helloworld-elixir/lib/hello_web/templates/page/index.html.eex @@ -0,0 +1,12 @@ +
+

<%= @greeting %>

+ +

$TARGET = <%= @target %>

+
+ +

Environment

+
    + <%= for key <- Enum.sort(Map.keys(@env)) do %> +
  • <%= key %> = <%= Map.get(@env, key) %>
  • + <% end %> +
diff --git a/serving/samples/helloworld-elixir/lib/hello_web/views/error_helpers.ex b/serving/samples/helloworld-elixir/lib/hello_web/views/error_helpers.ex new file mode 100644 index 000000000..fa06eae03 --- /dev/null +++ b/serving/samples/helloworld-elixir/lib/hello_web/views/error_helpers.ex @@ -0,0 +1,44 @@ +defmodule HelloWeb.ErrorHelpers do + @moduledoc """ + Conveniences for translating and building error messages. + """ + + use Phoenix.HTML + + @doc """ + Generates tag for inlined form input errors. + """ + def error_tag(form, field) do + Enum.map(Keyword.get_values(form.errors, field), fn (error) -> + content_tag :span, translate_error(error), class: "help-block" + end) + end + + @doc """ + Translates an error message using gettext. + """ + def translate_error({msg, opts}) do + # When using gettext, we typically pass the strings we want + # to translate as a static argument: + # + # # Translate "is invalid" in the "errors" domain + # dgettext "errors", "is invalid" + # + # # Translate the number of files with plural rules + # dngettext "errors", "1 file", "%{count} files", count + # + # Because the error messages we show in our forms and APIs + # are defined inside Ecto, we need to translate them dynamically. + # This requires us to call the Gettext module passing our gettext + # backend as first argument. + # + # Note we use the "errors" domain, which means translations + # should be written to the errors.po file. The :count option is + # set by Ecto and indicates we should also apply plural rules. + if count = opts[:count] do + Gettext.dngettext(HelloWeb.Gettext, "errors", msg, msg, count, opts) + else + Gettext.dgettext(HelloWeb.Gettext, "errors", msg, opts) + end + end +end diff --git a/serving/samples/helloworld-elixir/lib/hello_web/views/error_view.ex b/serving/samples/helloworld-elixir/lib/hello_web/views/error_view.ex new file mode 100644 index 000000000..90f183947 --- /dev/null +++ b/serving/samples/helloworld-elixir/lib/hello_web/views/error_view.ex @@ -0,0 +1,16 @@ +defmodule HelloWeb.ErrorView do + use HelloWeb, :view + + # If you want to customize a particular status code + # for a certain format, you may uncomment below. + # def render("500.html", _assigns) do + # "Internal Server Error" + # end + + # By default, Phoenix returns the status message from + # the template name. For example, "404.html" becomes + # "Not Found". + def template_not_found(template, _assigns) do + Phoenix.Controller.status_message_from_template(template) + end +end diff --git a/serving/samples/helloworld-elixir/lib/hello_web/views/hello_view.ex b/serving/samples/helloworld-elixir/lib/hello_web/views/hello_view.ex new file mode 100644 index 000000000..dc29df264 --- /dev/null +++ b/serving/samples/helloworld-elixir/lib/hello_web/views/hello_view.ex @@ -0,0 +1,3 @@ +defmodule HelloWeb.HelloView do + use HelloWeb, :view +end diff --git a/serving/samples/helloworld-elixir/lib/hello_web/views/layout_view.ex b/serving/samples/helloworld-elixir/lib/hello_web/views/layout_view.ex new file mode 100644 index 000000000..59c874284 --- /dev/null +++ b/serving/samples/helloworld-elixir/lib/hello_web/views/layout_view.ex @@ -0,0 +1,3 @@ +defmodule HelloWeb.LayoutView do + use HelloWeb, :view +end diff --git a/serving/samples/helloworld-elixir/lib/hello_web/views/page_view.ex b/serving/samples/helloworld-elixir/lib/hello_web/views/page_view.ex new file mode 100644 index 000000000..9a271179a --- /dev/null +++ b/serving/samples/helloworld-elixir/lib/hello_web/views/page_view.ex @@ -0,0 +1,3 @@ +defmodule HelloWeb.PageView do + use HelloWeb, :view +end diff --git a/serving/samples/helloworld-elixir/mix.exs b/serving/samples/helloworld-elixir/mix.exs new file mode 100644 index 000000000..6127c4c80 --- /dev/null +++ b/serving/samples/helloworld-elixir/mix.exs @@ -0,0 +1,44 @@ +defmodule Hello.Mixfile do + use Mix.Project + + def project do + [ + app: :hello, + version: "0.0.1", + elixir: "~> 1.4", + elixirc_paths: elixirc_paths(Mix.env), + compilers: [:phoenix, :gettext] ++ Mix.compilers, + start_permanent: Mix.env == :prod, + deps: deps() + ] + end + + # Configuration for the OTP application. + # + # Type `mix help compile.app` for more information. + def application do + [ + mod: {Hello.Application, []}, + extra_applications: [:logger, :runtime_tools] + ] + end + + # Specifies which paths to compile per environment. + defp elixirc_paths(:test), do: ["lib", "test/support"] + defp elixirc_paths(_), do: ["lib"] + + # Specifies your project dependencies. + # + # Type `mix help deps` for examples and options. + defp deps do + [ + {:phoenix, "~> 1.3.2"}, + {:phoenix_pubsub, "~> 1.0"}, + {:phoenix_html, "~> 2.10"}, + {:phoenix_live_reload, "~> 1.0", only: :dev}, + {:gettext, "~> 0.11"}, + {:cowboy, "~> 1.0"}, + {:distillery, "~> 1.5"} + ] + end +end diff --git a/serving/samples/helloworld-elixir/mix.lock b/serving/samples/helloworld-elixir/mix.lock new file mode 100644 index 000000000..f87e0b7e0 --- /dev/null +++ b/serving/samples/helloworld-elixir/mix.lock @@ -0,0 +1,15 @@ +%{ + "cowboy": {:hex, :cowboy, "1.1.2", "61ac29ea970389a88eca5a65601460162d370a70018afe6f949a29dca91f3bb0", [:rebar3], [{:cowlib, "~> 1.0.2", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3.2", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"}, + "cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], [], "hexpm"}, + "distillery": {:hex, :distillery, "1.5.2", "eec18b2d37b55b0bcb670cf2bcf64228ed38ce8b046bb30a9b636a6f5a4c0080", [:mix], [], "hexpm"}, + "file_system": {:hex, :file_system, "0.2.5", "a3060f063b116daf56c044c273f65202e36f75ec42e678dc10653056d3366054", [:mix], [], "hexpm"}, + "gettext": {:hex, :gettext, "0.15.0", "40a2b8ce33a80ced7727e36768499fc9286881c43ebafccae6bab731e2b2b8ce", [:mix], [], "hexpm"}, + "mime": {:hex, :mime, "1.3.0", "5e8d45a39e95c650900d03f897fbf99ae04f60ab1daa4a34c7a20a5151b7a5fe", [:mix], [], "hexpm"}, + "phoenix": {:hex, :phoenix, "1.3.2", "2a00d751f51670ea6bc3f2ba4e6eb27ecb8a2c71e7978d9cd3e5de5ccf7378bd", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, + "phoenix_html": {:hex, :phoenix_html, "2.11.2", "86ebd768258ba60a27f5578bec83095bdb93485d646fc4111db8844c316602d6", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, + "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.1.5", "8d4c9b1ef9ca82deee6deb5a038d6d8d7b34b9bb909d99784a49332e0d15b3dc", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.0 or ~> 1.2 or ~> 1.3", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm"}, + "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.0.2", "bfa7fd52788b5eaa09cb51ff9fcad1d9edfeb68251add458523f839392f034c1", [:mix], [], "hexpm"}, + "plug": {:hex, :plug, "1.5.1", "1ff35bdecfb616f1a2b1c935ab5e4c47303f866cb929d2a76f0541e553a58165", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.3", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"}, + "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, + "ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], [], "hexpm"}, +} diff --git a/serving/samples/helloworld-elixir/priv/gettext/en/LC_MESSAGES/errors.po b/serving/samples/helloworld-elixir/priv/gettext/en/LC_MESSAGES/errors.po new file mode 100644 index 000000000..cdec3a113 --- /dev/null +++ b/serving/samples/helloworld-elixir/priv/gettext/en/LC_MESSAGES/errors.po @@ -0,0 +1,11 @@ +## `msgid`s in this file come from POT (.pot) files. +## +## Do not add, change, or remove `msgid`s manually here as +## they're tied to the ones in the corresponding POT file +## (with the same domain). +## +## Use `mix gettext.extract --merge` or `mix gettext.merge` +## to merge POT files into PO files. +msgid "" +msgstr "" +"Language: en\n" diff --git a/serving/samples/helloworld-elixir/priv/gettext/errors.pot b/serving/samples/helloworld-elixir/priv/gettext/errors.pot new file mode 100644 index 000000000..6988141a6 --- /dev/null +++ b/serving/samples/helloworld-elixir/priv/gettext/errors.pot @@ -0,0 +1,10 @@ +## This file is a PO Template file. +## +## `msgid`s here are often extracted from source code. +## Add new translations manually only if they're dynamic +## translations that can't be statically extracted. +## +## Run `mix gettext.extract` to bring this file up to +## date. Leave `msgstr`s empty as changing them here as no +## effect: edit them in PO (`.po`) files instead. + diff --git a/serving/samples/helloworld-elixir/rel/config.exs b/serving/samples/helloworld-elixir/rel/config.exs new file mode 100644 index 000000000..7452b3f5f --- /dev/null +++ b/serving/samples/helloworld-elixir/rel/config.exs @@ -0,0 +1,53 @@ +# Import all plugins from `rel/plugins` +# They can then be used by adding `plugin MyPlugin` to +# either an environment, or release definition, where +# `MyPlugin` is the name of the plugin module. +Path.join(["rel", "plugins", "*.exs"]) +|> Path.wildcard() +|> Enum.map(&Code.eval_file(&1)) + +use Mix.Releases.Config, + # This sets the default release built by `mix release` + default_release: :default, + # This sets the default environment used by `mix release` + default_environment: Mix.env() + +# For a full list of config options for both releases +# and environments, visit https://hexdocs.pm/distillery/configuration.html + + +# You may define one or more environments in this file, +# an environment's settings will override those of a release +# when building in that environment, this combination of release +# and environment configuration is called a profile + +environment :dev do + # If you are running Phoenix, you should make sure that + # server: true is set and the code reloader is disabled, + # even in dev mode. + # It is recommended that you build with MIX_ENV=prod and pass + # the --env flag to Distillery explicitly if you want to use + # dev mode. + set dev_mode: true + set include_erts: false + set cookie: :"Bps5@RVvPgL9c~C~D(DCQ5*Iu! Note: `ksvc` is an alias for `services.serving.knative.dev`. If you have + an older version (version 0.1.0) of Knative installed, you'll need to use + the long name until you upgrade to version 0.1.1 or higher. See + [Checking Knative Installation Version](../../../install/check-install-version.md) + to learn how to see what version you have installed. + 1. Now you can make a request to your app to see the results. Replace `{IP_ADDRESS}` with the address you see returned in the previous step. diff --git a/serving/samples/helloworld-haskell/.gitignore b/serving/samples/helloworld-haskell/.gitignore new file mode 100644 index 000000000..9a6cf65ad --- /dev/null +++ b/serving/samples/helloworld-haskell/.gitignore @@ -0,0 +1,6 @@ +.stack-work/ +*.cabal +*~ +/.idea/ +/dist/ +/out/ diff --git a/serving/samples/helloworld-haskell/Dockerfile b/serving/samples/helloworld-haskell/Dockerfile new file mode 100644 index 000000000..8a9626b4c --- /dev/null +++ b/serving/samples/helloworld-haskell/Dockerfile @@ -0,0 +1,21 @@ +# Use the existing Haskell image as our base +FROM haskell:8.2.2 as builder + +# Checkout our code onto the Docker container +WORKDIR /app +ADD . /app + +# Build and test our code, then install the “helloworld-haskell-exe” executable +RUN stack setup +RUN stack build --copy-bins + +# Copy the "helloworld-haskell-exe" executable to the image using docker multi stage build +FROM fpco/haskell-scratch:integer-gmp +WORKDIR /root/ +COPY --from=builder /root/.local/bin/helloworld-haskell-exe . + +# Expose a port to run our application +EXPOSE 8080 + +# Run the server command +CMD ["./helloworld-haskell-exe"] diff --git a/serving/samples/helloworld-haskell/README.md b/serving/samples/helloworld-haskell/README.md new file mode 100644 index 000000000..0cd30e662 --- /dev/null +++ b/serving/samples/helloworld-haskell/README.md @@ -0,0 +1,204 @@ +# Hello World - Haskell sample + +A simple web app written in Haskell that you can use for testing. +It reads in an env variable `TARGET` and prints "Hello World: ${TARGET}!". If +TARGET is not specified, it will use "NOT SPECIFIED" as the TARGET. + +## Prerequisites + +* A Kubernetes cluster with Knative installed. Follow the + [installation instructions](https://github.com/knative/docs/blob/master/install/README.md) if you need + to create one. +* [Docker](https://www.docker.com) installed and running on your local machine, + and a Docker Hub account configured (we'll use it for a container registry). + +## Recreating the sample code + +While you can clone all of the code from this directory, hello world +apps are generally more useful if you build them step-by-step. The +following instructions recreate the source files from this folder. + +1. Create a new file named `stack.yaml` and paste the following code: + + ```yaml + flags: {} + packages: + - . + extra-deps: [] + resolver: lts-10.7 + ``` +1. Create a new file named `package.yaml` and paste the following code + + ```yaml + name: helloworld-haskell + version: 0.1.0.0 + dependencies: + - base >= 4.7 && < 5 + - scotty + - text + + executables: + helloworld-haskell-exe: + main: Main.hs + source-dirs: app + ghc-options: + - -threaded + - -rtsopts + - -with-rtsopts=-N + ``` + +1. Create a `app` folder, then create a new file named `Main.hs` in that folder + and paste the following code. This code creates a basic web server which + listens on port 8080: + + ```haskell + {-# LANGUAGE OverloadedStrings #-} + + import Data.Maybe + import Data.Monoid ((<>)) + import Data.Text.Lazy (Text) + import Data.Text.Lazy + import System.Environment (lookupEnv) + import Web.Scotty (ActionM, ScottyM, scotty) + import Web.Scotty.Trans + + main :: IO () + main = do + t <- fromMaybe "NOT SPECIFIED" <$> lookupEnv "TARGET" + scotty 8080 (route t) + + route :: String -> ScottyM() + route t = get "/" $ hello t + + hello :: String -> ActionM() + hello t = text $ pack ("Hello world: " ++ t) + ``` + +1. In your project directory, create a file named `Dockerfile` and copy the code + block below into it. + + ```docker + # Use the existing Haskell image as our base + FROM haskell:8.2.2 as builder + + # Checkout our code onto the Docker container + WORKDIR /app + ADD . /app + + # Build and test our code, then install the “helloworld-haskell-exe” executable + RUN stack setup + RUN stack build --copy-bins + + # Copy the "helloworld-haskell-exe" executable to the image using docker multi stage build + FROM fpco/haskell-scratch:integer-gmp + WORKDIR /root/ + COPY --from=builder /root/.local/bin/helloworld-haskell-exe . + + # Expose a port to run our application + EXPOSE 8080 + + # Run the server command + CMD ["./helloworld-haskell-exe"] + ``` + +1. Create a new file, `service.yaml` and copy the following service definition + into the file. Make sure to replace `{username}` with your Docker Hub username. + +```yaml +apiVersion: serving.knative.dev/v1alpha1 +kind: Service +metadata: + name: helloworld-haskell + namespace: default +spec: + runLatest: + configuration: + revisionTemplate: + spec: + container: + image: docker.io/{username}/helloworld-haskell + env: + - name: TARGET + value: "Haskell Sample v1" +``` + +## Build and deploy this sample + +Once you have recreated the sample code files (or used the files in the sample +folder) you're ready to build and deploy the sample app. + +1. Use Docker to build the sample code into a container. To build and push with + Docker Hub, enter these commands replacing `{username}` with your + Docker Hub username: + + ```shell + # Build the container on your local machine + docker build -t {username}/helloworld-haskell . + + # Push the container to docker registry + docker push {username}/helloworld-haskell + ``` + +1. After the build has completed and the container is pushed to Docker Hub, you + can deploy the app into your cluster. Ensure that the container image value + in `service.yaml` matches the container you built in + the previous step. Apply the configuration using `kubectl`: + + ```shell + kubectl apply -f service.yaml + ``` + +1. Now that your service is created, Knative will perform the following steps: + * Create a new immutable revision for this version of the app. + * Network programming to create a route, ingress, service, and load balance for your app. + * Automatically scale your pods up and down (including to zero active pods). + +1. To find the IP address for your service, enter + `kubectl get svc knative-ingressgateway -n istio-system` to get the ingress IP for your + cluster. If your cluster is new, it may take some time for the service to get assigned + an external IP address. + + ```shell + kubectl get svc knative-ingressgateway -n istio-system + + NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE + knative-ingressgateway LoadBalancer 10.23.247.74 35.203.155.229 80:32380/TCP,443:32390/TCP,32400:32400/TCP 2d + + ``` + + For minikube or bare-metal, get IP_ADDRESS by running the following command + + ```shell + echo $(kubectl get node -o 'jsonpath={.items[0].status.addresses[0].address}'):$(kubectl get svc knative-ingressgateway -n istio-system -o 'jsonpath={.spec.ports[?(@.port==80)].nodePort}') + + ``` + +1. To find the URL for your service, enter: + ``` + kubectl get ksvc helloworld-haskell -o=custom-columns=NAME:.metadata.name,DOMAIN:.status.domain + NAME DOMAIN + helloworld-haskell helloworld-haskell.default.example.com + ``` + + > Note: `ksvc` is an alias for `services.serving.knative.dev`. If you have + an older version (version 0.1.0) of Knative installed, you'll need to use + the long name until you upgrade to version 0.1.1 or higher. See + [Checking Knative Installation Version](../../../install/check-install-version.md) + to learn how to see what version you have installed. + +1. Now you can make a request to your app and see the result. Replace + `{IP_ADDRESS}` with the address you see returned in the previous step. + + ```shell + curl -H "Host: helloworld-haskell.default.example.com" http://{IP_ADDRESS} + Hello world: Haskell Sample v1 + ``` + +## Removing the sample app deployment + +To remove the sample app from your cluster, delete the service record: + +```shell +kubectl delete -f service.yaml +``` + diff --git a/serving/samples/helloworld-haskell/app/Main.hs b/serving/samples/helloworld-haskell/app/Main.hs new file mode 100644 index 000000000..6f69ab9ce --- /dev/null +++ b/serving/samples/helloworld-haskell/app/Main.hs @@ -0,0 +1,20 @@ +{-# LANGUAGE OverloadedStrings #-} + +import Data.Maybe +import Data.Monoid ((<>)) +import Data.Text.Lazy (Text) +import Data.Text.Lazy +import System.Environment (lookupEnv) +import Web.Scotty (ActionM, ScottyM, scotty) +import Web.Scotty.Trans + +main :: IO () +main = do + t <- fromMaybe "NOT SPECIFIED" <$> lookupEnv "TARGET" + scotty 8080 (route t) + +route :: String -> ScottyM() +route t = get "/" $ hello t + +hello :: String -> ActionM() +hello t = text $ pack ("Hello world: " ++ t) diff --git a/serving/samples/helloworld-haskell/package.yaml b/serving/samples/helloworld-haskell/package.yaml new file mode 100644 index 000000000..12178e943 --- /dev/null +++ b/serving/samples/helloworld-haskell/package.yaml @@ -0,0 +1,15 @@ +name: helloworld-haskell +version: 0.1.0.0 +dependencies: +- base >= 4.7 && < 5 +- scotty +- text + +executables: + helloworld-haskell-exe: + main: Main.hs + source-dirs: app + ghc-options: + - -threaded + - -rtsopts + - -with-rtsopts=-N diff --git a/serving/samples/helloworld-haskell/service.yaml b/serving/samples/helloworld-haskell/service.yaml new file mode 100644 index 000000000..07a7e0a83 --- /dev/null +++ b/serving/samples/helloworld-haskell/service.yaml @@ -0,0 +1,15 @@ +apiVersion: serving.knative.dev/v1alpha1 +kind: Service +metadata: + name: helloworld-haskell + namespace: default +spec: + runLatest: + configuration: + revisionTemplate: + spec: + container: + image: docker.io/{username}/helloworld-haskell + env: + - name: TARGET + value: "Haskell Sample v1" diff --git a/serving/samples/helloworld-haskell/stack.yaml b/serving/samples/helloworld-haskell/stack.yaml new file mode 100644 index 000000000..e63cd13e1 --- /dev/null +++ b/serving/samples/helloworld-haskell/stack.yaml @@ -0,0 +1,5 @@ +flags: {} +packages: +- . +extra-deps: [] +resolver: lts-10.7 diff --git a/serving/samples/helloworld-java/README.md b/serving/samples/helloworld-java/README.md index f8190cf55..2b31a5bd9 100644 --- a/serving/samples/helloworld-java/README.md +++ b/serving/samples/helloworld-java/README.md @@ -154,11 +154,17 @@ folder) you're ready to build and deploy the sample app. 1. To find the URL for your service, use ``` - kubectl get services.serving.knative.dev helloworld-java -o=custom-columns=NAME:.metadata.name,DOMAIN:.status.domain + kubectl get ksvc helloworld-java -o=custom-columns=NAME:.metadata.name,DOMAIN:.status.domain NAME DOMAIN helloworld-java helloworld-java.default.example.com ``` + > Note: `ksvc` is an alias for `services.serving.knative.dev`. If you have + an older version (version 0.1.0) of Knative installed, you'll need to use + the long name until you upgrade to version 0.1.1 or higher. See + [Checking Knative Installation Version](../../../install/check-install-version.md) + to learn how to see what version you have installed. + 1. Now you can make a request to your app to see the result. Replace `{IP_ADDRESS}` with the address you see returned in the previous step. diff --git a/serving/samples/helloworld-nodejs/README.md b/serving/samples/helloworld-nodejs/README.md index 5b5448b86..5a0a27ced 100644 --- a/serving/samples/helloworld-nodejs/README.md +++ b/serving/samples/helloworld-nodejs/README.md @@ -172,11 +172,17 @@ folder) you're ready to build and deploy the sample app. 1. To find the URL for your service, use ``` - kubectl get services.serving.knative.dev helloworld-nodejs -o=custom-columns=NAME:.metadata.name,DOMAIN:.status.domain + kubectl get ksvc helloworld-nodejs -o=custom-columns=NAME:.metadata.name,DOMAIN:.status.domain NAME DOMAIN helloworld-nodejs helloworld-nodejs.default.example.com ``` + > Note: `ksvc` is an alias for `services.serving.knative.dev`. If you have + an older version (version 0.1.0) of Knative installed, you'll need to use + the long name until you upgrade to version 0.1.1 or higher. See + [Checking Knative Installation Version](../../../install/check-install-version.md) + to learn how to see what version you have installed. + 1. Now you can make a request to your app to see the result. Replace `{IP_ADDRESS}` with the address you see returned in the previous step. diff --git a/serving/samples/helloworld-php/README.md b/serving/samples/helloworld-php/README.md index 5a1fc5007..667271a4f 100644 --- a/serving/samples/helloworld-php/README.md +++ b/serving/samples/helloworld-php/README.md @@ -113,11 +113,17 @@ you're ready to build and deploy the sample app. 1. To find the URL for your service, use ``` - kubectl get services.serving.knative.dev helloworld-php -o=custom-columns=NAME:.metadata.name,DOMAIN:.status.domain + kubectl get ksvc helloworld-php -o=custom-columns=NAME:.metadata.name,DOMAIN:.status.domain NAME DOMAIN helloworld-php helloworld-php.default.example.com ``` + > Note: `ksvc` is an alias for `services.serving.knative.dev`. If you have + an older version (version 0.1.0) of Knative installed, you'll need to use + the long name until you upgrade to version 0.1.1 or higher. See + [Checking Knative Installation Version](../../../install/check-install-version.md) + to learn how to see what version you have installed. + 1. Now you can make a request to your app to see the result. Replace `{IP_ADDRESS}` with the address you see returned in the previous step. diff --git a/serving/samples/helloworld-python/README.md b/serving/samples/helloworld-python/README.md index f1b3640b6..298864ef9 100644 --- a/serving/samples/helloworld-python/README.md +++ b/serving/samples/helloworld-python/README.md @@ -125,11 +125,17 @@ folder) you're ready to build and deploy the sample app. 1. To find the URL for your service, use ``` - kubectl get services.serving.knative.dev helloworld-python -o=custom-columns=NAME:.metadata.name,DOMAIN:.status.domain + kubectl get ksvc helloworld-python -o=custom-columns=NAME:.metadata.name,DOMAIN:.status.domain NAME DOMAIN helloworld-python helloworld-python.default.example.com ``` + > Note: `ksvc` is an alias for `services.serving.knative.dev`. If you have + an older version (version 0.1.0) of Knative installed, you'll need to use + the long name until you upgrade to version 0.1.1 or higher. See + [Checking Knative Installation Version](../../../install/check-install-version.md) + to learn how to see what version you have installed. + 1. Now you can make a request to your app to see the result. Replace `{IP_ADDRESS}` with the address you see returned in the previous step. diff --git a/serving/samples/helloworld-ruby/README.md b/serving/samples/helloworld-ruby/README.md index 403b4c064..53732b9d4 100644 --- a/serving/samples/helloworld-ruby/README.md +++ b/serving/samples/helloworld-ruby/README.md @@ -140,10 +140,15 @@ you're ready to build and deploy the sample app. 1. To find the URL for your service, use ``` - kubectl get services.serving.knative.dev helloworld-ruby -o=custom-columns=NAME:.metadata.name,DOMAIN:.status.domain + kubectl get ksvc helloworld-ruby -o=custom-columns=NAME:.metadata.name,DOMAIN:.status.domain NAME DOMAIN helloworld-ruby helloworld-ruby.default.example.com ``` + > Note: `ksvc` is an alias for `services.serving.knative.dev`. If you have + an older version (version 0.1.0) of Knative installed, you'll need to use + the long name until you upgrade to version 0.1.1 or higher. See + [Checking Knative Installation Version](../../../install/check-install-version.md) + to learn how to see what version you have installed. 1. Now you can make a request to your app to see the result. Replace `{IP_ADDRESS}` with the address you see returned in the previous step. diff --git a/serving/samples/helloworld-rust/README.md b/serving/samples/helloworld-rust/README.md index 9a8ee1d8e..0010c984d 100644 --- a/serving/samples/helloworld-rust/README.md +++ b/serving/samples/helloworld-rust/README.md @@ -156,11 +156,17 @@ folder) you're ready to build and deploy the sample app. 1. To find the URL for your service, enter: ``` - kubectl get services.serving.knative.dev helloworld-rust -o=custom-columns=NAME:.metadata.name,DOMAIN:.status.domain + kubectl get ksvc helloworld-rust -o=custom-columns=NAME:.metadata.name,DOMAIN:.status.domain NAME DOMAIN helloworld-rust helloworld-rust.default.example.com ``` + > Note: `ksvc` is an alias for `services.serving.knative.dev`. If you have + an older version (version 0.1.0) of Knative installed, you'll need to use + the long name until you upgrade to version 0.1.1 or higher. See + [Checking Knative Installation Version](../../../install/check-install-version.md) + to learn how to see what version you have installed. + 1. Now you can make a request to your app and see the result. Replace `{IP_ADDRESS}` with the address you see returned in the previous step. diff --git a/serving/samples/source-to-url-go/README.md b/serving/samples/source-to-url-go/README.md index c0f468a0e..e4286e7f8 100644 --- a/serving/samples/source-to-url-go/README.md +++ b/serving/samples/source-to-url-go/README.md @@ -189,16 +189,6 @@ container for the application. revisionName: app-from-source-00007 ``` - -1. After the build has completed and the container is pushed to Docker Hub, you - can deploy the app into your cluster. Ensure that the container image value - in `service.yaml` matches the container you built in - the previous step. Apply the configuration using `kubectl`: - - ```shell - kubectl apply -f service.yaml - ``` - 1. Now that your service is created, Knative will perform the following steps: * Fetch the revision specified from GitHub and build it into a container * Push the container to Docker Hub @@ -220,11 +210,17 @@ container for the application. 1. To find the URL for your service, type: ```shell - $ kubectl get services.serving.knative.dev app-from-source -o=custom-columns=NAME:.metadata.name,DOMAIN:.status.domain + $ kubectl get ksvc app-from-source -o=custom-columns=NAME:.metadata.name,DOMAIN:.status.domain NAME DOMAIN app-from-source app-from-source.default.example.com ``` + > Note: `ksvc` is an alias for `services.serving.knative.dev`. If you have + an older version (version 0.1.0) of Knative installed, you'll need to use + the long name until you upgrade to version 0.1.1 or higher. See + [Checking Knative Installation Version](../../../install/check-install-version.md) + to learn how to see what version you have installed. + 1. Now you can make a request to your app to see the result. Replace `{IP_ADDRESS}` with the address that you got in the previous step: diff --git a/serving/samples/telemetry-go/README.md b/serving/samples/telemetry-go/README.md index f904c0e1c..aee717105 100644 --- a/serving/samples/telemetry-go/README.md +++ b/serving/samples/telemetry-go/README.md @@ -2,100 +2,163 @@ This sample runs a simple web server that makes calls to other in-cluster services and responds to requests with "Hello World!". -The purpose of this sample is to show generating metrics, logs and distributed traces -(see [Logs](../../accessing-logs.md), [Metrics](../../accessing-metrics.md), and [Traces](../../accessing-traces.md) for more information). -This sample also creates a dedicated Prometheus instances rather than using the one -that is installed by default as a showcase of installing dedicated Prometheus instances. +The purpose of this sample is to show generating [metrics](../../accessing-metrics.md), +[logs](../../accessing-logs.md) and distributed [traces](../../accessing-traces.md). +This sample also shows how to create a dedicated Prometheus instance rather than +using the default installation. ## Prerequisites -1. [Install Knative Serving](https://github.com/knative/docs/blob/master/install/README.md) -2. [Install Knative monitoring component](../../installing-logging-metrics-traces.md) -3. Install [docker](https://www.docker.com/) - +1. A Kubernetes cluster with [Knative Serving](https://github.com/knative/docs/blob/master/install/README.md) +installed. +2. Check if Knative monitoring components are installed: +``` +kubectl get pods -n monitoring +``` + * If pods aren't found, install [Knative monitoring component](../../installing-logging-metrics-traces.md). +3. Install [Docker](https://docs.docker.com/get-started/#prepare-your-docker-environment). +4. Check out the code: +``` +go get -d github.com/knative/docs/serving/samples/telemetry-go +``` ## Setup -Build the app container and publish it to your registry of choice: +Build the application container and publish it to a container registry: -```shell -REPO="gcr.io/" +1. Move into the sample directory: +``` +cd $GOPATH/src/github.com/knative/docs +``` -# Build and publish the container, run from the root directory. +2. Set your preferred container registry: +``` +export REPO="gcr.io/" +``` + This example shows how to use Google Container Registry (GCR). You will need + a Google Cloud Project and to enable the [Google Container Registry +API](https://console.cloud.google.com/apis/library/containerregistry.googleapis.com). + +3. Use Docker to build your application container: +``` docker build \ --tag "${REPO}/serving/samples/telemetry-go" \ --file=serving/samples/telemetry-go/Dockerfile . +``` + +4. Push your container to a container registry: +``` docker push "${REPO}/serving/samples/telemetry-go" +``` -# Replace the image reference with our published image. -perl -pi -e "s@github.com/knative/docs/serving/samples/telemetry-go@${REPO}/serving/samples/telemetry-go@g" serving/samples/telemetry-go/*.yaml +5. Replace the image reference path with our published image path in the +configuration file (`serving/samples/telemetry-go/sample.yaml`): + * Manually replace: + `image: github.com/knative/docs/serving/samples/telemetry-go` with + `image: /serving/samples/telemetry-go` -# Deploy the Knative Serving sample + Or + + * Use run this command: + ``` + perl -pi -e "s@github.com/knative/docs@${REPO}@g" serving/samples/telemetry-go/sample.yaml + ``` + +## Deploy the Service + +Deploy this application to Knative Serving: +``` kubectl apply -f serving/samples/telemetry-go/ ``` -## Exploring +## Explore the Service -Once deployed, you can inspect the created resources with `kubectl` commands: +Inspect the created resources with the `kubectl` commands: -```shell -# This will show the route that we created: -kubectl get route -o yaml + * View the created Route resource: + ``` + kubectl get route -o yaml + ``` -# This will show the configuration that we created: -kubectl get configurations -o yaml + * View the created Configuration resource: + ``` + kubectl get configurations -o yaml + ``` -# This will show the Revision that was created by our configuration: -kubectl get revisions -o yaml + * View the Revision that was created by the Configuration: + ``` + kubectl get revisions -o yaml + ``` + +## Access the Service + +To access this service via `curl`, you need to determine its ingress address. + +1. To determine if your service is ready: + Check the status of your Knative gateway: + ``` + kubectl get svc knative-ingressgateway -n istio-system --watch + ``` + + When the service is ready, you'll see an IP address in the `EXTERNAL-IP` field: + ``` + NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE + knative-ingressgateway LoadBalancer 10.23.247.74 35.203.155.229 80:32380/TCP,443:32390/TCP,32400:32400/TCP 2d + ``` + CTRL+C to end watch. + + Check the status of your route: + ``` + kubectl get route -o yaml + ``` + When the route is ready, you'll see the following fields reported as: + ```YAML + status: + conditions: + ... + status: "True" + type: Ready + domain: telemetrysample-route.default.example.com + ``` + +2. Export the ingress hostname and IP as environment +variables: ``` - -To access this service via `curl`, we first need to determine its ingress address: -```shell -watch kubectl get svc knative-ingressgateway -n istio-system -NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE -knative-ingressgateway LoadBalancer 10.23.247.74 35.203.155.229 80:32380/TCP,443:32390/TCP,32400:32400/TCP 2d -``` - -Once the `EXTERNAL-IP` gets assigned to the cluster, you can run: - -```shell -# Put the Host name into an environment variable. export SERVICE_HOST=`kubectl get route telemetrysample-route -o jsonpath="{.status.domain}"` - -# Put the ingress IP into an environment variable. export SERVICE_IP=`kubectl get svc knative-ingressgateway -n istio-system -o jsonpath="{.status.loadBalancer.ingress[*].ip}"` +``` -# Curl the ingress IP "as-if" DNS were properly configured. +3. Make a request to the service to see the `Hello World!` message: +``` curl --header "Host:$SERVICE_HOST" http://${SERVICE_IP} -Hello World! ``` -Generate some logs to STDOUT and files under `/var/log` in `Json` or plain text formats. - -```shell +4. Make a request to the `/log` endpoint to generate logs to the `stdout` file +and generate files under `/var/log` in both `JSON` and plain text formats: +``` curl --header "Host:$SERVICE_HOST" http://${SERVICE_IP}/log -Sending logs done. ``` -## Accessing logs -You can access to the logs from Kibana UI - see [Logs](../../accessing-logs.md) for more information. +## Access Logs +You can access to the logs from Kibana UI - see [Logs](../../accessing-logs.md) +for more information. -## Accessing per request traces -You can access to per request traces from Zipkin UI - see [Traces](../../accessing-traces.md) for more information. +## Access per Request Traces +You can access to per request traces from Zipkin UI - see [Traces](../../accessing-traces.md) +for more information. -## Accessing custom metrics -You can see published metrics using Prometheus UI. To access to the UI, forward the Prometheus server to your machine: - -```bash +## Accessing Custom Metrics +You can see published metrics using Prometheus UI. To access to the UI, forward +the Prometheus server to your machine: +``` kubectl port-forward $(kubectl get pods --selector=app=prometheus,prometheus=test --output=jsonpath="{.items[0].metadata.name}") 9090 ``` Then browse to http://localhost:9090. -## Cleaning up +## Clean up To clean up the sample service: - -```shell -kubectl delete -f serving/samples/telemetrysample-go/ +``` +kubectl delete -f serving/samples/telemetry-go/ ``` diff --git a/serving/samples/telemetry-go/configuration.yaml b/serving/samples/telemetry-go/sample.yaml similarity index 100% rename from serving/samples/telemetry-go/configuration.yaml rename to serving/samples/telemetry-go/sample.yaml diff --git a/serving/samples/thumbnailer-go/README.md b/serving/samples/thumbnailer-go/README.md index 6fce3399d..8f1c27bcd 100644 --- a/serving/samples/thumbnailer-go/README.md +++ b/serving/samples/thumbnailer-go/README.md @@ -109,8 +109,12 @@ perl -pi -e "s@DOCKER_REPO_OVERRIDE@$REPO@g" sample.yaml # Install the Kaniko build template used to build this sample (in the # build-templates repo). kubectl apply -f https://raw.githubusercontent.com/knative/build-templates/master/kaniko/kaniko.yaml + +# Create the Knative route and configuration for the application +kubectl apply -f sample.yaml ``` + Now, if you look at the `status` of the revision, you will see that a build is in progress: ```shell @@ -201,4 +205,4 @@ curl -H "Host: $SERVICE_HOST" \ Although this demo uses an external application, the Knative Serving deployment steps would be similar for any 'dockerized' app you may already have. -Just copy the `thumbnailer.yaml` and change a few variables. +Just copy the `sample.yaml` and change a few variables. diff --git a/serving/using-external-dns.md b/serving/using-external-dns.md new file mode 100644 index 000000000..e0806023b --- /dev/null +++ b/serving/using-external-dns.md @@ -0,0 +1,161 @@ +# Using ExternalDNS to automate DNS setup + +[ExternalDNS](https://github.com/kubernetes-incubator/external-dns) is a tool +that synchronizes exposed Kubernetes Services and Ingresses with DNS providers. + +This doc explains how to set up ExternalDNS within a Knative cluster using +[Google Cloud DNS](https://cloud.google.com/dns/) to automate the process +of publishing the Knative domain. + +## Prerequisite + +1. A Google Kubernetes Engine cluster with Cloud DNS scope. + You can create a GKE cluster with Cloud DNS scope by entering the following command: + ```shell + gcloud container clusters create "external-dns" \ + --scopes "https://www.googleapis.com/auth/ndev.clouddns.readwrite" + ``` +1. [Knative Serving](https://github.com/knative/docs/blob/master/install/README.md) installed on your cluster. +1. A public domain that will be used in Knative. +1. Knative configured to use your custom domain. +```shell +kubectl edit cm config-domain -n knative-serving +``` +This command opens your default text editor and allows you to edit the config +map. +``` +apiVersion: v1 +data: + example.com: "" +kind: ConfigMap +[...] +``` +Edit the file to replace `example.com` with the domain you'd like to use and +save your changes. In this example, we use domain `external-dns-test.my-org.do` + for all routes: +``` +apiVersion: v1 +data: + external-dns-test.my-org.do: "" +kind: ConfigMap +[...] +``` + +## Setup steps + +This guide uses Google Cloud Platform as an example to show how to set up +ExternalDNS. You can find detailed instructions for other cloud providers in the +[ExternalDNS documentation](https://github.com/kubernetes-incubator/external-dns#deploying-to-a-cluster). + +### Choose a DNS provider + +Skip this step if you already have a DNS provider for your domain. + +Here is a [list](https://github.com/kubernetes-incubator/external-dns#the-latest-release-v05) +of DNS providers supported by ExternalDNS. Choose a DNS provider from the list. + +### Create a DNS zone for managing DNS records + +Skip this step if you already have a zone for managing the DNS records of your +custom domain. + +A DNS zone which will contain the managed DNS records needs to be created. +Assume your custom domain is `external-dns-test.my-org.do`. + +Use the following command to create a DNS zone with [Google Cloud DNS](https://cloud.google.com/dns/): +```shell +gcloud dns managed-zones create "external-dns-zone" \ + --dns-name "external-dns-test.my-org.do." \ + --description "Automatically managed zone by kubernetes.io/external-dns" +``` +Make a note of the nameservers that were assigned to your new zone. +```shell +gcloud dns record-sets list \ + --zone "external-dns-zone" \ + --name "external-dns-test.my-org.do." \ + --type NS +``` +You should see output similar to the following: +``` +NAME TYPE TTL DATA +external-dns-test.my-org.do. NS 21600 ns-cloud-e1.googledomains.com.,ns-cloud-e2.googledomains.com.,ns-cloud-e3.googledomains.com.,ns-cloud-e4.googledomains.com. +``` +In this case, the DNS nameservers are `ns-cloud-{e1-e4}.googledomains.com`. +Yours could differ slightly, e.g. {a1-a4}, {b1-b4} etc. + +If this zone has the parent zone, you need to add NS records of this zone into +the parent zone so that this zone can be found from the parent. +Assuming the parent zone is `my-org-do` and the parent domain is `my-org.do`, +and the parent zone is also hosted at Google Cloud DNS, you can follow these +steps to add the NS records of this zone into the parent zone: +```shell +gcloud dns record-sets transaction start --zone "my-org-do" +gcloud dns record-sets transaction add ns-cloud-e{1..4}.googledomains.com. \ + --name "external-dns-test.my-org.do." --ttl 300 --type NS --zone "my-org-do" +gcloud dns record-sets transaction execute --zone "my-org-do" +``` + +### Deploy ExternalDNS + +Use the following command to apply the [manifest](https://github.com/kubernetes-incubator/external-dns/blob/master/docs/tutorials/gke.md#manifest-for-clusters-without-rbac-enabled) to install ExternalDNS +```shell +cat < +EOF +``` +Note that you need to set the argument `domain-filter` to your custom domain. + +You should see ExternalDNS is installed by running: +```shell +kubectl get deployment external-dns +``` + +### Configuring Knative Gateway service + +In order to publish the Knative Gateway service, the annotation +`external-dns.alpha.kubernetes.io/hostname: '*.external-dns-test.my-org.do'` +needs to be added into Knative gateway service: +```shell +kubectl edit svc knative-ingressgateway -n istio-system +``` +This command opens your default text editor and allows you to add the +annotation to `knative-ingressgateway` service. After you've added your +annotation, your file may look similar to this: +``` +apiVersion: v1 +kind: Service +metadata: + annotations: + external-dns.alpha.kubernetes.io/hostname: '*.external-dns-test.my-org.do' + ... +``` + +### Verify ExternalDNS works + +After roughly two minutes, check that a corresponding DNS record for your +service was created. + +```shell +gcloud dns record-sets list --zone "external-dns-zone" --name "*.external-dns-test.my-org.do." +``` +You should see output similar to: + +``` +NAME TYPE TTL DATA +*.external-dns-test.my-org.do. A 300 35.231.248.30 +*.external-dns-test.my-org.do. TXT 300 "heritage=external-dns,external-dns/owner=my-identifier,external-dns/resource=service/istio-system/knative-ingressgateway" +``` + +### Verify domain has been published + +You can check if the domain has been published to the Internet be entering +the following command: +```shell +host test.external-dns-test.my-org.do +``` +You should see the below result after the domain is published: +``` +test.external-dns-test.my-org.do has address 35.231.248.30 +``` +> Note: The process of publishing the domain to the Internet can take several +minutes. diff --git a/test/e2e-tests.sh b/test/e2e-tests.sh index 7798fe58d..12e5c86be 100755 --- a/test/e2e-tests.sh +++ b/test/e2e-tests.sh @@ -23,11 +23,7 @@ # Calling this script without arguments will create a new cluster in # project $PROJECT_ID, run the tests and delete the cluster. -# Load github.com/knative/test-infra/images/prow-tests/scripts/e2e-tests.sh -[ -f /workspace/e2e-tests.sh ] \ - && source /workspace/e2e-tests.sh \ - || eval "$(docker run --entrypoint sh gcr.io/knative-tests/test-infra/prow-tests -c 'cat e2e-tests.sh')" -[ -v KNATIVE_TEST_INFRA ] || exit 1 +source $(dirname $0)/../vendor/github.com/knative/test-infra/scripts/e2e-tests.sh # Script entry point. diff --git a/test/presubmit-tests.sh b/test/presubmit-tests.sh index 4ede9b244..0aec68778 100755 --- a/test/presubmit-tests.sh +++ b/test/presubmit-tests.sh @@ -18,11 +18,7 @@ # It is started by prow for each PR. # For convenience, it can also be executed manually. -# Load github.com/knative/test-infra/images/prow-tests/scripts/presubmit-tests.sh -[ -f /workspace/presubmit-tests.sh ] \ - && source /workspace/presubmit-tests.sh \ - || eval "$(docker run --entrypoint sh gcr.io/knative-tests/test-infra/prow-tests -c 'cat presubmit-tests.sh')" -[ -v KNATIVE_TEST_INFRA ] || exit 1 +source $(dirname $0)/../vendor/github.com/knative/test-infra/scripts/presubmit-tests.sh function build_tests() { header "TODO(#67): Write build tests" diff --git a/vendor/github.com/knative/test-infra/scripts/README.md b/vendor/github.com/knative/test-infra/scripts/README.md new file mode 100644 index 000000000..5ff9ccbce --- /dev/null +++ b/vendor/github.com/knative/test-infra/scripts/README.md @@ -0,0 +1,3 @@ +# Helper scripts + +This directory contains helper scripts used by Prow test jobs, as well and local development scripts. diff --git a/vendor/github.com/knative/test-infra/scripts/e2e-tests.sh b/vendor/github.com/knative/test-infra/scripts/e2e-tests.sh new file mode 100755 index 000000000..1262f9821 --- /dev/null +++ b/vendor/github.com/knative/test-infra/scripts/e2e-tests.sh @@ -0,0 +1,313 @@ +#!/bin/bash + +# Copyright 2018 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is a helper script for Knative E2E test scripts. To use it: +# 1. Source this script. +# 2. [optional] Write the teardown() function, which will tear down your test +# resources. +# 3. [optional] Write the dump_extra_cluster_state() function. It will be called +# when a test fails, and can dump extra information about the current state of +# the cluster (tipically using kubectl). +# 4. Call the initialize() function passing $@ (without quotes). +# 5. Write logic for the end-to-end tests. Run all go tests using report_go_test() +# and call fail_test() or success() if any of them failed. The envitronment +# variables DOCKER_REPO_OVERRIDE, K8S_CLUSTER_OVERRIDE and K8S_USER_OVERRIDE +# will be set accordingly to the test cluster. You can also use the following +# boolean (0 is false, 1 is true) environment variables for the logic: +# EMIT_METRICS: true if --emit-metrics is passed. +# USING_EXISTING_CLUSTER: true if the test cluster is an already existing one, +# and not a temporary cluster created by kubetest. +# All environment variables above are marked read-only. +# Notes: +# 1. Calling your script without arguments will create a new cluster in the GCP +# project $PROJECT_ID and run the tests against it. +# 2. Calling your script with --run-tests and the variables K8S_CLUSTER_OVERRIDE, +# K8S_USER_OVERRIDE and DOCKER_REPO_OVERRIDE set will immediately start the +# tests against the cluster. + +source $(dirname ${BASH_SOURCE})/library.sh + +# Build a resource name based on $E2E_BASE_NAME, a suffix and $BUILD_NUMBER. +# Restricts the name length to 40 chars (the limit for resource names in GCP). +# Name will have the form $E2E_BASE_NAME-$BUILD_NUMBER. +# Parameters: $1 - name suffix +function build_resource_name() { + local prefix=${E2E_BASE_NAME}-$1 + local suffix=${BUILD_NUMBER} + # Restrict suffix length to 20 chars + if [[ -n "${suffix}" ]]; then + suffix=${suffix:${#suffix}<20?0:-20} + fi + echo "${prefix:0:20}${suffix}" +} + +# Test cluster parameters +readonly E2E_BASE_NAME=k$(basename ${REPO_ROOT_DIR}) +readonly E2E_CLUSTER_NAME=$(build_resource_name e2e-cls) +readonly E2E_NETWORK_NAME=$(build_resource_name e2e-net) +readonly E2E_CLUSTER_REGION=us-central1 +readonly E2E_CLUSTER_ZONE=${E2E_CLUSTER_REGION}-a +readonly E2E_CLUSTER_NODES=3 +readonly E2E_CLUSTER_MACHINE=n1-standard-4 +readonly TEST_RESULT_FILE=/tmp/${E2E_BASE_NAME}-e2e-result + +# Tear down the test resources. +function teardown_test_resources() { + header "Tearing down test environment" + # Free resources in GCP project. + if (( ! USING_EXISTING_CLUSTER )) && [[ "$(type -t teardown)" == "function" ]]; then + teardown + fi + + # Delete Knative Serving images when using prow. + if (( IS_PROW )); then + echo "Images in ${DOCKER_REPO_OVERRIDE}:" + gcloud container images list --repository=${DOCKER_REPO_OVERRIDE} + delete_gcr_images ${DOCKER_REPO_OVERRIDE} + else + # Delete the kubernetes source downloaded by kubetest + rm -fr kubernetes kubernetes.tar.gz + fi +} + +# Exit test, dumping current state info. +# Parameters: $1 - error message (optional). +function fail_test() { + [[ -n $1 ]] && echo "ERROR: $1" + dump_cluster_state + exit 1 +} + +# Download the k8s binaries required by kubetest. +function download_k8s() { + local version=${SERVING_GKE_VERSION} + if [[ "${version}" == "latest" ]]; then + # Fetch latest valid version + local versions="$(gcloud container get-server-config \ + --project=${GCP_PROJECT} \ + --format='value(validMasterVersions)' \ + --region=${E2E_CLUSTER_REGION})" + local gke_versions=(`echo -n ${versions//;/ /}`) + # Get first (latest) version, excluding the "-gke.#" suffix + version="${gke_versions[0]%-*}" + echo "Latest GKE is ${version}, from [${versions//;/, /}]" + elif [[ "${version}" == "default" ]]; then + echo "ERROR: `default` GKE version is not supported yet" + return 1 + fi + # Download k8s to staging dir + version=v${version} + local staging_dir=${GOPATH}/src/k8s.io/kubernetes/_output/gcs-stage + rm -fr ${staging_dir} + staging_dir=${staging_dir}/${version} + mkdir -p ${staging_dir} + pushd ${staging_dir} + export KUBERNETES_PROVIDER=gke + export KUBERNETES_RELEASE=${version} + curl -fsSL https://get.k8s.io | bash + local result=$? + if [[ ${result} -eq 0 ]]; then + mv kubernetes/server/kubernetes-server-*.tar.gz . + mv kubernetes/client/kubernetes-client-*.tar.gz . + rm -fr kubernetes + # Create an empty kubernetes test tarball; we don't use it but kubetest will fetch it + tar -czf kubernetes-test.tar.gz -T /dev/null + fi + popd + return ${result} +} + +# Dump info about the test cluster. If dump_extra_cluster_info() is defined, calls it too. +# This is intended to be called when a test fails to provide debugging information. +function dump_cluster_state() { + echo "***************************************" + echo "*** TEST FAILED ***" + echo "*** Start of information dump ***" + echo "***************************************" + echo ">>> All resources:" + kubectl get all --all-namespaces + echo ">>> Services:" + kubectl get services --all-namespaces + echo ">>> Events:" + kubectl get events --all-namespaces + [[ "$(type -t dump_extra_cluster_state)" == "function" ]] && dump_extra_cluster_state + echo "***************************************" + echo "*** TEST FAILED ***" + echo "*** End of information dump ***" + echo "***************************************" +} + +# Create a test cluster with kubetest and call the current script again. +function create_test_cluster() { + header "Creating test cluster" + # Smallest cluster required to run the end-to-end-tests + local CLUSTER_CREATION_ARGS=( + --gke-create-args="--enable-autoscaling --min-nodes=1 --max-nodes=${E2E_CLUSTER_NODES} --scopes=cloud-platform" + --gke-shape={\"default\":{\"Nodes\":${E2E_CLUSTER_NODES}\,\"MachineType\":\"${E2E_CLUSTER_MACHINE}\"}} + --provider=gke + --deployment=gke + --cluster="${E2E_CLUSTER_NAME}" + --gcp-zone="${E2E_CLUSTER_ZONE}" + --gcp-network="${E2E_NETWORK_NAME}" + --gke-environment=prod + ) + if (( ! IS_PROW )); then + CLUSTER_CREATION_ARGS+=(--gcp-project=${PROJECT_ID:?"PROJECT_ID must be set to the GCP project where the tests are run."}) + else + CLUSTER_CREATION_ARGS+=(--gcp-service-account=/etc/service-account/service-account.json) + fi + # SSH keys are not used, but kubetest checks for their existence. + # Touch them so if they don't exist, empty files are create to satisfy the check. + touch $HOME/.ssh/google_compute_engine.pub + touch $HOME/.ssh/google_compute_engine + # Clear user and cluster variables, so they'll be set to the test cluster. + # DOCKER_REPO_OVERRIDE is not touched because when running locally it must + # be a writeable docker repo. + export K8S_USER_OVERRIDE= + export K8S_CLUSTER_OVERRIDE= + # Get the current GCP project + export GCP_PROJECT=${PROJECT_ID} + [[ -z ${GCP_PROJECT} ]] && export GCP_PROJECT=$(gcloud config get-value project) + # Assume test failed (see more details at the end of this script). + echo -n "1"> ${TEST_RESULT_FILE} + local test_cmd_args="--run-tests" + (( EMIT_METRICS )) && test_cmd_args+=" --emit-metrics" + echo "Test script is ${E2E_SCRIPT}" + download_k8s || return 1 + kubetest "${CLUSTER_CREATION_ARGS[@]}" \ + --up \ + --down \ + --extract local \ + --gcp-node-image ${SERVING_GKE_IMAGE} \ + --test-cmd "${E2E_SCRIPT}" \ + --test-cmd-args "${test_cmd_args}" + echo "Test subprocess exited with code $?" + # Delete target pools and health checks that might have leaked. + # See https://github.com/knative/serving/issues/959 for details. + # TODO(adrcunha): Remove once the leak issue is resolved. + local http_health_checks="$(gcloud compute target-pools list \ + --project=${GCP_PROJECT} --format='value(healthChecks)' --filter="instances~-${E2E_CLUSTER_NAME}-" | \ + grep httpHealthChecks | tr '\n' ' ')" + local target_pools="$(gcloud compute target-pools list \ + --project=${GCP_PROJECT} --format='value(name)' --filter="instances~-${E2E_CLUSTER_NAME}-" | \ + tr '\n' ' ')" + if [[ -n "${target_pools}" ]]; then + echo "Found leaked target pools, deleting" + gcloud compute forwarding-rules delete -q --project=${GCP_PROJECT} --region=${E2E_CLUSTER_REGION} ${target_pools} + gcloud compute target-pools delete -q --project=${GCP_PROJECT} --region=${E2E_CLUSTER_REGION} ${target_pools} + fi + if [[ -n "${http_health_checks}" ]]; then + echo "Found leaked health checks, deleting" + gcloud compute http-health-checks delete -q --project=${GCP_PROJECT} ${http_health_checks} + fi + local result="$(cat ${TEST_RESULT_FILE})" + echo "Test result code is $result" + exit ${result} +} + +# Setup the test cluster for running the tests. +function setup_test_cluster() { + # Fail fast during setup. + set -o errexit + set -o pipefail + + # Set the required variables if necessary. + if [[ -z ${K8S_USER_OVERRIDE} ]]; then + export K8S_USER_OVERRIDE=$(gcloud config get-value core/account) + fi + + if [[ -z ${K8S_CLUSTER_OVERRIDE} ]]; then + USING_EXISTING_CLUSTER=0 + export K8S_CLUSTER_OVERRIDE=$(kubectl config current-context) + acquire_cluster_admin_role ${K8S_USER_OVERRIDE} ${E2E_CLUSTER_NAME} ${E2E_CLUSTER_ZONE} + # Make sure we're in the default namespace. Currently kubetest switches to + # test-pods namespace when creating the cluster. + kubectl config set-context $K8S_CLUSTER_OVERRIDE --namespace=default + fi + readonly USING_EXISTING_CLUSTER + + if [[ -z ${DOCKER_REPO_OVERRIDE} ]]; then + export DOCKER_REPO_OVERRIDE=gcr.io/$(gcloud config get-value project)/${E2E_BASE_NAME}-e2e-img + fi + + echo "- Cluster is ${K8S_CLUSTER_OVERRIDE}" + echo "- User is ${K8S_USER_OVERRIDE}" + echo "- Docker is ${DOCKER_REPO_OVERRIDE}" + + trap teardown_test_resources EXIT + + if (( USING_EXISTING_CLUSTER )) && [[ "$(type -t teardown)" == "function" ]]; then + echo "Deleting any previous SUT instance" + teardown + fi + + readonly K8S_CLUSTER_OVERRIDE + readonly K8S_USER_OVERRIDE + readonly DOCKER_REPO_OVERRIDE + + # Handle failures ourselves, so we can dump useful info. + set +o errexit + set +o pipefail +} + +function success() { + # kubetest teardown might fail and thus incorrectly report failure of the + # script, even if the tests pass. + # We store the real test result to return it later, ignoring any teardown + # failure in kubetest. + # TODO(adrcunha): Get rid of this workaround. + echo -n "0"> ${TEST_RESULT_FILE} + echo "**************************************" + echo "*** ALL TESTS PASSED ***" + echo "**************************************" + exit 0 +} + +RUN_TESTS=0 +EMIT_METRICS=0 +USING_EXISTING_CLUSTER=1 +E2E_SCRIPT="" + +# Parse flags and initialize the test cluster. +function initialize() { + # Normalize calling script path; we can't use readlink because it's not available everywhere + E2E_SCRIPT=$0 + [[ ${E2E_SCRIPT} =~ ^[\./].* ]] || E2E_SCRIPT="./$0" + E2E_SCRIPT="$(cd ${E2E_SCRIPT%/*} && echo $PWD/${E2E_SCRIPT##*/})" + readonly E2E_SCRIPT + + cd ${REPO_ROOT_DIR} + for parameter in $@; do + case $parameter in + --run-tests) RUN_TESTS=1 ;; + --emit-metrics) EMIT_METRICS=1 ;; + *) + echo "error: unknown option ${parameter}" + echo "usage: $0 [--run-tests][--emit-metrics]" + exit 1 + ;; + esac + shift + done + readonly RUN_TESTS + readonly EMIT_METRICS + + if (( ! RUN_TESTS )); then + create_test_cluster + else + setup_test_cluster + fi +} diff --git a/vendor/github.com/knative/test-infra/scripts/library.sh b/vendor/github.com/knative/test-infra/scripts/library.sh new file mode 100755 index 000000000..d9a2e0bdb --- /dev/null +++ b/vendor/github.com/knative/test-infra/scripts/library.sh @@ -0,0 +1,309 @@ +#!/bin/bash + +# Copyright 2018 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is a collection of useful bash functions and constants, intended +# to be used in test scripts and the like. It doesn't do anything when +# called from command line. + +# Default GKE version to be used with Knative Serving +readonly SERVING_GKE_VERSION=latest +readonly SERVING_GKE_IMAGE=cos + +# Public images and yaml files. +readonly KNATIVE_ISTIO_YAML=https://storage.googleapis.com/knative-releases/serving/latest/istio.yaml +readonly KNATIVE_SERVING_RELEASE=https://storage.googleapis.com/knative-releases/serving/latest/release.yaml +readonly KNATIVE_BUILD_RELEASE=https://storage.googleapis.com/knative-releases/build/latest/release.yaml +readonly KNATIVE_EVENTING_RELEASE=https://storage.googleapis.com/knative-releases/eventing/latest/release.yaml + +# Useful environment variables +[[ -n "${PROW_JOB_ID}" ]] && IS_PROW=1 || IS_PROW=0 +readonly IS_PROW +readonly REPO_ROOT_DIR="$(git rev-parse --show-toplevel)" + +# Display a box banner. +# Parameters: $1 - character to use for the box. +# $2 - banner message. +function make_banner() { + local msg="$1$1$1$1 $2 $1$1$1$1" + local border="${msg//[-0-9A-Za-z _.,]/$1}" + echo -e "${border}\n${msg}\n${border}" +} + +# Simple header for logging purposes. +function header() { + local upper="$(echo $1 | tr a-z A-Z)" + make_banner "=" "${upper}" +} + +# Simple subheader for logging purposes. +function subheader() { + make_banner "-" "$1" +} + +# Simple warning banner for logging purposes. +function warning() { + make_banner "!" "$1" +} + +# Remove ALL images in the given GCR repository. +# Parameters: $1 - GCR repository. +function delete_gcr_images() { + for image in $(gcloud --format='value(name)' container images list --repository=$1); do + echo "Checking ${image} for removal" + delete_gcr_images ${image} + for digest in $(gcloud --format='get(digest)' container images list-tags ${image} --limit=99999); do + local full_image="${image}@${digest}" + echo "Removing ${full_image}" + gcloud container images delete -q --force-delete-tags ${full_image} + done + done +} + +# Waits until the given object doesn't exist. +# Parameters: $1 - the kind of the object. +# $2 - object's name. +# $3 - namespace (optional). +function wait_until_object_does_not_exist() { + local KUBECTL_ARGS="get $1 $2" + local DESCRIPTION="$1 $2" + + if [[ -n $3 ]]; then + KUBECTL_ARGS="get -n $3 $1 $2" + DESCRIPTION="$1 $3/$2" + fi + echo -n "Waiting until ${DESCRIPTION} does not exist" + for i in {1..150}; do # timeout after 5 minutes + kubectl ${KUBECTL_ARGS} 2>&1 > /dev/null || return 0 + echo -n "." + sleep 2 + done + echo -e "\n\nERROR: timeout waiting for ${DESCRIPTION} not to exist" + kubectl ${KUBECTL_ARGS} + return 1 +} + +# Waits until all pods are running in the given namespace. +# Parameters: $1 - namespace. +function wait_until_pods_running() { + echo -n "Waiting until all pods in namespace $1 are up" + for i in {1..150}; do # timeout after 5 minutes + local pods="$(kubectl get pods --no-headers -n $1 2>/dev/null)" + # All pods must be running + local not_running=$(echo "${pods}" | grep -v Running | grep -v Completed | wc -l) + if [[ -n "${pods}" && ${not_running} -eq 0 ]]; then + local all_ready=1 + while read pod ; do + local status=(`echo -n ${pod} | cut -f2 -d' ' | tr '/' ' '`) + # All containers must be ready + [[ -z ${status[0]} ]] && all_ready=0 && break + [[ -z ${status[1]} ]] && all_ready=0 && break + [[ ${status[0]} -lt 1 ]] && all_ready=0 && break + [[ ${status[1]} -lt 1 ]] && all_ready=0 && break + [[ ${status[0]} -ne ${status[1]} ]] && all_ready=0 && break + done <<< $(echo "${pods}" | grep -v Completed) + if (( all_ready )); then + echo -e "\nAll pods are up:\n${pods}" + return 0 + fi + fi + echo -n "." + sleep 2 + done + echo -e "\n\nERROR: timeout waiting for pods to come up\n${pods}" + kubectl get pods -n $1 + return 1 +} + +# Waits until the given service has an external IP address. +# Parameters: $1 - namespace. +# $2 - service name. +function wait_until_service_has_external_ip() { + echo -n "Waiting until service $2 in namespace $1 has an external IP" + for i in {1..150}; do # timeout after 15 minutes + local ip=$(kubectl get svc -n $1 $2 -o jsonpath="{.status.loadBalancer.ingress[0].ip}") + if [[ -n "${ip}" ]]; then + echo -e "\nService $2.$1 has IP $ip" + return 0 + fi + echo -n "." + sleep 6 + done + echo -e "\n\nERROR: timeout waiting for service $svc.$ns to have an external IP" + kubectl get pods -n $1 + return 1 +} + +# Returns the name of the pod of the given app. +# Parameters: $1 - app name. +# $2 - namespace (optional). +function get_app_pod() { + local namespace="" + [[ -n $2 ]] && namespace="-n $2" + kubectl get pods ${namespace} --selector=app=$1 --output=jsonpath="{.items[0].metadata.name}" +} + +# Sets the given user as cluster admin. +# Parameters: $1 - user +# $2 - cluster name +# $3 - cluster zone +function acquire_cluster_admin_role() { + # Get the password of the admin and use it, as the service account (or the user) + # might not have the necessary permission. + local password=$(gcloud --format="value(masterAuth.password)" \ + container clusters describe $2 --zone=$3) + kubectl config set-credentials cluster-admin \ + --username=admin --password=${password} + kubectl config set-context $(kubectl config current-context) \ + --user=cluster-admin + kubectl create clusterrolebinding cluster-admin-binding \ + --clusterrole=cluster-admin \ + --user=$1 + # Reset back to the default account + gcloud container clusters get-credentials \ + $2 --zone=$3 --project $(gcloud config get-value project) +} + +# Runs a go test and generate a junit summary through bazel. +# Parameters: $1... - parameters to go test +function report_go_test() { + # Just run regular go tests if not on Prow. + if (( ! IS_PROW )); then + go test $@ + return + fi + local report=$(mktemp) + local summary=$(mktemp) + local failed=0 + # Run tests in verbose mode to capture details. + # go doesn't like repeating -v, so remove if passed. + local args=("${@/-v}") + go test -race -v ${args[@]} > ${report} || failed=$? + # Tests didn't run. + [[ ! -s ${report} ]] && return 1 + # Create WORKSPACE file, required to use bazel + touch WORKSPACE + local targets="" + # Parse the report and generate fake tests for each passing/failing test. + while read line ; do + local fields=(`echo -n ${line}`) + local field0="${fields[0]}" + local field1="${fields[1]}" + local name=${fields[2]} + # Ignore subtests (those containing slashes) + if [[ -n "${name##*/*}" ]]; then + if [[ ${field1} == PASS: || ${field1} == FAIL: ]]; then + # Populate BUILD.bazel + local src="${name}.sh" + echo "exit 0" > ${src} + if [[ ${field1} == "FAIL:" ]]; then + read error + echo "cat < ${src} + echo "${error}" >> ${src} + echo "ERROR-EOF" >> ${src} + echo "exit 1" >> ${src} + fi + chmod +x ${src} + echo "sh_test(name=\"${name}\", srcs=[\"${src}\"])" >> BUILD.bazel + elif [[ ${field0} == FAIL || ${field0} == ok ]]; then + # Update the summary with the result for the package + echo "${line}" >> ${summary} + # Create the package structure, move tests and BUILD file + local package=${field1/github.com\//} + mkdir -p ${package} + targets="${targets} //${package}/..." + mv *.sh BUILD.bazel ${package} + fi + fi + done < ${report} + # If any test failed, show the detailed report. + # Otherwise, just show the summary. + # Exception: when emitting metrics, dump the full report. + if (( failed )) || [[ "$@" == *" -emitmetrics"* ]]; then + cat ${report} + else + cat ${summary} + fi + # Always generate the junit summary. + bazel test ${targets} > /dev/null 2>&1 + return ${failed} +} + +# Install the latest stable Knative/serving in the current cluster. +function start_latest_knative_serving() { + header "Starting Knative Serving" + subheader "Installing Istio" + kubectl apply -f ${KNATIVE_ISTIO_YAML} || return 1 + wait_until_pods_running istio-system || return 1 + kubectl label namespace default istio-injection=enabled || return 1 + subheader "Installing Knative Serving" + kubectl apply -f ${KNATIVE_SERVING_RELEASE} || return 1 + wait_until_pods_running knative-serving || return 1 + wait_until_pods_running knative-build || return 1 +} + +# Install the latest stable Knative/build in the current cluster. +function start_latest_knative_build() { + header "Starting Knative Build" + subheader "Installing Istio" + kubectl apply -f ${KNATIVE_ISTIO_YAML} || return 1 + wait_until_pods_running istio-system || return 1 + subheader "Installing Knative Build" + kubectl apply -f ${KNATIVE_BUILD_RELEASE} || return 1 + wait_until_pods_running knative-build || return 1 +} + +# Run dep-collector, installing it first if necessary. +# Parameters: $1..$n - parameters passed to dep-collector. +function run_dep_collector() { + local local_dep_collector="$(which dep-collector)" + if [[ -z ${local_dep_collector} ]]; then + go get -u github.com/mattmoor/dep-collector + fi + dep-collector $@ +} + +# Run dep-collector to update licenses. +# Parameters: $1 - output file, relative to repo root dir. +# $2...$n - directories and files to inspect. +function update_licenses() { + cd ${REPO_ROOT_DIR} || return 1 + local dst=$1 + shift + run_dep_collector $@ > ./${dst} +} + +# Run dep-collector to check for forbidden liceses. +# Parameters: $1...$n - directories and files to inspect. +function check_licenses() { + # Fetch the google/licenseclassifier for its license db + go get -u github.com/google/licenseclassifier + # Check that we don't have any forbidden licenses in our images. + run_dep_collector -check $@ +} + +# Check links in all .md files in the repo. +function check_links_in_markdown() { + local checker="markdown-link-check" + if ! hash ${checker} 2>/dev/null; then + warning "${checker} not installed, not checking links in .md files" + return 0 + fi + local failed=0 + for md_file in $(find ${REPO_ROOT_DIR} -name \*.md); do + ${checker} -q ${md_file} || failed=1 + done + return ${failed} +} diff --git a/vendor/github.com/knative/test-infra/scripts/presubmit-tests.sh b/vendor/github.com/knative/test-infra/scripts/presubmit-tests.sh new file mode 100755 index 000000000..384e3c5ae --- /dev/null +++ b/vendor/github.com/knative/test-infra/scripts/presubmit-tests.sh @@ -0,0 +1,118 @@ +#!/bin/bash + +# Copyright 2018 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is a helper script to run the presubmit tests. To use it: +# 1. Source this script. +# 2. Define the functions build_tests(), unit_tests() and +# integration_tests(). They should run all tests (i.e., not fail +# fast), and return 0 if all passed, 1 if a failure occurred. +# The environment variables RUN_BUILD_TESTS, RUN_UNIT_TESTS and +# RUN_INTEGRATION_TESTS are set to 0 (false) or 1 (true) accordingly. +# If --emit-metrics is passed, EMIT_METRICS will be set to 1. +# 3. Call the main() function passing $@ (without quotes). +# +# Running the script without parameters, or with the --all-tests +# flag, causes all tests to be executed, in the right order. +# Use the flags --build-tests, --unit-tests and --integration-tests +# to run a specific set of tests. The flag --emit-metrics is used +# to emit metrics when running the tests. + +source $(dirname ${BASH_SOURCE})/library.sh + +# Extensions or file patterns that don't require presubmit tests. +readonly NO_PRESUBMIT_FILES=(\.md \.png ^OWNERS) + +# Options set by command-line flags. +RUN_BUILD_TESTS=0 +RUN_UNIT_TESTS=0 +RUN_INTEGRATION_TESTS=0 +EMIT_METRICS=0 + +# Exit presubmit tests if only documentation files were changed. +function exit_if_presubmit_not_required() { + if [[ -n "${PULL_PULL_SHA}" ]]; then + # On a presubmit job + local changes="$(git diff --name-only ${PULL_PULL_SHA} ${PULL_BASE_SHA})" + local no_presubmit_pattern="${NO_PRESUBMIT_FILES[*]}" + local no_presubmit_pattern="\(${no_presubmit_pattern// /\\|}\)$" + echo -e "Changed files in commit ${PULL_PULL_SHA}:\n${changes}" + if [[ -z "$(echo "${changes}" | grep -v ${no_presubmit_pattern})" ]]; then + # Nothing changed other than files that don't require presubmit tests + header "Commit only contains changes that don't affect tests, skipping" + exit 0 + fi + fi +} + +# Process flags and run tests accordingly. +function main() { + exit_if_presubmit_not_required + + local all_parameters=$@ + [[ -z $1 ]] && all_parameters="--all-tests" + + for parameter in ${all_parameters}; do + case ${parameter} in + --all-tests) + RUN_BUILD_TESTS=1 + RUN_UNIT_TESTS=1 + RUN_INTEGRATION_TESTS=1 + shift + ;; + --build-tests) + RUN_BUILD_TESTS=1 + shift + ;; + --unit-tests) + RUN_UNIT_TESTS=1 + shift + ;; + --integration-tests) + RUN_INTEGRATION_TESTS=1 + shift + ;; + --emit-metrics) + EMIT_METRICS=1 + shift + ;; + *) + echo "error: unknown option ${parameter}" + exit 1 + ;; + esac + done + + readonly RUN_BUILD_TESTS + readonly RUN_UNIT_TESTS + readonly RUN_INTEGRATION_TESTS + readonly EMIT_METRICS + + cd ${REPO_ROOT_DIR} + + # Tests to be performed, in the right order if --all-tests is passed. + + local result=0 + if (( RUN_BUILD_TESTS )); then + build_tests || result=1 + fi + if (( RUN_UNIT_TESTS )); then + unit_tests || result=1 + fi + if (( RUN_INTEGRATION_TESTS )); then + integration_tests || result=1 + fi + exit ${result} +} diff --git a/vendor/github.com/knative/test-infra/scripts/release.sh b/vendor/github.com/knative/test-infra/scripts/release.sh new file mode 100755 index 000000000..abf89ddf4 --- /dev/null +++ b/vendor/github.com/knative/test-infra/scripts/release.sh @@ -0,0 +1,118 @@ +#!/bin/bash + +# Copyright 2018 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is a helper script for Knative release scripts. To use it: +# 1. Source this script. +# 2. Call the parse_flags() function passing $@ (without quotes). +# 3. Call the run_validation_tests() passing the script or executable that +# runs the release validation tests. +# 4. Write logic for the release process. Use the following boolean (0 is +# false, 1 is true) environment variables for the logic: +# SKIP_TESTS: true if --skip-tests is passed. This is handled automatically +# by the run_validation_tests() function. +# TAG_RELEASE: true if --tag-release is passed. In this case, the +# environment variable TAG will contain the release tag in the +# form vYYYYMMDD-. +# PUBLISH_RELEASE: true if --publish is passed. In this case, the environment +# variable KO_FLAGS will be updated with the -L option. +# SKIP_TESTS, TAG_RELEASE and PUBLISH_RELEASE default to false for safety. +# All environment variables above, except KO_FLAGS, are marked read-only once +# parse_flags() is called. + +source $(dirname ${BASH_SOURCE})/library.sh + +# Simple banner for logging purposes. +function banner() { + make_banner "@" "$1" +} + +# Tag images in the yaml file with a tag. If not tag is passed, does nothing. +# Parameters: $1 - yaml file to parse for images. +# $2 - registry where the images are stored. +# $3 - tag to apply (optional). +function tag_images_in_yaml() { + [[ -z $3 ]] && return 0 + echo "Tagging images with $3" + for image in $(grep -o "$2/[a-z\./-]\+@sha256:[0-9a-f]\+" $1); do + gcloud -q container images add-tag ${image} ${image%%@*}:$3 + done +} + +# Copy the given yaml file to a GCS bucket. Image is tagged :latest, and optionally :$2. +# Parameters: $1 - yaml file to copy. +# $2 - destination bucket name. +# $3 - tag to apply (optional). +function publish_yaml() { + gsutil cp $1 gs://$2/latest/ + [[ -n $3 ]] && gsutil cp $1 gs://$2/previous/$3/ +} + +SKIP_TESTS=0 +TAG_RELEASE=0 +PUBLISH_RELEASE=0 +TAG="" +KO_FLAGS="-P -L" + +# Parses flags and sets environment variables accordingly. +function parse_flags() { + cd ${REPO_ROOT_DIR} + for parameter in $@; do + case $parameter in + --skip-tests) SKIP_TESTS=1 ;; + --tag-release) TAG_RELEASE=1 ;; + --notag-release) TAG_RELEASE=0 ;; + --publish) + PUBLISH_RELEASE=1 + # Remove -L from ko flags + KO_FLAGS="${KO_FLAGS/-L}" + ;; + --nopublish) + PUBLISH_RELEASE=0 + # Add -L to ko flags + KO_FLAGS="-L ${KO_FLAGS}" + shift + ;; + *) + echo "error: unknown option ${parameter}" + exit 1 + ;; + esac + shift + done + + TAG="" + if (( TAG_RELEASE )); then + # Currently we're not considering the tags in refs/tags namespace. + commit=$(git describe --always --dirty) + # Like kubernetes, image tag is vYYYYMMDD-commit + TAG="v$(date +%Y%m%d)-${commit}" + fi + + readonly SKIP_TESTS + readonly TAG_RELEASE + readonly PUBLISH_RELEASE + readonly TAG +} + +# Run tests (unless --skip-tests was passed). Conveniently displays a banner indicating so. +# Parameters: $1 - executable that runs the tests. +function run_validation_tests() { + if (( ! SKIP_TESTS )); then + banner "Running release validation tests" + # Run tests. + $1 + fi +}