initial commit
This commit is contained in:
commit
d6f8214a00
28
Dockerfile
Normal file
28
Dockerfile
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
########################
|
||||||
|
# — Build stage —
|
||||||
|
########################
|
||||||
|
FROM golang:1.23.4-alpine AS builder
|
||||||
|
|
||||||
|
# git is needed to fetch modules and root CAs
|
||||||
|
RUN apk add --no-cache git ca-certificates
|
||||||
|
|
||||||
|
WORKDIR /src
|
||||||
|
|
||||||
|
# Copy *both* dependency files first so Docker-layer caching works
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
# Bring in the rest of the source
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build static binary
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /out/action ./main.go
|
||||||
|
|
||||||
|
########################
|
||||||
|
# — Runtime stage —
|
||||||
|
########################
|
||||||
|
FROM alpine:3.20
|
||||||
|
RUN apk add --no-cache ca-certificates
|
||||||
|
ENV PATH="/usr/bin:/usr/sbin:/bin:/sbin"
|
||||||
|
COPY --from=builder /out/action /usr/local/bin/action
|
||||||
|
ENTRYPOINT ["/usr/local/bin/action"]
|
||||||
76
README.md
Normal file
76
README.md
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
# Patch Odoo Ticket Gitea Action
|
||||||
|
|
||||||
|
Update an Odoo ticket via a FastAPI middleware whenever a Gitea Issue is created/updated/closed.
|
||||||
|
|
||||||
|
This action reads details from the issue event context and POSTs a JSON payload to your FastAPI endpoint at `/api/v1/patch_gitea_odoo`. The FastAPI service is responsible for talking to Odoo via JSON-RPC.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What it sends
|
||||||
|
|
||||||
|
The action builds a JSON array with a single object from the issue event:
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"task_id": 123,
|
||||||
|
"title": "Issue Title",
|
||||||
|
"body": "Issue body/description",
|
||||||
|
"state": "open",
|
||||||
|
"author": "jdoe",
|
||||||
|
"assignees": ["alice", "bob"],
|
||||||
|
"labels": ["bug", "backend"],
|
||||||
|
"date_deadline": "2025-05-06"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
- `task_id`: from `${{ gitea.event.issue.number }}`
|
||||||
|
- `title`: from `${{ gitea.event.issue.title }}`
|
||||||
|
- `body`: from `${{ gitea.event.issue.body }}`
|
||||||
|
- `state`: from `${{ gitea.event.issue.state }}` ("open" or "closed")
|
||||||
|
- `author`: from `${{ gitea.event.issue.user.login }}`
|
||||||
|
- `assignees`: from `${{ join(gitea.event.issue.assignees.*.login, ',') }}` (expanded and split)
|
||||||
|
- `labels`: from `${{ join(gitea.event.issue.labels.*.name, ',') }}` (expanded and split)
|
||||||
|
- `date_deadline`: from `${{ gitea.event.issue.due_date }}` if present
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Inputs
|
||||||
|
|
||||||
|
- `base_url` (required): Base URL of your FastAPI server, e.g. `https://fastapi.example.com`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Trigger on the events you care about (edit, labeled, closed, etc.). Example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: Sync Odoo ticket
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [edited, closed, reopened, assigned, unassigned, labeled, unlabeled]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
patch-odoo:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Send update to Odoo via FastAPI
|
||||||
|
uses: https://gitea.ethumada.com/gitea/patch-odoo-ticket
|
||||||
|
with:
|
||||||
|
base_url: ${{ vars.FASTAPI_BASE_URL }}
|
||||||
|
```
|
||||||
|
|
||||||
|
Secrets/variables:
|
||||||
|
- Set `FASTAPI_BASE_URL` secret to your FastAPI base URL.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## FastAPI endpoint contract
|
||||||
|
|
||||||
|
- Method: POST
|
||||||
|
- URL: `{base_url}/api/v1/patch_gitea_odoo`
|
||||||
|
- Body: JSON array of one or more objects as above
|
||||||
|
- Return: 2xx on success, any body will be logged for debugging
|
||||||
26
action.yml
Normal file
26
action.yml
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
name: 'patch-odoo-ticket'
|
||||||
|
description: 'Update an Odoo ticket via FastAPI when a Gitea Issue changes.'
|
||||||
|
|
||||||
|
author: 'Mandresy RABENJAHARISON <mandresy.rabenjaharison@gmail.com>'
|
||||||
|
branding:
|
||||||
|
icon: send
|
||||||
|
color: blue
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
base_url:
|
||||||
|
description: 'Base URL of the FastAPI server.'
|
||||||
|
required: true
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: 'docker'
|
||||||
|
image: 'Dockerfile'
|
||||||
|
env:
|
||||||
|
BASE_URL: ${{ inputs.base_url }}
|
||||||
|
ISSUE_NUMBER: ${{ gitea.event.issue.number }}
|
||||||
|
ISSUE_TITLE: ${{ gitea.event.issue.title }}
|
||||||
|
ISSUE_BODY: ${{ gitea.event.issue.body }}
|
||||||
|
ISSUE_STATE: ${{ gitea.event.issue.state }}
|
||||||
|
ISSUE_AUTHOR: ${{ gitea.event.issue.user.login }}
|
||||||
|
ISSUE_ASSIGNEES: ${{ join(gitea.event.issue.assignees.*.login, ',') }}
|
||||||
|
ISSUE_LABELS: ${{ join(gitea.event.issue.labels.*.name, ',') }}
|
||||||
|
ISSUE_DUE_DATE: ${{ gitea.event.issue.due_date }}
|
||||||
101
main.go
Normal file
101
main.go
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Payload struct {
|
||||||
|
TaskID int64 `json:"task_id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Body string `json:"body"`
|
||||||
|
State string `json:"state"`
|
||||||
|
Author string `json:"author"`
|
||||||
|
Assignees []string `json:"assignees"`
|
||||||
|
Labels []string `json:"labels"`
|
||||||
|
DueDate string `json:"date_deadline,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitCSV(value string) []string {
|
||||||
|
if strings.TrimSpace(value) == "" {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
parts := strings.Split(value, ",")
|
||||||
|
result := make([]string, 0, len(parts))
|
||||||
|
for _, p := range parts {
|
||||||
|
p = strings.TrimSpace(p)
|
||||||
|
if p != "" {
|
||||||
|
result = append(result, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
baseURL := os.Getenv("BASE_URL")
|
||||||
|
if baseURL == "" {
|
||||||
|
fmt.Println("BASE_URL not provided.")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logger
|
||||||
|
logFile, err := os.OpenFile("action.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error creating log file: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
defer logFile.Close()
|
||||||
|
logger := log.New(io.MultiWriter(os.Stdout, logFile), "", log.LstdFlags)
|
||||||
|
|
||||||
|
// Build payload from env
|
||||||
|
issueNumber := os.Getenv("ISSUE_NUMBER")
|
||||||
|
issueTitle := os.Getenv("ISSUE_TITLE")
|
||||||
|
issueBody := os.Getenv("ISSUE_BODY")
|
||||||
|
issueState := os.Getenv("ISSUE_STATE")
|
||||||
|
issueAuthor := os.Getenv("ISSUE_AUTHOR")
|
||||||
|
issueAssignees := splitCSV(os.Getenv("ISSUE_ASSIGNEES"))
|
||||||
|
issueLabels := splitCSV(os.Getenv("ISSUE_LABELS"))
|
||||||
|
issueDueDate := os.Getenv("ISSUE_DUE_DATE")
|
||||||
|
|
||||||
|
var taskID int64
|
||||||
|
if issueNumber != "" {
|
||||||
|
// Best-effort parse; if it fails, taskID remains 0
|
||||||
|
fmt.Sscanf(issueNumber, "%d", &taskID)
|
||||||
|
}
|
||||||
|
|
||||||
|
payload := Payload{
|
||||||
|
TaskID: taskID,
|
||||||
|
Title: issueTitle,
|
||||||
|
Body: issueBody,
|
||||||
|
State: issueState,
|
||||||
|
Author: issueAuthor,
|
||||||
|
Assignees: issueAssignees,
|
||||||
|
Labels: issueLabels,
|
||||||
|
DueDate: issueDueDate,
|
||||||
|
}
|
||||||
|
|
||||||
|
payloadBytes, err := json.Marshal([]Payload{payload})
|
||||||
|
if err != nil {
|
||||||
|
logger.Printf("Error marshalling JSON: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoint := strings.TrimRight(baseURL, "/") + "/api/v1/patch_gitea_odoo"
|
||||||
|
resp, err := http.Post(endpoint, "application/json", bytes.NewBuffer(payloadBytes))
|
||||||
|
if err != nil {
|
||||||
|
logger.Printf("HTTP request failed: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
logger.Printf("Status: %s\nResponse: %s\n", resp.Status, string(body))
|
||||||
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user