I currently retrieve my organization (family) Google Calendars via the Google Calendar API, using Python.
For reference, the credentials file used in both cases is ((...) is redacted information)
{
"type": "service_account",
"project_id": "da(...)",
"private_key_id": "8(...)4",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAI(...)Kag==\n-----END PRIVATE KEY-----\n",
"client_email": "da(...)#(...).iam.gserviceaccount.com",
"client_id": "1(...)9",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/da(...)iam.gserviceaccount.com"
}
I would like to port this to Go. To do so, I used the following code (partly copied from the documentation)
package main
import (
"context"
"fmt"
"time"
log "github.com/sirupsen/logrus"
"google.golang.org/api/calendar/v3"
"google.golang.org/api/option"
)
func googlecalendar() (err error) {
ctx := context.Background()
calendarService, err := calendar.NewService(ctx, option.WithCredentialsFile("googlecalendar.json"))
if err != nil {
return err
}
t := time.Now().Format(time.RFC3339)
events, err := calendarService.Events.List("john#example.com").ShowDeleted(false).
SingleEvents(true).TimeMin(t).MaxResults(10).OrderBy("startTime").Do()
if err != nil {
log.Fatalf("Unable to retrieve next ten of the user's events: %v", err)
}
fmt.Println("Upcoming events:")
if len(events.Items) == 0 {
fmt.Println("No upcoming events found.")
} else {
for _, item := range events.Items {
date := item.Start.DateTime
if date == "" {
date = item.Start.Date
}
fmt.Printf("%v (%v)\n", item.Summary, date)
}
}
return nil
}
In the code above john#example.com is the "Calendar ID" provided in Google Calendar (the actual ID is used in the code of course). This outputs the error
time="2021-05-05T11:39:12+02:00" level=fatal msg="Unable to retrieve next ten of the user's events: googleapi: Error 404: Not Found, notFound"
My understanding is that this means that john#example.com was not recognized. Why?
When instead of john#example.com I use primary (as in the docs), the code runs correctly and outputs
Upcoming events:
No upcoming events found.
This means that the whole authentication part seems to work (using the same JSON credentials as in Python, retrieved from the console) - it is just that the reference to the calendar is not correct. How to fix this?
Following a request in comments, below is the general Python code (that works).
import json
import logging.config
import os
import arrow
import googleapiclient.discovery
import paho.mqtt.publish
from google.oauth2 import service_account as google_oauth2_service_account
# setup logging
logging.config.dictConfig({
'formatters': {
'standard': {
'format': "%(asctime)s [%(module)s] %(levelname)s %(message)s"
},
},
'handlers': {
'default': {
'formatter': 'standard',
'class': 'logging.StreamHandler',
},
},
'loggers': {
'': { # root logger
'handlers': ['default'],
'level': logging.ERROR,
'propagate': False
},
'googlecalendar': {
'handlers': ['default'],
'level': logging.DEBUG if os.environ.get('DEBUG', '').lower() == 'true' else logging.INFO,
'propagate': False
},
},
"disable_existing_loggers": True,
"version": 1,
})
log = logging.getLogger('googlecalendar')
def getevents(calendar=None, google_service =None):
now = arrow.now()
try:
events_result = google_service.events().list(
calendarId=calendar,
timeMin=now.shift(days=0).isoformat(),
timeMax=now.shift(days=+7).isoformat(),
singleEvents=True,
orderBy='startTime'
).execute()
except Exception as e:
log.error(f"error connecting to google for {calendar}: {e}")
return
# transform events and send out
events = []
if events_result.get('items'):
# remove multiday items (no strat or end date)
for i in events_result['items']:
# try to extract the data, some are not available so the event is not interesing (multiday, ...)
try:
events.append({
'start': arrow.get(i['start']['dateTime']).isoformat(),
'end': arrow.get(i['end']['dateTime']).isoformat(),
'timestamp': arrow.get(i['start']['dateTime']).timestamp,
'name': i['summary'],
'id': i['id'],
'important': True if i.get('colorId') else False
})
except KeyError:
pass
# send the data to MQTT
# (...)
log.debug(f"{calendar}: {events}")
google_credentials = google_oauth2_service_account.Credentials.from_service_account_info(
{
"type": "service_account",
"project_id": "da(...)",
"private_key_id": "8(...)4",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAI(...)Kag==\n-----END PRIVATE KEY-----\n",
"client_email": "da(...)#(...).iam.gserviceaccount.com",
"client_id": "1(...)9",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/da(...)iam.gserviceaccount.com"
},
scopes=['https://www.googleapis.com/auth/calendar'],
subject='john#example.com'
)
google_service = googleapiclient.discovery.build('calendar', 'v3', credentials=google_credentials, cache_discovery=False)
for calendar in [
"john#example.com",
"mary#example.net",
"something_idsd#group.calendar.google.com",
]:
getevents(calendar=calendar, google_service=google_service)
Answer
The result you are getting is normal, the problem is that you are not performing Domain-Wide Delegation correctly. I come to this conclusion because of the following scenarios:
Calendar Id: john#example.com. If the service account has not impressed the user, it is expected that the user's calendar will not be found.
Calendar Id: primary. If the primary calendar of the service account does not have any events, it is expected that the list method does not return any results.
Solution
Comparing your code with the one in the documentation, I don't see where you place config.Subject = userEmail, as #DalmTo says. Try the following code to create the calendar service:
ctx := context.Background()
jsonCredentials, err := ioutil.ReadFile("googlecalendar.json")
if err != nil {
return nil, err
}
config, err := google.JWTConfigFromJSON(jsonCredentials, "https://www.googleapis.com/auth/calendar")
if err != nil {
return nil, fmt.Errorf("JWTConfigFromJSON: %v", err)
}
config.Subject = "john#example.com"
ts := config.TokenSource(ctx)
calendarService, err := calendar.NewService(ctx, option.WithTokenSource(ts))
References:
Domain-Wide Delegation
Related
I'm trying to use otelgin to trace my project, and it's says that where required. It is available from gin.Context.Request.Context(), so how can i get the detail information from gin.context to see if it works.
Officially, they add a stdout as the exporter when init tracer provider:
func initTracer() (*sdktrace.TracerProvider, error) {
// stdout as exporter
exporter, err := stdout.New(stdout.WithPrettyPrint())
if err != nil {
return nil, err
}
tp := sdktrace.NewTracerProvider(
sdktrace.WithSampler(sdktrace.AlwaysSample()),
sdktrace.WithBatcher(exporter),
)
otel.SetTracerProvider(tp)
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
return tp, nil
}
here is part of the command line output:
"Name": "/users/:id",
"SpanContext": {
"TraceID": "e7a43d30c0e507b4c59fd65dc3bc6d77",
"SpanID": "636c22201c903573",
"TraceFlags": "01",
"TraceState": "",
"Remote": false
},
"Parent": {
"TraceID": "00000000000000000000000000000000",
"SpanID": "0000000000000000",
"TraceFlags": "00",
"TraceState": "",
"Remote": false
},
"SpanKind": 2,
"StartTime": "2022-11-12T16:02:07.871843+08:00",
"EndTime": "2022-11-12T16:02:07.871843+08:00",
But is there any way to get this form gin.Context? Or can I get the exporter's detail information from gin.Context? I have tried to add a middleware to capture them below:
func TracerGetMiddleware(c *gin.Context) {
//var tracer oteltrace.Tracer
//tracerInterface, ok := c.Get("otel-go-contrib-tracer")
//if ok {
/// tracer, ok = tracerInterface.(oteltrace.Tracer)
//}
//tracer.Start(c, "test")
fmt.Println(c.Request.Context())
}
But this is output of c.Request.Context()
(type *http.contextKey, val <not Stringer>).WithValue(type *http.contextKey, val [::1]:8088).WithCancel.WithCancel.WithValue(type trace.traceContextKeyType, val <not Stringer>)
At present, I understand the answer I originally wanted to get, which is to get traceID, SpanID and other data in gin.context
import (
"fmt"
"github.com/gin-gonic/gin"
oteltrace "go.opentelemetry.io/otel/trace"
)
func GetIDMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
if oteltrace.SpanFromContext(c.Request.Context()).SpanContext().IsValid() {
TraceID := oteltrace.SpanFromContext(c.Request.Context()).SpanContext().TraceID().String()
SpanID := oteltrace.SpanFromContext(c.Request.Context()).SpanContext().SpanID().String()
fmt.Println(TraceID)
fmt.Println(SpanID)
}
}
}
My initial idea was to pass trace information in the context through otelgin, and then capture traceID and so on through context in gin's middleware for zap logging, but at that time I didn't know how to get TraceID and other information, and when I saw gin-contrib/zap library find the right way.
As the title says, I have a JSON file generated based on https://mkjwk.org/ and now I want to use the generated values to sign a JWT. I will do this for multiple "clients", each with its own signature.
These will then be verified against a set of JWKS containing the public keys for the different "clients".
However, I'm struggling to understand how to use the generated JSON values..
I know I can generate one priv/pub keys directly with the crypto/rsa module. I've also seen examples where the ppk is given as input in X509 format, though I was trying to avoid it if possible (I guess I'm just being picky as I prefer to be able to read the contents of a file rather than seeing random chars..)
From the multiple examples and numerous searches I've done, I haven't seen one where the process of taking a json file and generating a rsa.privateKey is performed.
I'm also using the jwt-go and jwx modules to handle the rest of the use cases, but this one is escaping me..
So, what am I missing? How can I go from a JSON like the one below to a rsa.PrivateKey?
{
"p": "vQXloZI9y5..._PPE6m05J-VqhIF6-FQjvwc",
"kty": "RSA",
"q": "u1OqnCCLyEZDMoSfK...SfcIGNSd90PEcPs",
"d": "eoAj0Z0PkK3A0pc5t...9Q8iqGntoxMVARBtQ",
"e": "AQAB",
"use": "sig",
"kid": "r9cuFC3...HiAc7VhSME",
"qi": "upOjfCF_na...H4a7Bs2pWGoS6w5mcqXU",
"dp": "HKGzCclEEP...GdofDVR5Yas",
"alg": "RS256",
"dq": "PDyRNhc5G7OMVChVTm61kn2ShRFTe...BLytOxGBm5ntJv4V0HRBU",
"n": "ilEVn6tSuh7-tZBV8qXmlvzWDE5jTS...sNH8-wPf6gKCuZzEyyS2AyZE8S4NzqoFkaepVpdoOPtb3Q"
}
Thanks
The jwx package has a function to parse a JWK:
func ParseString(s string)
You can use it to parse the JWK like this, and use the key to sign and verify a JWT (pay attention to the code comments for further details):
package main
import(
"log"
"github.com/lestrrat-go/jwx/jwk"
"github.com/lestrrat-go/jwx/jws"
"github.com/lestrrat-go/jwx/jwa"
)
func main() {
// Here's an example of a JWK, containing a public and private RSA Keypair
var keysJWK = `{
"keys": [
{
"p": "59pSssPVWNBMd1dFBhkSx0P6uo3b3WC2MOitj7UI-9VS9gKPbPsqtDJSPdbRjK7JWXarFt_h4aPf9NPFLieu1k22jp3ProCH87geazJ8tNtV_SpaUtWQFZ-dvgGrIM-3MLf_fG7Tq5sV5R0vA9wg_INkYJ2uX5EdmVyHhxvh0eM",
"kty": "RSA",
"q": "jeofLDkteXfWcpif3JmX3xv8S6jWX2Axrwe9tLkWzlgxYDWvXExxD0sc4XfbVbSrqTkAdW48DYL_wcziFLYHxOYv2stqWvElF9CqdKJJrQAc7Z_qKXpWckDYZBJAO9W2WGXTBfdJfw_KQPHHTbY90ngdxMXuiwYbfbFY4H_XDM0",
"d": "Qrx8U40tLhsy4tdnKuEmjlGF-VkB6F_DawLXwuZ2a5ZS7cwFUDRHNz9Jbl9MxvNcNMSMGaAN8lxTSDlpfT0jDqKF6lel88rUtCnN6h1FNdkD5TjkbWs-dfhftDFc1Sy8RdWPZ8LiTo0TbZaf3rPvdLw_S9FE-itnKV_1il572rT-1PvlyrPctnREQCKL5wArD4eYHwRjfVm-KvlIvo9rLj4NYzATBAAwh6PsEnSganEf1ErOvFH8qhrVqsy2kevLsFbCA0hIfoDNhL7hxlaMSJTJie3V2Ie0Kb7j_L2LsQXka3kshO1T6re-d-nGgaRp7b0buUtwS6aTax0H2cZuOQ",
"e": "AQAB",
"use": "sig",
"kid": "223",
"qi": "moGHNM3TFLeSQeM6V4izMcK6wwapSwo67r7DXk7vK_2FaSUQtijwQCHFx3nrhbQAVdwFt7pSYlmlFPlaAXixrBBNtNULnR7z6-WrRuWgqoL9LN8xARB42l94HmiOL0pp8ORyw2W338k3LHuzUy1NKZrL6a8zPIkva_Z5hFULhQE",
"dp": "TcUm3j3gL3VXYOSOC5iXeu2ria4R5PUOx-MUbNLd25NXy5taPsUVMvJ6MbIAAj-S3IZ4pyib3RMaCUaLqoq3E71nkfkPc8o7UB4fXffGaufztQLi30wxk39B6z0mCNCD8zyU30lRiQtxUbPzVEkfa3QrVFkv53CGzC2EbGaG3d8",
"alg": "RS256",
"dq": "L1emNJOShw4iXTJrSiV3E7f7T6YwdbraeECF2c9RO18Sgb0HFixuHyL4rILWid3u0lIww_wVTpCgD5_w3-Xl65q65iur_FCsBijXZHdrSqpZ_C-350RnqE_XoHKyOQPPg-fcIQZg32F-IHJIAbXFI_xsOeOp83kDHMhYFPSw4hU",
"n": "gIdJV4qWKyt3wkS66yBG5Ii9ew-eofuPU49TjlRIU5Iu5jX2mRMoHdcI7V78iKYSQHKYxz17cqzQyERxKnEiDgy_gwouStRgvPdm3H4rq__7p0t15SunsG2T1rEVf0sZEDnQ5qRkm7iqs6ZG1NqqIUtnOTd1Pd1MhbEqeENFtaPHvN37eZL82WmsQlJviFH4I9iZQVR_QT4GREQlRro8IjJTaloUyeDQTOQ-4ll1-4-g_ug2tZ-s9xleLzl5L9ZKSVJFhtMLn8WGaVldagarwa7kMLfuiVe8B5Lr7poQa4NCAR54ECPWoOHrABdPZKrkkxjVypTXUzL5cPzmzFC2xw"
}
]
}`
// Parse the JWK to a set of keys
setOfKeys, err := jwk.ParseString(keysJWK )
if err != nil {
log.Printf("failed to parse JWK: %s", err)
return
}
// extract the private key from the set, index 0 because w only have one key
rsaPrivatekey, success := setOfKeys.Get(0)
if !success {
log.Printf("could not find key at given index")
return
}
// sign a token with the private key
token, err := jws.Sign([]byte(`{"userId":1}`), jwa.RS256, rsaPrivatekey)
if err != nil {
log.Printf("failed to created JWS message: %s", err)
return
}
// show the signed token
log.Printf("Token! -> %s", token)
// get a public key from a private key
rsaPublicKey, err := jwk.PublicKeyOf(rsaPrivatekey)
if err != nil {
log.Printf("failed created public key from private key: %s", err)
return
}
// verify the token that we created above with the public key
payload, err := jws.Verify(token, jwa.RS256, rsaPublicKey)
if err != nil {
log.Printf("failed to verify message: %s", err)
return
}
// show the payload of the verified token
log.Printf("signature verified! Payload -> %s", payload)
}
try it on the Go Playground
As the title says, I have a JSON file generated based on https://mkjwk.org/ and now I want to use the generated values to sign a JWT. I will do this for multiple "clients", each with its own signature.
These will then be verified against a set of JWKS containing the public keys for the different "clients".
However, I'm struggling to understand how to use the generated JSON values..
I know I can generate one priv/pub keys directly with the crypto/rsa module. I've also seen examples where the ppk is given as input in X509 format, though I was trying to avoid it if possible (I guess I'm just being picky as I prefer to be able to read the contents of a file rather than seeing random chars..)
From the multiple examples and numerous searches I've done, I haven't seen one where the process of taking a json file and generating a rsa.privateKey is performed.
I'm also using the jwt-go and jwx modules to handle the rest of the use cases, but this one is escaping me..
So, what am I missing? How can I go from a JSON like the one below to a rsa.PrivateKey?
{
"p": "vQXloZI9y5..._PPE6m05J-VqhIF6-FQjvwc",
"kty": "RSA",
"q": "u1OqnCCLyEZDMoSfK...SfcIGNSd90PEcPs",
"d": "eoAj0Z0PkK3A0pc5t...9Q8iqGntoxMVARBtQ",
"e": "AQAB",
"use": "sig",
"kid": "r9cuFC3...HiAc7VhSME",
"qi": "upOjfCF_na...H4a7Bs2pWGoS6w5mcqXU",
"dp": "HKGzCclEEP...GdofDVR5Yas",
"alg": "RS256",
"dq": "PDyRNhc5G7OMVChVTm61kn2ShRFTe...BLytOxGBm5ntJv4V0HRBU",
"n": "ilEVn6tSuh7-tZBV8qXmlvzWDE5jTS...sNH8-wPf6gKCuZzEyyS2AyZE8S4NzqoFkaepVpdoOPtb3Q"
}
Thanks
The jwx package has a function to parse a JWK:
func ParseString(s string)
You can use it to parse the JWK like this, and use the key to sign and verify a JWT (pay attention to the code comments for further details):
package main
import(
"log"
"github.com/lestrrat-go/jwx/jwk"
"github.com/lestrrat-go/jwx/jws"
"github.com/lestrrat-go/jwx/jwa"
)
func main() {
// Here's an example of a JWK, containing a public and private RSA Keypair
var keysJWK = `{
"keys": [
{
"p": "59pSssPVWNBMd1dFBhkSx0P6uo3b3WC2MOitj7UI-9VS9gKPbPsqtDJSPdbRjK7JWXarFt_h4aPf9NPFLieu1k22jp3ProCH87geazJ8tNtV_SpaUtWQFZ-dvgGrIM-3MLf_fG7Tq5sV5R0vA9wg_INkYJ2uX5EdmVyHhxvh0eM",
"kty": "RSA",
"q": "jeofLDkteXfWcpif3JmX3xv8S6jWX2Axrwe9tLkWzlgxYDWvXExxD0sc4XfbVbSrqTkAdW48DYL_wcziFLYHxOYv2stqWvElF9CqdKJJrQAc7Z_qKXpWckDYZBJAO9W2WGXTBfdJfw_KQPHHTbY90ngdxMXuiwYbfbFY4H_XDM0",
"d": "Qrx8U40tLhsy4tdnKuEmjlGF-VkB6F_DawLXwuZ2a5ZS7cwFUDRHNz9Jbl9MxvNcNMSMGaAN8lxTSDlpfT0jDqKF6lel88rUtCnN6h1FNdkD5TjkbWs-dfhftDFc1Sy8RdWPZ8LiTo0TbZaf3rPvdLw_S9FE-itnKV_1il572rT-1PvlyrPctnREQCKL5wArD4eYHwRjfVm-KvlIvo9rLj4NYzATBAAwh6PsEnSganEf1ErOvFH8qhrVqsy2kevLsFbCA0hIfoDNhL7hxlaMSJTJie3V2Ie0Kb7j_L2LsQXka3kshO1T6re-d-nGgaRp7b0buUtwS6aTax0H2cZuOQ",
"e": "AQAB",
"use": "sig",
"kid": "223",
"qi": "moGHNM3TFLeSQeM6V4izMcK6wwapSwo67r7DXk7vK_2FaSUQtijwQCHFx3nrhbQAVdwFt7pSYlmlFPlaAXixrBBNtNULnR7z6-WrRuWgqoL9LN8xARB42l94HmiOL0pp8ORyw2W338k3LHuzUy1NKZrL6a8zPIkva_Z5hFULhQE",
"dp": "TcUm3j3gL3VXYOSOC5iXeu2ria4R5PUOx-MUbNLd25NXy5taPsUVMvJ6MbIAAj-S3IZ4pyib3RMaCUaLqoq3E71nkfkPc8o7UB4fXffGaufztQLi30wxk39B6z0mCNCD8zyU30lRiQtxUbPzVEkfa3QrVFkv53CGzC2EbGaG3d8",
"alg": "RS256",
"dq": "L1emNJOShw4iXTJrSiV3E7f7T6YwdbraeECF2c9RO18Sgb0HFixuHyL4rILWid3u0lIww_wVTpCgD5_w3-Xl65q65iur_FCsBijXZHdrSqpZ_C-350RnqE_XoHKyOQPPg-fcIQZg32F-IHJIAbXFI_xsOeOp83kDHMhYFPSw4hU",
"n": "gIdJV4qWKyt3wkS66yBG5Ii9ew-eofuPU49TjlRIU5Iu5jX2mRMoHdcI7V78iKYSQHKYxz17cqzQyERxKnEiDgy_gwouStRgvPdm3H4rq__7p0t15SunsG2T1rEVf0sZEDnQ5qRkm7iqs6ZG1NqqIUtnOTd1Pd1MhbEqeENFtaPHvN37eZL82WmsQlJviFH4I9iZQVR_QT4GREQlRro8IjJTaloUyeDQTOQ-4ll1-4-g_ug2tZ-s9xleLzl5L9ZKSVJFhtMLn8WGaVldagarwa7kMLfuiVe8B5Lr7poQa4NCAR54ECPWoOHrABdPZKrkkxjVypTXUzL5cPzmzFC2xw"
}
]
}`
// Parse the JWK to a set of keys
setOfKeys, err := jwk.ParseString(keysJWK )
if err != nil {
log.Printf("failed to parse JWK: %s", err)
return
}
// extract the private key from the set, index 0 because w only have one key
rsaPrivatekey, success := setOfKeys.Get(0)
if !success {
log.Printf("could not find key at given index")
return
}
// sign a token with the private key
token, err := jws.Sign([]byte(`{"userId":1}`), jwa.RS256, rsaPrivatekey)
if err != nil {
log.Printf("failed to created JWS message: %s", err)
return
}
// show the signed token
log.Printf("Token! -> %s", token)
// get a public key from a private key
rsaPublicKey, err := jwk.PublicKeyOf(rsaPrivatekey)
if err != nil {
log.Printf("failed created public key from private key: %s", err)
return
}
// verify the token that we created above with the public key
payload, err := jws.Verify(token, jwa.RS256, rsaPublicKey)
if err != nil {
log.Printf("failed to verify message: %s", err)
return
}
// show the payload of the verified token
log.Printf("signature verified! Payload -> %s", payload)
}
try it on the Go Playground
So I have a json keyfile that looks like this:
{
"user_agent": null,
"_scopes": "https://www.googleapis.com/auth/bigquery",
"token_uri": "https://www.googleapis.com/oauth2/v4/token",
"refresh_token": null,
"_service_account_email": "...",
"assertion_type": null,
"_kwargs": {},
"revoke_uri": "https://accounts.google.com/o/oauth2/revoke",
"_private_key_pkcs8_pem": "-----BEGIN PRIVATE KEY----- ..."
...
}
I want to login using this file to bigquery, with Golang. I looked through the examples at https://github.com/GoogleCloudPlatform/google-cloud-go but couldn't find anything related there how to create a new bigquery client using the keyfile. Am I missing something obvious?
In python the quivalent is:
credentials = ServiceAccountCredentials.from_json_keyfile_name(
'keyfile.json',
'https://www.googleapis.com/auth/bigquery')
...
First: the file I posted is the wrong keyfile, for more details, check this answer: ('Unexpected credentials type', None, 'Expected', 'service_account') with oauth2client (Python)
Second: this is the code that worked for me (listing the available datasets):
package main
import (
"cloud.google.com/go/bigquery"
"fmt"
"golang.org/x/net/context"
"google.golang.org/api/iterator"
"google.golang.org/api/option"
)
func main() {
ctx := context.Background()
client, err := bigquery.NewClient(ctx, "project-name", option.WithCredentialsFile("keyfile.json"))
if err != nil {
panic(err.Error())
}
it := client.Datasets(ctx)
for {
dataset, err := it.Next()
if err == iterator.Done {
break
}
fmt.Println(dataset.DatasetID)
}
println("logged in")
}
That took forever to find out today …
Here is my simple rest service:
// Package classification User API.
//
// the purpose of this application is to provide an application
// that is using plain go code to define an API
//
// This should demonstrate all the possible comment annotations
// that are available to turn go code into a fully compliant swagger 2.0 spec
//
// Terms Of Service:
//
// there are no TOS at this moment, use at your own risk we take no responsibility
//
// Schemes: http, https
// Host: localhost
// BasePath: /v2
// Version: 0.0.1
// License: MIT http://opensource.org/licenses/MIT
// Contact: John Doe<john.doe#example.com> http://john.doe.com
//
// Consumes:
// - application/json
// - application/xml
//
// Produces:
// - application/json
// - application/xml
//
//
// swagger:meta
package main
import (
"github.com/gin-gonic/gin"
"strconv"
"database/sql"
_ "github.com/go-sql-driver/mysql"
"gopkg.in/gorp.v1"
"log"
)
// swagger:model
// User represents the user for this application
//
// A user is the security principal for this application.
// It's also used as one of main axis for reporting.
//
// A user can have friends with whom they can share what they like.
//
type User struct {
// the id for this user
//
// required: true
// min: 1
Id int64 `db:"id" json:"id"`
// the first name for this user
// required: true
// min length: 3
Firstname string `db:"firstname" json:"firstname"`
// the last name for this user
// required: true
// min length: 3
Lastname string `db:"lastname" json:"lastname"`
}
func main() {
r := gin.Default()
r.Use(Cors())
v1 := r.Group("api/v1")
{
v1.GET("/users", GetUsers)
v1.GET("/users/:id", GetUser)
v1.POST("/users", PostUser)
v1.PUT("/users/:id", UpdateUser)
v1.DELETE("/users/:id", DeleteUser)
v1.OPTIONS("/users", OptionsUser) // POST
v1.OPTIONS("/users/:id", OptionsUser) // PUT, DELETE
}
r.Run(":8696")
}
func GetUsers(c *gin.Context) {
// swagger:route GET /user listPets pets users
//
// Lists pets filtered by some parameters.
//
// This will show all available pets by default.
// You can get the pets that are out of stock
//
// Consumes:
// - application/json
// - application/x-protobuf
//
// Produces:
// - application/json
// - application/x-protobuf
//
// Schemes: http, https, ws, wss
//
// Security:
// api_key:
// oauth: read, write
//
// Responses:
// default: genericError
// 200: someResponse
// 422: validationError
var users []User
_, err := dbmap.Select(&users, "SELECT * FROM user")
if err == nil {
c.JSON(200, users)
} else {
c.JSON(404, gin.H{"error": "no user(s) into the table"})
}
// curl -i http://localhost:8080/api/v1/users
}
func GetUser(c *gin.Context) {
id := c.Params.ByName("id")
var user User
err := dbmap.SelectOne(&user, "SELECT * FROM user WHERE id=?", id)
if err == nil {
user_id, _ := strconv.ParseInt(id, 0, 64)
content := &User{
Id: user_id,
Firstname: user.Firstname,
Lastname: user.Lastname,
}
c.JSON(200, content)
} else {
c.JSON(404, gin.H{"error": "user not found"})
}
// curl -i http://localhost:8080/api/v1/users/1
}
func PostUser(c *gin.Context) {
var user User
c.Bind(&user)
if user.Firstname != "" && user.Lastname != "" {
if insert, _ := dbmap.Exec(`INSERT INTO user (firstname, lastname) VALUES (?, ?)`, user.Firstname, user.Lastname); insert != nil {
user_id, err := insert.LastInsertId()
if err == nil {
content := &User{
Id: user_id,
Firstname: user.Firstname,
Lastname: user.Lastname,
}
c.JSON(201, content)
} else {
checkErr(err, "Insert failed")
}
}
} else {
c.JSON(422, gin.H{"error": "fields are empty"})
}
// curl -i -X POST -H "Content-Type: application/json" -d "{ \"firstname\": \"Thea\", \"lastname\": \"Queen\" }" http://localhost:8080/api/v1/users
}
func UpdateUser(c *gin.Context) {
id := c.Params.ByName("id")
var user User
err := dbmap.SelectOne(&user, "SELECT * FROM user WHERE id=?", id)
if err == nil {
var json User
c.Bind(&json)
user_id, _ := strconv.ParseInt(id, 0, 64)
user := User{
Id: user_id,
Firstname: json.Firstname,
Lastname: json.Lastname,
}
if user.Firstname != "" && user.Lastname != ""{
_, err = dbmap.Update(&user)
if err == nil {
c.JSON(200, user)
} else {
checkErr(err, "Updated failed")
}
} else {
c.JSON(422, gin.H{"error": "fields are empty"})
}
} else {
c.JSON(404, gin.H{"error": "user not found"})
}
// curl -i -X PUT -H "Content-Type: application/json" -d "{ \"firstname\": \"Thea\", \"lastname\": \"Merlyn\" }" http://localhost:8080/api/v1/users/1
}
func DeleteUser(c *gin.Context) {
id := c.Params.ByName("id")
var user User
err := dbmap.SelectOne(&user, "SELECT id FROM user WHERE id=?", id)
if err == nil {
_, err = dbmap.Delete(&user)
if err == nil {
c.JSON(200, gin.H{"id #" + id: " deleted"})
} else {
checkErr(err, "Delete failed")
}
} else {
c.JSON(404, gin.H{"error": "user not found"})
}
// curl -i -X DELETE http://localhost:8080/api/v1/users/1
}
var dbmap = initDb()
func initDb() *gorp.DbMap {
db, err := sql.Open("mysql",
"root:max_123#tcp(127.0.0.1:3306)/gotest")
checkErr(err, "sql.Open failed")
dbmap := &gorp.DbMap{Db: db, Dialect: gorp.MySQLDialect{"InnoDB", "UTF8"}}
dbmap.AddTableWithName(User{}, "User").SetKeys(true, "Id")
err = dbmap.CreateTablesIfNotExists()
checkErr(err, "Create table failed")
return dbmap
}
func checkErr(err error, msg string) {
if err != nil {
log.Fatalln(msg, err)
}
}
func Cors() gin.HandlerFunc {
return func(c *gin.Context) {
c.Writer.Header().Add("Access-Control-Allow-Origin", "*")
c.Next()
}
}
func OptionsUser(c *gin.Context) {
c.Writer.Header().Add("Access-Control-Allow-Origin", "*")
c.Writer.Header().Set("Access-Control-Allow-Methods", "DELETE,POST, PUT")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type")
c.Next()
}
Now when I'm executing :
swagger generate spec -o ./swagger.json
to generate the json spec I'm getting:
{
"consumes": ["application/json", "application/xml"],
"produces": ["application/json", "application/xml"],
"schemes": ["http", "https"],
"swagger": "2.0",
"info": {
"description": "the purpose of this application is to provide an application\nthat is using plain go code to define an API\n\nThis should demonstrate all the possible comment annotations\nthat are available to turn go code into a fully compliant swagger 2.0 spec",
"title": "User API.",
"termsOfService": "there are no TOS at this moment, use at your own risk we take no responsibility",
"contact": {
"name": "John Doe",
"url": "http://john.doe.com",
"email": "john.doe#example.com"
},
"license": {
"name": "MIT",
"url": "http://opensource.org/licenses/MIT"
},
"version": "0.0.1"
},
"host": "localhost",
"basePath": "/v2",
"paths": {
"/user": {
"get": {
"description": "This will show all available pets by default.\nYou can get the pets that are out of stock",
"consumes": ["application/json", "application/x-protobuf"],
"produces": ["application/json", "application/x-protobuf"],
"schemes": ["http", "https", "ws", "wss"],
"tags": ["listPets", "pets"],
"summary": "Lists pets filtered by some parameters.",
"operationId": "users",
"security": [{
"api_key": null
}, {
"oauth": ["read", "write"]
}],
"responses": {
"200": {
"$ref": "#/responses/someResponse"
},
"422": {
"$ref": "#/responses/validationError"
},
"default": {
"$ref": "#/responses/genericError"
}
}
}
}
},
"definitions": {}
}
Note that my definitions are empty, not sure why.
If I paste the same json spec in http://editor.swagger.io/#/
It says
Error
Object
message: "options.definition is required"
code: "UNCAUGHT_SWAY_WORKER_ERROR"
Any directions on what is the right way to generate swagger documentation would help
It's because go-swagger can't detect the usage of your definitions. I made the assumption that you'd always have a struct describing the parameters and that those would always use the definitions that are in use.
It would be great if you could submit this question as an issue on the repo with your sample program. I'll add it to my tests.