Skip to content

Commit d103b23

Browse files
Build with go-ora using tags (#396)
* Build with go-ora using tags Signed-off-by: Anders Swanson <anders.swanson@oracle.com>
1 parent d42aeed commit d103b23

File tree

9 files changed

+201
-59
lines changed

9 files changed

+201
-59
lines changed

Dockerfile

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ ENV GOOS=${GOOS:-linux}
77
ARG GOARCH
88
ENV GOARCH=${GOARCH:-amd64}
99

10+
ARG TAGS
11+
ENV TAGS=${TAGS:-godror}
12+
13+
ARG CGO_ENABLED
14+
ENV CGO_ENABLED=${CGO_ENABLED:-1}
15+
1016
RUN microdnf install wget gzip gcc && \
1117
wget -q https://go.dev/dl/go1.23.10.${GOOS}-${GOARCH}.tar.gz && \
1218
rm -rf /usr/local/go && \
@@ -22,9 +28,9 @@ RUN go mod download
2228
ARG VERSION
2329
ENV VERSION=${VERSION:-1.0.0}
2430

25-
RUN CGO_ENABLED=1 GOOS=${GOOS} GOARCH=${GOARCH} go build -v -ldflags "-X main.Version=${VERSION} -s -w"
31+
RUN CGO_ENABLED=${CGO_ENABLED} GOOS=${GOOS} GOARCH=${GOARCH} go build --tags=${TAGS} -v -ldflags "-X main.Version=${VERSION} -s -w"
2632

27-
FROM ${BASE_IMAGE:-ghcr.io/oracle/oraclelinux:8-slim} AS exporter
33+
FROM ${BASE_IMAGE:-ghcr.io/oracle/oraclelinux:8-slim} AS exporter-godror
2834
LABEL org.opencontainers.image.authors="Oracle America, Inc."
2935
LABEL org.opencontainers.image.description="Oracle Database Observability Exporter"
3036

@@ -56,3 +62,18 @@ EXPOSE 9161
5662
USER 1000
5763

5864
ENTRYPOINT ["/oracledb_exporter"]
65+
66+
FROM ${BASE_IMAGE:-ghcr.io/oracle/oraclelinux:8-slim} AS exporter-goora
67+
68+
COPY --from=build /go/src/oracledb_exporter/oracle-db-appdev-monitoring /oracledb_exporter
69+
ADD ./default-metrics.toml /default-metrics.toml
70+
71+
# create the mount point for alert log exports (default location)
72+
RUN mkdir /log && chown 1000:1000 /log
73+
RUN mkdir /wallet && chown 1000:1000 /wallet
74+
75+
EXPOSE 9161
76+
77+
USER 1000
78+
79+
ENTRYPOINT ["/oracledb_exporter"]

Makefile

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@ OS_TYPE ?= $(shell uname -s | tr '[:upper:]' '[:lower:]')
33
ARCH_TYPE ?= $(subst x86_64,amd64,$(patsubst i%86,386,$(ARCH)))
44
GOOS ?= $(shell go env GOOS)
55
GOARCH ?= $(shell go env GOARCH)
6+
TAGS ?= godror
7+
DOCKER_TARGET ?= exporter-godror
8+
CGO_ENABLED ?= 1
69
VERSION ?= 2.1.0
710
LDFLAGS := -X main.Version=$(VERSION)
8-
GOFLAGS := -ldflags "$(LDFLAGS) -s -w"
11+
GOFLAGS := -ldflags "$(LDFLAGS) -s -w" --tags $(TAGS)
912
BUILD_ARGS = --build-arg VERSION=$(VERSION)
1013
OUTDIR = ./dist
1114

@@ -37,31 +40,31 @@ go-build:
3740

3841
.PHONY: go-build-linux-amd64
3942
go-build-linux-amd64:
40-
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 $(MAKE) go-build -j2
43+
CGO_ENABLED=$(CGO_ENABLED) GOOS=linux GOARCH=amd64 $(MAKE) go-build -j2
4144

4245
.PHONY: go-build-linux-arm64
4346
go-build-linux-arm64:
44-
CGO_ENABLED=1 GOOS=linux GOARCH=arm64 $(MAKE) go-build -j2
47+
CGO_ENABLED=$(CGO_ENABLED) GOOS=linux GOARCH=arm64 $(MAKE) go-build -j2
4548

4649
.PHONY: go-build-linux-gcc-arm64
4750
go-build-linux-gcc-arm64:
48-
CGO_ENABLED=1 CC=aarch64-linux-gnu-gcc GOOS=linux GOARCH=arm64 $(MAKE) go-build -j2
51+
CGO_ENABLED=$(CGO_ENABLED) CC=aarch64-linux-gnu-gcc GOOS=linux GOARCH=arm64 $(MAKE) go-build -j2
4952

5053
.PHONY: go-build-darwin-amd64
5154
go-build-darwin-amd64:
52-
CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 $(MAKE) go-build -j2
55+
CGO_ENABLED=$(CGO_ENABLED) GOOS=darwin GOARCH=amd64 $(MAKE) go-build -j2
5356

5457
.PHONY: go-build-darwin-arm64
5558
go-build-darwin-arm64:
56-
CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 $(MAKE) go-build -j2
59+
CGO_ENABLED=$(CGO_ENABLED) GOOS=darwin GOARCH=arm64 $(MAKE) go-build -j2
5760

5861
.PHONY: go-build-windows-amd64
5962
go-build-windows-amd64:
60-
CGO_ENABLED=1 GOOS=windows GOARCH=amd64 $(MAKE) go-build -j2
63+
CGO_ENABLED=$(CGO_ENABLED) GOOS=windows GOARCH=amd64 $(MAKE) go-build -j2
6164

6265
.PHONY: go-build-windows-x86
6366
go-build-windows-x86:
64-
CGO_ENABLED=1 GOOS=windows GOARCH=386 $(MAKE) go-build -j2
67+
CGO_ENABLED=$(CGO_ENABLED) GOOS=windows GOARCH=386 $(MAKE) go-build -j2
6568

6669
dist: go-build-linux-gcc-arm64 go-build-linux-amd64
6770

@@ -89,10 +92,10 @@ push-images:
8992
@make --no-print-directory push-oraclelinux-image
9093

9194
docker:
92-
docker build --no-cache --progress=plain $(BUILD_ARGS) -t "$(IMAGE_ID)-amd64" --build-arg BASE_IMAGE=$(ORACLE_LINUX_BASE_IMAGE) --build-arg GOARCH=amd64 .
95+
docker build --no-cache --target=$(DOCKER_TARGET) --progress=plain $(BUILD_ARGS) -t "$(IMAGE_ID)-amd64" --build-arg BASE_IMAGE=$(ORACLE_LINUX_BASE_IMAGE) --build-arg GOARCH=amd64 .
9396

9497
docker-arm:
95-
docker buildx build --platform linux/arm64 --load --no-cache --progress=plain $(BUILD_ARGS) -t "$(IMAGE_ID)-arm64" --build-arg BASE_IMAGE=$(ORACLE_LINUX_BASE_IMAGE) --build-arg GOARCH=arm64 .
98+
docker buildx build --target=$(DOCKER_TARGET) --platform linux/arm64 --load --no-cache --progress=plain $(BUILD_ARGS) -t "$(IMAGE_ID)-arm64" --build-arg BASE_IMAGE=$(ORACLE_LINUX_BASE_IMAGE) --build-arg GOARCH=arm64 .
9699

97100
push-oraclelinux-image:
98101
docker push $(IMAGE_ID)

collector/connect_godror.go

Lines changed: 2 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
11
// Copyright (c) 2025, Oracle and/or its affiliates.
22
// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
33

4-
//go:build !goora
4+
//go:build godror
55

66
package collector
77

88
import (
9-
"context"
109
"database/sql"
1110
"errors"
1211
"fmt"
1312
"github.com/godror/godror"
1413
"github.com/godror/godror/dsn"
1514
"log/slog"
16-
"strings"
1715
"time"
1816
)
1917

@@ -78,53 +76,10 @@ func connect(logger *slog.Logger, dbname string, dbconfig DatabaseConfig) *sql.D
7876
// note that this just configures the connection, it does not actually connect until later
7977
// when we call db.Ping()
8078
db := sql.OpenDB(godror.NewConnector(P))
81-
logger.Debug(fmt.Sprintf("set max idle connections to %d", dbconfig.MaxIdleConns), "database", dbname)
82-
db.SetMaxIdleConns(dbconfig.GetMaxIdleConns())
83-
logger.Debug(fmt.Sprintf("set max open connections to %d", dbconfig.MaxOpenConns), "database", dbname)
84-
db.SetMaxOpenConns(dbconfig.GetMaxOpenConns())
85-
db.SetConnMaxLifetime(0)
86-
logger.Debug(fmt.Sprintf("Successfully configured connection to %s", maskDsn(dbconfig.URL)), "database", dbname)
87-
88-
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
89-
defer cancel()
90-
if _, err := db.ExecContext(ctx, `
91-
begin
92-
dbms_application_info.set_client_info('oracledb_exporter');
93-
end;`); err != nil {
94-
logger.Info("Could not set CLIENT_INFO.", "database", dbname)
95-
}
96-
97-
var sysdba string
98-
if err := db.QueryRowContext(ctx, "select sys_context('USERENV', 'ISDBA') from dual").Scan(&sysdba); err != nil {
99-
logger.Error("error checking my database role", "error", err, "database", dbname)
100-
}
101-
logger.Info("Connected as SYSDBA? "+sysdba, "database", dbname)
102-
79+
initdb(logger, dbname, dbconfig, db)
10380
return db
10481
}
10582

106-
// ping the database. If the database is disconnected, try to reconnect.
107-
// If the database type is unknown, try to reload it.
108-
func (d *Database) ping(logger *slog.Logger) error {
109-
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
110-
defer cancel()
111-
err := d.Session.PingContext(ctx)
112-
if err != nil {
113-
d.Up = 0
114-
if isInvalidCredentialsError(err) {
115-
d.invalidate()
116-
return err
117-
}
118-
// If database is closed, try to reconnect
119-
if strings.Contains(err.Error(), "sql: database is closed") {
120-
d.Session = connect(logger, d.Name, d.Config)
121-
}
122-
return err
123-
}
124-
d.Up = 1
125-
return nil
126-
}
127-
12883
func isInvalidCredentialsError(err error) bool {
12984
err = errors.Unwrap(err)
13085
if err == nil {

collector/connect_goora.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Copyright (c) 2025, Oracle and/or its affiliates.
2+
// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
3+
4+
//go:build goora
5+
6+
package collector
7+
8+
import (
9+
"database/sql"
10+
"errors"
11+
"fmt"
12+
_ "github.com/sijms/go-ora/v2"
13+
"github.com/sijms/go-ora/v2/network"
14+
"log/slog"
15+
)
16+
17+
func connect(logger *slog.Logger, dbname string, dbconfig DatabaseConfig) *sql.DB {
18+
logger.Debug("Launching connection to "+maskDsn(dbconfig.URL), "database", dbname)
19+
20+
password := dbconfig.GetPassword()
21+
username := dbconfig.GetUsername()
22+
dbconfig.ExternalAuth = password == ""
23+
24+
logger.Debug(fmt.Sprintf("external authentication set to %t", dbconfig.ExternalAuth), "database", dbname)
25+
26+
msg := "Using Username/Password Authentication."
27+
if dbconfig.ExternalAuth {
28+
msg = "Database Password not specified; will attempt to use external authentication (ignoring user input)."
29+
dbconfig.Username = ""
30+
}
31+
logger.Info(msg, "database", dbname)
32+
33+
// Build connection string for go-ora
34+
var dsn string
35+
if dbconfig.ExternalAuth {
36+
// go-ora doesn't directly support external authentication
37+
// So we rely on OS authentication (set Oracle wallet/env)
38+
dsn = fmt.Sprintf("oracle://@%s", dbconfig.URL)
39+
} else if username != "" {
40+
dsn = fmt.Sprintf("oracle://%s:%s@%s", username, password, dbconfig.URL)
41+
} else {
42+
dsn = fmt.Sprintf("oracle://%s", dbconfig.URL)
43+
}
44+
45+
// open connection (lazy until first use)
46+
db, err := sql.Open("oracle", dsn)
47+
if err != nil {
48+
logger.Error("Failed to create DB handle", "error", err, "database", dbname)
49+
return nil
50+
}
51+
52+
// Configure connection pool (sql.DB handles pooling)
53+
setConnectionPool(logger, dbname, dbconfig, db)
54+
initdb(logger, dbname, dbconfig, db)
55+
return db
56+
}
57+
58+
func setConnectionPool(logger *slog.Logger, dbname string, dbconfig DatabaseConfig, db *sql.DB) {
59+
if dbconfig.GetPoolMaxConnections() > 0 {
60+
logger.Debug(fmt.Sprintf("set pool max connections to %d", dbconfig.PoolMaxConnections), "database", dbname)
61+
db.SetMaxOpenConns(dbconfig.GetPoolMaxConnections())
62+
} else {
63+
db.SetMaxOpenConns(dbconfig.GetMaxOpenConns())
64+
}
65+
if dbconfig.GetPoolMinConnections() > 0 {
66+
logger.Debug(fmt.Sprintf("set pool min connections to %d", dbconfig.PoolMinConnections), "database", dbname)
67+
db.SetMaxIdleConns(dbconfig.GetPoolMinConnections())
68+
} else {
69+
db.SetMaxIdleConns(dbconfig.GetMaxIdleConns())
70+
}
71+
}
72+
73+
func isInvalidCredentialsError(err error) bool {
74+
if err == nil {
75+
return false
76+
}
77+
var oraErr *network.OracleError
78+
ok := errors.As(err, &oraErr)
79+
if !ok {
80+
return false
81+
}
82+
return oraErr.ErrCode == ora01017code || oraErr.ErrCode == ora28000code
83+
}

collector/database.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ package collector
66
import (
77
"context"
88
"database/sql"
9+
"fmt"
910
"github.com/prometheus/client_golang/prometheus"
1011
"log/slog"
12+
"strings"
1113
"time"
1214
)
1315

@@ -91,10 +93,56 @@ func (d *Database) WarmupConnectionPool(logger *slog.Logger) {
9193
}
9294
}
9395

96+
// ping the database. If the database is disconnected, try to reconnect.
97+
// If the database type is unknown, try to reload it.
98+
func (d *Database) ping(logger *slog.Logger) error {
99+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
100+
defer cancel()
101+
err := d.Session.PingContext(ctx)
102+
if err != nil {
103+
d.Up = 0
104+
if isInvalidCredentialsError(err) {
105+
d.invalidate()
106+
return err
107+
}
108+
// If database is closed, try to reconnect
109+
if strings.Contains(err.Error(), "sql: database is closed") {
110+
d.Session = connect(logger, d.Name, d.Config)
111+
}
112+
return err
113+
}
114+
d.Up = 1
115+
return nil
116+
}
117+
94118
func (d *Database) IsValid() bool {
95119
return d.Valid
96120
}
97121

98122
func (d *Database) invalidate() {
99123
d.Valid = false
100124
}
125+
126+
func initdb(logger *slog.Logger, dbname string, dbconfig DatabaseConfig, db *sql.DB) {
127+
logger.Debug(fmt.Sprintf("set max idle connections to %d", dbconfig.MaxIdleConns), "database", dbname)
128+
db.SetMaxIdleConns(dbconfig.GetMaxIdleConns())
129+
logger.Debug(fmt.Sprintf("set max open connections to %d", dbconfig.MaxOpenConns), "database", dbname)
130+
db.SetMaxOpenConns(dbconfig.GetMaxOpenConns())
131+
db.SetConnMaxLifetime(0)
132+
logger.Debug(fmt.Sprintf("Successfully configured connection to %s", maskDsn(dbconfig.URL)), "database", dbname)
133+
134+
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
135+
defer cancel()
136+
if _, err := db.ExecContext(ctx, `
137+
begin
138+
dbms_application_info.set_client_info('oracledb_exporter');
139+
end;`); err != nil {
140+
logger.Info("Could not set CLIENT_INFO.", "database", dbname)
141+
}
142+
143+
var sysdba string
144+
if err := db.QueryRowContext(ctx, "select sys_context('USERENV', 'ISDBA') from dual").Scan(&sysdba); err != nil {
145+
logger.Error("error checking my database role", "error", err, "database", dbname)
146+
}
147+
logger.Info("Connected as SYSDBA? "+sysdba, "database", dbname)
148+
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ require (
5353
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
5454
github.com/prometheus/client_model v0.6.2 // indirect
5555
github.com/prometheus/procfs v0.16.1 // indirect
56+
github.com/sijms/go-ora/v2 v2.9.0 // indirect
5657
github.com/ryanuber/go-glob v1.0.0 // indirect
5758
github.com/sony/gobreaker v0.5.0 // indirect
5859
github.com/xhit/go-str2duration/v2 v2.1.0 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,8 @@ github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU
213213
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
214214
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
215215
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
216+
github.com/sijms/go-ora/v2 v2.9.0 h1:+iQbUeTeCOFMb5BsOMgUhV8KWyrv9yjKpcK4x7+MFrg=
217+
github.com/sijms/go-ora/v2 v2.9.0/go.mod h1:QgFInVi3ZWyqAiJwzBQA+nbKYKH77tdp1PYoCqhR2dU=
216218
github.com/sony/gobreaker v0.5.0 h1:dRCvqm0P490vZPmy7ppEk2qCnCieBooFJ+YoXGYB+yg=
217219
github.com/sony/gobreaker v0.5.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
218220
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=

site/docs/advanced/go-ora.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
title: go-ora Driver
3+
sidebar_position: 5
4+
---
5+
6+
# Using the go-ora database driver
7+
8+
The Oracle Database Metrics Exporter experimentally supports compiling with the [go-ora database driver](https://github.com/sijms/go-ora). By default, the exporter compiles using the `godror` database driver, which uses CGO execution to invoke Oracle Instant Client. the go-ora driver presents an option for users who want to use a "thin" database client without the Oracle Instant Client and CGO.
9+
10+
### Configuring go-ora
11+
12+
Because go-ora does not use Oracle Instant Client, it is recommended to provide all connection string options in the `database.url` property:
13+
14+
```yaml
15+
databases:
16+
go_ora_db:
17+
username: myuser
18+
password: ******
19+
url: my_tnsname?wallet=/path/to/wallet&ssl=1
20+
```
21+
22+
### Build with go-ora
23+
24+
To build using `go-ora` instead of `godror`, set `TAGS=goora CGO_ENABLED=0`:
25+
26+
```bash
27+
make go-build TAGS=goora CGO_ENABLED=0
28+
```

site/docs/releases/changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ List of upcoming and historic changes to the exporter.
1111

1212
Our current priorities to support metrics for advanced database features and use cases, like Exadata, GoldenGate, and views included in the Oracle Diagnostics Pack.
1313

14+
- Added experimental support for the [go-ora](https://github.com/sijms/go-ora)
1415
- Move `oracledb_dbtype` metric to the default metrics. You may now disable or override this metric like any other database metric.
1516
- Document required database permissions for the exporter.
1617
- Fix an issue where some metrics would not be cached when using a per-metric scrape interval with a global scrape interval.

0 commit comments

Comments
 (0)