Start adding acs workflows talk.
This commit is contained in:
		
							
								
								
									
										148
									
								
								2023-07-31-acs-workflows/README.org
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								2023-07-31-acs-workflows/README.org
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,148 @@
 | 
			
		||||
#+TITLE: RHACS Workflows & Integration
 | 
			
		||||
#+AUTHOR: James Blair
 | 
			
		||||
#+DATE: <2023-07-29 Sat 23:15>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
This is a short demo I gave on [[https://www.redhat.com/en/technologies/cloud-computing/openshift/advanced-cluster-security-kubernetes][Red Hat Advanced Cluster Security]].
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
* Pre-requisites
 | 
			
		||||
 | 
			
		||||
This demo setup process assumes you already have an OpenShift 4.12+ cluster running, and are logged into the ~oc~ cli locally with cluster administration privileges.
 | 
			
		||||
 | 
			
		||||
For this demo I have an OpenShift ~4.12.12~ cluster running on AWS provisioned through the [[https://demo.redhat.com/catalog?item=babylon-catalog-prod/sandboxes-gpte.elt-ocp4-hands-on-acs.prod&utm_source=webapp&utm_medium=share-link][Red Hat Demo system]].
 | 
			
		||||
 | 
			
		||||
#+NAME: Check oc status
 | 
			
		||||
#+begin_src bash :results silent
 | 
			
		||||
oc version | grep Server
 | 
			
		||||
oc status
 | 
			
		||||
#+end_src
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
* Developer workflow integration
 | 
			
		||||
 | 
			
		||||
A key element of any cloud native security platform is how it can be incorporated into software development workflows to enable security teams to gain visibility of emerging security issues and also empower developers to understand the security posture of what they are building.
 | 
			
		||||
 | 
			
		||||
For this demonstration we will be using [[https://developers.redhat.com/products/openshift-dev-spaces/overview][OpenShift Dev Spaces]] as a cloud based development environment, and [[https://marketplace.visualstudio.com/items?itemName=redhat.vscode-tekton-pipelines][OpenShift Pipelines]] for a continuous integration environment.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
** Install dev spaces operator
 | 
			
		||||
 | 
			
		||||
The first step to prepare the demo is to install the dev spaces operator so our cluster will be able to create cloud based development environments. We can install the operator programmatically by creating a ~subscription~ resource:
 | 
			
		||||
 | 
			
		||||
#+begin_src bash :results silent
 | 
			
		||||
cat << EOF | oc apply -f -
 | 
			
		||||
apiVersion: operators.coreos.com/v1alpha1
 | 
			
		||||
kind: Subscription
 | 
			
		||||
metadata:
 | 
			
		||||
  name: devspaces
 | 
			
		||||
  namespace: openshift-operators
 | 
			
		||||
spec:
 | 
			
		||||
  channel: stable
 | 
			
		||||
  installPlanApproval: Automatic
 | 
			
		||||
  name: devspaces
 | 
			
		||||
  source: redhat-operators
 | 
			
		||||
  sourceNamespace: openshift-marketplace
 | 
			
		||||
EOF
 | 
			
		||||
#+end_src
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
** Create devspaces controller
 | 
			
		||||
 | 
			
		||||
Once the operator is installed we can create a devspaces controller instance, this will be what is actually responsible for instantiating new individual developer workspaces.
 | 
			
		||||
 | 
			
		||||
Once again we can do this programmatically by creating a ~checluster~ resource:
 | 
			
		||||
 | 
			
		||||
#+begin_src bash :results silent
 | 
			
		||||
cat << EOF | oc apply -f -
 | 
			
		||||
apiVersion: org.eclipse.che/v2
 | 
			
		||||
kind: CheCluster
 | 
			
		||||
metadata:
 | 
			
		||||
  name: devspaces
 | 
			
		||||
  namespace: openshift-operators
 | 
			
		||||
spec:
 | 
			
		||||
  components:
 | 
			
		||||
    cheServer:
 | 
			
		||||
      debug: false
 | 
			
		||||
      logLevel: INFO
 | 
			
		||||
    dashboard: {}
 | 
			
		||||
    database:
 | 
			
		||||
      externalDb: false
 | 
			
		||||
    devWorkspace: {}
 | 
			
		||||
    devfileRegistry: {}
 | 
			
		||||
    imagePuller:
 | 
			
		||||
      enable: false
 | 
			
		||||
      spec: {}
 | 
			
		||||
    metrics:
 | 
			
		||||
      enable: true
 | 
			
		||||
    pluginRegistry: {}
 | 
			
		||||
  containerRegistry: {}
 | 
			
		||||
  devEnvironments:
 | 
			
		||||
    containerBuildConfiguration:
 | 
			
		||||
      openShiftSecurityContextConstraint: container-build
 | 
			
		||||
    defaultNamespace:
 | 
			
		||||
      autoProvision: true
 | 
			
		||||
      template: <username>-devspaces
 | 
			
		||||
    maxNumberOfWorkspacesPerUser: -1
 | 
			
		||||
    secondsOfInactivityBeforeIdling: 36000
 | 
			
		||||
    secondsOfRunBeforeIdling: -1
 | 
			
		||||
    startTimeoutSeconds: 300
 | 
			
		||||
    storage:
 | 
			
		||||
      pvcStrategy: per-user
 | 
			
		||||
  gitServices: {}
 | 
			
		||||
  networking:
 | 
			
		||||
    auth:
 | 
			
		||||
      gateway:
 | 
			
		||||
        configLabels:
 | 
			
		||||
          app: che
 | 
			
		||||
          component: che-gateway-config
 | 
			
		||||
EOF
 | 
			
		||||
#+end_src
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
** Create individual dev space
 | 
			
		||||
 | 
			
		||||
Once the dev workspace operator and controller are ready we can create our individual developer workspace.
 | 
			
		||||
 | 
			
		||||
#+begin_src bash :results silent
 | 
			
		||||
cat << EOF | oc apply -f -
 | 
			
		||||
kind: DevWorkspace
 | 
			
		||||
apiVersion: workspace.devfile.io/v1alpha2
 | 
			
		||||
metadata:
 | 
			
		||||
  name: vscode
 | 
			
		||||
  namespace: opentlc-mgr-devspaces
 | 
			
		||||
spec:
 | 
			
		||||
  started: true
 | 
			
		||||
  template:
 | 
			
		||||
    projects:
 | 
			
		||||
      - name: talks
 | 
			
		||||
        git:
 | 
			
		||||
          remotes:
 | 
			
		||||
            origin: "https://github.com/jmhbnz/talks.git"
 | 
			
		||||
    components:
 | 
			
		||||
      - name: dev
 | 
			
		||||
        container:
 | 
			
		||||
          image: quay.io/devfile/universal-developer-image:latest
 | 
			
		||||
    commands:
 | 
			
		||||
      - id: install-roxctl
 | 
			
		||||
        exec:
 | 
			
		||||
          component: dev
 | 
			
		||||
          commandLine: curl -O https://mirror.openshift.com/pub/rhacs/assets/4.1.2/bin/Linux/roxctl && chmod +x roxctl
 | 
			
		||||
          workingDir: ${PROJECT_SOURCE}
 | 
			
		||||
  contributions:
 | 
			
		||||
    - name: che-code
 | 
			
		||||
      uri: https://eclipse-che.github.io/che-plugin-registry/main/v3/plugins/che-incubator/che-code/latest/devfile.yaml
 | 
			
		||||
      components:
 | 
			
		||||
        - name: che-code-runtime-description
 | 
			
		||||
          container:
 | 
			
		||||
            env:
 | 
			
		||||
              - name: CODE_HOST
 | 
			
		||||
                value: 0.0.0.0
 | 
			
		||||
EOF
 | 
			
		||||
#+end_src
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
** Deploy sample application
 | 
			
		||||
 | 
			
		||||
In order to showcase incorporating ~roxctl~ into developer workflows we need a sample application to tinker with.
 | 
			
		||||
							
								
								
									
										18
									
								
								2023-07-31-acs-workflows/guestbook/Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								2023-07-31-acs-workflows/guestbook/Dockerfile
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
			
		||||
FROM golang as builder
 | 
			
		||||
COPY main.go /guestbook/
 | 
			
		||||
COPY go.mod /guestbook/
 | 
			
		||||
COPY go.sum /guestbook/
 | 
			
		||||
RUN cd /guestbook && go build
 | 
			
		||||
 | 
			
		||||
FROM docker.io/ubuntu:latest
 | 
			
		||||
 | 
			
		||||
COPY --from=builder /guestbook/guestbook /app/guestbook
 | 
			
		||||
 | 
			
		||||
ADD public/index.html /app/public/index.html
 | 
			
		||||
ADD public/script.js /app/public/script.js
 | 
			
		||||
ADD public/style.css /app/public/style.css
 | 
			
		||||
ADD public/jquery.min.js /app/public/jquery.min.js
 | 
			
		||||
 | 
			
		||||
WORKDIR /app
 | 
			
		||||
CMD ["./guestbook"]
 | 
			
		||||
EXPOSE 3000
 | 
			
		||||
							
								
								
									
										12
									
								
								2023-07-31-acs-workflows/guestbook/go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								2023-07-31-acs-workflows/guestbook/go.mod
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,12 @@
 | 
			
		||||
module guestbook
 | 
			
		||||
 | 
			
		||||
go 1.19
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/codegangsta/negroni v1.0.0 // indirect
 | 
			
		||||
	github.com/garyburd/redigo v1.6.4 // indirect
 | 
			
		||||
	github.com/gomodule/redigo v1.8.9 // indirect
 | 
			
		||||
	github.com/gorilla/mux v1.8.0 // indirect
 | 
			
		||||
	github.com/xyproto/pinterface v1.5.3 // indirect
 | 
			
		||||
	github.com/xyproto/simpleredis v0.0.0-20220117114834-9a1000fbd7af // indirect
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										29
									
								
								2023-07-31-acs-workflows/guestbook/go.sum
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								2023-07-31-acs-workflows/guestbook/go.sum
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,29 @@
 | 
			
		||||
github.com/codegangsta/negroni v1.0.0 h1:+aYywywx4bnKXWvoWtRfJ91vC59NbEhEY03sZjQhbVY=
 | 
			
		||||
github.com/codegangsta/negroni v1.0.0/go.mod h1:v0y3T5G7Y1UlFfyxFn/QLRU4a2EuNau2iZY63YTKWo0=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
			
		||||
github.com/garyburd/redigo v1.6.4 h1:LFu2R3+ZOPgSMWMOL+saa/zXRjw0ID2G8FepO53BGlg=
 | 
			
		||||
github.com/garyburd/redigo v1.6.4/go.mod h1:rTb6epsqigu3kYKBnaF028A7Tf/Aw5s0cqA47doKKqw=
 | 
			
		||||
github.com/gomodule/redigo v1.8.8/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE=
 | 
			
		||||
github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws=
 | 
			
		||||
github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE=
 | 
			
		||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
 | 
			
		||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
			
		||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
			
		||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 | 
			
		||||
github.com/xyproto/pinterface v1.5.3 h1:RKkNT88cwrSqD9hU4cYAO5yeo8srg4TG+74Pcj88iz0=
 | 
			
		||||
github.com/xyproto/pinterface v1.5.3/go.mod h1:X5B5pKE49ak7SpyDh5QvJvLH9cC9XuZNDcl5hEyYc34=
 | 
			
		||||
github.com/xyproto/simpleredis v0.0.0-20150522151523-2fc7642209b5 h1:Pq2witO9kTO0Sxn0XqeBOUeKTG88JKn9uCfXEAF0XfA=
 | 
			
		||||
github.com/xyproto/simpleredis v0.0.0-20150522151523-2fc7642209b5/go.mod h1:uSYFxIza9OX4jlWn/KHQRd0YDCXza/L/S4WatobDE0U=
 | 
			
		||||
github.com/xyproto/simpleredis v0.0.0-20150523000142-9f9bdf9000d1 h1:+vB14RyCTr3FERRzenZftip8alGqRbhx7kzS5za9/VQ=
 | 
			
		||||
github.com/xyproto/simpleredis v0.0.0-20150523000142-9f9bdf9000d1/go.mod h1:uSYFxIza9OX4jlWn/KHQRd0YDCXza/L/S4WatobDE0U=
 | 
			
		||||
github.com/xyproto/simpleredis v0.0.0-20150526220545-97bd090877ec h1:qbZz02VvdGKFARwMxKlr3bbZsr/f5/O9oOGVigF6MUo=
 | 
			
		||||
github.com/xyproto/simpleredis v0.0.0-20150526220545-97bd090877ec/go.mod h1:uSYFxIza9OX4jlWn/KHQRd0YDCXza/L/S4WatobDE0U=
 | 
			
		||||
github.com/xyproto/simpleredis v0.0.0-20180505135304-2f4b48d695d6 h1:SA/bq+lPFQuyt4bb7oxLi6tkTg/8rKmu6dky68xgPus=
 | 
			
		||||
github.com/xyproto/simpleredis v0.0.0-20180505135304-2f4b48d695d6/go.mod h1:uSYFxIza9OX4jlWn/KHQRd0YDCXza/L/S4WatobDE0U=
 | 
			
		||||
github.com/xyproto/simpleredis v0.0.0-20220117114834-9a1000fbd7af h1:cysD3MzP3R/pQETtrLsxudVsc79USsWNFY27kYooQbY=
 | 
			
		||||
github.com/xyproto/simpleredis v0.0.0-20220117114834-9a1000fbd7af/go.mod h1:klBJiwXWN4OvxC5qVNAr7RnRYowZh9WyeAJoU6aZjZ0=
 | 
			
		||||
github.com/xyproto/simpleredis v2.6.5+incompatible h1:eghMfrjX+r1Ox9luPSZHctEEy5gd5tqgH7Azqvmfhu8=
 | 
			
		||||
github.com/xyproto/simpleredis v2.6.5+incompatible/go.mod h1:uSYFxIza9OX4jlWn/KHQRd0YDCXza/L/S4WatobDE0U=
 | 
			
		||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
			
		||||
							
								
								
									
										293
									
								
								2023-07-31-acs-workflows/guestbook/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										293
									
								
								2023-07-31-acs-workflows/guestbook/main.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,293 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2014 The Kubernetes 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.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"math/rand"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/codegangsta/negroni"
 | 
			
		||||
	"github.com/gorilla/mux"
 | 
			
		||||
	"github.com/xyproto/simpleredis"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	// For when Redis is used
 | 
			
		||||
	masterPool *simpleredis.ConnectionPool
 | 
			
		||||
	slavePool  *simpleredis.ConnectionPool
 | 
			
		||||
 | 
			
		||||
	// For when Redis is not used, we just keep it in memory
 | 
			
		||||
	lists map[string][]string = map[string][]string{}
 | 
			
		||||
 | 
			
		||||
	// For Healthz
 | 
			
		||||
	startTime time.Time
 | 
			
		||||
	delay     float64 = 10 + 5*rand.Float64()
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Input struct {
 | 
			
		||||
	InputText string `json:"input_text"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Tone struct {
 | 
			
		||||
	ToneName string `json:"tone_name"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetList(key string) ([]string, error) {
 | 
			
		||||
	// Using Redis
 | 
			
		||||
	if slavePool != nil {
 | 
			
		||||
		list := simpleredis.NewList(slavePool, key)
 | 
			
		||||
		if result, err := list.GetAll(); err == nil {
 | 
			
		||||
			return result, err
 | 
			
		||||
		}
 | 
			
		||||
		// if we can't talk to the slave then assume its not running yet
 | 
			
		||||
		// so just try to use the master instead
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// if the slave doesn't exist, read from the master
 | 
			
		||||
	if masterPool != nil {
 | 
			
		||||
		list := simpleredis.NewList(masterPool, key)
 | 
			
		||||
		return list.GetAll()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// if neither exist, we're probably in "in-memory" mode
 | 
			
		||||
	return lists[key], nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func AppendToList(item string, key string) ([]string, error) {
 | 
			
		||||
	var err error
 | 
			
		||||
	items := []string{}
 | 
			
		||||
 | 
			
		||||
	// Using Redis
 | 
			
		||||
	if masterPool != nil {
 | 
			
		||||
		list := simpleredis.NewList(masterPool, key)
 | 
			
		||||
		list.Add(item)
 | 
			
		||||
		items, err = list.GetAll()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		items = lists[key]
 | 
			
		||||
		items = append(items, item)
 | 
			
		||||
		lists[key] = items
 | 
			
		||||
	}
 | 
			
		||||
	return items, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ListRangeHandler(rw http.ResponseWriter, req *http.Request) {
 | 
			
		||||
	var data []byte
 | 
			
		||||
 | 
			
		||||
	items, err := GetList(mux.Vars(req)["key"])
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		data = []byte("Error getting list: " + err.Error() + "\n")
 | 
			
		||||
	} else {
 | 
			
		||||
		if data, err = json.MarshalIndent(items, "", ""); err != nil {
 | 
			
		||||
			data = []byte("Error marhsalling list: " + err.Error() + "\n")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	rw.Write(data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ListPushHandler(rw http.ResponseWriter, req *http.Request) {
 | 
			
		||||
	var data []byte
 | 
			
		||||
 | 
			
		||||
	key := mux.Vars(req)["key"]
 | 
			
		||||
	value := mux.Vars(req)["value"]
 | 
			
		||||
 | 
			
		||||
	// propogate headers to analyzer service
 | 
			
		||||
	headers := getForwardHeaders(req.Header)
 | 
			
		||||
 | 
			
		||||
	// Add in the "tone" analyzer results
 | 
			
		||||
	value += " : " + getPrimaryTone(value, headers)
 | 
			
		||||
 | 
			
		||||
	items, err := AppendToList(value, key)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		data = []byte("Error adding to list: " + err.Error() + "\n")
 | 
			
		||||
	} else {
 | 
			
		||||
		if data, err = json.MarshalIndent(items, "", ""); err != nil {
 | 
			
		||||
			data = []byte("Error marshalling list: " + err.Error() + "\n")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
	rw.Write(data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func InfoHandler(rw http.ResponseWriter, req *http.Request) {
 | 
			
		||||
	info := ""
 | 
			
		||||
 | 
			
		||||
	// Using Redis
 | 
			
		||||
	if masterPool != nil {
 | 
			
		||||
		i, err := masterPool.Get(0).Do("INFO")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			info = "Error getting DB info: " + err.Error()
 | 
			
		||||
		} else {
 | 
			
		||||
			info = string(i.([]byte))
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		info = "In-memory datastore (not redis)"
 | 
			
		||||
	}
 | 
			
		||||
	rw.Write([]byte(info + "\n"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func EnvHandler(rw http.ResponseWriter, req *http.Request) {
 | 
			
		||||
	environment := make(map[string]string)
 | 
			
		||||
	for _, item := range os.Environ() {
 | 
			
		||||
		splits := strings.Split(item, "=")
 | 
			
		||||
		key := splits[0]
 | 
			
		||||
		val := strings.Join(splits[1:], "=")
 | 
			
		||||
		environment[key] = val
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	data, err := json.MarshalIndent(environment, "", "")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		data = []byte("Error marshalling env vars: " + err.Error())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	rw.Write(data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func HelloHandler(rw http.ResponseWriter, req *http.Request) {
 | 
			
		||||
	rw.Write([]byte("Hello from guestbook. " +
 | 
			
		||||
		"Your app is up! (Hostname: " +
 | 
			
		||||
		os.Getenv("HOSTNAME") +
 | 
			
		||||
		")\n"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func HealthzHandler(rw http.ResponseWriter, req *http.Request) {
 | 
			
		||||
	if time.Now().Sub(startTime).Seconds() > delay {
 | 
			
		||||
		http.Error(rw, "Timeout, Health check error!", http.StatusForbidden)
 | 
			
		||||
	} else {
 | 
			
		||||
		rw.Write([]byte("OK!"))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Note: This function will not work until we hook-up the Tone Analyzer service
 | 
			
		||||
func getPrimaryTone(value string, headers http.Header) (tone string) {
 | 
			
		||||
	u := Input{InputText: value}
 | 
			
		||||
	b := new(bytes.Buffer)
 | 
			
		||||
	json.NewEncoder(b).Encode(u)
 | 
			
		||||
 | 
			
		||||
	client := &http.Client{}
 | 
			
		||||
	req, err := http.NewRequest("POST", "http://analyzer:80/tone", b)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "Error talking to tone analyzer service: " + err.Error()
 | 
			
		||||
	}
 | 
			
		||||
	req.Header.Add("Content-Type", "application/json")
 | 
			
		||||
	// add headers
 | 
			
		||||
	for k := range headers {
 | 
			
		||||
		req.Header.Add(k, headers.Get(k))
 | 
			
		||||
	}
 | 
			
		||||
	// print out headers for debug
 | 
			
		||||
	// fmt.Printf("getPrimaryTone headers %v", req.Header)
 | 
			
		||||
 | 
			
		||||
	res, err := client.Do(req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "Error detecting tone: " + err.Error()
 | 
			
		||||
	}
 | 
			
		||||
	defer res.Body.Close()
 | 
			
		||||
 | 
			
		||||
	body := []Tone{}
 | 
			
		||||
	json.NewDecoder(res.Body).Decode(&body)
 | 
			
		||||
	if len(body) > 0 {
 | 
			
		||||
		// 7 tones:  anger, fear, joy, sadness, analytical, confident, and tentative
 | 
			
		||||
		if body[0].ToneName == "Joy" {
 | 
			
		||||
			return body[0].ToneName + " (✿◠‿◠)"
 | 
			
		||||
		} else if body[0].ToneName == "Anger" {
 | 
			
		||||
			return body[0].ToneName + " (ಠ_ಠ)"
 | 
			
		||||
		} else if body[0].ToneName == "Fear" {
 | 
			
		||||
			return body[0].ToneName + " (ง’̀-‘́)ง"
 | 
			
		||||
		} else if body[0].ToneName == "Sadness" {
 | 
			
		||||
			return body[0].ToneName + " (︶︿︶)"
 | 
			
		||||
		} else if body[0].ToneName == "Analytical" {
 | 
			
		||||
			return body[0].ToneName + " ( °□° )"
 | 
			
		||||
		} else if body[0].ToneName == "Confident" {
 | 
			
		||||
			return body[0].ToneName + " (▀̿Ĺ̯▀̿ ̿)"
 | 
			
		||||
		} else if body[0].ToneName == "Tentative" {
 | 
			
		||||
			return body[0].ToneName + " (•_•)"
 | 
			
		||||
		}
 | 
			
		||||
		return body[0].ToneName
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return "No Tone Detected"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// return the needed header for distributed tracing
 | 
			
		||||
func getForwardHeaders(h http.Header) (headers http.Header) {
 | 
			
		||||
	incomingHeaders := []string{
 | 
			
		||||
		"x-request-id",
 | 
			
		||||
		"x-b3-traceid",
 | 
			
		||||
		"x-b3-spanid",
 | 
			
		||||
		"x-b3-parentspanid",
 | 
			
		||||
		"x-b3-sampled",
 | 
			
		||||
		"x-b3-flags",
 | 
			
		||||
		"x-ot-span-context"}
 | 
			
		||||
 | 
			
		||||
	header := make(http.Header, len(incomingHeaders))
 | 
			
		||||
	for _, element := range incomingHeaders {
 | 
			
		||||
		val := h.Get(element)
 | 
			
		||||
		if val != "" {
 | 
			
		||||
			header.Set(element, val)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return header
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Support multiple URL schemes for different use cases
 | 
			
		||||
func findRedisURL() string {
 | 
			
		||||
	host := os.Getenv("REDIS_MASTER_SERVICE_HOST")
 | 
			
		||||
	port := os.Getenv("REDIS_MASTER_SERVICE_PORT")
 | 
			
		||||
	password := os.Getenv("REDIS_MASTER_SERVICE_PASSWORD")
 | 
			
		||||
	master_port := os.Getenv("REDIS_MASTER_PORT")
 | 
			
		||||
 | 
			
		||||
	if host != "" && port != "" && password != "" {
 | 
			
		||||
		return password + "@" + host + ":" + port
 | 
			
		||||
	} else if master_port != "" {
 | 
			
		||||
		return "redis-master:6379"
 | 
			
		||||
	}
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	// When using Redis, setup our DB connections
 | 
			
		||||
	url := findRedisURL()
 | 
			
		||||
	if url != "" {
 | 
			
		||||
		masterPool = simpleredis.NewConnectionPoolHost(url)
 | 
			
		||||
		defer masterPool.Close()
 | 
			
		||||
		slavePool = simpleredis.NewConnectionPoolHost("redis-slave:6379")
 | 
			
		||||
		defer slavePool.Close()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	startTime = time.Now()
 | 
			
		||||
 | 
			
		||||
	r := mux.NewRouter()
 | 
			
		||||
	r.Path("/lrange/{key}").Methods("GET").HandlerFunc(ListRangeHandler)
 | 
			
		||||
	r.Path("/rpush/{key}/{value}").Methods("GET").HandlerFunc(ListPushHandler)
 | 
			
		||||
	r.Path("/info").Methods("GET").HandlerFunc(InfoHandler)
 | 
			
		||||
	r.Path("/env").Methods("GET").HandlerFunc(EnvHandler)
 | 
			
		||||
	r.Path("/hello").Methods("GET").HandlerFunc(HelloHandler)
 | 
			
		||||
	r.Path("/healthz").Methods("GET").HandlerFunc(HealthzHandler)
 | 
			
		||||
 | 
			
		||||
	n := negroni.Classic()
 | 
			
		||||
	n.UseHandler(r)
 | 
			
		||||
	n.Run(":3000")
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										36
									
								
								2023-07-31-acs-workflows/guestbook/public/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								2023-07-31-acs-workflows/guestbook/public/index.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,36 @@
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
  <head>
 | 
			
		||||
    <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
 | 
			
		||||
    <meta charset="utf-8">
 | 
			
		||||
    <meta content="width=device-width" name="viewport">
 | 
			
		||||
    <link href="style.css" rel="stylesheet">
 | 
			
		||||
    <title>Guestbook - v2</title>
 | 
			
		||||
  </head>
 | 
			
		||||
  <body>
 | 
			
		||||
    <div id="header">
 | 
			
		||||
      <h1>Guestbook - v2</h1>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div id="guestbook-entries">
 | 
			
		||||
      <link href="https://afeld.github.io/emoji-css/emoji.css" rel="stylesheet">
 | 
			
		||||
      <p>Waiting for database connection... <i class='em em-boat'></i></p>
 | 
			
		||||
      
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div>
 | 
			
		||||
      <form id="guestbook-form">
 | 
			
		||||
        <input autocomplete="off" id="guestbook-entry-content" type="text">
 | 
			
		||||
        <a href="#" id="guestbook-submit">Submit</a>
 | 
			
		||||
      </form>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div>
 | 
			
		||||
      <p><h2 id="guestbook-host-address"></h2></p>
 | 
			
		||||
      <p><a href="env">/env</a>
 | 
			
		||||
      <a href="info">/info</a></p>
 | 
			
		||||
    </div>
 | 
			
		||||
    <script src="jquery.min.js"></script>
 | 
			
		||||
    <script src="script.js"></script>
 | 
			
		||||
  </body>
 | 
			
		||||
</html>
 | 
			
		||||
							
								
								
									
										4
									
								
								2023-07-31-acs-workflows/guestbook/public/jquery.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								2023-07-31-acs-workflows/guestbook/public/jquery.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										38
									
								
								2023-07-31-acs-workflows/guestbook/public/script.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								2023-07-31-acs-workflows/guestbook/public/script.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,38 @@
 | 
			
		||||
$(document).ready(function() {
 | 
			
		||||
  var headerTitleElement = $("#header h1");
 | 
			
		||||
  var entriesElement = $("#guestbook-entries");
 | 
			
		||||
  var formElement = $("#guestbook-form");
 | 
			
		||||
  var submitElement = $("#guestbook-submit");
 | 
			
		||||
  var entryContentElement = $("#guestbook-entry-content");
 | 
			
		||||
  var hostAddressElement = $("#guestbook-host-address");
 | 
			
		||||
 | 
			
		||||
  var appendGuestbookEntries = function(data) {
 | 
			
		||||
    entriesElement.empty();
 | 
			
		||||
    $.each(data, function(key, val) {
 | 
			
		||||
      entriesElement.append("<p>" + val + "</p>");
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  var handleSubmission = function(e) {
 | 
			
		||||
    e.preventDefault();
 | 
			
		||||
    var entryValue = entryContentElement.val()
 | 
			
		||||
    if (entryValue.length > 0) {
 | 
			
		||||
      entriesElement.append("<p>...</p>");
 | 
			
		||||
      $.getJSON("rpush/guestbook/" + entryValue, appendGuestbookEntries);
 | 
			
		||||
	  entryContentElement.val("")
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  submitElement.click(handleSubmission);
 | 
			
		||||
  formElement.submit(handleSubmission);
 | 
			
		||||
  hostAddressElement.append(document.URL);
 | 
			
		||||
 | 
			
		||||
  // Poll every second.
 | 
			
		||||
  (function fetchGuestbook() {
 | 
			
		||||
    $.getJSON("lrange/guestbook").done(appendGuestbookEntries).always(
 | 
			
		||||
      function() {
 | 
			
		||||
        setTimeout(fetchGuestbook, 1000);
 | 
			
		||||
      });
 | 
			
		||||
  })();
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										61
									
								
								2023-07-31-acs-workflows/guestbook/public/style.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								2023-07-31-acs-workflows/guestbook/public/style.css
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,61 @@
 | 
			
		||||
body, input {
 | 
			
		||||
  color: #123;
 | 
			
		||||
  font-family: "Gill Sans", sans-serif;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div {
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  padding: 1em 0;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
h1, h2, p, input, a {
 | 
			
		||||
  font-weight: 300;
 | 
			
		||||
  margin: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
h1 {
 | 
			
		||||
  color: #18d;
 | 
			
		||||
  font-size: 3.5em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
h2 {
 | 
			
		||||
  color: #999;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
form {
 | 
			
		||||
  margin: 0 auto;
 | 
			
		||||
  max-width: 50em;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input {
 | 
			
		||||
  border: 0;
 | 
			
		||||
  border-radius: 1000px;
 | 
			
		||||
  box-shadow: inset 0 0 0 2px #18d;
 | 
			
		||||
  display: inline;
 | 
			
		||||
  font-size: 1.5em;
 | 
			
		||||
  margin-bottom: 1em;
 | 
			
		||||
  outline: none;
 | 
			
		||||
  padding: .5em 5%;
 | 
			
		||||
  width: 55%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
form a {
 | 
			
		||||
  background: #18d;
 | 
			
		||||
  border: 0;
 | 
			
		||||
  border-radius: 1000px;
 | 
			
		||||
  color: #FFF;
 | 
			
		||||
  font-size: 1.25em;
 | 
			
		||||
  font-weight: 400;
 | 
			
		||||
  padding: .75em 2em;
 | 
			
		||||
  text-decoration: none;
 | 
			
		||||
  text-transform: uppercase;
 | 
			
		||||
  white-space: normal;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
p {
 | 
			
		||||
  font-size: 1.5em;
 | 
			
		||||
  line-height: 1.5;
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user