diff --git a/docs/serving/samples/hello-world/helloworld-shell/Dockerfile b/docs/serving/samples/hello-world/helloworld-shell/Dockerfile index 0220a75cbc6..fce5f140602 100644 --- a/docs/serving/samples/hello-world/helloworld-shell/Dockerfile +++ b/docs/serving/samples/hello-world/helloworld-shell/Dockerfile @@ -1,32 +1,13 @@ -# Use the official Golang image to create a build artifact. -# This is based on Debian and sets the GOPATH to /go. -# https://hub.docker.com/_/golang -FROM golang:1.13 as builder - -# Create and change to the app directory. -WORKDIR /app - -# Retrieve application dependencies using go modules. -# Allows container builds to reuse downloaded dependencies. -COPY go.* ./ -RUN go mod download - -# Copy local code to the container image. -COPY invoke.go ./ - -# Build the binary. -# -mod=readonly ensures immutable go.mod and go.sum in container builds. -RUN CGO_ENABLED=0 GOOS=linux go build -mod=readonly -v -o server - # Use the official Alpine image for a lean production container. # https://hub.docker.com/_/alpine -# https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds FROM alpine:3 -RUN apk add --no-cache ca-certificates -# Copy the binary to the production image from the builder stage. -COPY --from=builder /app/server /server -COPY script.sh ./ +# Update & install netcat (nc) +RUN apk update \ + && apk add netcat-openbsd + +# Copy over the service script +COPY script.sh / -# Run the web service on container startup. -CMD ["/server"] +# Start up the webserver +CMD [ "/bin/sh", "/script.sh" ] diff --git a/docs/serving/samples/hello-world/helloworld-shell/README.md b/docs/serving/samples/hello-world/helloworld-shell/README.md deleted file mode 100644 index b45b33a53c6..00000000000 --- a/docs/serving/samples/hello-world/helloworld-shell/README.md +++ /dev/null @@ -1,204 +0,0 @@ -A simple web app that executes a shell script. The shell script reads an env -variable `TARGET` and prints `Hello ${TARGET}!`. If the `TARGET` environment -variable is not specified, the script uses `World`. - -Follow the steps below to create the sample code and then deploy the app to your -cluster. You can also download a working copy of the sample, by running the -following commands: - -```shell -git clone -b "{{< branch >}}" https://github.com/knative/docs knative-docs -cd knative-docs/docs/serving/samples/hello-world/helloworld-shell -``` - -## Before you begin - -- A Kubernetes cluster with Knative installed and DNS configured. Follow the - [installation instructions](../../../../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 - -1. Create a new file named `script.sh` and paste the following script: - - ```sh - #!/bin/sh - echo Hello ${TARGET:=World}! - ``` - -1. Create a new file named `invoke.go` and paste the following code. We use a - basic web server written in Go to execute the shell script: - - ```go - package main - - import ( - "fmt" - "log" - "net/http" - "os" - "os/exec" - ) - - func handler(w http.ResponseWriter, r *http.Request) { - log.Print("helloworld: received a request") - - cmd := exec.CommandContext(r.Context(), "/bin/sh", "script.sh") - cmd.Stderr = os.Stderr - out, err := cmd.Output() - if err != nil { - w.WriteHeader(500) - } - w.Write(out) - } - - func main() { - log.Print("helloworld: starting server...") - - http.HandleFunc("/", handler) - - port := os.Getenv("PORT") - if port == "" { - port = "8080" - } - - log.Printf("helloworld: listening on %s", port) - log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), nil)) - } - ``` - -1. Create a new file named `Dockerfile` and copy the code block below into it. - - ```docker - # Use the official Golang image to create a build artifact. - # This is based on Debian and sets the GOPATH to /go. - # https://hub.docker.com/_/golang - FROM golang:1.13 as builder - - # Create and change to the app directory. - WORKDIR /app - - # Retrieve application dependencies using go modules. - # Allows container builds to reuse downloaded dependencies. - COPY go.* ./ - RUN go mod download - - # Copy local code to the container image. - COPY invoke.go ./ - - # Build the binary. - # -mod=readonly ensures immutable go.mod and go.sum in container builds. - RUN CGO_ENABLED=0 GOOS=linux go build -mod=readonly -v -o server - - # Use the official Alpine image for a lean production container. - # https://hub.docker.com/_/alpine - # https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds - FROM alpine:3 - RUN apk add --no-cache ca-certificates - - # Copy the binary to the production image from the builder stage. - COPY --from=builder /app/server /server - COPY script.sh ./ - - # Run the web service on container startup. - CMD ["/server"] - ``` - -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/v1 - kind: Service - metadata: - name: helloworld-shell - namespace: default - spec: - template: - spec: - containers: - - image: docker.io/{username}/helloworld-shell - env: - - name: TARGET - value: "Shell Sample v1" - ``` - -1. Use the go tool to create a - [`go.mod`](https://github.com/golang/go/wiki/Modules#gomod) manifest. - - ```shell - go mod init github.com/knative/docs/docs/serving/samples/hello-world/helloworld-shell - ``` - -## Building and deploying the 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, run these commands replacing `{username}` with your Docker Hub - username: - - ```shell - # Build the container on your local machine - docker build -t {username}/helloworld-shell . - - # Push the container to docker registry - docker push {username}/helloworld-shell - ``` - -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 --filename service.yaml - ``` - -1. Now that your service is created, Knative performs 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. Run the following command to find the domain URL for your service: - - ```shell - kubectl get ksvc helloworld-shell --output=custom-columns=NAME:.metadata.name,URL:.status.url - ``` - - Example: - - ```shell - NAME URL - helloworld-shell http://helloworld-shell.default.1.2.3.4.xip.io - ``` - -1. Now you can make a request to your app and see the result. Replace - the URL below with the URL returned in the previous command. - - ```shell - curl http://helloworld-shell.default.1.2.3.4.xip.io - ``` - - Example: - - ```shell - curl http://helloworld-shell.default.1.2.3.4.xip.io - Hello Shell Sample v1! - ``` - - > Note: Add `-v` option to get more detail if the `curl` command failed. - -## Removing the sample app deployment - -To remove the sample app from your cluster, delete the service record: - -```shell -kubectl delete --filename service.yaml -``` diff --git a/docs/serving/samples/hello-world/helloworld-shell/go.mod b/docs/serving/samples/hello-world/helloworld-shell/go.mod deleted file mode 100644 index b35cfcbcc15..00000000000 --- a/docs/serving/samples/hello-world/helloworld-shell/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module github.com/knative/docs/docs/serving/samples/hello-world/helloworld-shell - -go 1.14 diff --git a/docs/serving/samples/hello-world/helloworld-shell/index.md b/docs/serving/samples/hello-world/helloworld-shell/index.md index cc3bc0d7958..4ae90c5bd00 100644 --- a/docs/serving/samples/hello-world/helloworld-shell/index.md +++ b/docs/serving/samples/hello-world/helloworld-shell/index.md @@ -5,4 +5,206 @@ weight: 1 type: "docs" --- -{{% readfile file="README.md" %}} +This guide describes the steps required to create the `helloworld-shell` sample app and deploy it to your +cluster. + +The sample app reads a `TARGET` environment variable, and prints `Hello ${TARGET}!`. +If `TARGET` is not specified, `World` is used as the default value. + +You can also download a working copy of the sample, by running the +following commands: + +```shell +git clone -b "{{< branch >}}" https://github.com/knative/docs knative-docs +cd knative-docs/docs/serving/samples/hello-world/helloworld-shell +``` + +## Prerequisites + +- A Kubernetes cluster with Knative installed and DNS configured. Follow the + [installation instructions](../../../../install/README.md). +- [Docker](https://www.docker.com) installed and running on your local machine, + and a Docker Hub account configured. +- (optional) The Knatice CLI client [kn](https://github.com/knative/client/releases) that simplifies the deployment. Alternative you can also use [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) and apply resource files directly. + +## Build + +1. Create a new file named `script.sh` and paste the script below. This will run netcat (`nc`) in an endless loop, returning a friendly welcome message. + + ```shell + #!/bin/sh + while true ; do + echo -e "HTTP/1.1 200\n\n Hello ${TARGET:=World}!\n" | nc -l -p 8080 -q 1; + done + ``` + +1. Create a new file named `Dockerfile` and copy the code block below into it. + + ```docker + # Use the official Alpine image for a lean production container. + # https://hub.docker.com/_/alpine + FROM alpine:3 + + # Update & install netcat (nc) + RUN apk update \ + && apk add netcat-openbsd + + # Copy over the service script + COPY script.sh / + + # Start up the webserver + CMD [ "/bin/sh", "/script.sh" ] + ``` + +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, run these commands replacing `{username}` with your Docker Hub + username: + + ```shell + # Build the container on your local machine + docker build -t {username}/helloworld-shell . + + # Push the container to docker registry + docker push {username}/helloworld-shell + ``` + +## Deploying + +1. After the build has completed and the container is pushed to Docker Hub, you + can deploy the app into your cluster. + + {{< tabs name="helloworld_shell" default="kn" >}} + {{% tab name="yaml" %}} + + 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/v1 + kind: Service + metadata: + name: helloworld-shell + namespace: default + spec: + template: + spec: + containers: + - image: docker.io/{username}/helloworld-shell + env: + - name: TARGET + value: "Shell Sample v1" + ``` + + 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 --filename service.yaml + ``` + + {{< /tab >}} + {{% tab name="kn" %}} + + With `kn` you can deploy the service with + + ```shell + kn service create helloworld-shell --image=docker.io/{username}/helloworld-shell --env TARGET="Shell Sample v1" + ``` + + This will wait until your service is deployed and ready, and ultimately it will print the URL through which you can access the service. + + The output will look like: + + ``` + Creating service 'helloworld-shell' in namespace 'default': + + 0.035s The Configuration is still working to reflect the latest desired specification. + 0.139s The Route is still working to reflect the latest desired specification. + 0.250s Configuration "helloworld-shell" is waiting for a Revision to become ready. + 8.040s ... + 8.136s Ingress has not yet been reconciled. + 8.277s unsuccessfully observed a new generation + 8.398s Ready to serve. + + Service 'helloworld-shell' created to latest revision 'helloworld-shell-kwdpt-1' is available at URL: + http://helloworld-shell.default.1.2.3.4.xip.io + ``` + + {{< /tab >}} + {{< /tabs >}} + + During the creation of your service, Knative performs the following steps: + + - Creates of a new immutable revision for this version of the app. + - Programs the network to create a route, ingress, service, and load balance + for your app. + - Automatically scales your pods up and down (including to zero active pods). + +## Verification + +1. Run one of the followings commands to find the domain URL for your service. + + {{< tabs name="service_url" default="kn" >}} + {{% tab name="kubectl" %}} + ```shell + kubectl get ksvc helloworld-shell --output=custom-columns=NAME:.metadata.name,URL:.status.url + ``` + + Example: + + ```shell + NAME URL + helloworld-shell http://helloworld-shell.default.1.2.3.4.xip.io + ``` + + {{< /tab >}} + {{% tab name="kn" %}} + + ```shell + kn service describe helloworld-shell -o url + ``` + + Example: + + ```shell + http://helloworld-shell.default.1.2.3.4.xip.io + ``` + {{< /tab >}} + {{< /tabs >}} + +1. Now you can make a request to your app and see the result. Replace + the URL below with the URL returned in the previous command. + + Example: + + ```shell + curl http://helloworld-shell.default.1.2.3.4.xip.io + Hello Shell Sample v1! + + # Even easier with kn: + curl $(kn service describe helloworld-shell -o url) + ``` + + > Note: Add `-v` option to get more details if the `curl` command failed. + +## Removing + +To remove the sample app from your cluster, delete the service record. + +{{< tabs name="service_url" default="kn" >}} +{{% tab name="kubectl" %}} +```shell +kubectl delete --filename service.yaml +``` +{{< /tab >}} +{{% tab name="kn" %}} +```shell +kn service delete helloworld-shell +``` +{{< /tab >}} +{{< /tabs >}} diff --git a/docs/serving/samples/hello-world/helloworld-shell/invoke.go b/docs/serving/samples/hello-world/helloworld-shell/invoke.go deleted file mode 100644 index f4ba32ddbff..00000000000 --- a/docs/serving/samples/hello-world/helloworld-shell/invoke.go +++ /dev/null @@ -1,35 +0,0 @@ -package main - -import ( - "fmt" - "log" - "net/http" - "os" - "os/exec" -) - -func handler(w http.ResponseWriter, r *http.Request) { - log.Print("helloworld: received a request") - - cmd := exec.CommandContext(r.Context(), "/bin/sh", "script.sh") - cmd.Stderr = os.Stderr - out, err := cmd.Output() - if err != nil { - w.WriteHeader(500) - } - w.Write(out) -} - -func main() { - log.Print("helloworld: starting server...") - - http.HandleFunc("/", handler) - - port := os.Getenv("PORT") - if port == "" { - port = "8080" - } - - log.Printf("helloworld: listening on %s", port) - log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), nil)) -} diff --git a/docs/serving/samples/hello-world/helloworld-shell/script.sh b/docs/serving/samples/hello-world/helloworld-shell/script.sh index 060ef02de48..d5fff1b19bb 100755 --- a/docs/serving/samples/hello-world/helloworld-shell/script.sh +++ b/docs/serving/samples/hello-world/helloworld-shell/script.sh @@ -1,4 +1,4 @@ #!/bin/sh -echo Hello ${TARGET:=World}! - - +while true ; do + echo -e "HTTP/1.1 200\n\n Hello ${TARGET:=World}!\n" | nc -l -p 8080 -q 1; +done diff --git a/test/sampleapp/config.yaml b/test/sampleapp/config.yaml index 188a4660b62..402b381210d 100644 --- a/test/sampleapp/config.yaml +++ b/test/sampleapp/config.yaml @@ -58,11 +58,7 @@ languages: - "Dockerfile" - language: "shell" expectedOutput: "Hello Shell Sample v1!" - preCommands: - - exec: "cp" - args: "../../docs/serving/samples/hello-world/helloworld-shell/go.mod helloworld-shell_tmp/go.mod" copies: - "script.sh" - - "invoke.go" - "service.yaml" - "Dockerfile" diff --git a/test/sampleconsistency/sampleconsistency_test.go b/test/sampleconsistency/sampleconsistency_test.go index 2eba590bec1..9e7e6a50937 100644 --- a/test/sampleconsistency/sampleconsistency_test.go +++ b/test/sampleconsistency/sampleconsistency_test.go @@ -18,6 +18,7 @@ package sampleconsistency import ( "bufio" + "fmt" "os" "path" "strings" @@ -94,12 +95,16 @@ func checkContains(t *testing.T, rl []string, src string) { best = 0 } if best != len(sl) { - t.Fatalf("README.md file is missing line %d ('%s') from file '%s'\nAdditional info:\n%s", best, sl[best], src, sampleapp.ActionMsg) + t.Fatalf("README.md/index.md file is missing line %d ('%s') from file '%s'\nAdditional info:\n%s", best, sl[best], src, sampleapp.ActionMsg) } } func checkDoc(t *testing.T, lc sampleapp.LanguageConfig) { - readme := path.Join(lc.SrcDir, "README.md") + readme, err := getDocFile(lc.SrcDir) + if err != nil { + t.Fatalf("Error: %v.", err) + return + } rl := readlines(t, readme) for _, f := range lc.Copies { src := path.Join(lc.SrcDir, f) @@ -107,6 +112,20 @@ func checkDoc(t *testing.T, lc sampleapp.LanguageConfig) { } } +func getDocFile(dir string) (string, error) { + for _, f := range []string{"README.md", "index.md"} { + path := path.Join(dir, f) + _, err := os.Stat(path) + if err == nil { + return path, nil + } + if !os.IsNotExist(err) { + return "", nil + } + } + return "", fmt.Errorf("cannot find README.md or index.md in %s", dir) +} + // TestDocSrc checks content of README.md files, and ensures that the real code of the samples // is properly embedded in the docs. func TestDocSrc(t *testing.T) {