Unauthorized when using gocosmos to create a document - go

I got go-sql-driver for Azure CosmosDB from https://github.com/btnguyen2k/gocosmos.
It goes well when i call gocosmos.NewRestClient to get a rest client, CreateDatabase() to create database and CreateCollection() to create collection.
The problem is when i use CreateDocument(), i get response with statuscode 401 and body like this
{"code":"Unauthorized","message":"The input authorization token can't serve the request. Please check that the expected payload is built as per the protocol, and check the key being used. Server used the following payload to sign: 'post\ndocs\ndbs/ToDoList/colls/Items\nmon, 31 may 2021 13:31:44 gmt\n\n'\r\nActivityId: a9bbd729-3495-400f-9d79-ddec3737aa92, Microsoft.Azure.Documents.Common/2.11.0"}
i've tried all the solutions I've seen, but i haven't solved the problem.

I followed this tutorial and with this sample code, I can successfully create database, collection and document. Here's my testing result, can it help you?
// connects to MongoDB
func connect() *mongo.Client {
mongoDBConnectionString := os.Getenv(mongoDBConnectionStringEnvVarName)
if mongoDBConnectionString == "" {
log.Fatal("missing environment variable: ", mongoDBConnectionStringEnvVarName)
}
database = os.Getenv(mongoDBDatabaseEnvVarName)
if database == "" {
log.Fatal("missing environment variable: ", mongoDBDatabaseEnvVarName)
}
collection = os.Getenv(mongoDBCollectionEnvVarName)
if collection == "" {
log.Fatal("missing environment variable: ", mongoDBCollectionEnvVarName)
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
clientOptions := options.Client().ApplyURI(mongoDBConnectionString).SetDirect(true)
c, err := mongo.NewClient(clientOptions)
err = c.Connect(ctx)
if err != nil {
log.Fatalf("unable to initialize connection %v", err)
}
err = c.Ping(ctx, nil)
if err != nil {
log.Fatalf("unable to connect %v", err)
}
return c
}
// creates a todo
func create(desc string) {
c := connect()
ctx := context.Background()
defer c.Disconnect(ctx)
todoCollection := c.Database(database).Collection(collection)
r, err := todoCollection.InsertOne(ctx, Todo{Description: desc, Status: statusPending})
if err != nil {
log.Fatalf("failed to add todo %v", err)
}
fmt.Println("added todo", r.InsertedID)
}

Related

How to update DB connection in GORM with new AWS token

I have a gorm connection that I initially create by passing an AWS authentication token that expires every 15 minutes. The service will be able to connect to the DB for 15 minute. I have some functions for connecting to the db, for creating a new token, and for using a cron library to "refresh" the connection. I'm stuck in knowing how to actually provide the new token to GORM. I though of just doing gorm.Open() from within the cron job, but was told this would be bad because it'd not be thread safe, and would leave the old connections open. I was directed to this but I'm not sure what to do with it:
https://golang.org/pkg/database/sql/driver/#Connector
There is an example here:
https://github.com/aws/aws-sdk-go/issues/3043#issuecomment-581931580
But I'm not sure what I'd replace the driver and connector code in that example for GORM. Anyhow.
Here's the code I have so far. First this connection function that wraps around gorm.Open:
func Connect(conf *config.DbConfig) (*gorm.DB, error) {
host, port, err := net.SplitHostPort(conf.Host)
if err != nil {
return nil, err
}
sslMode := "require"
if conf.DisableTLS {
sslMode = "disable"
}
return gorm.Open(conf.Dialect,
fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=%s",
host, port, conf.Username, conf.Password, conf.Name, sslMode))
}
This is called in main like so:
//IF WE WANT TO USE AWS TOKEN, REPLACE PW IN CONFIG WITH TOKEN
if conf.DB.UseAwsToken {
authToken, err := field.CreateAuthToken(&conf.DB)
if err != nil {
logger.Fatal(err)
}
conf.DB.Password = authToken
}
//CONNECT TO DB
db, err := field.Connect(&conf.DB)
if err != nil {
logger.Fatal(err)
}
defer db.Close()
//INITIALIZE CRON JOB THAT RUNS IN SEPARATE THREAD TO REFRESH CONNECTION
if conf.DB.UseAwsToken {
processorCron := cron.New()
cronInterval := "#every 10m"
if conf.DB.CronInterval > 0 {
cronInterval = fmt.Sprintf("#every %s", conf.DB.CronInterval)
}
processorCron.AddFunc(cronInterval, func() {
repo.RenewDB(&conf.DB)
})
processorCron.Start()
}
The part I'm stuck with the actual code for the RenewDB function, in the spot with the TODO:
type Repo struct {
db *gorm.DB
}
// FUNCTION
func (r *Repo) RenewDB(dbConf *config.DbConfig) {
var err error
if dbConf.Password, err = CreateAuthToken(dbConf); err != nil {
panic(fmt.Sprintf("failed at createAuthToken: %s\n", err.Error()))
}
//TODO: HOW DO I UPDATE GORM TO USE THE NEW AUTH TOKEN THAT I INSERTED
// INTO dbConf?
}
//AUTH TOKEN GENERATOR
func CreateAuthToken(dbConf *config.DbConfig) (string, error) {
awsSession, err := session.NewSession()
if err != nil {
return "", err
}
authToken, errToken := rdsutils.BuildAuthToken(dbConf.Host, dbConf.AwsRegion, dbConf.Username, awsSession.Config.Credentials)
if errToken != nil {
return "", errToken
}
return authToken, nil
}

How to turn DataBase access into a Function idiomatically in Go

I have built a Backend API in Go, it works however I want refactor the code for the DB access layer into a function - idiomatically.
// Get the form data entered by client; FirstName, LastName, phone Number,
// assign the person a unique i.d
// check to see if that user isn't in the database already
// if they are send an error message with the a 'bad' response code
// if they aren't in db add to db and send a message with success
func CreateStudentAccountEndpoint(response http.ResponseWriter, request *http.Request){
client, err := mongo.NewClient("mongodb://localhost:27017")
if err != nil {
log.Fatalf("Error connecting to mongoDB client Host: Err-> %v\n ", err)
}
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
err = client.Connect(ctx)
if err != nil {
log.Fatalf("Error Connecting to MongoDB at context.WtihTimeout: Err-> %v\n ", err)
}
response.Header().Set("Content-Type", "application/json")
studentCollection := client.Database(dbName).Collection("students")
_, err = studentCollection.InsertOne(context.Background(),data)
if err != nil {
response.WriteHeader(501)
response.Write([]byte(`{ "message": "` + err.Error() + `" }`))
}
// encoding json object for returning to the client
jsonStudent, err := json.Marshal(student)
if err != nil {
http.Error(response, err.Error(), http.StatusInternalServerError)
}
response.Write(jsonStudent)
}
I understand that I can create a method which returns (*mongoClient, err) as I utilise the client local variable later on in the code.
However I am lost as to how to implement the defer cancel() part because it executes once the method CreateStudenAccountEndpoint is at the end. But I am at a loss on how to implement this defer section in a method that will recognise that I want the defer to happen at the end of the function that calls the DB access layer method e.g CreateStudentAccountEndpoint not the actual db access method itself.
As I understand it, the connection should be long-lived and set up as a part of a constructor, i.e. not part of the request flow.
This will typically look something like this:
type BackendAPI struct {
client *mongo.Client
}
func NewBackendAPI(mongoURI string) (*BackendAPI, error) {
client, err := mongo.NewClient(mongoURI)
if err != nil {
return nil, err
}
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
err = client.Connect(ctx)
if err != nil {
return nil, err
}
return &BackendAPI{client}, nil
}
func (api *BackendAPI) func CreateStudentAccountEndpoint(response http.ResponseWriter, request *http.Request) {
response.Header().Set("Content-Type", "application/json")
// note the use of the long-lived api.client, which is connected already.
studentCollection := api.client.Database(dbName).Collection("students")
_, err = studentCollection.InsertOne(context.Background() ,data)
if err != nil {
response.WriteHeader(501)
response.Write([]byte(`{ "message": "` + err.Error() + `" }`))
return // at this point, the method should return
}
// encoding json object for returning to the client
jsonStudent, err := json.Marshal(student)
if err != nil {
http.Error(response, err.Error(), http.StatusInternalServerError)
}
response.Write(jsonStudent)
}
If you worry about losing the connection, you could implement a call to api.client.Ping in there, but in my opinion this should only be attempted if you encounter a failure you believe you can recover from by reconnecting.

how to edit an existing data in cloud storage of GCP using an api in golang

I am creating an application which is communicating with google datastore, to fetch the existing data, and perform add, edit and delete operations on that existing data. I am able to fetch the existing data, and delete the data there. But not getting how to edit/update the data there through api in golang.
Giving the code snippet which I am trying to execute for this :
func EditCustomer(w http.ResponseWriter, r *http.Request){
ctx := context.Background()
params := mux.Vars(r)
customer_id :=params["partner_id"]
projectID := util.MustGetenv("GOOGLE_CLOUD_PROJECT")
client, err := datastore.NewClient(ctx, projectID)
if err != nil {
log.Fatalf("Failed to create client: %v", err)
}
var customer models.Customer
kind := util.MustGetenv("DATA_STORE_KIND")
ds.EditCustomer(client,kind,customer_id,&customer,ctx)
json.NewEncoder(w).Encode(customer)
}
EditCustomer method in dao is as :
func EditCustomer(client *datastore.Client,kind string ,name string,dst interface{},ctx context.Context) {
taskKey := datastore.NameKey(kind, name, nil)
< some methos here to edit and update itin datstorage >
}
Please advise for this. Anybody there who working with api's dev in golang ?
I would update like this:
currentCustomer := &Customer{}
_, err := client.RunInTransaction(ctx, func(tx *datastore.Transaction) error {
if getErr := tx.Get(key, currentCustomer); getErr != nil {
return getErr
}
// edit your object
var putErr error
_, putErr = tx.Put(key, updatedCustomer)
return putErr
})
if err != nil {
return nil, err
}
_, err = client.Put(ctx, key, customer)

gRPC - GoLang - Stackdriver tracer

I am trying to get the stackdriver tracer to work with gRPC and I need some help. I have been looking at these two links for reference and I still can't get it to work:
https://medium.com/#harlow/tracing-grpc-calls-in-golang-with-google-stackdriver-b22495763a06#.81oa9q21v
https://rakyll.org/grpc-trace/
For simplicity, I am just working with the hello world gRPC example. Here's my client:
func main() {
// Set up a connection to the server.
conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure(), grpc.WithUnaryInterceptor(grpc.UnaryClientInterceptor(clientInterceptor)))
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewGreeterClient(conn)
ctx := context.Background()
tc, err := trace.NewClient(ctx, "{PROJECT-ID}")
if err != nil {
log.Fatal(err)
}
span := tc.NewSpan("/greeter/SayHello")
defer span.Finish()
ctx = trace.NewContext(ctx, span)
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: "world"})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
println("Response:", r.Message)
}
func clientInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
// trace current request w/ child span
span := trace.FromContext(ctx).NewChild(method)
defer span.Finish()
// new metadata, or copy of existing
md, ok := metadata.FromContext(ctx)
if !ok {
md = metadata.New(nil)
} else {
md = md.Copy()
}
// append trace header to context metadata
// header specification: https://cloud.google.com/trace/docs/faq
md["X-Cloud-Trace-Context"] = append(
md["X-Cloud-Trace-Context"], fmt.Sprintf("%s/%d;o=1", span.TraceID(), 0),
)
ctx = metadata.NewContext(ctx, md)
return invoker(ctx, method, req, reply, cc, opts...)
}
.. and my gRPC server:
// server is used to implement helloworld.GreeterServer.
type server struct{}
// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
println("HERE")
return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}
func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
ctx := context.Background()
tc, err := trace.NewClient(ctx, "{PROJECT-ID}")
if err != nil {
log.Fatal(err)
}
s := grpc.NewServer(EnableGRPCTracingServerOption(tc))
pb.RegisterGreeterServer(s, &server{})
println("listening on :50051")
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
// EnableGRPCTracingServerOption enables parsing google trace header from metadata
// and adds a new child span to the incoming request context.
func EnableGRPCTracingServerOption(traceClient *trace.Client) grpc.ServerOption {
return grpc.UnaryInterceptor(serverInterceptor(traceClient))
}
func serverInterceptor(traceClient *trace.Client) grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
// fetch metadata from request context
md, ok := metadata.FromContext(ctx)
if !ok {
md = metadata.New(nil)
}
header := strings.Join(md["X-Cloud-Trace-Context"], "")
// create new child span from google trace header, add to
// current request context
span := traceClient.SpanFromHeader(info.FullMethod, header)
defer span.Finish()
ctx = trace.NewContext(ctx, span)
return handler(ctx, req)
}
}
I when I run the client to initiate the trace, I get the error:
rpc error: code = 13 desc = stream terminated by RST_STREAM with error code: 1
I'm confused because I don't see anything else about authentication; only providing the project ID which can't be enough to initiate tracing for a specific project. What am I missing?
The issue was with:
defer span.Finish()
That call does not block so because I was just doing preliminary testing with one call my program was exiting before the traces could be uploaded. I contacted the author of https://rakyll.org/grpc-trace/ and she actually updated her post with the option of using:
defer span.FinishWait()
which blocks and that fixed it by allowing the traces to be successfully uploaded before the program exited.
Also, with a long running webserver this wouldn't have been an issue because the process wouldn't have been terminated.
I followed those same tutorials and ran into similar problems.
Header keys are converted to lowercase. If you retrieve it on the server side with header := strings.Join(md["x-cloud-trace-context"], "") you should be good.
You can also define your metadata headers with:
span := trace.FromContext(ctx).NewChild(method)
defer span.Finish()
md := metadata.Pairs(
"x-cloud-trace-context", fmt.Sprintf("%s/%d;o=1", span.TraceID(), 0),
)
ctx = metadata.NewContext(ctx, md)

Golang google sheets API V4 - Write/Update example?

Trying to write a simple three column table ([][]string) with Go, but can't.
The quick start guide is very nice, I now can read sheets, but there no any example of how to write data to a sheet, maybe it is trivial, but not for me it seems.
The Golang library for my brains is just too complicated to figure out.
And there not a single example I could google...
This C# example very looks close, but I am not sure I clearly understand C#
Well after some tryouts, there is an answer. Everything is same as in https://developers.google.com/sheets/quickstart/go Just changes in the main function
func write() {
ctx := context.Background()
b, err := ioutil.ReadFile("./Google_Sheets_API_Quickstart/client_secret.json")
if err != nil {
log.Fatalf("Unable to read client secret file: %v", err)
}
// If modifying these scopes, delete your previously saved credentials
// at ~/.credentials/sheets.googleapis.com-go-quickstart.json
config, err := google.ConfigFromJSON(b, "https://www.googleapis.com/auth/spreadsheets")
if err != nil {
log.Fatalf("Unable to parse client secret file to config: %v", err)
}
client := getClient(ctx, config)
srv, err := sheets.New(client)
if err != nil {
log.Fatalf("Unable to retrieve Sheets Client %v", err)
}
spreadsheetId := "YOUR SPREADSHEET ID"
writeRange := "A1"
var vr sheets.ValueRange
myval := []interface{}{"One", "Two", "Three"}
vr.Values = append(vr.Values, myval)
_, err = srv.Spreadsheets.Values.Update(spreadsheetId, writeRange, &vr).ValueInputOption("RAW").Do()
if err != nil {
log.Fatalf("Unable to retrieve data from sheet. %v", err)
}
}
Well if you are looking for Service Account based authentication, then the following worked for me.
Download the client secret file for service account from https://console.developers.google.com
import (
"fmt"
"golang.org/x/net/context"
"google.golang.org/api/option"
"google.golang.org/api/sheets/v4"
"log"
)
const (
client_secret_path = "./credentials/client_secret.json"
)
func NewSpreadsheetService() (*SpreadsheetService, error) {
// Service account based oauth2 two legged integration
ctx := context.Background()
srv, err := sheets.NewService(ctx, option.WithCredentialsFile(client_secret_path), option.WithScopes(sheets.SpreadsheetsScope))
if err != nil {
log.Fatalf("Unable to retrieve Sheets Client %v", err)
}
c := &SpreadsheetService{
service: srv,
}
return c, nil
}
func (s *SpreadsheetService) WriteToSpreadsheet(object *SpreadsheetPushRequest) error {
var vr sheets.ValueRange
vr.Values = append(vr.Values, object.Values)
res, err := s.service.Spreadsheets.Values.Append(object.SpreadsheetId, object.Range, &vr).ValueInputOption("RAW").Do()
fmt.Println("spreadsheet push ", res)
if err != nil {
fmt.Println("Unable to update data to sheet ", err)
}
return err
}
type SpreadsheetPushRequest struct {
SpreadsheetId string `json:"spreadsheet_id"`
Range string `json:"range"`
Values []interface{} `json:"values"`
}
change the scope in the quickstart example from spreadsheets.readonly to spreadsheets for r/w access
here is the write snippet:
writeRange := "A1" // or "sheet1:A1" if you have a different sheet
values := []interface{}{"It worked!"}
var vr sheets.ValueRange
vr.Values = append(vr.Values,values
_, err = srv.Spreadsheets.Values.Update(spreadsheetId,writeRange,&vr).ValueInputOption("RAW").Do()
and that should work:

Resources