package main import ( "encoding/json" "flag" "fmt" "log" "net/http" "os" "path" "strconv" "strings" "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/s3" "github.com/drone/drone-go/drone" "github.com/gin-gonic/gin" "golang.org/x/oauth2" ) var ( droneToken = "" droneConfig *http.Client droneEndpoint string s3_endpoint string s3_region = "us" s3_bucket string s3_access_key string s3_secret_key string s3_path_style bool ) func init() { flag.StringVar(&droneToken, "drone-token", droneToken, "Token for Drone Oauth") flag.StringVar(&droneEndpoint, "drone-endpoint", droneEndpoint, "Drone Endpoint") s3_endpoint, _ = os.LookupEnv("S3_ENDPOINT") if region, ok := os.LookupEnv("S3_REGION"); ok { s3_region = region } s3_bucket, _ = os.LookupEnv("S3_BUCKET") s3_access_key, _ = os.LookupEnv("S3_ACCESS_KEY") s3_secret_key, _ = os.LookupEnv("S3_SECRET_KEY") if path_style, ok := os.LookupEnv("S3_PATH_STYLE"); ok { s3_path_style = path_style == "1" || path_style == "ON" || path_style == "on" || path_style == "TRUE" || path_style == "true" || path_style == "yes" || path_style == "YES" } flag.StringVar(&s3_endpoint, "s3-endpoint", s3_endpoint, "When using S3 backend, endpoint to use") flag.StringVar(&s3_region, "s3-region", s3_region, "When using S3 backend, region to use") flag.StringVar(&s3_bucket, "s3-bucket", s3_bucket, "When using S3 backend, bucket to use") flag.StringVar(&s3_access_key, "s3-access-key", s3_access_key, "When using S3 backend, Access Key") flag.StringVar(&s3_secret_key, "s3-secret-key", s3_secret_key, "When using S3 backend, Secret Key") flag.BoolVar(&s3_path_style, "s3-path-style", s3_path_style, "When using S3 backend, force path style (when using minio)") } func initializeDroneOauth() { if droneToken != "" { config := new(oauth2.Config) droneConfig = config.Client( oauth2.NoContext, &oauth2.Token{ AccessToken: droneToken, }, ) } } func s3NewSession() (*session.Session, error) { return session.NewSession(&aws.Config{ Credentials: credentials.NewStaticCredentials(s3_access_key, s3_secret_key, ""), Endpoint: aws.String(s3_endpoint), Region: aws.String(s3_region), S3ForcePathStyle: &s3_path_style, }) } func declareAPIAuthRepositoriesRoutes(router *gin.RouterGroup) { router.GET("/repositories", func(c *gin.Context) { var u *User if user, ok := c.Get("user"); ok { u = user.(*User) } else { u = c.MustGet("LoggedUser").(*User) } repositories, err := u.GetRepositories() if err != nil { log.Println("Unable to GetRepositories:", err) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve your repositories. Please try again in a few moment."}) return } c.JSON(http.StatusOK, repositories) }) router.POST("/repositories", func(c *gin.Context) { var u *User if user, ok := c.Get("user"); ok { u = user.(*User) } else { u = c.MustGet("LoggedUser").(*User) } var repository Repository if err := c.ShouldBindJSON(&repository); err != nil { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) return } var w *Work if work, ok := c.Get("work"); ok { w = work.(*Work) } else if repository.IdWork > 0 { var err error w, err = getWork(int(repository.IdWork)) if err != nil { c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Unable to find the given work identifier."}) return } } else { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Unable to find the given work identifier."}) return } k, err := u.NewRepository(w, repository.URI) if err != nil { log.Println("Unable to NewRepository:", err) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to register your public repository. Please try again in a few moment."}) return } c.JSON(http.StatusOK, k) }) repositoriesRoutes := router.Group("/repositories/:rid") repositoriesRoutes.Use(repositoryHandler) repositoriesRoutes.GET("", func(c *gin.Context) { repo := c.MustGet("repository").(*Repository) c.JSON(http.StatusOK, repo) }) repositoriesRoutes.PUT("", func(c *gin.Context) { current := c.MustGet("repository").(*Repository) var new Repository if err := c.ShouldBindJSON(&new); err != nil { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) return } new.Id = current.Id u := c.MustGet("LoggedUser").(*User) if new.IdUser != current.IdUser && !u.IsAdmin { c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "Operation not allowed."}) return } if repository, err := new.Update(); err != nil { log.Println("Unable to Update repository:", err) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("An error occurs during repository updation: %s", err.Error())}) return } else { c.JSON(http.StatusOK, repository) } }) repositoriesRoutes.DELETE("", func(c *gin.Context) { repository := c.MustGet("repository").(*Repository) if _, err := repository.Delete(); err != nil { log.Println("Unable to Delete repository:", err) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("An error occurs during repository deletion: %s", err.Error())}) return } else { c.JSON(http.StatusOK, nil) } }) repositoriesRoutes.POST("/trigger", func(c *gin.Context) { var u *User if user, ok := c.Get("user"); ok { u = user.(*User) } else { u = c.MustGet("LoggedUser").(*User) } repo := c.MustGet("repository").(*Repository) work, err := getWork(int(repo.IdWork)) if err != nil { c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to find related work."}) return } now := time.Now() /*if repo.LastCheck != nil && !repo.LastCheck.Before(now.Add(-5*time.Minute)) { c.AbortWithStatusJSON(http.StatusPaymentRequired, gin.H{"errmsg": "Please wait between two pulls."}) return }*/ client := drone.NewClient(droneEndpoint, droneConfig) result, err := client.BuildCreate("srs", "atsebay.t-worker", "", "master", map[string]string{ "REPO_URL": repo.URI, "REPO_TAG": work.Tag, "LOGIN": u.Login, "DEST": fmt.Sprintf("%d", work.Id), }) if err != nil { log.Println("Unable to communicate with Drone:", err.Error()) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to communication with the extraction service."}) return } repo.DroneRef = fmt.Sprintf("%s/%s/%d", "srs", "atsebay.t-worker", result.Number) repo.LastCheck = &now repo.Update() c.JSON(http.StatusOK, repo) }) repositoriesRoutes.GET("/state", func(c *gin.Context) { repo := c.MustGet("repository").(*Repository) tmp := strings.Split(repo.DroneRef, "/") nbuild, err := strconv.Atoi(tmp[2]) if err != nil { c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Bad build number. Please retry pulling your work."}) return } client := drone.NewClient(droneEndpoint, droneConfig) result, err := client.Build(tmp[0], tmp[1], nbuild) if err != nil { c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Unable to find the referenced extraction."}) return } c.JSON(http.StatusOK, result) }) repositoriesRoutes.GET("/submission", func(c *gin.Context) { var u *User if user, ok := c.Get("user"); ok { u = user.(*User) } else { u = c.MustGet("LoggedUser").(*User) } repo := c.MustGet("repository").(*Repository) work, err := getWork(int(repo.IdWork)) if err != nil { c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to find related work."}) return } s, err := s3NewSession() if err != nil { c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Something goes wrong."}) return } log.Println(path.Join(fmt.Sprintf("%d", work.Id), fmt.Sprintf("rendu-%s.metadata", u.Login))) result, err := s3.New(s).GetObject(&s3.GetObjectInput{ Bucket: aws.String(s3_bucket), Key: aws.String(path.Join(fmt.Sprintf("%d", work.Id), fmt.Sprintf("rendu-%s.metadata", u.Login))), }) if err != nil { c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Submission not found."}) return } tmp := map[string]interface{}{} err = json.NewDecoder(result.Body).Decode(&tmp) if err != nil { log.Println("Unable to decode JSON metadata:", err.Error()) c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Submission not found."}) return } c.JSON(http.StatusOK, tmp) }) } func repositoryHandler(c *gin.Context) { var u *User if user, ok := c.Get("user"); ok { u = user.(*User) } else { u = c.MustGet("LoggedUser").(*User) } if rid, err := strconv.Atoi(string(c.Param("rid"))); err != nil { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Bad repository identifier."}) return } else if u.IsAdmin { if repository, err := getRepository(rid); err != nil { c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Repository not found."}) return } else { c.Set("repository", repository) c.Next() } } else if repository, err := u.getRepository(rid); err != nil { c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Repository not found."}) return } else { c.Set("repository", repository) c.Next() } } type Repository struct { Id int64 `json:"id"` IdUser int64 `json:"id_user"` IdWork int64 `json:"id_work"` URI string `json:"uri"` LastCheck *time.Time `json:"last_check"` DroneRef string `json:"drone_ref,omitempty"` } func (u *User) GetRepositories() (repositories []*Repository, err error) { if rows, errr := DBQuery("SELECT id_repository, id_user, id_work, uri, last_check, droneref FROM user_work_repositories WHERE id_user=?", u.Id); errr != nil { return nil, errr } else { defer rows.Close() for rows.Next() { var repo Repository if err = rows.Scan(&repo.Id, &repo.IdUser, &repo.IdWork, &repo.URI, &repo.LastCheck, &repo.DroneRef); err != nil { return } repositories = append(repositories, &repo) } if err = rows.Err(); err != nil { return } return } } func getRepository(id int) (r *Repository, err error) { r = new(Repository) err = DBQueryRow("SELECT id_repository, id_user, id_work, uri, last_check, droneref FROM user_work_repositories WHERE id_repository=?", id).Scan(&r.Id, &r.IdUser, &r.IdWork, &r.URI, &r.LastCheck, &r.DroneRef) return } func (u *User) getRepository(id int) (r *Repository, err error) { r = new(Repository) err = DBQueryRow("SELECT id_repository, id_user, id_work, uri, last_check, droneref FROM user_work_repositories WHERE id_repository=? AND id_user=?", id, u.Id).Scan(&r.Id, &r.IdUser, &r.IdWork, &r.URI, &r.LastCheck, &r.DroneRef) return } func (u *User) NewRepository(w *Work, uri string) (*Repository, error) { if res, err := DBExec("INSERT INTO user_work_repositories (id_user, id_work, uri, droneref) VALUES (?, ?, ?, ?)", u.Id, w.Id, uri, ""); err != nil { return nil, err } else if rid, err := res.LastInsertId(); err != nil { return nil, err } else { return &Repository{rid, u.Id, w.Id, uri, nil, ""}, nil } } func (r *Repository) Update() (*Repository, error) { if _, err := DBExec("UPDATE user_work_repositories SET id_user = ?, id_work = ?, uri = ?, last_check = ?, droneref = ? WHERE id_repository = ?", r.IdUser, r.IdWork, r.URI, r.LastCheck, r.DroneRef, r.Id); err != nil { return nil, err } else { return r, err } } func (r Repository) Delete() (int64, error) { if res, err := DBExec("DELETE FROM user_work_repositories WHERE id_repository = ?", r.Id); err != nil { return 0, err } else if nb, err := res.RowsAffected(); err != nil { return 0, err } else { return nb, err } } func ClearRepositories() (int64, error) { if res, err := DBExec("DELETE FROM user_work_repositories"); err != nil { return 0, err } else if nb, err := res.RowsAffected(); err != nil { return 0, err } else { return nb, err } }