Fabric-sdk-go client.Query returns error: failed to create transactor: Channel_Cfg_Cache - cache is closed - hyperledger-fabric-sdk-go

I am trying to make a simple demo using the fabric-sdk-go. I wonder anybod know why the code below ("client.Query") returns the error "failed to create transactor: Channel_Cfg_Cache - cache is closed"?
func initSdkClient() (*channel.Client){
sdk, err := fabsdk.New(config.FromFile("config.yaml"))
if err != nil {
logger.Fatalf("Failed to create new SDK: %s", err)
}
defer sdk.Close()
//prepare channel client context using client context
clientChannelContext := sdk.ChannelContext(channelID, fabsdk.WithUser("User1"), fabsdk.WithOrg(orgName))
// Channel client is used to query and execute transactions (Org1 is default org)
client, err := channel.New(clientChannelContext)
if err != nil {
logger.Fatalf("Failed to create new channel client: %s", err)
}
return client
}
func queryCC(client *channel.Client, targetEndpoints ...string) []byte {
response, err := client.Query(channel.Request{ChaincodeID: ccID, Fcn: "invoke", Args: defaultQueryArgs},
channel.WithRetry(retry.DefaultChannelOpts),
channel.WithTargetEndpoints(targetEndpoints...),
)
if err != nil {
***logger.Fatalf("Failed to query funds: %s", err)*** // error: failed to create transactor: Channel_Cfg_Cache - cache is closed
}
return response.Payload
}
func main() {
client := initSdkClient()
existingValue := queryCC(client)
logger.Info(existingValue)
logger.Info("hello, world\n")
}

Probably because you're closing the SDK instance at the end of initSdkClient function. Rather close the SDK at the end of main function.
Do something like
type Setup struct {
sdk *fabsdk.FabricSDK
client *channel.Client
}
func (setup *Setup) initSdkClient() *channel.Client {
sdk, err := fabsdk.New(config.FromFile("config.yaml"))
if err != nil {
fmt.Errorf("Failed to create new SDK: %s", err)
}
setup.sdk = sdk
//prepare channel client context using client context
clientChannelContext := setup.sdk.ChannelContext(channelID, fabsdk.WithUser("User1"), fabsdk.WithOrg(orgName))
// Channel client is used to query and execute transactions (Org1 is default org)
client, err := channel.New(clientChannelContext)
if err != nil {
fmt.Errorf("Failed to create new channel client: %s", err)
}
setup.client = client
return client
}
func (setup *Setup) queryCC(client *channel.Client, targetEndpoints ...string) []byte {
response, err := setup.client.Query(channel.Request{ChaincodeID: ccID, Fcn: "invoke", Args: defaultQueryArgs},
channel.WithRetry(retry.DefaultChannelOpts),
channel.WithTargetEndpoints(targetEndpoints...),
)
if err != nil {
fmt.Errorf("Failed to query funds: %s", err)
}
return response.Payload
}
func main() {
var setup Setup
client := setup.initSdkClient()
defer setup.sdk.Close()
existingValue := setup.queryCC(client)
}

Related

RDS access via IAM role from a Golang microservice

This bounty has ended. Answers to this question are eligible for a +50 reputation bounty. Bounty grace period ends in 21 hours.
Abhisek Roy wants to draw more attention to this question:
Looking for a workaround to the current issue.
I was implementing RDS IAM authentication from my Golang service and came across an issue. I am using the init function to call create a function where I have created a sync for every 10 mins where every 10 mins the token should get refreshed, it is creating the new token but it's not passing the same to the main function. When the existing thread that was initiated between DB and the service gets killed it is unable to re-authenticate the connection for the second time. Some observations that I have noticed were-
When initially deploying the application it is creating the connection to RDS successfully but after some time as soon as the initial thread between service and DB gets killed it's unable to authenticate the reason which I checked was- the address where the token is getting stored. While making an API call the service picks the token from a static address where the initial token gets stored at the time of service deployment. However, while the token gets refreshed every 10 mins its getting stored on dynamic addresses from where the service is unable to pick up the token.
Here is the go file where actually I am calling and creating the DB function-
package db
import (
"fmt"
"log"
"os"
"time"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/rds/rdsutils"
"github.com/go-co-op/gocron"
"github.com/golang-migrate/migrate/v4"
"github.com/golang-migrate/migrate/v4/database/postgres"
_ "github.com/golang-migrate/migrate/v4/source/file"
"github.com/jinzhu/gorm"
_ "github.com/lib/pq"
)
const DatabaseVersion = 1
const Success = "Database connection successful"
const database = "postgres"
// Service ...
type Service interface {
InitDatabaseConnection(db **gorm.DB)
CreateDatabaseConnection(db **gorm.DB)
Migrate(db *gorm.DB) error
}
type service struct {
}
// NewService ...
func NewService() Service {
return &service{}
}
func (s *service) InitDatabaseConnection(db **gorm.DB) {
log.Print("Creating Db Connection")
s.CreateDatabaseConnection(db)
//todo: add if; if env for iam run the below code
// todo: change 1 min to 10 mins
startTime := time.Now().Local().Add(time.Minute + time.Duration(1))
sch := gocron.NewScheduler(time.UTC)
sch.Every(600).Seconds().StartAt(startTime).Do(func() {
log.Printf("refreshing rds creds")
s.CreateDatabaseConnection(db)
})
sch.StartAsync()
}
func (s *service) CreateDatabaseConnection(db **gorm.DB) {
configuration, _ := common.New()
var dbURI = ConnectDataBase(configuration.UserName, configuration.Password,
configuration.Port, configuration.DbName, configuration.Host, configuration.SearchPath)
var err error
*db, err = gorm.Open(database, dbURI)
if err != nil {
panic(err)
}
print("\n \n", dbURI)
print("\n \n in server.go \n \n ", &db)
if err != nil {
log.Println(err.Error())
os.Exit(3)
} else {
log.Println(Success)
}
(*db).DB().SetMaxOpenConns(6)
(*db).DB().SetConnMaxLifetime(600 * time.Second)
(*db).DB().SetMaxIdleConns(2)
(*db).DB().SetConnMaxIdleTime(100 * time.Second)
}
func ConnectDataBase(username string, password string, port int, dbName string, host string, searchPath string) string {
var dbURI string
if searchPath == "" {
searchPath = "public"
}
dbEndpoint := fmt.Sprintf("%s:%d", host, port)
sess := session.Must(session.NewSession())
creds := sess.Config.Credentials
authToken, err := rdsutils.BuildAuthToken(dbEndpoint, "us-east-1", username, creds)
print("\n \n", authToken)
if err != nil {
panic(err)
}
dbURI = fmt.Sprintf("host=%s port=%d user=%s dbname=%s sslmode=require password=%s search_path=%s",
host, port, username, dbName, authToken, searchPath) //Build connection string
// if password != "" && username != "" {
// dbURI = fmt.Sprintf("host=%s port=%d user=%s dbname=%s sslmode=require password=%s search_path=%s",
// host, port, username, dbName, authToken, searchPath) //Build connection string
// } else {
// dbURI = fmt.Sprintf("host=%s port=%d dbname=%s sslmode=disable ",
// host, port, dbName) //Build connection string
// }
return dbURI
}
func (s *service) Migrate(db *gorm.DB) error {
migrationSourceURL := "file://../resources/db/migrations/"
database := db.DB()
row := db.Table("schema_migrations").Limit(1).Row()
var version int8
var dirty bool
err := row.Scan(&version, &dirty)
if err == nil {
log.Printf("database is currently on version : %v \n", version)
}
log.Println("Migrating database to version ", DatabaseVersion)
driver, err := postgres.WithInstance(database, &postgres.Config{})
if err != nil {
return err
}
m, err := migrate.NewWithDatabaseInstance(migrationSourceURL, "postgres", driver)
if err != nil {
return err
}
m.Log = LogService{}
err = m.Migrate(uint(DatabaseVersion))
if err != nil {
log.Println(err.Error())
return err
}
return nil
}
The common property above is from this piece of code-
package common
import (
"fmt"
"log"
"os"
"github.com/spf13/viper"
)
type ApiResponse struct {
Code int `json:"code"`
Message string `json:"message"`
}
type Constants struct {
Port int `json:"port"`
Host string `json:"host"`
DbName string `json:"dbName"`
UserName string `json:"userName"`
Password string `json:"password"`
AuthUserName string `json:"authUserName"`
AuthPassword string `json:"authPassword"`
SearchPath string `json:"searchPath"`
AwsRegion string `json:"awsRegion"`
}
func New() (*Constants, error) {
fmt.Println("reached constants")
config := Constants{}
constants, err := initViper()
config = constants
if err != nil {
return &config, err
}
return &config, nil
}
func initViper() (Constants, error) {
// temporary, will be removed later when auto deploy is available
env := os.Getenv("CS_ENV")
var configName string
if env == "develop" {
configName = "config-develop"
} else {
configName = "config-local"
}
viper.SetConfigName(configName) // Constants fileName without the .TOML or .YAML extension
viper.AddConfigPath("../resources/config") // Search the root directory for the configuration file
err := viper.ReadInConfig() // Find and read the config file
if err != nil {
log.Println(err.Error()) // Handle errors reading the config file
return Constants{}, err
}
if err = viper.ReadInConfig(); err != nil {
log.Panicf("Error reading config file, %s", err)
}
var constants Constants
err = viper.Unmarshal(&constants)
return constants, err
}
Also attaching the main function go file where I am calling the DB function for connecting the service to DB-
package main
import (
"bufio"
"encoding/base64"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"strings"
"time"
gokitlog "github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/level"
"github.com/gorilla/mux"
"github.com/jinzhu/gorm"
_ "github.com/lib/pq"
)
func runServer(config *common.Constants) {
var logger gokitlog.Logger
logger = gokitlog.NewLogfmtLogger(gokitlog.NewSyncWriter(os.Stderr))
logger = gokitlog.With(logger, "ts", gokitlog.DefaultTimestampUTC)
dbService := db2.NewService()
var db *gorm.DB
dbService.InitDatabaseConnection(&db)
err := dbService.Migrate(db)
if err != nil {
fmt.Println("Migrations scripts did not run :", err.Error())
if err.Error() != "no change" {
fmt.Println("failed to run migrations hence exiting", err.Error())
os.Exit(1)
}
}
level.Info(logger).Log("Starting Darwin ==========>")
repo, err := allocation.NewUserAllocationRepository(db, logger)
if err != nil {
level.Error(logger).Log("exit", err)
os.Exit(3)
}
userAllocationService := allocation.NewUserAllocationService(repo, logger)
endpoints := allocation.MakeEndpoints(userAllocationService)
router := mux.NewRouter()
sh := http.StripPrefix("/swaggerui/", http.FileServer(http.Dir("../resources/swaggerui/")))
router.PathPrefix("/swaggerui/").Handler(sh)
httpLogger := gokitlog.With(logger, "component", "http")
subRouter := router.PathPrefix("/api/v1").Subrouter()
subRouter.NotFoundHandler = http.HandlerFunc(notFound)
subRouter.Use(checkBasicAuth(config))
subRouter.HandleFunc("/healthCheck", healthCheck).Methods("GET")
allocation.MakeHandler(subRouter, httpLogger, endpoints)
http.Handle("/", subRouter)
log.Print("Running server on port 8080")
f, err := os.OpenFile("../performance.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
log.Printf("error opening file: %v", err)
log.Fatal(http.ListenAndServe(":8080", router))
} else {
by := bufio.NewWriter(f)
defer f.Close()
log.Fatal(http.ListenAndServe(":8080", Logger(by, router)))
}
}
func healthCheck(w http.ResponseWriter, r *http.Request) {
resp := map[string]string{
"Status": "Success",
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(resp)
}
func notFound(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json;")
w.WriteHeader(http.StatusNotFound)
json.NewEncoder(w).Encode(map[string]string{"error": "api not found"})
}
func authHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json;")
w.WriteHeader(http.StatusUnauthorized)
json.NewEncoder(w).Encode(map[string]string{"error": "Not authorized"})
}
func checkBasicAuth(config *common.Constants) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.RequestURI() != "/api/v1/healthCheck" {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
w.Header().Set("Content-Type", "application/json;")
s := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
if len(s) != 2 {
authHandler(w, r)
return
}
b, err := base64.StdEncoding.DecodeString(s[1])
if err != nil {
authHandler(w, r)
return
}
pair := strings.SplitN(string(b), ":", 2)
if len(pair) != 2 {
authHandler(w, r)
return
}
if pair[0] != config.AuthUserName || pair[1] != config.AuthPassword {
authHandler(w, r)
return
}
}
next.ServeHTTP(w, r)
})
}
}
func Logger(out *bufio.Writer, h http.Handler) http.Handler {
logger := log.New(out, "", 0)
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
o := &responseObserver{ResponseWriter: w, status: http.StatusOK}
start := time.Now()
h.ServeHTTP(o, r)
endTime := time.Now()
if !strings.Contains(r.URL.String(), "/health") && strings.Contains(r.URL.String(), "/api") {
logger.Printf("Method: %s Path: %s Status: %d ExecutionTime: %d ms", r.Method, r.URL, o.status, endTime.Sub(start).Milliseconds())
out.Flush()
}
})
}
type responseObserver struct {
http.ResponseWriter
status int
}
func (r *responseObserver) WriteHeader(status int) {
r.status = status
r.ResponseWriter.WriteHeader(status)
}

How to write an unit test for a handler that invokes a function that interacts with db in Golang using pgx driver?

I've been trying to write unit tests for my http handler. The code segment is as below:
func (s *Server) handleCreateTicketOption(w http.ResponseWriter, r *http.Request) {
var t ticket.Ticket
body, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, er.ErrInternal.Error(), http.StatusInternalServerError)
return
}
err = json.Unmarshal(body, &t)
if err != nil {
http.Error(w, er.ErrInvalidData.Error(), http.StatusBadRequest)
return
}
ticket, err := s.TicketService.CreateTicketOption(r.Context(), t)
if err != nil {
http.Error(w, er.ErrInternal.Error(), http.StatusInternalServerError)
return
}
res, err := json.Marshal(ticket)
if err != nil {
http.Error(w, er.ErrInternal.Error(), http.StatusInternalServerError)
return
}
log.Printf("%v tickets allocated with name %v\n", t.Allocation, t.Name)
s.sendResponse(w, res, http.StatusOK)
}
Actual logic that interacts with DB. This code segment is invoked by the handler as you can see in the code above. ticket, err := s.TicketService.CreateTicketOption(r.Context(), t)
func (t *TicketService) CreateTicketOption(ctx context.Context, ticket ticket.Ticket) (*ticket.Ticket, error) {
tx, err := t.db.dbPool.Begin(ctx)
if err != nil {
return nil, er.ErrInternal
}
defer tx.Rollback(ctx)
var id int
err = tx.QueryRow(ctx, `INSERT INTO ticket (name, description, allocation) VALUES ($1, $2, $3) RETURNING id`, ticket.Name, ticket.Description, ticket.Allocation).Scan(&id)
if err != nil {
return nil, er.ErrInternal
}
ticket.Id = id
return &ticket, tx.Commit(ctx)
}
And that is my unit test for the handler.
func TestCreateTicketOptionHandler(t *testing.T) {
caseExpected, _ := json.Marshal(&ticket.Ticket{Id: 1, Name: "baris", Description: "test-desc", Allocation: 10})
srv := NewServer()
// expected := [][]byte{
// _, _ = json.Marshal(&ticket.Ticket{Id: 1, Name: "baris", Description: "test-desc", Allocation: 20}),
// // json.Marshal(&ticket.Ticket{Id: 1, Name: "baris", Description: "test-desc", Allocation: 20})
// }
tt := []struct {
name string
entry *ticket.Ticket
want []byte
code int
}{
{
"valid",
&ticket.Ticket{Name: "baris", Description: "test-desc", Allocation: 10},
caseExpected,
http.StatusOK,
},
}
var buf bytes.Buffer
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
json.NewEncoder(&buf).Encode(tc.entry)
req, err := http.NewRequest(http.MethodPost, "/ticket_options", &buf)
log.Println("1")
if err != nil {
log.Println("2")
t.Fatalf("could not create request: %v", err)
}
log.Println("3")
rec := httptest.NewRecorder()
log.Println("4")
srv.handleCreateTicketOption(rec, req)
log.Println("5")
if rec.Code != tc.code {
t.Fatalf("got status %d, want %v", rec.Code, tc.code)
}
log.Println("6")
if reflect.DeepEqual(rec.Body.Bytes(), tc.want) {
log.Println("7")
t.Fatalf("NAME:%v, got %v, want %v", tc.name, rec.Body.Bytes(), tc.want)
}
})
}
}
I did research about mocking pgx about most of them were testing the logic part not through the handler. I want to write unit test for both handler and logic itself seperately. However, the unit test I've written for the handler panics as below
github.com/bariis/gowit-case-study/psql.(*TicketService).CreateTicketOption(0xc000061348, {0x1485058, 0xc0000260c0}, {0x0, {0xc000026dd0, 0x5}, {0xc000026dd5, 0x9}, 0xa})
/Users/barisertas/workspace/gowit-case-study/psql/ticket.go:24 +0x125
github.com/bariis/gowit-case-study/http.(*Server).handleCreateTicketOption(0xc000061340, {0x1484bf0, 0xc000153280}, 0xc00018e000)
/Users/barisertas/workspace/gowit-case-study/http/ticket.go:77 +0x10b
github.com/bariis/gowit-case-study/http.TestCreateTicketOptionHandler.func2(0xc000119860)
/Users/barisertas/workspace/gowit-case-study/http/ticket_test.go:80 +0x305
psql/ticket.go:24: tx, err := t.db.dbPool.Begin(ctx)
http/ticket.go:77: ticket, err := s.TicketService.CreateTicketOption(r.Context(), t)
http/ticket_test.go:80: srv.handleCreateTicketOption(rec, req)
How can I mock this type of code?
Create an interface which has the required DB functions
Your DB handler implements this interface. You use the handler in actual execution
Create a mock handler using testify/mock and use this in place of DB handler in test cases
From what I can read, you have the following structure:
type Server struct {
TicketService ticket.Service
}
type TicketService struct {
db *sql.Db // ..or similar
}
func (ts *TicketService) CreateTicketOption(...)
The trick to mock this is by ensuring ticket.Service is an interface instead of a struct.
Like this:
type TicketService interface {
CreateTicketOption(ctx context.Context, ticket ticket.Ticket) (*ticket.Ticket, error) {
}
By doing this, your Server expects a TicketService interface.
Then you could do this:
type postgresTicketService struct {
db *sql.Db
}
func (pst *postgresTicketService) CreateTicketOption(...)...
Which means that the postgresTicketService satisfies the requirements to be passed as a ticket.Service to the Server.
This also means that you can do this:
type mockTicketService struct {
}
func (mts *mockTicketService) CreateTicketOption(...)...
This way you decouple the Server from the actual implementation, and you could just init the Server with the mockTicketService when testing and postgresTicketService when deploying.

Sending a POST Request to external API from GCP cloud function returns 500 but not when sent locally

I'm currently trying to send a POST request to an external API from a GCP Cloud Function. I've tested the function extensively locally and it fulfills the request every time and also works from Postman, but when I run the exact same code from within a cloud function, it returns a 500 from the external API every single time.
I'm genuinely at a loss as to why when sending the POST request from within the cloud function it fails every single time.
Does GCP add any headers that might interfere with an external API call or is there a configuration option within the cloud function settings that needs to be configured to allow an external POST request?
I've attempted to implement an http retry mechanism, but that did not work either.
Again, locally and from Postman, the exact same code is successful every time I run it.
Here is the code I use to generate and send the request:
package email
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"net/http"
"github.com/hashicorp/go-retryablehttp"
)
var FailedRequestErr = errors.New("failed request to moosend")
const (
successCode = 0
moosendHost = "api.moosend.com/v3"
dailyNewsletterMailingListID = "2e461f4c-99d1-4a8e-80ea-168b20bdaf5f"
mainEmail = "jason#functionalbits.io"
campaignNameBase = "Functional Bits Newsletter - Issue"
campaignSubjectBase = "Functional Bits Issue"
)
type CreatingADraftCampaignRequest struct {
Name string `json:"Name"`
Subject string `json:"Subject"`
SenderEmail string `json:"SenderEmail"`
ReplyToEmail string `json:"ReplyToEmail"`
IsAB string `json:"IsAB"`
ConfirmationToEmail string `json:"ConfirmationToEmail,omitempty"`
WebLocation string `json:"WebLocation,omitempty"`
MailingLists []MailingLists `json:"MailingLists,omitempty"`
SegmentID string `json:"SegmentID,omitempty"`
ABCampaignType string `json:"ABCampaignType,omitempty"`
TrackInGoogleAnalytics string `json:"TrackInGoogleAnalytics,omitempty"`
DontTrackLinkClicks string `json:"DontTrackLinkClicks,omitempty"`
SubjectB string `json:"SubjectB,omitempty"`
WebLocationB string `json:"WebLocationB,omitempty"`
SenderEmailB string `json:"SenderEmailB,omitempty"`
HoursToTest string `json:"HoursToTest,omitempty"`
ListPercentage string `json:"ListPercentage,omitempty"`
ABWinnerSelectionType string `json:"ABWinnerSelectionType,omitempty"`
}
type MailingLists struct {
MailingListID string `json:"MailingListId"`
SegmentID float64 `json:"SegmentId,omitempty"`
}
type CampaignResponse struct {
Code int32 `json:"Code"`
Err interface{} `json:"Error"`
Context interface{} `json:"Context"`
}
type MoosendAPI struct {
apiKey string
client *http.Client
}
func NewMoosendAPI(apiKey string) *MoosendAPI {
retryClient := retryablehttp.NewClient()
retryClient.RetryMax = 5
standardClient := retryClient.StandardClient()
return &MoosendAPI{
apiKey: apiKey,
client: standardClient,
}
}
func (m *MoosendAPI) CreateDraftCampaign(issueNumber string, webLocation string) (*CampaignResponse, error) {
campaign := CreatingADraftCampaignRequest{
Name: fmt.Sprintf("%s %s", campaignNameBase, issueNumber),
Subject: fmt.Sprintf("%s %s", campaignSubjectBase, issueNumber),
IsAB: "false",
WebLocation: webLocation,
MailingLists: []MailingLists{{MailingListID: dailyNewsletterMailingListID}},
SenderEmail: mainEmail,
ReplyToEmail: mainEmail,
ConfirmationToEmail: mainEmail,
TrackInGoogleAnalytics: "true",
}
body, err := json.Marshal(&campaign)
if err != nil {
log.Println("error marshalling campaign request")
return nil, err
}
fullURL := fmt.Sprintf("https://%s/campaigns/create.json?apikey=%s", moosendHost, m.apiKey)
req, err := http.NewRequest(http.MethodPost, fullURL, bytes.NewBuffer(body))
if err != nil {
log.Println("request error")
return nil, err
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Accept", "application/json")
log.Printf("request: %+v", req)
resp, err := m.client.Do(req)
if resp.StatusCode != http.StatusOK {
return nil, FailedRequestErr
}
if err != nil {
log.Println("error sending request")
return nil, err
}
log.Printf("response: %+v", resp)
defer resp.Body.Close()
respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println("error reading response body")
return nil, err
}
var draftResponse CampaignResponse
if err := json.Unmarshal(respBody, &draftResponse); err != nil {
log.Println("error unmarshalling response")
log.Printf("%+v", draftResponse)
return nil, err
}
return &draftResponse, nil
}
func (m *MoosendAPI) SendCampaign(campaignID string) error {
fullURL := fmt.Sprintf("https://%s/campaigns/%s/send.json?apikey=%s", moosendHost, campaignID, m.apiKey)
req, err := http.NewRequest(http.MethodPost, fullURL, nil)
if err != nil {
log.Println("error creating request")
return err
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Accept", "application/json")
resp, err := m.client.Do(req)
if err != nil {
log.Println("error sending request")
return err
}
defer resp.Body.Close()
respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println("error reading response body")
return err
}
var sendResponse CampaignResponse
if err := json.Unmarshal(respBody, &sendResponse); err != nil {
log.Println("error unmarshalling response")
log.Printf("%+v", sendResponse)
return err
}
return nil
}
Then how it's run in the main function code:
package function
import (
"context"
"encoding/json"
"errors"
"log"
"os"
"github.com/Functional-Bits/emailer-service/internal/email"
"github.com/Functional-Bits/emailer-service/internal/publish"
)
func CampaignGenerator(ctx context.Context, m publish.PubSubMessage) error {
moosendAPIKey, ok := os.LookupEnv("MOOSEND_API_KEY")
if !ok {
log.Println("missing moosendAPIKey")
}
mAPI := email.NewMoosendAPI(moosendAPIKey)
var msg publish.IncomingMessage
if err := json.Unmarshal(m.Data, &msg); err != nil {
log.Println(err)
return err
}
log.Printf("received message: %+v", msg)
log.Printf("generating draft campaign for issue %s", msg.IssueNumber)
draftResponse, err := mAPI.CreateDraftCampaign(msg.IssueNumber, msg.FileURL)
if err != nil {
log.Println(err)
return err
}
log.Printf("draft response: %+v", draftResponse)
campaignID, ok := draftResponse.Context.(string)
if !ok {
log.Printf("response didn't contain an ID: %+v", draftResponse)
return errors.New("no campaign generated")
}
log.Printf("sending campgain %s", campaignID)
if err := mAPI.SendCampaign(campaignID); err != nil {
log.Println(err)
return err
}
log.Printf("campaign successfully sent for issue number %s", msg.IssueNumber)
return nil
}
When this code is run locally, It correctly makes the 2 calls and sends an email campaign. When run from the cloud function I get a 500 internal server error with no additional information as to why. Link to API docs.
I get the following response from the external API (from my cloud function logs)
response: &{
Status:500 Internal Server Error
StatusCode:500
Proto:HTTP/1.1
ProtoMajor:1
ProtoMinor:1
Header:map[Access-Control-Allow-Headers:[Content-Type, Accept, Cache-Control, X-Requested-With]
Access-Control-Allow-Methods:[GET, POST, OPTIONS, DELETE, PUT]
Access-Control-Allow-Origin:[*]
Cache-Control:[private]
Content-Length:[12750]
Content-Type:[text/html; charset=utf-8]
Date:[Sun, 12 Dec 2021 07:00:09 GMT]
Server:[Microsoft-IIS/10.0]
X-Aspnet-Version:[4.0.30319]
X-Powered-By:[ASP.NET]
X-Robots-Tag:[noindex, nofollow]
X-Server-Id:[1]]
Body:0xc0003f04c0
ContentLength:12750
TransferEncoding:[]
Close:false
Uncompressed:false
Trailer:map[]
Request:0xc000160b00
TLS:0xc000500630
}
The response causes an unmarshal error because no campaign ID is returned.

Create unit test for WS in golang using Echo router

I use gorilla/websocket for ws and labstack/echo as router. I need to create unit test for the handler. I find topic with solving this problem with default go router, but i don't understand how to use it with echo.
I have this:
func TestWS(t *testing.T){
provider := handler.New(coordinateservice.New())
e := echo.New()
rec := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/admin/orders/:id/details", nil)
c := e.NewContext(req, rec)
c.SetPath("/admin/orders/:id/details")
c.SetParamNames("id")
c.SetParamValues("9999")
if assert.NoError(t, provider.OrderHandler.OpenWs(c)) {
assert.Equal(t, http.StatusOK, rec.Code)
}
u := url.URL{Scheme: "ws", Host: "127.0.0.1", Path: "/admin/orders/9999/details"}
fmt.Println(u.String())
// Connect to the server
ws, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
if err != nil {
t.Fatalf("%v", err)
}
defer ws.Close()
_, p, err := ws.ReadMessage()
if err != nil {
t.Fatalf("%v", err)
}
fmt.Println(string(p))
}
And error websocket: the client is not using the websocket protocol: 'upgrade' token not found in 'Connection' header in this line:
ws, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
if err != nil {
t.Fatalf("%v", err)
}
What i need to do for connecting ws to the echo handler?
The this is my golang unit test websocket example.
The stability of my httptest library is not guaranteed, but you can refer to using net.Pipe to create a connection for ws.
use echo:
func main() {
app := echo.New()
app.GET("/", hello)
client := httptest.NewClient(app)
go func() {
// use http.Handler, if not has host.
client.NewRequest("GET", "/example/wsio").WithWebsocket(handlerGobwasWebsocket).Do().Out()
// use network
client.NewRequest("GET", "http://localhost:8088/example/wsio").WithWebsocket(handlerGobwasWebsocket).Do().Out()
}()
app.Start(":8088")
}
func hello(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
}
func handlerGobwasWebsocket(conn net.Conn) {
go func() {
wsutil.WriteClientBinary(conn, []byte("aaaaaa"))
wsutil.WriteClientBinary(conn, []byte("bbbbbb"))
wsutil.WriteClientBinary(conn, []byte("ccccc"))
}()
defer conn.Close()
for {
b, err := wsutil.ReadServerBinary(conn)
fmt.Println("ws io client read: ", string(b), err)
if err != nil {
fmt.Println("gobwas client err:", err)
return
}
}
}

Golang Escape Question Mark Character for Cisco Regex

So, Cisco's regex allows the question mark character. But the catch is that you have to precede typing a question mark with Ctrl-Shift-v in order for it to be interpreted as a question mark and not a help command... Link to Cisco regex guidelines
I have a Go program that logs into a set of devices and runs a set of commands on each device. When trying to use a regex containing a question mark, though, the Cisco device always interprets the question mark as a help command. Using string literals in Go does not fix the problem nor does sending the command as a slice of bytes.
For example, if I try to send the command show boot | include (c|cat)[0-9]+[a-zA-Z]? the Cisco CLI returns
switch-1#show boot | include (c|cat)[0-9]+[a-zA-Z]?
LINE <cr>
switch-1#
instead of interpreting the question mark as a regex match of 0 or 1 for the [a-zA-Z] group.
However, using the command ssh user#switch-1 'show boot | include (c|cat)[0-9]+[a-zA-Z]?' works as expected and interprets the regex pattern correctly.
How can I replicate the behaviour of the ssh command? Is there a way to send Ctrl-Shift-v before each question mark or escape each question mark character?
My code as requested:
package main
import (
"golang.org/x/crypto/ssh"
"net"
"fmt"
"os"
"bufio"
"time"
"golang.org/x/crypto/ssh/terminal"
"io"
"io/ioutil"
"sync"
"strings"
)
// ReadLines reads a file line-by-line and returns a slice of the lines.
func ReadLines(filename string) ([]string, error) {
f, err := os.Open(filename)
if err != nil {
return nil, fmt.Errorf("failed to open file: %v", err)
}
defer f.Close()
var lines []string
s := bufio.NewScanner(f)
for s.Scan() {
lines = append(lines, s.Text())
}
if err := s.Err(); err != nil {
return nil, err
}
return lines, nil
}
// Type Result represents the result of running the Configure method.
type Result struct {
Host string // Hostname of device
Output []byte // Remote shell's stdin and stderr output
Err error // Remote shell errors
}
// Configure logs into a device, starts a remote shell, runs the set of
// commands, and waits for the remote shell to return or timeout.
func Configure(host string, config *ssh.ClientConfig, cmds []string, results chan<- *Result, wg *sync.WaitGroup) {
defer wg.Done()
res := &Result{
Host: host,
Output: nil,
Err: nil,
}
// Create client connection
client, err := ssh.Dial("tcp", net.JoinHostPort(host, "22"), config)
if err != nil {
res.Err = fmt.Errorf("failed to dial: %v", err)
results <- res
return
}
defer client.Close()
// Create new session
session, err := client.NewSession()
if err != nil {
res.Err = fmt.Errorf("failed to create session: %v", err)
results <- res
return
}
defer session.Close()
// Set session IO
stdin, err := session.StdinPipe()
if err != nil {
res.Err = fmt.Errorf("failed to create pipe to stdin: %v", err)
results <- res
return
}
defer stdin.Close()
stdout, err := session.StdoutPipe()
if err != nil {
res.Err = fmt.Errorf("failed to create pipe to stdout: %v", err)
results <- res
return
}
stderr, err := session.StderrPipe()
if err != nil {
res.Err = fmt.Errorf("failed to create pipe to stderr: %v", err)
results <- res
return
}
// Start remote shell
if err := session.RequestPty("vt100", 0, 0, ssh.TerminalModes{
ssh.ECHO: 0,
ssh.TTY_OP_ISPEED: 14400,
ssh.TTY_OP_OSPEED: 14400,
}); err != nil {
res.Err = fmt.Errorf("failed to request pseudoterminal: %v", err)
results <- res
return
}
if err := session.Shell(); err != nil {
res.Err = fmt.Errorf("failed to start remote shell: %v", err)
results <- res
return
}
// Run commands
for _, cmd := range cmds {
if _, err := io.WriteString(stdin, cmd+"\n"); err != nil {
res.Err = fmt.Errorf("failed to run: %v", err)
results <- res
return
}
}
// Wait for remote commands to return or timeout
exit := make(chan error, 1)
go func(exit chan<- error) {
exit <- session.Wait()
}(exit)
timeout := time.After(1 * time.Minute)
select {
case <-exit:
output, err := ioutil.ReadAll(io.MultiReader(stdout, stderr))
if err != nil {
res.Err = fmt.Errorf("failed to read output: %v", err)
results <- res
return
}
res.Output = output
results <- res
return
case <-timeout:
res.Err = fmt.Errorf("session timed out")
results <- res
return
}
}
func main() {
hosts, err := ReadLines(os.Args[1])
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
cmds, err := ReadLines(os.Args[2])
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Fprint(os.Stderr, "Password: ")
secret, err := terminal.ReadPassword(int(os.Stdin.Fd()))
if err != nil {
fmt.Fprintf(os.Stderr, "failed to read password: %v\n", err)
os.Exit(1)
}
fmt.Fprintln(os.Stderr)
config := &ssh.ClientConfig{
User: "user",
Auth: []ssh.AuthMethod{ssh.Password(string(secret))},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Timeout: 30 * time.Second,
}
config.SetDefaults()
config.Ciphers = append(config.Ciphers, "aes128-cbc", "3des-cbc", "aes192-cbc", "aes256-cbc")
results := make(chan *Result, len(hosts))
var wg sync.WaitGroup
wg.Add(len(hosts))
for _, host := range hosts {
go Configure(host, config, cmds, results, &wg)
}
wg.Wait()
close(results)
for res := range results {
if res.Err != nil {
fmt.Fprintf(os.Stderr, "Error %s: %v\n", res.Host, res.Err)
continue
}
fmt.Printf("Host %s\n%s\n%s\n", res.Host, res.Output, strings.Repeat("-", 50))
}
}
Try forcing the IOS terminal server into line mode (as opposed to character mode). Send these telnet negotiation sequences:
IAC DONT ECHO
IAC WONT ECHO
IAC DONT SUPPRESS-GO-AHEAD
IAC WONT SUPPRESS-GO-AHEAD
See: https://www.rfc-editor.org/rfc/rfc858