feat: implement versioning and release management in the project
CI / changes (push) Successful in 7s
CI / commitlint (push) Has been skipped
CI / openapi (push) Successful in 23s
CI / web (push) Successful in 28s
CI / go (push) Successful in 26s
CI / bird2 (push) Successful in 15s

- Added automatic versioning based on git tags using semantic-release in Gitea Actions.
- Introduced new API endpoints `GET /version` and `GET /v1/version` to expose build metadata.
- Updated Docker build process to include version and build time information in Go binaries.
- Enhanced documentation with details on release processes and versioning guidelines.
- Integrated version display in the web application for better user visibility.
This commit is contained in:
Denozordec
2026-05-20 00:56:02 +07:00
parent 4c23232c4e
commit bb334b10f5
26 changed files with 8210 additions and 190 deletions
+11 -4
View File
@@ -21,6 +21,7 @@ import (
"evobgp/internal/pipeline"
"evobgp/internal/reports"
"evobgp/internal/store"
"evobgp/internal/version"
)
// Handler returns the root HTTP handler (system routes public; rest under /v1/ authenticated).
@@ -30,6 +31,7 @@ func (s *Server) Handler() http.Handler {
wrappedV1 := http.StripPrefix("/v1", v1)
s.mux.Handle("GET /metrics", observability.MetricsHandler())
s.mux.HandleFunc("GET /version", s.handleVersion)
s.mux.HandleFunc("GET /v1/health", s.handleHealth)
s.mux.HandleFunc("GET /v1/ready", s.handleReady)
s.mux.HandleFunc("GET /v1/version", s.handleVersion)
@@ -96,11 +98,16 @@ func (s *Server) handleReady(w http.ResponseWriter, r *http.Request) {
}
func (s *Server) handleVersion(w http.ResponseWriter, r *http.Request) {
sha := strings.TrimSpace(os.Getenv("EVOBGP_GIT_SHA"))
if sha == "" {
sha = "unknown"
ver, sha, buildTime := version.Info()
resp := map[string]string{
"version": ver,
"api_version": ver,
"git_sha": sha,
}
writeJSON(w, http.StatusOK, map[string]string{"api_version": "0.1.0", "git_sha": sha})
if buildTime != "" {
resp["build_time"] = buildTime
}
writeJSON(w, http.StatusOK, resp)
}
func moduleJSON(mod *store.Module) map[string]any {
+58
View File
@@ -13,6 +13,7 @@ import (
"evobgp/internal/jobs"
"evobgp/internal/signing"
"evobgp/internal/version"
)
const testBundleSeed = "0101010101010101010101010101010101010101010101010101010101010101"
@@ -371,3 +372,60 @@ evobgp_p_01234567 BGP master start 2026-04-09 Connect
t.Fatalf("expected protocol state Connect, got %q", got["evobgp_p_01234567"])
}
}
func TestVersionEndpoints(t *testing.T) {
srv, err := New(Options{
InsecureDev: true,
SeedDemo: true,
BundleSeedHex: testBundleSeed,
})
if err != nil {
t.Fatal(err)
}
defer srv.Close()
oldVer := version.Version
oldSHA := version.GitSHA
oldTime := version.BuildTime
version.Version = "1.2.3"
version.GitSHA = "abc123def456"
version.BuildTime = "2026-05-20T12:00:00Z"
t.Cleanup(func() {
version.Version = oldVer
version.GitSHA = oldSHA
version.BuildTime = oldTime
})
ts := httptest.NewServer(srv.Handler())
defer ts.Close()
for _, path := range []string{"/version", "/v1/version"} {
t.Run(path, func(t *testing.T) {
resp, err := http.Get(ts.URL + path)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
b, _ := io.ReadAll(resp.Body)
t.Fatalf("status %d: %s", resp.StatusCode, b)
}
var body map[string]string
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
t.Fatal(err)
}
if body["version"] != "1.2.3" {
t.Fatalf("version=%q want 1.2.3", body["version"])
}
if body["api_version"] != "1.2.3" {
t.Fatalf("api_version=%q want 1.2.3", body["api_version"])
}
if body["git_sha"] != "abc123def456" {
t.Fatalf("git_sha=%q", body["git_sha"])
}
if body["build_time"] != "2026-05-20T12:00:00Z" {
t.Fatalf("build_time=%q", body["build_time"])
}
})
}
}