E2e scenario 3 (#6073)

* add e2e test for binding

Signed-off-by: anandrkskd <anandrkskd@gmail.com>

* fails on testing binding's

Signed-off-by: anandrkskd <anandrkskd@gmail.com>

* update e2e test

Signed-off-by: anandrkskd <anandrkskd@gmail.com>

* remove FIt

Signed-off-by: anandrkskd <anandrkskd@gmail.com>

* incorporate requested changes

Signed-off-by: anandrkskd <anandrkskd@gmail.com>

* incorporate requested changes

Signed-off-by: anandrkskd <anandrkskd@gmail.com>

* dont run go sec on example directory

Signed-off-by: anandrkskd <anandrkskd@gmail.com>

* fix flaky behaviour

Signed-off-by: anandrkskd <anandrkskd@gmail.com>

* fix flaky behaviour

Signed-off-by: anandrkskd <anandrkskd@gmail.com>

* nit: cleanup

Signed-off-by: anandrkskd <anandrkskd@gmail.com>

* Apply suggestions from code review

Signed-off-by: anandrkskd <anandrkskd@gmail.com>
Co-authored-by: Armel Soro <armel@rm3l.org>
This commit is contained in:
Anand Kumar Singh
2022-11-25 23:15:26 +05:30
committed by GitHub
parent c9875200ed
commit 6172a16fc7
9 changed files with 584 additions and 9 deletions

View File

@@ -111,7 +111,7 @@ vet:
.PHONY: sec .PHONY: sec
sec: sec:
go run $(COMMON_GOFLAGS) github.com/securego/gosec/v2/cmd/gosec -severity medium -confidence medium -exclude G304,G204,G107 -quiet ./tests/... go run $(COMMON_GOFLAGS) github.com/securego/gosec/v2/cmd/gosec -severity medium -confidence medium -exclude G304,G204,G107 -quiet ./tests/integration/... ./tests/helper... ./tests/e2escenarios/...
go run $(COMMON_GOFLAGS) github.com/securego/gosec/v2/cmd/gosec -severity medium -confidence medium -exclude G304,G204 -quiet ./cmd/... ./pkg/... go run $(COMMON_GOFLAGS) github.com/securego/gosec/v2/cmd/gosec -severity medium -confidence medium -exclude G304,G204 -quiet ./cmd/... ./pkg/...
.PHONY: clean .PHONY: clean

View File

@@ -1,9 +1,8 @@
//go:build linux || darwin || dragonfly || solaris || openbsd || netbsd || freebsd
// +build linux darwin dragonfly solaris openbsd netbsd freebsd
package e2escenarios package e2escenarios
import ( import (
"bytes"
"encoding/json"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
@@ -29,7 +28,10 @@ var _ = Describe("E2E Test", func() {
checkIfDevEnvIsUp := func(url, assertString string) { checkIfDevEnvIsUp := func(url, assertString string) {
Eventually(func() string { Eventually(func() string {
resp, err := http.Get(fmt.Sprintf("http://%s", url)) resp, err := http.Get(fmt.Sprintf("http://%s", url))
Expect(err).ToNot(HaveOccurred()) if err != nil {
fmt.Fprintf(GinkgoWriter, "error while trying to GET %q: %v\n", url, err)
return ""
}
defer resp.Body.Close() defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body) body, _ := io.ReadAll(resp.Body)
@@ -57,10 +59,10 @@ var _ = Describe("E2E Test", func() {
helper.SendLine(ctx, "JavaScript") helper.SendLine(ctx, "JavaScript")
helper.ExpectString(ctx, "Select project type") helper.ExpectString(ctx, "Select project type")
helper.SendLine(ctx, "Node.js\n") helper.SendLine(ctx, "Node.js")
helper.ExpectString(ctx, "Which starter project do you want to use") helper.ExpectString(ctx, "Which starter project do you want to use")
helper.SendLine(ctx, "nodejs-starter\n") helper.SendLine(ctx, "nodejs-starter")
helper.ExpectString(ctx, "Enter component name") helper.ExpectString(ctx, "Enter component name")
helper.SendLine(ctx, componentName) helper.SendLine(ctx, componentName)
@@ -173,11 +175,11 @@ var _ = Describe("E2E Test", func() {
helper.ExpectString(ctx, "Project type: Node.js") helper.ExpectString(ctx, "Project type: Node.js")
helper.ExpectString(ctx, "Is this correct") helper.ExpectString(ctx, "Is this correct")
helper.SendLine(ctx, "\n") helper.SendLine(ctx, "")
helper.ExpectString(ctx, "Select container for which you want to change configuration?") helper.ExpectString(ctx, "Select container for which you want to change configuration?")
helper.SendLine(ctx, "\n") helper.SendLine(ctx, "")
helper.ExpectString(ctx, "Enter component name") helper.ExpectString(ctx, "Enter component name")
@@ -270,4 +272,137 @@ var _ = Describe("E2E Test", func() {
Eventually(string(commonVar.CliRunner.Run(getSVCArgs...).Out.Contents()), 60, 3).ShouldNot(ContainSubstring(serviceName)) Eventually(string(commonVar.CliRunner.Run(getSVCArgs...).Out.Contents()), 60, 3).ShouldNot(ContainSubstring(serviceName))
}) })
}) })
Context("starting with non-empty Directory add Binding", func() {
componentName := helper.RandString(6)
sendDataEntry := func(url string) map[string]interface{} {
values := map[string]interface{}{"name": "joe",
"location": "tokyo",
"age": 23,
}
json_data, err := json.Marshal(values)
Expect(err).To(BeNil())
resp, err := http.Post(fmt.Sprintf("http://%s/api/newuser", url), "application/json", bytes.NewBuffer(json_data))
Expect(err).To(BeNil())
var res map[string]interface{}
err = json.NewDecoder(resp.Body).Decode(&res)
Expect(err).To(BeNil())
return res
}
receiveData := func(url string) (string, error) {
resp, err := http.Get(fmt.Sprintf("http://%s", url))
if err != nil {
return "", err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
Expect(err).To(BeNil())
return string(body), nil
}
var _ = BeforeEach(func() {
commonVar.CliRunner.EnsureOperatorIsInstalled("service-binding-operator")
commonVar.CliRunner.EnsureOperatorIsInstalled("cloud-native-postgresql")
Eventually(func() string {
out, _ := commonVar.CliRunner.GetBindableKinds()
return out
}, 120, 3).Should(ContainSubstring("Cluster"))
helper.Chdir(commonVar.Context)
helper.CopyExample(filepath.Join("source", "devfiles", "go"), commonVar.Context)
addBindableKind := commonVar.CliRunner.Run("apply", "-f", helper.GetExamplePath("source", "devfiles", "go", "cluster.yaml"))
Expect(addBindableKind.ExitCode()).To(BeEquivalentTo(0))
})
It("should verify developer workflow of using binding as env in innerloop", func() {
bindingName := helper.RandString(6)
command := []string{"odo", "init"}
_, err := helper.RunInteractive(command, nil, func(ctx helper.InteractiveContext) {
// helper.ExpectString(ctx, "Based on the files in the current directory odo detected")
helper.ExpectString(ctx, "Language: Go")
helper.ExpectString(ctx, "Project type: Go")
helper.ExpectString(ctx, "Is this correct")
helper.SendLine(ctx, "")
helper.ExpectString(ctx, "Select container for which you want to change configuration?")
helper.SendLine(ctx, "")
helper.ExpectString(ctx, "Enter component name")
helper.SendLine(ctx, componentName)
helper.ExpectString(ctx, "Your new component '"+componentName+"' is ready in the current directory")
})
Expect(err).To(BeNil())
Expect(helper.ListFilesInDir(commonVar.Context)).To(ContainElement("devfile.yaml"))
// // "execute odo dev and add changes to application"
var devSession helper.DevSession
var ports map[string]string
devSession, _, _, ports, err = helper.StartDevMode(helper.DevSessionOpts{})
Expect(err).ToNot(HaveOccurred())
// "send data"
_, err = receiveData(fmt.Sprintf(ports["8080"] + "/api/user"))
Expect(err).ToNot(BeNil()) // should fail as application is not connected to DB
//add binding information (binding as ENV)
helper.Cmd("odo", "add", "binding", "--name", bindingName, "--service", "cluster-example-initdb", "--bind-as-files=false").ShouldPass()
// Get new random port after restart
Eventually(func() map[string]string {
_, _, ports, err = devSession.GetInfo()
Expect(err).ToNot(HaveOccurred())
return ports
}, 180, 10).ShouldNot(BeEmpty())
// "send data"
data := sendDataEntry(ports["8080"])
Expect(data["message"]).To(Equal("User created successfully"))
// "get all data"
rec, err := receiveData(fmt.Sprintf(ports["8080"] + "/api/user"))
Expect(err).To(BeNil())
helper.MatchAllInOutput(rec, []string{"id", "1", "name", "joe", "location", "tokyo", "age", "23"})
// check odo describe to check for env
stdout := helper.Cmd("odo", "describe", "binding").ShouldPass().Out()
helper.MatchAllInOutput(stdout, []string{"Available binding information:", "CLUSTER_HOST", "CLUSTER_PASSWORD", "CLUSTER_USERNAME"})
// "running odo list"
stdout = helper.Cmd("odo", "list").ShouldPass().Out()
helper.MatchAllInOutput(stdout, []string{componentName, "Go", "Dev", bindingName})
// "exit dev mode"
devSession.Stop()
devSession.WaitEnd()
// remove bindings and check devfile to not contain binding info
// TODO: move `remove binding` inside devsession after https://github.com/redhat-developer/odo/issues/6101 is fixed
helper.Cmd("odo", "remove", "binding", "--name", bindingName).ShouldPass()
devSession, _, _, _, err = helper.StartDevMode(helper.DevSessionOpts{})
Expect(err).To(BeNil())
stdout = helper.Cmd("odo", "describe", "binding").ShouldPass().Out()
Expect(stdout).To(ContainSubstring("No ServiceBinding used by the current component"))
devSession.Stop()
devSession.WaitEnd()
// all resources should be deleted from the namespace
services := commonVar.CliRunner.GetServices(commonVar.Project)
Expect(services).NotTo(ContainSubstring(componentName))
pvcs := commonVar.CliRunner.GetAllPVCNames(commonVar.Project)
Expect(pvcs).NotTo(ContainElement(componentName)) //To(Not(ContainSubstring(componentName)))
pods := commonVar.CliRunner.GetAllPodNames(commonVar.Project)
Expect(pods).NotTo(ContainElement(componentName))
})
})
}) })

View File

@@ -0,0 +1,34 @@
apiVersion: postgresql.k8s.enterprisedb.io/v1
kind: Cluster
metadata:
name: cluster-example-initdb
spec:
instances: 1
bootstrap:
initdb:
database: appdb
owner: appuser
secret:
name: appuser-secret
postInitApplicationSQL:
- create table users (userid SERIAL PRIMARY KEY, name TEXT, age INT, location TEXT)
storage:
size: 1Gi
---
apiVersion: v1
stringData:
username: appuser
password: test-12password!
kind: Secret
metadata:
name: appuser-secret
type: kubernetes.io/basic-auth
---
apiVersion: v1
stringData:
username: appuser
password: test-12password!
kind: Secret
metadata:
name: cluster-example-initdb-appuser
type: kubernetes.io/basic-auth

View File

@@ -0,0 +1,7 @@
module go-postgres
require (
github.com/gorilla/mux v1.7.4
github.com/joho/godotenv v1.3.0
github.com/lib/pq v1.3.0
)

View File

@@ -0,0 +1,6 @@
github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=

View File

@@ -0,0 +1,15 @@
package main
import (
"fmt"
"go-postgres/router"
"log"
"net/http"
)
func main() {
r := router.Router()
fmt.Println("Starting server on the port 8080...")
log.Fatal(http.ListenAndServe(":8080", r))
}

View File

@@ -0,0 +1,348 @@
package middleware
import (
"database/sql"
"encoding/json"
"fmt"
"go-postgres/models"
"log"
"net/http"
"os"
"strconv"
"github.com/gorilla/mux"
_ "github.com/lib/pq"
)
// response format
type response struct {
ID int64 `json:"id,omitempty"`
Message string `json:"message,omitempty"`
}
// create connection with postgres db
func createConnection() *sql.DB {
// collect values form env
connStr := "postgres://" + os.Getenv("CLUSTER_USERNAME") + ":" + os.Getenv("CLUSTER_PASSWORD") + "@" + os.Getenv("CLUSTER_HOST") + "/" + os.Getenv("CLUSTER_DATABASE") + "?sslmode=disable"
// Open the connection
db, err := sql.Open("postgres", connStr)
if err != nil {
panic(err)
}
// check the connection
err = db.Ping()
if err != nil {
panic(err)
}
fmt.Println("Successfully connected!")
// return the connection
return db
}
// CreateUser create a user in the postgres db
func CreateUser(w http.ResponseWriter, r *http.Request) {
// create an empty user of type models.User
var user models.User
// decode the json request to user
err := json.NewDecoder(r.Body).Decode(&user)
if err != nil {
log.Fatalf("Unable to decode the request body. %v", err)
}
// call insert user function and pass the user
insertID := insertUser(user)
// format a response object
res := response{
ID: insertID,
Message: "User created successfully",
}
// send the response
json.NewEncoder(w).Encode(res)
}
// GetUser will return a single user by its id
func GetUser(w http.ResponseWriter, r *http.Request) {
// get the userid from the request params, key is "id"
params := mux.Vars(r)
// convert the id type from string to int
id, err := strconv.Atoi(params["id"])
if err != nil {
log.Fatalf("Unable to convert the string into int. %v", err)
}
// call the getUser function with user id to retrieve a single user
user, err := getUser(int64(id))
if err != nil {
log.Fatalf("Unable to get user. %v", err)
}
// send the response
json.NewEncoder(w).Encode(user)
}
// GetAllUser will return all the users
func GetAllUser(w http.ResponseWriter, r *http.Request) {
// get all the users in the db
users, err := getAllUsers()
if err != nil {
log.Fatalf("Unable to get all user. %v", err)
}
// send all the users as response
json.NewEncoder(w).Encode(users)
}
// UpdateUser update user's detail in the postgres db
func UpdateUser(w http.ResponseWriter, r *http.Request) {
// get the userid from the request params, key is "id"
params := mux.Vars(r)
// convert the id type from string to int
id, err := strconv.Atoi(params["id"])
if err != nil {
log.Fatalf("Unable to convert the string into int. %v", err)
}
// create an empty user of type models.User
var user models.User
// decode the json request to user
err = json.NewDecoder(r.Body).Decode(&user)
if err != nil {
log.Fatalf("Unable to decode the request body. %v", err)
}
// call update user to update the user
updatedRows := updateUser(int64(id), user)
// format the message string
msg := fmt.Sprintf("User updated successfully. Total rows/record affected %v", updatedRows)
// format the response message
res := response{
ID: int64(id),
Message: msg,
}
// send the response
json.NewEncoder(w).Encode(res)
}
// DeleteUser delete user's detail in the postgres db
func DeleteUser(w http.ResponseWriter, r *http.Request) {
// get the userid from the request params, key is "id"
params := mux.Vars(r)
// convert the id in string to int
id, err := strconv.Atoi(params["id"])
if err != nil {
log.Fatalf("Unable to convert the string into int. %v", err)
}
// call the deleteUser, convert the int to int64
deletedRows := deleteUser(int64(id))
// format the message string
msg := fmt.Sprintf("User updated successfully. Total rows/record affected %v", deletedRows)
// format the reponse message
res := response{
ID: int64(id),
Message: msg,
}
// send the response
json.NewEncoder(w).Encode(res)
}
//------------------------- handler functions ----------------
// insert one user in the DB
func insertUser(user models.User) int64 {
// create the postgres db connection
db := createConnection()
// close the db connection
defer db.Close()
// create the insert sql query
// returning userid will return the id of the inserted user
sqlStatement := `INSERT INTO users (name, location, age) VALUES ($1, $2, $3) RETURNING userid`
// the inserted id will store in this id
var id int64
// execute the sql statement
// Scan function will save the insert id in the id
err := db.QueryRow(sqlStatement, user.Name, user.Location, user.Age).Scan(&id)
if err != nil {
log.Fatalf("Unable to execute the query. %v", err)
}
fmt.Printf("Inserted a single record %v", id)
// return the inserted id
return id
}
// get one user from the DB by its userid
func getUser(id int64) (models.User, error) {
// create the postgres db connection
db := createConnection()
// close the db connection
defer db.Close()
// create a user of models.User type
var user models.User
// create the select sql query
sqlStatement := `SELECT * FROM users WHERE userid=$1`
// execute the sql statement
row := db.QueryRow(sqlStatement, id)
// unmarshal the row object to user
err := row.Scan(&user.ID, &user.Name, &user.Age, &user.Location)
switch err {
case sql.ErrNoRows:
fmt.Println("No rows were returned!")
return user, nil
case nil:
return user, nil
default:
log.Fatalf("Unable to scan the row. %v", err)
}
// return empty user on error
return user, err
}
// get one user from the DB by its userid
func getAllUsers() ([]models.User, error) {
// create the postgres db connection
db := createConnection()
// close the db connection
defer db.Close()
var users []models.User
// create the select sql query
sqlStatement := `SELECT * FROM users`
// execute the sql statement
rows, err := db.Query(sqlStatement)
if err != nil {
log.Fatalf("Unable to execute the query. %v", err)
}
// close the statement
defer rows.Close()
// iterate over the rows
for rows.Next() {
var user models.User
// unmarshal the row object to user
err = rows.Scan(&user.ID, &user.Name, &user.Age, &user.Location)
if err != nil {
log.Fatalf("Unable to scan the row. %v", err)
}
// append the user in the users slice
users = append(users, user)
}
// return empty user on error
return users, err
}
// update user in the DB
func updateUser(id int64, user models.User) int64 {
// create the postgres db connection
db := createConnection()
// close the db connection
defer db.Close()
// create the update sql query
sqlStatement := `UPDATE users SET name=$2, location=$3, age=$4 WHERE userid=$1`
// execute the sql statement
res, err := db.Exec(sqlStatement, id, user.Name, user.Location, user.Age)
if err != nil {
log.Fatalf("Unable to execute the query. %v", err)
}
// check how many rows affected
rowsAffected, err := res.RowsAffected()
if err != nil {
log.Fatalf("Error while checking the affected rows. %v", err)
}
fmt.Printf("Total rows/record affected %v", rowsAffected)
return rowsAffected
}
// delete user in the DB
func deleteUser(id int64) int64 {
// create the postgres db connection
db := createConnection()
// close the db connection
defer db.Close()
// create the delete sql query
sqlStatement := `DELETE FROM users WHERE userid=$1`
// execute the sql statement
res, err := db.Exec(sqlStatement, id)
if err != nil {
log.Fatalf("Unable to execute the query. %v", err)
}
// check how many rows affected
rowsAffected, err := res.RowsAffected()
if err != nil {
log.Fatalf("Error while checking the affected rows. %v", err)
}
fmt.Printf("Total rows/record affected %v", rowsAffected)
return rowsAffected
}

View File

@@ -0,0 +1,9 @@
package models
// User schema of the user table
type User struct {
ID int64 `json:"id"`
Name string `json:"name"`
Location string `json:"location"`
Age int64 `json:"age"`
}

View File

@@ -0,0 +1,21 @@
package router
import (
"go-postgres/middleware"
"github.com/gorilla/mux"
)
// Router is exported and used in main.go
func Router() *mux.Router {
router := mux.NewRouter()
router.HandleFunc("/api/user/{id}", middleware.GetUser).Methods("GET", "OPTIONS")
router.HandleFunc("/api/user", middleware.GetAllUser).Methods("GET", "OPTIONS")
router.HandleFunc("/api/newuser", middleware.CreateUser).Methods("POST", "OPTIONS")
router.HandleFunc("/api/user/{id}", middleware.UpdateUser).Methods("PUT", "OPTIONS")
router.HandleFunc("/api/deleteuser/{id}", middleware.DeleteUser).Methods("DELETE", "OPTIONS")
return router
}