go-atlassian

repository·main·Indexed 42 days ago

https://github.com/ctreminiom/go-atlassian

A Go library for interacting with Atlassian REST APIs, including Jira, Confluence, and Jira Service Management. It supports Basic Auth and OAuth 2.0 with automatic token renewal, providing a modular system of service interfaces for type-safe API communication. The library includes a Client.Call method for executing raw API endpoints not yet implemented in the core package.

Tokens
5.8K
Snippets
12
Records
15
Agent score
51%

What's inside go-atlassian

  1. Refresh OAuth 2.0 tokens

    main

    Access tokens expire and must be refreshed. You can handle this in two ways:

    1. Manual Refresh: Use client.OAuth.RefreshAccessToken(ctx, refreshToken) to get a new token, then manually update the client using client.Auth.SetBearerToken(newToken.AccessToken).
    2. Automatic Renewal (Recommended): When initializing the client, use the WithAutoRenewalToken(token) option or the convenience method WithOAuthWithAutoRenewal(oauthConfig, token). The client will automatically refresh tokens in the background (with a 5-minute buffer) and is thread-safe for concurrent use.
    // Option 1: Manual
    newToken, err := client.OAuth.RefreshAccessToken(ctx, token.RefreshToken)
    if err != nil {
        log.Fatal(err)
    }
    client.Auth.SetBearerToken(newToken.AccessToken)
    
    // Option 2: Automatic (Recommended)
    client, err = jira.New(
        http.DefaultClient,
        "https://your-domain.atlassian.net",
        jira.WithOAuthWithAutoRenewal(oauthConfig, token),
    )
  2. How services and interfaces work

    main
    The library is built around a modular system of service interfaces. Each Atlassian product (Jira, Confluence, etc.) is represented by a client that exposes various service interfaces. Each interface contains methods that map directly to REST API endpoints. For example, the BoardConnector interface provides methods to interact with Jira boards, such as Get, Create, Filter, and Sprints. This pattern ensures that the library is type-safe and follows the structure of the underlying Atlassian APIs.
  3. Discover multiple sites with OAuth 2.0

    main

    When using OAuth 2.0, a single access token may grant access to multiple Atlassian sites. Use client.OAuth.GetAccessibleResources to retrieve a list of available sites. You can then iterate through these resources and instantiate new service clients (e.g., Jira, Confluence) using the specific resource.URL for each site.

    resources, err := client.OAuth.GetAccessibleResources(ctx, token.AccessToken)
    if err != nil {
        log.Fatal(err)
    }
    
    for _, resource := range resources {
        fmt.Printf("Site: %s (%s)\n", resource.Name, resource.URL)
    
        // Create a client for this specific site
        siteClient, err := jira.New(
            http.DefaultClient,
            resource.URL,
            jira.WithOAuth(oauthConfig),
        )
        if err != nil {
            continue
        }
    
        siteClient.Auth.SetBearerToken(token.AccessToken)
        // Use siteClient for API calls to this site
    }
  4. Authenticate using an Atlassian API Token

    main

    To use the package with an API token, create a client for your specific service (e.g., Confluence) and use SetBasicAuth with your Atlassian account email and the generated API token. You can also pass a preconfigured http.Client to the New constructor to control timeouts, proxies, or connection settings.

    instance, err := confluence.New(nil, "INSTANCE_HOST")
    if err != nil {
        log.Fatal(err)
    }
    instance.Auth.SetBasicAuth("YOUR_CLIENT_MAIL", "YOUR_APP_ACCESS_TOKEN")
  5. Authenticate using OAuth 2.0 (3-legged OAuth)

    main

    The library supports OAuth 2.0 for building apps that act on behalf of users. This works across Jira (v2, v3, Agile, Service Management), Confluence (v1, v2), Admin, Assets, and Bitbucket.

    To implement the flow:

    1. Create an OAuth 2.0 app in the Atlassian Developer Console to get a ClientID and ClientSecret.
    2. Configure an OAuth2Config using the common package.
    3. Initialize the client with WithOAuth(oauthConfig).
    4. Generate an authorization URL using client.OAuth.GetAuthorizationURL.
    5. After the user authorizes, exchange the code for tokens using client.OAuth.ExchangeAuthorizationCode.
    6. Use the token via SetBearerToken or, ideally, use WithAutoRenewalToken to enable automatic background refreshing.
    // Configure OAuth
    oauthConfig := &common.OAuth2Config{
        ClientID:     "YOUR_CLIENT_ID",
        ClientSecret: "YOUR_CLIENT_SECRET",
        RedirectURI:  "https://your-app.com/callback",
    }
    
    // Create client with OAuth support
    client, err := jira.New(
        http.DefaultClient,
        "https://api.atlassian.com",
        jira.WithOAuth(oauthConfig),
    )
    if err != nil {
        log.Fatal(err)
    }
    
    // Generate authorization URL
    scopes := []string{"read:jira-work", "write:jira-work"}
    authURL, err := client.OAuth.GetAuthorizationURL(scopes, "state")
    if err != nil {
        log.Fatal(err)
    }
    
    // After authorization, exchange code for tokens
    ctx := context.Background()
    token, err := client.OAuth.ExchangeAuthorizationCode(ctx, "AUTH_CODE")
    if err != nil {
        log.Fatal(err)
    }
    
    // Recommended: Create new client with auto-renewal
    client, err = jira.New(
        http.DefaultClient,
        "https://your-domain.atlassian.net",
        jira.WithOAuth(oauthConfig),
        jira.WithAutoRenewalToken(token),
    )
  6. Get issue transitions using Issue service

    main

    You can use the Issue service to retrieve detailed information about a specific Jira issue, including its available transitions. The Get method accepts an expand parameter to include additional data in the response.

    ctx := context.Background()
    issueKey := "KP-2"
    expand := []string{"transitions"}
    issue, response, err := atlassian.Issue.Get(ctx,issueKey, nil, expand)
    if err != nil {
    	log.Fatal(err)
    }
    log.Println(issue.Key)
    for _, transition := range issue.Transitions {
    	log.Println(transition.Name, transition.ID, transition.To.ID, transition.HasScreen)
    }
  7. Call a RAW API Endpoint using Client.Call

    main

    If an Atlassian API endpoint is not yet implemented in the go-atlassian library, you can execute custom HTTP requests using the Client.Call method.

    To do this:

    1. Initialize a client (e.g., using v3.New).
    2. Configure authentication (e.g., atlassian.Auth.SetBasicAuth).
    3. Define your target apiEndpoint string.
    4. Use atlassian.NewRequest to create a request object with the appropriate method, endpoint, and context.
    5. Define a custom struct to unmarshal the expected JSON response.
    6. Pass the request and the pointer to your response struct into atlassian.Call.
    package main  
      
    import (
        "context"
        "fmt"
        "github.com/ctreminiom/go-atlassian/v2/jira/v3"
        "log"
        "net/http"
        "os"
    )
    
    type IssueTypeMetadata struct {
        IssueTypes []struct {
           ID          string `json:"id"` 
           Name        string `json:"name"` 
           Description string `json:"description"` 
        } `json:"issueTypes"` 
    }
    
    func main() {
    
        var (
           host  = os.Getenv("SITE") 
           mail  = os.Getenv("MAIL") 
           token = os.Getenv("TOKEN") 
        )
    
        atlassian, err := v3.New(nil, host) 
        if err != nil { 
           log.Fatal(err) 
        } 
    
        atlassian.Auth.SetBasicAuth(mail, token) 
    
        // Define the RAW endpoint 
        apiEndpoint := "rest/api/3/issue/createmeta/KP/issuetypes" 
    
        request, err := atlassian.NewRequest(context.Background(), http.MethodGet, apiEndpoint, "", nil) 
        if err != nil { 
           log.Fatal(err) 
        } 
    
        customResponseStruct := new(IssueTypeMetadata) 
        response, err := atlassian.Call(request, &customResponseStruct) 
        if err != nil { 
           log.Fatal(err) 
        } 
    
        fmt.Println(response.Status) 
    }
  8. Available go-atlassian packages

    main

    The library is modularized into several packages depending on the Atlassian product and API version you need to interact with. Import the specific module path into your project to access the corresponding functionality.

    | Module | Path | URL's |
    |-------------------------------------|----------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|
    | Jira v2 | `github.com/ctreminiom/go-atlassian/v2/jira/v2` | [Getting Started](https://docs.go-atlassian.io/jira-software-cloud/introduction) |
    | Jira v3 | `github.com/ctreminiom/go-atlassian/v2/jira/v3` | [Getting Started](https://docs.go-atlassian.io/jira-software-cloud/introduction) |
    | Jira Software Agile | `github.com/ctreminiom/go-atlassian/v2/jira/agile` | [Getting Started](https://docs.go-atlassian.io/jira-agile/introduction) |
    | Jira Service Management | `github.com/ctreminiom/go-atlassian/v2/jira/sm` | [Getting Started](https://docs.go-atlassian.io/jira-service-management/introduction) |
    | Jira Assets | `github.com/ctreminiom/go-atlassian/v2/assets` | [Getting Started](https://docs.go-atlassian.io/jira-assets/overview) |
    | Confluence | `github.com/ctreminiom/go-atlassian/v2/confluence` | [Getting Started](httpshttps://docs.go-atlassian.io/confluence-cloud/introduction) |
    | Confluence v2 | `github.com/ctreminiom/go-atlassian/v2/confluence/v2` | [Getting Started](https://docs.go-atlassian.io/confluence-cloud/v2/introduction) |
    | Admin Cloud | `github.com/ctreminiom/go-atlassian/v2/admin` | [Getting Started](https://docs.go-atlassian.io/atlassian-admin-cloud/overview) |
    | Bitbucket Cloud *(In Progress)* | `github.com/ctreminiom/go-atlassian/v2/bitbucket` | [Getting Started](https://docs.go-atlassian.io/bitbucket-cloud/introduction) |
  9. Reference: BoardConnector interface

    main

    The BoardConnector interface represents Jira boards and provides methods to search, manage, and query board-related data.

    type BoardConnector interface {
    	Get(ctx context.Context, boardID int) (*model.BoardScheme, *model.ResponseScheme, error)
    	Create(ctx context.Context, payload *model.BoardPayloadScheme) (*model.BoardScheme, *model.ResponseScheme, error)
    	Filter(ctx context.Context, filterID, startAt, maxResults int) (*model.BoardPageScheme, *model.ResponseScheme, error)
    	Backlog(ctx context.Context, boardID int, opts *model.IssueOptionScheme, startAt, maxResults int) (*model.BoardIssuePageScheme, *model.ResponseScheme, error)
    	Configuration(ctx context.Context, boardID int) (*model.BoardConfigurationScheme, *model.ResponseScheme, error)
    	Epics(ctx context.Context, boardID, startAt, maxResults int, done bool) (*model.BoardEpicPageScheme, *model.ResponseScheme, error)
    	IssuesWithoutEpic(ctx context.Context, boardID int, opts *model.IssueOptionScheme, startAt, maxResults int) (
    		*model.BoardIssuePageScheme, *model.ResponseScheme, error)
    	IssuesByEpic(ctx context.Context, boardID, epicID int, opts *model.IssueOptionScheme, startAt, maxResults int) (*model.BoardIssuePageScheme, *model.ResponseScheme, error)
    	Issues(ctx context.Context, boardID int, opts *model.IssueOptionScheme, startAt, maxResults int) (*model.BoardIssuePageScheme,
    		*model.ResponseScheme, error)
    	Move(ctx context.Context, boardID int, payload *model.BoardMovementPayloadScheme) (*model.ResponseScheme, error)
    	Projects(ctx context.Context, boardID, startAt, maxResults int) (*model.BoardProjectPageScheme, *model.ResponseScheme, error)
    	Sprints(ctx context.Context, boardID, startAt, maxResults int, states []string) (*model.BoardSprintPageScheme,
    		*model.ResponseScheme, error)
    	IssuesBySprint(ctx context.Context, boardID, sprintID int, opts *model.IssueOptionScheme, startAt, maxResults int) (*model.BoardIssuePageScheme,
    		*model.ResponseScheme, error)
    	Versions(ctx context.Context, boardID, startAt, maxResults int, released bool) (*model.BoardVersionPageScheme,
    		*model.ResponseScheme, error)
    	Delete(ctx context.Context, boardID int) (*model.ResponseScheme, error)
    	Gets(ctx context.Context, opts *model.GetBoardsOptions, startAt, maxResults int) (*model.BoardPageScheme,
    		*model.ResponseScheme, error)
    }
  10. Use the Client interface for Atlassian service requests

    main

    The Client interface defines the standard contract for interacting with Atlassian services. It provides methods for constructing various types of HTTP requests (JSON, standard, and form-encoded), executing those requests, and transforming responses or structures into readers. Implementations of this interface handle the underlying complexities of Atlassian API communication, including request construction and response parsing into models.ResponseScheme.

    // Example of how a Client implementation is used to manage requests
    type Client interface {
    	NewJSONRequest(ctx context.Context, method, apiEndpoint string, payload io.Reader) (*http.Request, error)
    	NewRequest(ctx context.Context, method, apiEndpoint string, payload io.Reader) (*http.Request, error)
    	NewFormRequest(ctx context.Context, method, apiEndpoint, formDataContentType string, payload io.Reader) (*http.Request, error)
    	Call(request *http.Request, structure interface{}) (*models.ResponseScheme, error)
    	TransformTheHTTPResponse(response *http.Response, structure interface{}) (*models.ResponseScheme, error)
    	TransformStructToReader(structure interface{}) (io.Reader, error)
    }
  11. Use the ServerConnector interface for Jira

    main

    The ServerConnector interface provides the entrypoint for interacting with a Jira server instance. It defines methods to retrieve metadata and information about the Jira environment. The primary method available is Info, which returns details about the Jira instance such as version and plugin information.

    // Example usage of ServerConnector
    func GetJiraDetails(connector jira.ServerConnector) {
    	ctx := context.Background()
    	info, resp, err := connector.Info(ctx)
    	if err != nil {
    		// handle error
    	}
    	// Use info (*model.ServerInformationScheme)
    	// Use resp (*model.ResponseScheme)
    }