From d6f8214a0067847425caa81d4bf9b068d4b7ff88 Mon Sep 17 00:00:00 2001 From: Mandresy RABENJAHARISON Date: Mon, 11 Aug 2025 15:58:36 +0300 Subject: [PATCH] initial commit --- Dockerfile | 28 +++++++++++++++ README.md | 76 ++++++++++++++++++++++++++++++++++++++++ action.yml | 26 ++++++++++++++ go.mod | 3 ++ main.go | 101 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 234 insertions(+) create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 action.yml create mode 100644 go.mod create mode 100644 main.go diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..23a26cd --- /dev/null +++ b/Dockerfile @@ -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"] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..74a66f9 --- /dev/null +++ b/README.md @@ -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 diff --git a/action.yml b/action.yml new file mode 100644 index 0000000..c412106 --- /dev/null +++ b/action.yml @@ -0,0 +1,26 @@ +name: 'patch-odoo-ticket' +description: 'Update an Odoo ticket via FastAPI when a Gitea Issue changes.' + +author: 'Mandresy RABENJAHARISON ' +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 }} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..51c4407 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module callfastapi + +go 1.21 diff --git a/main.go b/main.go new file mode 100644 index 0000000..58a4dde --- /dev/null +++ b/main.go @@ -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) + } +}