Skip to main content

3 posts tagged with "Automation"

View All Tags
← Back to the liblab blog

An SDK is a set of software development tools that enable developers to create applications for a specific software platform or framework. In the context of APIs, an SDK typically includes libraries, code samples, and documentation that simplify the process of integrating the API into an application.

Offering your developers a good SDK will increase the adoption of your APIs and provide the necessary safeguards for how to access them.

Why is adding an SDK to your API beneficial?

Here are some of the reasons why adding an SDK to your API is beneficial:

Reduce Development Time and Complexity

By providing an SDK, you can significantly reduce the time and effort required to integrate your API into an application. The SDK provides pre-built code libraries that developers can use to quickly and easily interact with your API, reducing the amount of time they need to spend coding and debugging. If you provide your developers only with an API, they will need to define things like auth logic, pagination, error handling, and other complex logic. This can be built-in into your SDK so developers can implement your API within one line of code.

For example, instead of storing pagination tokens and asking your developers to implement their own logic, they can simply use something like mySDK.getData(skip: 10).

Improved Developer Experience

When you provide an SDK, you're not just providing code libraries; you're also providing documentation and code samples that help developers understand how to use your API effectively. This makes it easier for developers to integrate your API into their applications and reduces the likelihood of errors or issues arising during development. IntelliSense and code suggestions will improve the DevEx of your API by giving your developers information about the available calls they can make. Built-in documentation guides developers on what to expect (request/response) in each API call.

SDK Auto completion

Increased Adoption

When you make it easier for developers to use your API, you increase the likelihood that they will actually do so. This can help to drive adoption of your API and increase its overall usage. If you want to increase your API adoption, offering SDKs will help.

Easier Maintenance and Upgrades

When you provide an SDK, you have more control over how developers interact with your API. This makes it easier to make changes or upgrades to your API without breaking existing applications that use it. By providing an SDK, you can ensure that developers are using your API in a consistent and supported way, which can reduce the risk of issues arising.

Better Security

An SDK can also help to improve the security of your API. By providing pre-built code libraries, you can ensure that developers are using secure coding practices when interacting with your API. This reduces the likelihood of security vulnerabilities arising due to coding errors or mistakes. You can also catch API errors in the SDK and have client-side logic to handle it. Retries are an example of how you can throttle the API access in event of errors.

Conclusion

Adding an SDK to your API can provide significant benefits for both developers and API providers. It simplifies the integration process, improves the developer experience, increases adoption, makes maintenance and upgrades easier, and improves security. If you're developing an API, consider providing an SDK to help developers integrate your API more easily and effectively.

Building true idiomatic SDKs in multiple languages takes time and knowledge. Here at liblab, we offer it as a simple, fast straightforward service. Give liblab a try and check how we can automatically build SDKs for your API in seconds, not months.

← Back to the liblab blog

In 2016 GitHub released the first large-scale, public GraphQL API, proving to everyone that GraphQL is here to stay. Their API is robust and constantly evolving, and it provides developers the tools to “create integrations, retrieve data, and automate your workflows”.

Later on in 2018, Github came out with GitHub Actions — a tool to help developers do what they love most: automate things. With GitHub Actions you can automate your CI/CD and general project management using YAML files.

You can use GitHub’s GraphQL API in the GitHub Actions workflow files to create the automation of your dreams.

A Quick Overview of GraphQL

GraphQL was originally developed by Facebook as a way of cutting down response payload. With a REST get request, you get back everything that an entry has to offer. Let’s say we have a database of customers where a customer entry might look like the following:

"Customer" : {
"id" : "101010",
"first_name" : "Bjarne",
"last_name" : "Stroustrup",
"email" : "[email protected]",
"City" : "Austin",
"State" : "Texas",
...
}

Now let’s say we have the customer’s id and we just want their first name so we can print “Hello, Bjarne!”. Using a REST call will return all the fields for a customer when we only need a single field. This is where GraphQL comes in. With GraphQL you create a query specifying the fields you need and only those fields are returned.

Let’s look at a simple GraphQL query that calls on the GitHub GraphQL API:

query FindRepoID {
repository(owner:"YOUR_ORGANIZATION_NAME", name:"REPOSITORY_NAME") {
id
}
}

The query calls on the repository query with your organization name and the repository name, and returns the id of that repository.

Here is an example of a response of this query:

{
"data": {
"repository": {
"id": "R_ktHp3iGA",
}
}
}

The repository object has 119 fields, but we only asked for one and therefore only received one. Had we used a REST call we would have received all 119 fields.

The Basics of GitHub Actions

To create a workflow for a repository we need to create a workflow YAML file in the .github/workflows directory. If your project doesn’t have those directories, go ahead and create them.

Let’s look at a very basic workflow.yaml file:

name: My First Action ## name of our workflow
on: create ## branch or tag being created trigger
jobs: ## jobs to run when triggered
echo-branch-name: ## name of a job
runs-on: ubuntu-latest ## which machine the job runs on
steps: ## steps to run
- name: Step One ## name of a step
run: | ## commands to run
echo '${{ github.event.ref }}'

A workflow is triggered by events (you can see a list of events that trigger a workflow here). In this case our workflow is triggered by a branch or a tag being created in the workflow’s repository. Once the workflow is triggered, our jobs start running on the type of machine we chose. In this case we simply echo the name of the branch from the ${{ github.event }} variable.

Connecting Branches With Issues Using GitHub Actions and GitHub GraphQL API

First, let’s decide on a branch naming system. For this example we decide that for every Issue there is a branch with a name that starts with the Issue’s number. For example, an Issue numbered #42 will have a branch with a name that starts with #42, like “#42/awesome_branch”.

We’ll start off by getting the Issue number from the branch’s name.

name: New Branch Created 

on:
create

jobs:
On-New-Branch:
runs-on: ubuntu-latest
steps:
- name: Get Issue Number
run: |
branch_name=`echo '${{ github.event.ref }}'`
issue_num=`echo ${branch_name#*#} | egrep -o '^[^/]+'`


For this example if a branch was created without an issue number at the start of its name we will ignore it and the workflow will not continue. To do so we’ll create output variables that the next job can use to determine if to execute or not.

name: New Branch Created 

on:
create

jobs:
Check-Branch-Name:
runs-on: ubuntu-latest
outputs:
issue_num: ${{ steps.step1.outputs.issue_num }}
tracked: ${{ steps.step1.outputs.tracked }}
steps:
- name: Get Created Issue Number
id: step1
run: |
branch_name=`echo '${{ github.event.ref }}'`
issue_num=`echo ${branch_name#*#} | egrep -o '^[^/]+'`
re='^[0-9]+$'
if ! [[ $issue_num =~ $re ]] ; then
echo "::set-output name=tracked::false"
else
echo "::set-output name=tracked::true"
fi
echo "::set-output name=issue_num::$issue_num"

We check if issue_num is actually a number - if it is a number, we set an output variable named ‘tracked’ to be ‘true’, and if it is not a number we set it to be ‘false’. To use the issue number in the next steps, we also save it in an output variable named ‘issue_num’.

The next job created will need those outputs to run:

name: New Branch Created 

on:
create

jobs:
Check-Branch-Name:
runs-on: ubuntu-latest
outputs:
issue_num: ${{ steps.step1.outputs.issue_num }}
tracked: ${{ steps.step1.outputs.tracked }}
steps:
- name: Get Created Issue Number
id: step1
run: |
branch_name=`echo '${{ github.event.ref }}'`
issue_num=`echo ${branch_name#*#} | egrep -o '^[^/]+'`
re='^[0-9]+$'
if ! [[ $issue_num =~ $re ]] ; then
echo "::set-output name=tracked::false"
else
echo "::set-output name=tracked::true"
fi
echo "::set-output name=issue_num::$issue_num"

Add-Linked-Issue-To-Project:
needs: Check-Branch-Name
if: needs.Check-Branch-Name.outputs.tracked == 'true'
env:
ISSUE_NUM: ${{ needs.Check-Branch-Name.outputs.issue_num}}
runs-on: ubuntu-latest
steps:
- name: Get Issue ${{ env.ISSUE_NUM }} Project Item ID
run: |

We specify the dependency on the first job with ‘needs’ and check if the job should execute using an if statement with the output variable ‘tracked’ from the previous job. We also make use of env variables here - environment variables are used to store information that you want to reference in your workflow. Env variables can be set for an entire workflow, for a specific job or for a specific step. To access the content of an env variable use ${{ env.NAME_OF_VARIABLE }}.

To make calls on the GraphQL API we need a GitHub authorization bearer token for our repository. To create one go through these steps , copy your generated token and save it under your repository’s settings as an Actions secret (repository Settings → Secrets → Actions → New repository secret). Name it PERSONAL_TOKEN to match the example. We’re also going to need our organization name and the repository’s name set as our workflow’s environment variables:

name: New Branch Created 

on:
create

env:
GH_TOKEN: ${{ secrets.PERSONAL_TOKEN }}
ORGANIZATION: your-organization-name
REPO: the-repository-name

jobs:
Check-Branch-Name:
runs-on: ubuntu-latest
outputs:
issue_num: ${{ steps.step1.outputs.issue_num }}
tracked: ${{ steps.step1.outputs.tracked }}
steps:
- name: Get Created Issue Number
id: step1
run: |
branch_name=`echo '${{ github.event.ref }}'`
issue_num=`echo ${branch_name#*#} | egrep -o '^[^/]+'`
re='^[0-9]+$'
if ! [[ $issue_num =~ $re ]] ; then
echo "::set-output name=tracked::false"
else
echo "::set-output name=tracked::true"
fi
echo "::set-output name=issue_num::$issue_num"

Add-Linked-Issue-To-Project:
needs: Check-Branch-Name
if: needs.Check-Branch-Name.outputs.tracked == 'true'
env:
ISSUE_NUM: ${{ needs.Check-Branch-Name.outputs.issue_num}}
runs-on: ubuntu-latest
steps:
- name: Get Issue ${{ env.ISSUE_NUM }} ID and State
run: |
gh api graphql -f query='query FindIssueID {
repository(owner:"${{ env.ORGANIZATION }}",name:"${{ env.REPO }}") {
issue(number:${{ env.ISSUE_NUM }}) {
id,
state
}
}
}' > project_data.json
echo 'ISSUE_ID='
$(jq '.data.repository.issue.id' project_data.json)
>> $GITHUB_ENV
echo 'ISSUE_STATE='
$(jq '.data.repository.issue.state' project_data.json)
>> $GITHUB_ENV

We created a step called “Get Issue ${{ env.ISSUE_NUM }} ID and State”. In this step we run a GraphQL query and save its result into project_data.json. The query finds a repository by its organization name and repo name, then looks for an issue by its number and returns the issue’s id and state. We save the issue id and issue state from the result into env variables called ISSUE_ID and ISSUE_STATE for later use.

We use GitHub’s “gh api” to make an authenticated HTTP request to the GitHub API.

gh api <endpoint> [flags]

Our endpoint is the GraphQL API, and the flag -f adds a string parameter in key=value format so the key ‘query’ is set to be our raw GraphQL query FindIssueID.

name: New Branch Created 

on:
create

env:
GH_TOKEN: ${{ secrets.PERSONAL_TOKEN }}
ORGANIZATION: your-organization-name
REPO: the-repository-name

jobs:
Check-Branch-Name:
runs-on: ubuntu-latest
outputs:
issue_num: ${{ steps.step1.outputs.issue_num }}
tracked: ${{ steps.step1.outputs.tracked }}
steps:
- name: Get Created Issue Number
id: step1
run: |
branch_name=`echo '${{ github.event.ref }}'`
issue_num=`echo ${branch_name#*#} | egrep -o '^[^/]+'`
re='^[0-9]+$'
if ! [[ $issue_num =~ $re ]] ; then
echo "::set-output name=tracked::false"
else
echo "::set-output name=tracked::true"
fi
echo "::set-output name=issue_num::$issue_num"

Add-Linked-Issue-To-Project:
needs: Check-Branch-Name
if: needs.Check-Branch-Name.outputs.tracked == 'true'
env:
ISSUE_NUM: ${{ needs.Check-Branch-Name.outputs.issue_num}}
runs-on: ubuntu-latest
steps:
- name: Get Issue ${{ env.ISSUE_NUM }} ID and State
run: |
gh api graphql -f query='query FindIssueID {
repository(owner:"${{ env.ORGANIZATION }}",name:"${{ env.REPO }}") {
issue(number:${{ env.ISSUE_NUM }}) {
id
state
}
}
}' > project_data.json
echo 'ISSUE_ID='
$(jq '.data.repository.issue.id' project_data.json)
>> $GITHUB_ENV
echo 'ISSUE_STATE='
$(jq '.data.repository.issue.state' project_data.json)
>> $GITHUB_ENV

- name: Reopen Issue
if: ${{ env.ISSUE_STATE }} == 'CLOSED'
run: |
gh api graphql -f query='
mutation {
reopenIssue(input: { issueId: ${{ env.ISSUE_ID }} } ){
issue {
title
state
}
}
}'

We added “Reopen Issue”, a Mutation query that runs if the issue state is closed.

We run this query in case we closed the issue in the past and want to reopen it once a branch for it exists, implying that the fix was not done.

We now have an easy-to-read, quick and simple Workflow file that uses GitHub Actions with GraphQL queries to reopen an issue by a branch being created for it. You can modify this basic example to your own needs — you can run a job when a PR is created, you can run a job when an issue is opened, you can run a job every time a commit is made to a branch or run a job on any event trigger you desire. You now have the tools to create your own automation.

More Advanced Examples

Here are some more advanced examples of using the GitHub GraphQL API with GitHub Actions:

  • This workflow moves a newly created issue into a specific column in a project.
  • This workflow connects branches to issues by branch name. When a branch is created for an issue, the issue will move to a specific column in a project. If the issue was closed prior to the creation of the branch, the issue will reopen.
  • This workflow connects PRs to issues by branch name. When a PR is created/closed/merged the issue will move to a specific column in a project. Once an issue is moved to the ‘Done’ column the issue will be closed.

Not Convinced?

Not ready to embark on the GraphQL adventure? Looking for something out of the box to automate your workflows? Looking to mix it all up? Check out GitHub’s REST API and GitHub Marketplace. You can use the REST API instead of the GraphQL API, or you can integrate one of the apps or actions available in the GitHub Marketplace into your workflows. Whichever way you choose, automating your workflows will save you valuable time that you can now use for more coding! Woohoo!

Tips

Trying to use a Marketplace app or action but getting “Resource not accessible by integration” error? Most Marketplace apps and actions use the default “secrets.GITHUB_TOKEN”, but this token doesn’t come with read/write access to all scopes. For example, it doesn’t have write permissions for items in Projects. Set a personal access token like we did in the examples and use it instead of the GITHUB_TOKEN (make sure you give it the correct permissions).

If you’re using the GitHub GraphQL Documentation but having a hard time understanding and making queries work, take a look at the GitHub repository on Apollo GraphQL. The Documentation there is clear and they have an Explorer section where you can try out queries and see their responses.

Our goal here at liblab is to make the lives of developers easier, and that includes our own developers. If we can automate it and save time, we will — leaving our developers all the time they need to focus on the tasks that matter. If you’re looking to save time and automate your SDK creation, if you want flawless SDKs in multiple languages directly from your API, and if you care about making developers’ lives easier, check out what liblab has to offer and contact us to start your SDK journey.

← Back to the liblab blog

This post will take you through the steps to write files to GitHub with Octokit and TypeScript.

Install Octokit

To get started we are going to install Octokit.

npm install @octokit/rest

Create the code

Then we can create our typescript entry point. In this case src/index.ts

import { Octokit } from '@octokit/rest';
const client = new Octokit({
auth: '<AUTH TOKEN>'
});

We instantiate the Octokit constructor and create a new client. We will need to replace the <AUTH TOKEN> with a personal access token from GitHub. Checkout the guide to getting yourself a personal access token from GitHub.

Now that we have our client setup we are going to look at how we can create files and commit them to a repository. In this tutorial I am going to be writing to an existing repo. This will allow you to write to any repo public or private that you have write access to.

Just like using git or the GitHub desktop application we need to do a couple of things to add a file to a repository.

  1. Generate a tree
  2. Commit files to the tree
  3. Push the files

Generate a tree

To create a tree we need to get the latest commits. We will use the repos.listCommits method and we will pass an owner and repo argument. owner is the username or name of the organization the repository belongs to and repo is the name of the repository.

const commits = await client.repos.listCommits({
owner: "<USER OR ORGANIZATION NAME>",
repo: "<REPOSITORY NAME>",
});

We now want to take that list of commits and get the first item from it and retrieve its SHA hash. This will be used to tell the tree where in the history our commits should go. To get that we can make a variable to store the commit hash.

const commitSHA = commits.data[0].sha;

Add files to the tree

Now that we have our latest commit hash we can begin constructing our tree. We are going to pass the files we want to update or create to the tree construction method. In this case I will be representing the files I want to add as an Array of Objects. In my example I will be adding 2 files. [test.md](http://test.md) which will hold the string Hello World and time.txt which will store the latest timestamp.

const files = [
{
name: "test.md",
contents: "Hello World"
},
{
name: "time.txt",
contents: new Date().toString()
}
];

Octokit will want the files in a specific format:

interface File {
path: string;
mode: '100644' | '100755' | '040000' | '160000' | '120000';
type: 'commit' | 'tree' | 'blob';
sha?: string | null;
content: string;
}

There are a couple of properties in this interface.

  • path - Where in the repository the file should be stored.
  • mode - This is a code that represents what kind of file we are adding. Here is a quick run down:
    • File = '100644'
    • ExecutableFile = '100755'
    • Directory = '040000'
    • Submodule = '160000'
    • Symlink = '120000'
  • type - The type of action you are performing on the tree. In this case we are making a file commit
  • sha - The last known hash of the file if you plan on overwriting it. (This is not needed)
  • content - Whatever should be in the file

We can map to transform our file array into this proper format:

const commitableFiles: File[] = files.map(({name, contents}) => {
return {
path: name,
mode: '100644',
type: 'commit',
content: contents
}
})

Now that we have an array of all the files we want to commit we will pass them to the createTree() method. You can think of this as adding your files in git.

const {
data: { sha: currentTreeSHA },
} = await client.git.createTree({
owner: "<USER OR ORGANIZATION NAME>",
repo: "<REPOSITORY NAME>",
tree: commitableFiles,
base_tree: CommitSHA,
message: 'Updated programatically with Octokit',
parents: [CommitSHA],
});

Afterwards we have the variable currentTreeSHA . We will need this when we actually commit the files.

Next we go to actually make a commit on the tree

const {
data: { sha: newCommitSHA },
} = await client.git.createCommit({
owner: "<USER OR ORGANIZATION NAME>",
repo: "<REPOSITORY NAME>",
tree: currentTreeSHA,
message: `Updated programatically with Octokit`,
parents: [latestCommitSHA],
});

Push the commit

Then we push the commit

await client.git.updateRef({
owner: "<USER OR ORGANIZATION NAME>",
repo: "<REPOSITORY NAME>",
sha: newCommitSHA,
ref: "heads/main", // Whatever branch you want to push to
});

That is all you need to do to push files to a GitHub repository. We have found this functionality to be really useful when we need to push files that are automatically generated or often change.

If you find yourself needing to manage SDKs in multiple languages from an API, checkout liblab. Our tools make generating SDKs dead simple with the ability to connect to the CI/CD tools you are probably already using.

liblab!