Reputation: 9251
I want to commit a pipeline config for a user via the Github API.
So far I am able to commit just a file named main.yaml
into the root directory of the repo but I need the file to sit within .github/workflows/main.yaml
.
The code I have so far is this:
const commitWorkflowConfig = async ({
ownerName,
repoName
}) => {
const pipelineConfig = fs.readFileSync(__dirname + '/../../../templates/main.yaml', { encoding: "utf-8" })
// Create Blob from the content
const blob = await axios.post(`https://api.github.com/repos/${ownerName}/${repoName}/git/blobs`, {
content: pipelineConfig,
encoding: "utf-8"
})
// Get last commit hash of Master
const {
commit: {
sha: masterSha
}
} = await axios.get(`https://api.github.com/repos/${ownerName}/${repoName}/branches/master`)
// Create new branch from master
const branch = await axios.post(`https://api.github.com/repos/${ownerName}/${repoName}/git/refs`,
{
"ref": "refs/heads/workflow-pipeline",
"sha": masterSha
})
// Create commit to new branch
const commit = await axios.put(`https://api.github.com/repos/${ownerName}/${repoName}/contents/main.yaml`, {
message: "New commit",
content: pipelineConfig,
sha: blob.sha,
branch: "workflow-pipeline"
})
// Open Pull Request
const response = await axios.post(`https://api.github.com/repos/${ownerName}/${repoName}/pulls`, {
title: "New PR",
head: "workflow-pipeline",
base: "master"
})
} const commitWorkflowConfig = async ({
ownerName,
repoName
}) => {
const pipelineConfig = fs.readFileSync(__dirname + '/../../../templates/main.yaml', { encoding: "utf-8" })
// Create Blob from the content
const blob = await axios.post(`https://api.github.com/repos/${ownerName}/${repoName}/git/blobs`, {
content: pipelineConfig,
encoding: "utf-8"
})
// Get last commit hash of Master
const {
commit: {
sha: masterSha
}
} = await axios.get(`https://api.github.com/repos/${ownerName}/${repoName}/branches/master`)
// Create new branch from master
const branch = await axios.post(`https://api.github.com/repos/${ownerName}/${repoName}/git/refs`,
{
"ref": "refs/heads/workflow-pipeline",
"sha": masterSha
})
// Create commit to new branch
const commit = await axios.put(`https://api.github.com/repos/${ownerName}/${repoName}/contents/main.yaml`, {
message: "New commit",
content: pipelineConfig,
sha: blob.sha,
branch: "workflow-pipeline"
})
// Open Pull Request
const response = await axios.post(`https://api.github.com/repos/${ownerName}/${repoName}/pulls`, {
title: "New PR",
head: "workflow-pipeline",
base: "master"
})
}
This feels like a lot of API calls all for just committing one file and opening a PR, I suspect I am doing something wrong?
When I create the new commit, it doesn't allow me to add any path or add .github/workflows/main.yaml
to the end of the URL as I get a 404. Is there any way I can update this commit to commit to a folder instead of the root directory?
To summarise, How can I make a simple commit of .github/workflows/main.yaml
to a new branch and open a PR for it?
const tree = await this.post(`https://api.github.com/repos/${ownerName}/${repoName}/git/trees`, {
base_tree: masterSha,
tree: [
{
path: ".github",
mode: "040000",
type: "tree"
},
{
path: ".github/workflow",
mode: "040000",
type: "tree"
},
{
path: ".github/workflow/main.yaml",
mode: "100755",
type: "tree",
content: pipelineConfig
},
]
})
const { tree } = await axios.post(`https://api.github.com/repos/${ownerName}/${repoName}/git/trees`, {
base_tree: masterSha,
tree: [
{
path: "workflows/main.yaml",
mode: "100644",
type: "blob",
sha
}
]
})
const workflowTree = tree.find(t => t.path === "workflows");
await axios.post(`https://api.github.com/repos/${ownerName}/${repoName}/git/trees`, {
base_tree: masterSha,
tree: [
{
path: ".github/workflows",
mode: workflowTree.mode, // "040000"
type: workflowTree.type, // "tree"
sha: workflowTree.sha
}
]
})
Upvotes: 4
Views: 1349
Reputation: 515
I hope this can help you, I am working on the same topic where I need to update files inside .github/workflows
folder by API and, I think I found a better and easy way.
I don't know since when but, with this endpoint https://docs.github.com/en/rest/reference/repos#create-or-update-file-contents with the right permissions (scope workflow
in your authToken), you can create and update files into the .github/workflows
folder.
// to create a new file
await octokit.request('PUT /repos/{owner}/{repo}/contents/{path}', {
owner: 'octocat',
repo: 'hello-world',
path: '.github/workflows/your-file.yml',
message: 'Your commit message',
content: 'contentInBase64'
});
// to update a file
// get the sha of the file that you want to update
const {
data: contentData
} = await octokit.request('GET /repos/{owner}/{repo}/contents/{path}', {
owner: 'octocat',
repo: 'hello-world',
path: '.github/workflows/your-file.yml',
});
const shaOfCurrentFileToUpdate = contentData.sha;
await octokit.request('PUT /repos/{owner}/{repo}/contents/{path}', {
owner: 'octocat',
repo: 'hello-world',
path: '.github/workflows/your-file.yml',
message: 'Your new commit message',
content: 'newContentInBase64'
sha: shaOfCurrentFileToUpdate
});
With this, I removed a lot of lines of code and solved the issue on my side, I hope is helpful for you too.
Upvotes: 1
Reputation: 2058
// Create commit to new branch
const commit = await axios.put(`https://api.github.com/repos/${ownerName}/${repoName}/contents/main.yaml`, {
message: "New commit",
content: pipelineConfig,
sha: blob.sha,
branch: "workflow-pipeline"
})
You cannot directly edit the contents of a file. You need to use the trees API to create an entirely new tree based off of the original tree.
Create the blob for the new file (https://docs.github.com/en/rest/reference/git#create-a-blob). You already did this step good job.
Get the tree that you want to make your new branch based off of (https://docs.github.com/en/rest/reference/repos#get-a-branch). Note that you have to get the tree sha, not the commit sha.
Create a new tree with that file added (https://docs.github.com/en/rest/reference/git#create-a-tree). I think this step will be the most complicated, because for each 'folder' tree created, a parent 'folder' tree will also need to be created to contain the newly created 'folder' tree. So if you want to modify the .github/workflows
folder, you'll first have to create a new tree based on.github/workflows
. Let's say that tree sha was abc...
. Then you'll need to create a new tree based on the .github
folder, and make the workflows
dir abc...
, and not the old one.
Create a commit (https://docs.github.com/en/rest/reference/git#create-a-commit). Use the sha of the root tree you created in the previous step.
Create a new branch (https://docs.github.com/en/rest/reference/git#create-a-reference). In the question code, you created it before you created the commit, which doesn't make sense. You need to create it after you create the commit, so that it's head will point to the sha of the commit you created.
Create the pull request (https://docs.github.com/en/rest/reference/pulls#create-a-pull-request). You already have this step in your code.
Here is a visual that explains the steps 2 and 3 for adding a main.yml
file to .github/workflows
:
Original tree | New Tree (but the '.github' tree references ^b, not b)
- sha: a --> - sha: ^a
- files: - files:
- .github --> - .github (but the 'workflows' tree references ^c, not c)
- sha: b - sha: ^b
- files: - files
- workflows --> - workflows (but with the main.yml)
- sha: c - sha: ^c
- files: - files:
- main.yml (reference the sha of the blob you created)
...
...
...
There are three -->
s in the visual. Each -->
is a request to make.
^c
tree, which is based off of the c
tree and has the added main.yml
file.^b
tree, which is based off of b
but has ^c
in it.^a
tree, which is based off of a
but has ^b
in it.And those are the steps to creating a simple simple, but complicated to create pull request.
It's surprising how many API calls are needed for this. 5 + {how deep the file you want to add is}
Upvotes: 3