linodego Go Client Library

repository·main·Indexed 41 days ago

https://github.com/linode/linodego

A Go client library for the Linode REST v4 API that provides structured access to Linode cloud resources. It includes support for auto-pagination, result filtering, response caching, and comprehensive management of account resources, beta programs, child accounts, and events.

Tokens
123.3K
Snippets
489
Records
576
Agent score
50%

What's inside linodego

  1. Handle API errors

    main

    Errors in linodego provide structured information about the failure:

    • Single Entity Errors (e.g., Get): If an entity is not found or you lack permission, the returned object will be nil and the error will contain the status code and message.
    • List Errors: If a list request fails (e.g., due to an invalid filter), the returned slice will be empty ([]) and the error will contain the failure details.
    • Write Errors: When performing POST or PUT requests, multiple field validation errors are aggregated into a single error string in the format: [400] [field1] problem; [field2] problem.

    Error Fields:

    • err.Error(): String representation (e.g., [404] Not Found).
    • err.Code: The HTTP status code as a string (e.g., 404).
    • err.Message: The error message (e.g., Not Found).
    // Example: 404 Error
    linode, err := linodego.GetInstance(context.Background(), 555)
    // linode == nil
    // err.Error() == "[404] Not Found"
    // err.Code == "404"
    // err.Message == "Not Found"
    
    // Example: Invalid Filter Error
    linodes, err := linodego.ListInstances(context.Background(), linodego.NewListOptions(0, "{\"foo\":bar}"))
    // linodes == []
    // err.Error() == "[400] [X-Filter] Cannot filter on foo"
  2. Review HTTP client and Resty-specific logic

    main

    The client internals have migrated from Resty to net/http. If you use advanced hooks or custom error handling, you must update your implementation.

    Observable changes:

    • Request now maps to *http.Request.
    • Response now maps to *http.Response.
    • The Logger is now a local interface, not Resty's logger.
    • OnBeforeRequest and OnAfterResponse hooks now use *http.Request and *http.Response.
    • Retry logic: Retry condition and retry-after callback types now use *http.Response. Retry helper names are now exported (capitalized).
    • Error handling: Error handling is now centered around http.Response instead of Resty responses.
  3. Choose between Status Polling and Event Polling

    main

    Linode API calls are often asynchronous. linodego provides two primary ways to wait for completion:

    1. Status Polling: Use WaitFor*Status helpers when you care if a resource has reached a specific state (e.g., an instance is running).
    2. Event Polling: Use EventPoller when you care about a specific action completing (e.g., a linode_boot action reaches finished).

    Rule of thumb:

    • Use status waits to check if a resource looks ready.
    • Use event waits to check if a particular action completed.
    • Prefer EventPoller over WaitForEventFinished if you can instantiate the poller before triggering the mutating API call.
  4. Paginate through list results

    main

    The client supports two modes of pagination using ListOptions:

    1. Auto-Pagination: Passing nil or a page value of 0 to a list function will attempt to retrieve all available results.
    2. Single Page: Use linodego.NewListOptions(page, "") to request a specific page.

    Important Notes:

    • ListOptions are passed as pointers because the list functions mutate them. The resulting number of total results and total pages are stored within the ListOptions instance you provided.
    • Do not share a single ListOptions instance across multiple different list endpoint calls.
    // Auto-pagination (returns all results)
    // len(kernels) == 218
    // Note: opts will be mutated to include total results/pages
    opts := linodego.NewListOptions(0, "")
    kernels, err := linodego.ListKernels(context.Background(), opts)
    
    // Single page request
    // len(kernels) == 100
    opts := linodego.NewListOptions(2, "")
    // or manually:
    // opts := linodego.ListOptions{PageOptions: &linodego.PageOptions{Page: 2}, PageSize: 500}
    kernels, err := linodego.ListKernels(context.Background(), opts)
  5. Audit request/response type shape changes

    main

    Linodego v2 performs significant cleanup of pointer-heavy types. You should audit your code for the following patterns:

    1. Slice pointer changes: Many types have changed from pointer slices to value slices (e.g., []*T $\rightarrow$ []T or *[]string $\rightarrow$ []string).
    2. Nullability: Some string fields have become pointers to support nullable semantics.
    3. Payload splits: Create and Update types have been split for request payloads.
    4. JSON Semantics: A large number of JSON tags changed from omitempty to omitzero. This affects how you distinguish between "clearing" a field and "omitting" it. Run integration tests to validate update behavior.

    Review areas:

    • Code relying on nil checks.
    • Code relying on pointer identity.
    • Code distinguishing between "unset" and "empty".
    • Mutation of shared slices.
  6. Configure timeouts for wait operations

    main

    Wait helpers (like WaitForFinished or WaitForInstanceStatus) can block indefinitely if the expected state is never reached. Always pass a deadline-aware context.Context to the wait call. When the context expires, the helper returns an error containing ctx.Err().

    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
    defer cancel()
    
    instance, err := client.WaitForInstanceStatus(ctx, instanceID, linodego.InstanceRunning)
    if err != nil {
    	log.Fatal(err)
    }
  7. Use NewEventPollerWithoutEntity for creation operations

    main

    When creating a resource, the ID is unknown until the call returns. Use NewEventPollerWithoutEntity to create a poller that doesn't snapshot existing events.

    Important: You must set the poller.EntityID field immediately after receiving the new resource ID and before calling WaitForFinished.

    poller, err := client.NewEventPollerWithoutEntity(
    	linodego.EntityLinode,
    	linodego.ActionLinodeCreate,
    )
    if err != nil {
    	log.Fatal(err)
    }
    
    instance, err := client.CreateInstance(ctx, linodego.InstanceCreateOptions{
    	Region: "us-east",
    	Type:   "g6-nanode-1",
    	Label:  "create-poll-example",
    	Booted: linodego.Pointer(false),
    })
    if err != nil {
    	log.Fatal(err)
    }
    
    // Set this before waiting.
    poller.EntityID = instance.ID
    
    event, err := poller.WaitForFinished(ctx)
    if err != nil {
    	log.Fatal(err)
    }
    
    fmt.Printf("create event %d finished\n", event.ID)
  8. Run tests and manage fixtures

    main

    The project uses make for testing tasks:

    • make test-unit: Runs unit tests.
    • make test-int: Runs integration tests (uses fixtures).
    • make fixtures: Records new API responses into the fixtures/ directory.

    Warning: When running make fixtures, be careful not to commit sensitive account details. While some sanitization is attempted, fixtures/*Account*.yaml files are not automatically sanitized. To target a specific test for fixture updates, use: make TEST_ARGS="-run TestName" fixtures.

    make test-unit
    make test-int
    make fixtures
    make TEST_ARGS="-run TestListVolumes" fixtures
  9. Migrate to linodego v2

    main

    To upgrade from v1 to v2, follow these primary steps:

    1. Update imports: Change all imports from github.com/linode/linodego to github.com/linode/linodego/v2.
    2. Update dependencies: Run the following commands to update your Go module:
      go get github.com/linode/linodego/v2
      go mod tidy
    3. Handle NewClient changes: NewClient now returns (Client, error) instead of just Client. You must handle the error at every call site.
    4. Address API removals: Replace deprecated APIs, aliases, and temporary V2-suffixed compatibility APIs with their supported non-V2 names.
    5. Refactor method arguments: Many methods that previously took primitive parameters now require a single typed options struct (e.g., ...Options).
    6. Review HTTP client changes: The internal client has migrated from Resty to net/http. Hooks like OnBeforeRequest and OnAfterResponse now use *http.Request and *http.Response.
    // v1
    linodeClient := linodego.NewClient(oauth2Client)
    
    // v2
    linodeClient, err := linodego.NewClient(oauth2Client)
    if err != nil {
        return err
    }
  10. Migrate Object Storage APIs to region-based usage

    main

    Object Storage APIs have moved from cluster-oriented naming to region-based usage.

    Key changes:

    • Stop using ObjectStorageBucket.Cluster.
    • Use region IDs in all bucket, object, cert, and ACL calls.
    • Replace ListObjectStorageBucketsInCluster with ListObjectStorageBucketsInRegion.
    • Remove usage of deleted ObjectStorageCluster APIs.
    • Access Management: GetObjectStorageBucketAccess now returns the new access shape directly (no separate V2 type needed). UpdateObjectStorageBucketAccess now uses PUT, and ModifyObjectStorageBucketAccess is a new POST method.
    • Compatibility: Remove any temporary V2-suffixed Object Storage APIs and use the supported non-V2 names.
  11. Initialize a linodego client

    main

    To use linodego, you must provide an authenticated http.Client. This is typically done by creating an oauth2.Transport using a Linode API token (stored in an environment variable like LINODE_TOKEN) and passing that client to linodego.NewClient. You can also enable debug mode using client.SetDebug(true).

    package main
    
    import (
    	"context"
    	"fmt"
    	"log"
    	"net/http"
    	"os"
    
    	"github.com/linode/linodego/v2"
    	"golang.org/x/oauth2"
    )
    
    func main() {
    	apiKey, ok := os.LookupEnv("LINODE_TOKEN")
    	if !ok {
    		log.Fatal("Could not find LINODE_TOKEN, please assert it is set.")
    	}
    	tokenSource := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: apiKey})
    
    	oauth2Client := &http.Client{
    		Transport: &oauth2.Transport{
    			Source: tokenSource,
    		},
    	}
    
    	linodeClient, err := linodego.NewClient(oauth2Client)
    	if err != nil {
    		log.Fatal(err)
    	}
    	linodeClient.SetDebug(true)
    
    	res, err := linodeClient.GetInstance(context.Background(), 4090913)
    	if err != nil {
    		log.Fatal(err)
    	}
    	fmt.Printf("%v", res)
    }