Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions precommit/Default.ci
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
11 changes: 11 additions & 0 deletions precommit/Golang.ci
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
- repo: https://github.com/dnephin/pre-commit-golang
rev: master
hooks:
- id: go-fmt
- id: go-vet
- id: go-imports
- id: golangci-lint
- id: go-unit-tests
- id: go-mod-tidy
- id: go-mod-vendor
- id: go-build
4 changes: 4 additions & 0 deletions precommit/Node.ci
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- repo: https://github.com/pre-commit/mirrors-eslint
rev: v8.37.0
hooks:
- id: eslint
18 changes: 18 additions & 0 deletions precommit/Python.ci
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
- repo: https://github.com/psf/black
rev: 22.12.0
hooks:
- id: black
language_version: python3.10
- repo: https://github.com/pycqa/flake8
rev: 6.0.0
hooks:
- id: flake8
- repo: https://github.com/asottile/reorder_python_imports
rev: v3.9.0
hooks:
- id: reorder-python-imports
- repo: https://github.com/Lucas-C/pre-commit-hooks-safety
rev: v1.3.0
hooks:
- id: python-safety-dependencies-check
files: requirements.txt
81 changes: 81 additions & 0 deletions scripts/ci.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#!/usr/bin/env bash
#
# Exit on error. Append "|| true" if you expect an error.
set -o errexit
# Exit on error inside any functions or subshells.
set -o errtrace
# Do not allow use of undefined vars. Use ${VAR:-} to use an undefined VAR
set -o nounset
# Catch the error in case mysqldump fails (but gzip succeeds) in `mysqldump |gzip`
set -o pipefail

__repo_url=${1}
__repo_branch=${2}

__repo_name=$(basename ${__repo_url} .git) #get name of git repository
__temp_dir=$(mktemp -d)
__repo_dir="${__temp_dir}/${__repo_name}"
mkdir -p "${__repo_dir}"

ci_dir_path = "../precommit/CI/"


## @brief Pulls repository in the given path
## @param $1 repo url to clone.
## @param $2 path on which to clone the repo.
## @param $3 branch name of the git repository.
pullRepository() {
local repo_url=${1}
local repo_path=${2}
pushd "${repo_path}"
if [ "$#" -gt 2 ] ; then
local repo_branch=${3}
git clone -b "${repo_branch}" "${repo_url}" .
else
git clone "${repo_url}" .
fi
chmod -R 755 "${repo_path}"
echo "Repository successfully pulled - ${repo_url}"
popd
}


addCI(){
local repo_path=${1}
local ci_dir_path=${2}

pushd "${repo_path}"
cp -r "${ci_dir_path}" .
echo "Git hooks successfully added"
popd
}


## @brief Pushes repository to Github
## @param $1 path where updates to the repository are stored.
## @param $2 branch of the repository.
pushRepo(){
local repo_path=${1}
local repo_branch=${2}

pushd "${repo_path}"
git add -A .
git commit -m "[DeployBot] Initialized gitsecret and added git hooks"
git pull || true
git push -u origin "${repo_branch}"
echo "Git Repo successfully initialized and pushed"
popd
}

cleanup() {
local repo_path=$1
rm -rf "${repo_path}"
rm -rf "${__temp_dir}"
rm -rf "${__hooks_temp_dir}"
echo "Cleanup successfull"
}

pullRepository "${__repo_url}" "${__repo_dir}" "${__repo_branch}"
addCI "${__repo_dir}" "${ci_dir_path}"
pushRepo "${__repo_dir}" "${__repo_branch}"
cleanup "${__repo_dir}"
34 changes: 34 additions & 0 deletions src/git/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ var (
// githubUserName is the name of the user which is a member of the GitHub
//organization. githubAccessToken is a personal access token of this user
githubUserName string

// githubActionToken is used to verify the request coming from GitHub Actions
githubActionToken string

// Logdir is the directory where all logs will be stored
logDir string
Comment on lines +24 to +25
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need a separate logDir, can't we use the existing logger?

)

const (
Expand All @@ -26,6 +32,14 @@ const (

// apiURL is the url at which all APIs of github are rooted
apiURL = "https://api.github.com"

// root path of pre-commit files
BASE_FILES_PATH string = "../../precommit/"
// pre-commit config file
PRE_COMMIT_CONFIG string = "../../precommit/CI/pre-commit-config.yaml"
Comment on lines +37 to +39
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's keep the style consistent, maybe change it to camel case as is with the other variables?


// CI script name
ciScriptName = "ci.sh"
)

// Repository is the type that is used to store information about a repository
Expand All @@ -35,6 +49,19 @@ type Repository struct {
Branches []string `json:"branches"`
}

// CIAction : Used for unmarshalling the CI request
type CIAction struct {
Repo string `json:"repo"`
Python string `json:"python"`
Golang string `json:"golang"`
Node string `json:"node"`
Ts string `json:"ts"`
Flutter string `json:"flutter"`
Dart string `json:"dart"`
Docker string `json:"docker"`
Shell string `json:"shell"`
Comment on lines +55 to +62
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need all their types to be string. Can't we unmarshall to bool instead from the json request?

}

func init() {
githubSecret = helper.Env("GITHUB_SECRET", "None")
if githubSecret == "None" {
Expand All @@ -50,4 +77,11 @@ func init() {
if githubAccessToken == "None" {
log.Fatal("GITHUB_ACCESS_TOKEN is not present in environment, Exiting")
}

githubActionToken = helper.Env("GITHUB_ACTION_TOKEN", "None")
if githubActionToken == "None" {
log.Fatal("GITHUB_ACTION_TOKEN is not present in environment, Exiting")
}

logDir = helper.Env("LOG_DIR", "/var/logs/deploybot/")
}
122 changes: 122 additions & 0 deletions src/git/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import (
"io/ioutil"
"net/http"
"net/url"
"os"
"os/exec"
"path"
"reflect"

"github.com/devclub-iitd/DeployBot/src/helper"
log "github.com/sirupsen/logrus"
Expand Down Expand Up @@ -181,3 +184,122 @@ func CreatedRepo(r *http.Request) (*Repository, int, error) {
},
}, 200, nil
}

func validateActionRequest(r *http.Request) bool {
headerValue := r.Header.Get("Authorization")
return "Bearer " + githubActionToken == headerValue
}

// CIHandler handles the request to add CI Files to a repo
func CIHandler(w http.ResponseWriter, r *http.Request) {

if !validateActionRequest(r) {
log.Info("Request verification from Github Action: FAILED")
w.WriteHeader(403)
return
}
log.Info("Request verification from Github Action: SUCCESS")

if r.Method != "POST" {
w.WriteHeader(405)
log.Errorf("received a %s request, expected POST for CI Handler", r.Method)
return
}

r_body, err := ioutil.ReadAll(r.Body)
if err != nil {
w.WriteHeader(500)
log.Errorf("cannot read request body - %v", err)
return
}

var ci CIAction
if err := json.Unmarshal(r_body, &ci); err != nil {
w.WriteHeader(500)
log.Errorf("cannot unmarshal request body - %v", err)
return
}

var ciList []string
// add Default checks
ciList = append(ciList, "Default")
v := reflect.ValueOf(ci)
for i := 0; i < v.NumField(); i++ {
if v.Field(i).String() == "true" {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If CIAction can have bool types this should be changed accordingly.

ciList = append(ciList, v.Type().Field(i).Name)
}
}
addChecks(ciList)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this function call never return an error?

log.Info("Added checks to pre-commit-config.yaml")

// call a script to add this file to the repo

// get ssh_url of the repo
repositories, err := Repos()
if err != nil {
log.Error("Error getting repositories %v", err)
return
}
var repo Repository
for _, repository := range repositories {
if repository.Name == ci.Repo {
repo = repository
}
}
if repo.Name == "" {
log.Error("Repository not found")
return
}

for _, branch := range repo.Branches {
go addCI(repo.URL, repo.Name, branch)
}

}

func addChecks(ciList []string) {
// combine the data of all files in ciList to a file named .pre-commit-config.yaml

// create the file even if it exists
os.Create(PRE_COMMIT_CONFIG)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not very comfortable with the idea of using the same file PRE_COMMIT_CONFIG for every call to addChecks. Imagine if there were two consecutive requests for adding CI checks (maybe the user was impatient, and didn't like to wait). Then this file can be overwritten while ci.sh is being executed and this can lead to hard-to-spot bugs. I would suggest some alternatives, you are free to implement any one of these.

  1. Use locks. Basically you need to ensure that the sequence of operations addChecks -> go addCI() is done atomically. So you would have to acquire a lock in the HTTP handler itself, which is not a very efficient solution imo.
  2. Have different files. Just don't forget to clean them up.
    • You can change the file name PRE_COMMIT_CONFIG to have a repo signature. Something like -> PRE_COMMIT_CONFIG + "_" + repoName. The script ci.sh would have to copy only this file instead of the entire directory.
    • You can create a temp directory (which will have an arbitrary name) where you can write PRE_COMMIT_CONFIG, and pass the temp directory name to ci.sh so that it would copy the entire directory as it is.

f, err := os.OpenFile(PRE_COMMIT_CONFIG, os.O_APPEND|os.O_WRONLY, 0600)
if err != nil {
log.Error("Error opening file:pre-commit.config")
return
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe return the err from here

}

// close the file after the function returns
defer f.Close()

for i := 0; i < len(ciList); i++ {
// add the data from the file to the pre-commit-config file

var file_name string = ciList[i] + ".ci"
var file_path string = BASE_FILES_PATH + file_name

content, err := os.ReadFile(file_path)
if err != nil {
log.Error("Error reading file:" + file_path)
return
}

// append the data to the pre-commit-config file
_, err = fmt.Fprintf(f, string(content)+"\n")
if err != nil {
fmt.Printf("Error writing to file:%s\n", PRE_COMMIT_CONFIG)
return
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Return err here as well

}
}
}

func addCI(repoURL,repoName, branchName string) {
// call the script ciScriptName
log.Infof("Calling script %s for repo:%s and branch:%s",ciScriptName, repoName, branchName)
output,err := exec.Command(ciScriptName, repoURL, branchName).CombinedOutput()
helper.WriteToFile(path.Join(logDir, "git", fmt.Sprintf("%s:%s.txt", repoName, branchName)), string(output))
if err != nil {
log.Errorf("Add CI to git repo - %s: FAILED - %v", repoURL, err)
} else {
log.Infof("Add CI to git repo - %s: SUCCESS",repoURL)
}
}
4 changes: 4 additions & 0 deletions src/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/devclub-iitd/DeployBot/src/history"
"github.com/devclub-iitd/DeployBot/src/options"
"github.com/devclub-iitd/DeployBot/src/slack"
"github.com/devclub-iitd/DeployBot/src/git"
"github.com/robfig/cron"
log "github.com/sirupsen/logrus"
)
Expand Down Expand Up @@ -38,6 +39,9 @@ func main() {
// Github New repo creation HTTP handler
http.HandleFunc("/github/repo/", controllers.RepoHandler)

// Github CI HTTP handler
http.HandleFunc("/github/ci/", git.CIHandler)

// General status and history HTTP handlers
http.HandleFunc("/logs/", controllers.LogHandler)
http.HandleFunc("/status/", history.StatusHandler)
Expand Down