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
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/...
.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
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
@@ -29,7 +28,10 @@ var _ = Describe("E2E Test", func() {
checkIfDevEnvIsUp := func(url, assertString string) {
Eventually(func() string {
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()
body, _ := io.ReadAll(resp.Body)
@@ -57,10 +59,10 @@ var _ = Describe("E2E Test", func() {
helper.SendLine(ctx, "JavaScript")
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.SendLine(ctx, "nodejs-starter\n")
helper.SendLine(ctx, "nodejs-starter")
helper.ExpectString(ctx, "Enter component name")
helper.SendLine(ctx, componentName)
@@ -173,11 +175,11 @@ var _ = Describe("E2E Test", func() {
helper.ExpectString(ctx, "Project type: Node.js")
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.SendLine(ctx, "\n")
helper.SendLine(ctx, "")
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))
})
})
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
}