A GO API library for working with Marathon

Build Status GoDoc Go Report Card Coverage Status

Go-Marathon

Go-marathon is a API library for working with Marathon. It currently supports

  • Application and group deployment
  • Helper filters for pulling the status, configuration and tasks
  • Multiple Endpoint support for HA deployments
  • Marathon Event Subscriptions and Event Streams
  • Pods

Note: the library is still under active development; users should expect frequent (possibly breaking) API changes for the time being.

It requires Go version 1.6 or higher.

Code Examples

There is also an examples directory in the source which shows hints and snippets of code of how to use it — which is probably the best place to start.

You can use examples/docker-compose.yml in order to start a test cluster.

Creating a client

import (
	marathon "github.com/gambol99/go-marathon"
)

marathonURL := "http://10.241.1.71:8080"
config := marathon.NewDefaultConfig()
config.URL = marathonURL
client, err := marathon.NewClient(config)
if err != nil {
	log.Fatalf("Failed to create a client for marathon, error: %s", err)
}

applications, err := client.Applications(nil)
...

Note, you can also specify multiple endpoint for Marathon (i.e. you have setup Marathon in HA mode and having multiple running)

marathonURL := "http://10.241.1.71:8080,10.241.1.72:8080,10.241.1.73:8080"

The first one specified will be used, if that goes offline the member is marked as "unavailable" and a background process will continue to ping the member until it's back online.

You can also pass a custom path to the URL, which is especially needed in case of DCOS:

marathonURL := "http://10.241.1.71:8080/cluster,10.241.1.72:8080/cluster,10.241.1.73:8080/cluster"

If you specify a DCOSToken in the configuration file but do not pass a custom URL path, /marathon will be used.

Customizing the HTTP Clients

HTTP clients with reasonable timeouts are used by default. It is possible to pass custom clients to the configuration though if the behavior should be customized (e.g., to bypass TLS verification, load root CAs, or change timeouts).

Two clients can be given independently of each other:

  • HTTPClient used only for (non-SSE) HTTP API requests. By default, an http.Client with 10 seconds timeout for the entire request is used.
  • HTTPSSEClient used only for SSE-based subscription requests. Note that HTTPSSEClient cannot have a response read timeout set as this breaks SSE communication; trying to do so will lead to an error during the SSE connection setup. By default, an http.Client with 5 seconds timeout for dial and TLS handshake, and 10 seconds timeout for response headers received is used.

If no HTTPSSEClient is given but an HTTPClient is, it will be used for SSE subscriptions as well (thereby overriding the default SSE HTTP client).

marathonURL := "http://10.241.1.71:8080"
config := marathon.NewDefaultConfig()
config.URL = marathonURL
config.HTTPClient = &http.Client{
    Timeout: (time.Duration(10) * time.Second),
    Transport: &http.Transport{
        Dial: (&net.Dialer{
            Timeout:   10 * time.Second,
            KeepAlive: 10 * time.Second,
        }).Dial,
        TLSClientConfig: &tls.Config{
            InsecureSkipVerify: true,
        },
    },
}
config.HTTPSSEClient = &http.Client{
    // Invalid to set Timeout as it contains timeout for reading a response body
    Transport: &http.Transport{
        Dial: (&net.Dialer{
            Timeout:   10 * time.Second,
            KeepAlive: 10 * time.Second,
        }).Dial,
        TLSClientConfig: &tls.Config{
            InsecureSkipVerify: true,
        },
    },
}

Listing the applications

applications, err := client.Applications(nil)
if err != nil {
	log.Fatalf("Failed to list applications: %s", err)
}

log.Printf("Found %d application(s) running", len(applications.Apps))
for _, application := range applications.Apps {
	log.Printf("Application: %s", application)
	appID := application.ID

	details, err := client.Application(appID)
	if err != nil {
		log.Fatalf("Failed to get application %s: %s", appID, err)
	}
	if details.Tasks != nil {
		for _, task := range details.Tasks {
			log.Printf("application %s has task: %s", appID, task)
		}
	}
}

Creating a new application

log.Printf("Deploying a new application")
application := marathon.NewDockerApplication().
  Name(applicationName).
  CPU(0.1).
  Memory(64).
  Storage(0.0).
  Count(2).
  AddArgs("/usr/sbin/apache2ctl", "-D", "FOREGROUND").
  AddEnv("NAME", "frontend_http").
  AddEnv("SERVICE_80_NAME", "test_http").
  CheckHTTP("/health", 10, 5)

application.
  Container.Docker.Container("quay.io/gambol99/apache-php:latest").
  Bridged().
  Expose(80).
  Expose(443)

if _, err := client.CreateApplication(application); err != nil {
	log.Fatalf("Failed to create application: %s, error: %s", application, err)
} else {
	log.Printf("Created the application: %s", application)
}

Note: Applications may also be defined by means of initializing a marathon.Application struct instance directly. However, go-marathon's DSL as shown above provides a more concise way to achieve the same.

Scaling application

Change the number of application instances to 4

log.Printf("Scale to 4 instances")
if err := client.ScaleApplicationInstances(application.ID, 4); err != nil {
	log.Fatalf("Failed to delete the application: %s, error: %s", application, err)
} else {
	client.WaitOnApplication(application.ID, 30 * time.Second)
	log.Printf("Successfully scaled the application")
}

Pods

Pods allow you to deploy groups of tasks as a unit. All tasks in a single instance of a pod share networking and storage. View the Marathon documentation for more details on this feature.

Examples of their usage can be seen in the examples/pods directory, and a smaller snippet is below.

// Initialize a single-container pod running nginx
pod := marathon.NewPod()

image := marathon.NewDockerPodContainerImage().SetID("nginx")

container := marathon.NewPodContainer().
	SetName("container", i).
	CPUs(0.1).
	Memory(128).
	SetImage(image)

pod.Name("mypod").AddContainer(container)

// Create it and wait for it to start up
pod, err := client.CreatePod(pod)
err = client.WaitOnPod(pod.ID, time.Minute*1)

// Scale it
pod.Count(5)
pod, err = client.UpdatePod(pod, true)

// Delete it
id, err := client.DeletePod(pod.ID, true)

Subscription & Events

Request to listen to events related to applications — namely status updates, health checks changes and failures. There are two different event transports controlled by EventsTransport setting with the following possible values: EventsTransportSSE and EventsTransportCallback (default value). See Event Stream and Event Subscriptions for details.

Event subscriptions can also be individually controlled with the Subscribe and Unsubscribe functions. See Controlling subscriptions for more details.

Event Stream

Only available in Marathon >= 0.9.0. Does not require any special configuration or prerequisites.

// Configure client
config := marathon.NewDefaultConfig()
config.URL = marathonURL
config.EventsTransport = marathon.EventsTransportSSE

client, err := marathon.NewClient(config)
if err != nil {
	log.Fatalf("Failed to create a client for marathon, error: %s", err)
}

// Register for events
events, err = client.AddEventsListener(marathon.EventIDApplications)
if err != nil {
	log.Fatalf("Failed to register for events, %s", err)
}

timer := time.After(60 * time.Second)
done := false

// Receive events from channel for 60 seconds
for {
	if done {
		break
	}
	select {
	case <-timer:
		log.Printf("Exiting the loop")
		done = true
	case event := <-events:
		log.Printf("Received event: %s", event)
	}
}

// Unsubscribe from Marathon events
client.RemoveEventsListener(events)

Event Subscriptions

Requires to start a built-in web server accessible by Marathon to connect and push events to. Consider the following additional settings:

  • EventsInterface — the interface we should be listening on for events. Default "eth0".
  • EventsPort — built-in web server port. Default 10001.
  • CallbackURL — custom callback URL. Default "".
// Configure client
config := marathon.NewDefaultConfig()
config.URL = marathonURL
config.EventsInterface = marathonInterface
config.EventsPort = marathonPort

client, err := marathon.NewClient(config)
if err != nil {
	log.Fatalf("Failed to create a client for marathon, error: %s", err)
}

// Register for events
events, err = client.AddEventsListener(marathon.EventIDApplications)
if err != nil {
	log.Fatalf("Failed to register for events, %s", err)
}

timer := time.After(60 * time.Second)
done := false

// Receive events from channel for 60 seconds
for {
	if done {
		break
	}
	select {
	case <-timer:
		log.Printf("Exiting the loop")
		done = true
	case event := <-events:
		log.Printf("Received event: %s", event)
	}
}

// Unsubscribe from Marathon events
client.RemoveEventsListener(events)

See events.go for a full list of event IDs.

Controlling subscriptions

If you simply want to (de)register event subscribers (i.e. without starting an internal web server) you can use the Subscribe and Unsubscribe methods.

// Configure client
config := marathon.NewDefaultConfig()
config.URL = marathonURL

client, err := marathon.NewClient(config)
if err != nil {
	log.Fatalf("Failed to create a client for marathon, error: %s", err)
}

// Register an event subscriber via a callback URL
callbackURL := "http://10.241.1.71:9494"
if err := client.Subscribe(callbackURL); err != nil {
	log.Fatalf("Unable to register the callbackURL [%s], error: %s", callbackURL, err)
}

// Deregister the same subscriber
if err := client.Unsubscribe(callbackURL); err != nil {
	log.Fatalf("Unable to deregister the callbackURL [%s], error: %s", callbackURL, err)
}

Contributing

See the contribution guidelines.

Development

Marathon Fake

go-marathon employs a fake Marathon implementation for testing purposes. It maintains a YML-encoded list of HTTP response messages which are returned upon a successful match based upon a number of attributes, the so-called message identifier:

  • HTTP URI (without the protocol and the hostname, e.g., /v2/apps)
  • HTTP method (e.g., GET)
  • response content (i.e., the message returned)
  • scope (see below)

Response Content

The response content can be provided in one of two forms:

  • static: A pure response message returned on every match, including repeated queries.
  • index: A list of response messages associated to a particular (indexed) sequence order. A message will be returned iff it matches and its zero-based index equals the current request count.

An example for a trivial static response content is

- uri: /v2/apps
  method: POST
  content: |
		{
		"app": {
		}
		}

which would be returned for every POST request targetting /v2/apps.

An indexed response content would look like:

- uri: /v2/apps
  method: POST
  contentSequence:
		- index: 1
		- content: |
			{
			"app": {
				"id": "foo"
			}
			}
		- index: 3
		- content: |
			{
			"app": {
				"id": "bar"
			}
			}

What this means is that the first POST request to /v2/apps would yield a 404, the second one the foo app, the third one 404 again, the fourth one bar, and every following request thereafter a 404 again. Indexed responses enable more flexible testing required by some use cases.

Trying to define both a static and indexed response content constitutes an error and leads to panic.

Scope

By default, all responses are defined globally: Every message can be queried by any request across all tests. This enables reusability and allows to keep the YML definition fairly short. For certain cases, however, it is desirable to define a set of responses that are delivered exclusively for a particular test. Scopes offer a means to do so by representing a concept similar to namespaces. Combined with indexed responses, they allow to return different responses for message identifiers already defined at the global level.

Scopes do not have a particular format -- they are just strings. A scope must be defined in two places: The message specification and the server configuration. They are pure strings without any particular structure. Given the messages specification

- uri: /v2/apps
  method: GET
	# Note: no scope defined.
  content: |
		{
		"app": {
			"id": "foo"
		}
		}
- uri: /v2/apps
  method: GET
	scope: v1.1.1  # This one does have a scope.
  contentSequence:
		- index: 1
		- content: |
			{
			"app": {
				"id": "bar"
			}
			}

and the tests

func TestFoo(t * testing.T) {
	endpoint := newFakeMarathonEndpoint(t, nil)  // No custom configs given.
	defer endpoint.Close()
	app, err := endpoint.Client.Applications(nil)
	// Do something with "foo"
}

func TestFoo(t * testing.T) {
	endpoint := newFakeMarathonEndpoint(t, &configContainer{
		server: &serverConfig{
			scope: "v1.1.1",		// Matches the message spec's scope.
		},
	})
	defer endpoint.Close()
	app, err := endpoint.Client.Applications(nil)
	// Do something with "bar"
}

The "foo" response can be used by all tests using the default fake endpoint (such as TestFoo), while the "bar" response is only visible by tests that explicitly set the scope to 1.1.1 (as TestBar does) and query the endpoint twice.

Owner
Comments
  • Add pod support

    Add pod support

    New in Marathon 1.4.0: https://github.com/mesosphere/marathon/blob/master/changelog.md#changes-from-1310-to-140

    This adds support to query pods. Testing done on a Marathon deployment with version 1.4.2.

    Addresses issue https://github.com/gambol99/go-marathon/issues/271

  • Fix issue with setting empty values

    Fix issue with setting empty values

    Make properties pointers if sending an empty value (int -> 0, string -> "") to marathon has a semantic meaning. In order to be able to distinguish between null-values and unset-values (see go-github blog post discussing the same problem for go-github [0]) -- it's actually the same problem described in gambol99/go-marathon#124 but for all^W most properties.

    Instead of providing convenience functions such as github.String("string") that return a pointer to a string, I added methods that use the builder pattern'ish approach that quite a few properties had before as well, i.e. construction looks something like:

    application := marathon.NewDockerApplication().
      Name("myapp").
      Command("/hello-world-server").
      AddEnv("PORT", "8080").
      ...
      Count(0)
    
    application.Container.Docker.Container("alpine-hello-world-server").
      Bridged().
      ExposePort(8080, 0, 0, "tcp").
      SetPrivileged(false).
      SetForcePullImage(false).
      Parameter("ulimit", fmt.Sprintf("nofile=%d", concern.Limits.FileMax))
    

    There are specialized methods called for example EmptyArgs() to explicitly set an array or hash to be empty (vs. omiting it, which would mean it keeps the current value).

    For consistency's sake, I renamed Arg -> 'AddArgs' and Parameter -> AddParameter as this change is a breaking change anyways.

    [0] https://willnorris.com/2014/05/go-rest-apis-and-pointers

  • Generalize and enrich REST API error representations

    Generalize and enrich REST API error representations

    HTTP errors coming in from Marathon's REST API have a fairly limited representation in the library. In particular, the following two shortcomings exist:

    1. HTTP error codes are not included, making fault analysis more difficult for clients than necessary.
    2. For errors not represented by dedicated error structs, an attempt is made to retrieve a top-level message property from the HTTP response. Unfortunately, Marathon does not always provide such a property, causing go-marathon to fall back to a rather non-informative unknown error description. Or Marathon provides properties in addition to message which are missed out. An example for the former is a 409 returned after a POST /v2/apps/{appId} (which also contains the deployment ID in a deployments array); a case for the latter is a 422 response (Unprocessable Entity) when an invalid app ID is passed to Marathon. (The resulting response will repeat the violating ID in an attribute property along with error details in an error property, both wrapped in an errors object.)

    It'd be great if go-marathon could be more verbose regarding the error details it exposes.

  • Failover to other nodes in a Marathon cluster not working during event subscription

    Failover to other nodes in a Marathon cluster not working during event subscription

    So, we recently had an issue where one of our Marathon nodes was "up", but inaccessible. Unfortunately, our code thought Marathon was down and crashed. Looking at the logs, it looks like the AddEventListener attempted to connect to the same node several times (we use a retrier) instead of trying different nodes. It would be better if it would attempt to connect to a different node. A few thoughts:

    1. GetMember is refactored so that it round-robins through all of the nodes regardless of whether or not the node is up or down.
    2. Refactor the subscription code so that if there is an error connecting, it marks the node as down after N attempts.
    3. Combine both solutions to round-robin requests between nodes and mark nodes as down if there are multiple failures when attempting to connect to them.

    Right now, I have to remove the node from the config until the node comes back up.

  • Depointerize maps and slices

    Depointerize maps and slices

    As the zero value for a reference type is nil and becomes empty upon initialization, we do not need to use pointers for maps and structs when it comes to distinguishing omitted and empty JSON values.

    Other drive-by refactorings include:

    • Rename Empty{Label,Env} to Empty{labels,Envs} for consistency reasons.
    • Compare length with == instead of <=.
    • Fix in-code typos.
    • Minor cosmetics.

    Closes #137.

  • Add port definitions to application.

    Add port definitions to application.

    Application details currently provides the list of ports for an application, but does not provide port definitions. We are using the "name" field within definitions to denote port functionalities for things such as prometheus (metrics) exporters to aid in service discovery. This allows us to do things like configure prometheus scrapers to find all metrics exporter ports regardless of which application they are for automatically.

    This PR adds the PortDefinitions field the Application struct and adds a PortDefinition type.

    go-marathon is great by the way - saving us so much time!

  • createApplication with tested config fails

    createApplication with tested config fails

    I tried this config directly in my marathon instance and it works. But using the API it returns:

    error: Marathon API error: Object is not valid (attribute '': )

    { "id": "/appname", "container": { "type": "DOCKER", "docker": { "forcePullImage": true, "image": "dockerimagelocation", "network": "BRIDGE", "parameters": [], "portMappings": [ { "containerPort": 8080, "hostPort": 0, "servicePort": 10004, "protocol": "tcp", "name": "app-port", "labels": {} }, { "containerPort": 8000, "hostPort": 0, "servicePort": 10005, "protocol": "tcp", "name": "debug-port", "labels": {} }, { "containerPort": 6300, "hostPort": 0, "servicePort": 10006, "protocol": "tcp", "name": "code-coverage-port", "labels": {} } ], "privileged": true }, "volumes": [] }, "cpus": 1, "env": { "JAVA_OPTS": "-Xmx512m", "environment": "Production", }, "healthChecks": [ { "portIndex": 0, "path": "/health", "maxConsecutiveFailures": 3, "protocol": "HTTP", "gracePeriodSeconds": 300, "intervalSeconds": 60, "timeoutSeconds": 20 } ], "instances": 1, "mem": 1000, "ports": null, "dependencies": null, "uris": [], "labels": { "HAPROXY_10_VHOST": "app.marathon-lb.dcos.dev.zooz.co", "HAPROXY_GROUP": "external" }, "fetch": null }

  • Replaced glog with pkg/log and some housekeeping

    Replaced glog with pkg/log and some housekeeping

    • Replace glog with pkg/log
    • Added an glog example app
    • No multiline logging anymore (which makes it easier for anything that tries to parse log files)
    • Removed random and inconsistent logging. Don't log in functions that return an error anyway for example. Let the user decide what to do with the error.
    • Cleaned up apiOperation/ apiCall /httpRequest
    • Updated func (r *marathonCluster) GetMember() to call the MarkDown func. This separates concerns, and we don't have to worry about it in httpRequest() or in registerSSESubscription()

    Can someone give me some feedback? :-)

  • Implemented better error handling

    Implemented better error handling

    I would love to discuss with you some improvement I've been doing to support a better error handling. I would love to discuss with you the following changes, which unfortunately are not shown here for GitHub limits. Please watch the changes on your local machine. The changes regard the following:

    1. Introduced support for Godeps to enable repeatability of builds
    2. Introduced a package with errors, similar to what is done in other golang projects (i.e. kubernetes)
    3. Introduced different error handling types

    The goal here is to provide to the code using this library the possibility to understand what is going on, reasoning on the type of error received. All the errors implement the "error" interface.

    I understand that this changes are quite big, but I need this to have a better error handling in my go code, with the previous implementation I could only print the string representation of the message which is not enough to implement any logic in code.

    I'm still working on this but I opened the PR for early discussion. PLEASE DO NOT MERGE BEFORE END OF DISCUSSION.

  • Marathon Cluster Markdowns

    Marathon Cluster Markdowns

    • removed the linked lists and moved to slices
    • fixed up the code to manage the selection of a node
    • NOTE: this wouldnt fix the events subscription at the moment, need to work that in
    • changed some of the names of the structs and removed the cluster interface, not sure point in it
  • Race condition when sending events to listeners

    Race condition when sending events to listeners

    Some background: I modified Traefik, which uses this library, to periodically shutdown the Marathon client and create a new one. This is due to a bug in Marathon where Traefik will stop receiving events when an instance crashes. AFAIK, this hasn't been fixed yet and I don't know when, hence the work-around.

    So, after getting this code working better, I started seeing panic from the below code where it tries to send on a closed channel:

    for channel, filter := range r.listeners {
        // step: check if this listener wants this event type
        if event.ID&filter != 0 {
            go func(ch EventsChannel, e *Event) {
                ch <- e
            }(channel, event)
        }
    }
    

    The code I'm working on when it is told to stop will remove the listener, then close the channel it was using for updates. Looking at your code, this would appear to work fine until I realized that because you're sending the events to the channel within a goroutine, that what must be happening is the following:

    1. An event comes in and a goroutine is spawned to send the event to a channel.
      • The goroutine does not run at this time.
    2. My code gets a stop message.
    3. The listener is removed.
    4. The channel is closed.
    5. The goroutine in step one now runs and panics.

    Now, I do understand why the code is designed this way: you don't want to block other listeners from receiving events if one channel is full which will force the goroutine to block. However, I'm thinking it may be best to not use a goroutine in this case and document that buffered channels should be used instead. Perhaps a function could be added to create the correct channel vs. relying on the user of the API to make sure it's buffered?

    I understand this may be an atypical use case, but I wanted to report this bug so that it was known before this affects production systems.

  • Consider making a new release?

    Consider making a new release?

    Hello, there!

    I'm working on packaging your library on Debian and was wondering it it's possible for you to make a new release (v0.7.2?), several fixes you've made since v0.7.1 would be greatly appreciated :)

    Thanks in advance!

    Cheers,

  • Support for fetching files from task sandbox

    Support for fetching files from task sandbox

    Hello,

    I want to add support for list and fetching files from a task's sandbox via the endpoints described here: https://docs.mesosphere.com/1.12/monitoring/logging/logging-api/#/

    I was not sure how you felt about supporting DC/OS specific endpoints which this seems to be. The most appropriate place I see to add this would be as member functions on the Task struct. If this all sounds appropriate I will write it & PR.

    Thanks, Geoff

  • Closing SSE Subscriptions

    Closing SSE Subscriptions

    Hello,

    We recently noticed when using the event listener functionality that the RemoveEventListener() does not clean up the stream if using SSE mode.

    Based on the current code, it seems that this would not be an issue if you (a) reuse the marathon client or (b) use one global SSE listener for all deploys. Unfortunately we are currently recreating marathon client on every deploy, and also trying to subscribe one listener per deploy.

    While we have a few options that we control (namely moving towards a global listener or reusing our marathon client), I would think that adding support to RemoveEventListener() to close the stream would be helpful. Do you agree?

    If so, I can work on a PR. From a quick pass, it seems reasonable to add an SSE stream close channel to the marathon client struct and send a message down that channel to the stream listening goroutine when RemoveEventListener() is called in SSE mode.

  • Can wipe task support?

    Can wipe task support?

    https://github.com/gambol99/go-marathon/blob/6d1efaff86d484701d242bee396e7bdd35ce27d2/task.go#L70

    type KillTaskOpts struct {
    	Scale bool `url:"scale,omitempty"`
    	Force bool `url:"force,omitempty"`
    	Wipe  bool `url:"wipe,omitempty"`
    }
    
  • Client should be updated to follow /v2/events redirection responses

    Client should be updated to follow /v2/events redirection responses

    Hello!

    In mesosphere/marathon#6211, we will change the behavior in 1.7.x to respond with a redirect call when /v2/events is requested from a standby/non-leader Marathon instance.

    The code should be updated to follow redirects in such a case.

    The old proxying behavior can be optionally extended until Marathon 1.8.x, at which point the old proxying will be totally removed.

    See the changelog item for more details: https://github.com/mesosphere/marathon/pull/6211/files#diff-42ba1d994f4fa8c6ad17a7efae7936cc

Golang module for working with VK API

VK SDK for Golang VK SDK for Golang ready implementation of the main VK API functions for Go. Russian documentation Features Version API 5.131. API 40

Dec 10, 2022
A Go client for working with the Cryptohopper API

GoGoCryptohopper A Go client for working with the Cryptohopper API Structure of the client Request/Response The definitions for request and response f

Dec 23, 2021
Go SDK for working with the Nightfall Developer Platform

Nightfall Go SDK nightfall-go-sdk is a Go client library for accessing the Nightfall API. It allows you to add functionality to your applications to s

Jun 20, 2022
Repo for working on Cloud-based projects in Go

GoCloud Repo for working on Cloud-based projects in Go AWS checkout SDK: https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/gov2/s3 cd into /ho

Jan 10, 2022
Gophercises-quiz-one - Working solution of Gophercises Quiz 1 Course

Gophercises Quiz 1 Working Solution Description Create a program that will read

Feb 2, 2022
Go package providing opinionated tools and methods for working with the `aws-sdk-go/service/cloudfront` package.

go-aws-cloudfront Go package providing opinionated tools and methods for working with the aws-sdk-go/service/cloudfront package. Documentation Tools $

Feb 2, 2022
Simple-Weather-API - Simple weather api app created using golang and Open Weather API key
Simple-Weather-API - Simple weather api app created using golang and Open Weather API key

Simple Weather API Simple weather api app created using golang and Open Weather

Feb 6, 2022
Go library for accessing the MyAnimeList API: http://myanimelist.net/modules.php?go=api

go-myanimelist go-myanimelist is a Go client library for accessing the MyAnimeList API. Project Status The MyAnimeList API has been stable for years a

Sep 28, 2022
go-whatsapp-rest-API is a Go library for the WhatsApp web which use Swagger as api interface

go-whatsapp-rest-API go-whatsapp-rest-API is a Go library for the WhatsApp web which use Swagger as api interface Multi-devices (MD) Support. This ver

Dec 15, 2022
Go client for the YNAB API. Unofficial. It covers 100% of the resources made available by the YNAB API.

YNAB API Go Library This is an UNOFFICIAL Go client for the YNAB API. It covers 100% of the resources made available by the YNAB API. Installation go

Oct 6, 2022
An API client for the Notion API implemented in Golang

An API client for the Notion API implemented in Golang

Dec 30, 2022
lambda-go-api-proxy makes it easy to port APIs written with Go frameworks such as Gin to AWS Lambda and Amazon API Gateway.

aws-lambda-go-api-proxy makes it easy to run Golang APIs written with frameworks such as Gin with AWS Lambda and Amazon API Gateway.

Jan 6, 2023
A API scanner written in GOLANG to scan files recursively and look for API keys and IDs.

GO FIND APIS _____ ____ ______ _____ _ _ _____ _____ _____ _____ / ____|/ __ \ | ____|_ _| \ | | __ \ /\ | __ \_

Oct 25, 2021
The NVD API is an unofficial Go wrapper around the NVD API.

NVD API The NVD API is an unofficial Go wrapper around the NVD API. Supports: CVE CPE How to use The following shows how to basically use the wrapper

Jan 7, 2023
A Wrapper Client for Google Spreadsheet API (Sheets API)

Senmai A Wrapper Client for Google Spreadsheet API (Sheets API) PREPARATION Service Account and Key File Create a service account on Google Cloud Plat

Nov 5, 2021
💾 Wolke API is the API behind Wolke image storage and processing aswell as user management

?? Wolke API Wolke API is the API behind Wolke image storage and processing aswell as user management Deploying To deploy Wolke Bot you'll need podman

Dec 21, 2021
Upcoming mobiles api (UpMob API)

upcoming_mobiles_api (UpMob API) UpMob API scraps 91mobiles.com to get devices i

Dec 21, 2021
Arweave-api - Arweave API implementation in golang

Arweave API Go implementation of the Arweave API Todo A list of endpoints that a

Jan 16, 2022
Api-product - A basic REST-ish API that allows you to perform CRUD operations for Products

Description A basic REST-ish API that allows you to perform CRUD operations for

Jan 3, 2022