Skip to content

Commit e4bcf43

Browse files
authored
Add datasource syncer (#123)
* add datasource syncer * update README for datasource syncer
1 parent 7cbfcc0 commit e4bcf43

File tree

9 files changed

+1003
-4
lines changed

9 files changed

+1003
-4
lines changed

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@ You can follow the steps to enable it:
1919

2020
### Generate a JWT file & Assign IAM Permissions
2121

22-
1. If you don't have gcp project, add a new gcp project. [link](https://cloud.google.com/resource-manager/docs/creating-managing-projects#console)
22+
1. If you don't have a GCP project, add a new GCP project. [link](https://cloud.google.com/resource-manager/docs/creating-managing-projects#console)
2323
2. Open the [Credentials](https://console.developers.google.com/apis/credentials) page in the Google API Console
2424
3. Click **Create Credentials** then click **Service account**
2525
4. On the Create service account page, enter the Service account details
26-
5. On the `Create service account` page, fill in the `Service account details` and then click `Create and Continue`
26+
5. Fill in the `Service account details` and then click `Create and Continue`
2727
6. On the `Grant this service account access to project` section, add the `Logs Viewer` role and `Logs View Accessor` role under `Logging` to the service account. Click `Done`
2828
7. In the next step, click the service account you just created. Under the `Keys` tab and select `Add key` and `Create new key`
2929
8. Choose key type `JSON` and click `Create`. A JSON key file will be created and downloaded to your computer
@@ -32,6 +32,8 @@ If you want to access logs in multiple cloud projects, you need to ensure the se
3232

3333
If you host Grafana on a GCE VM, you can also use the [Compute Engine service account](https://cloud.google.com/compute/docs/access/service-accounts#serviceaccount). You need to make sure the service account has sufficient permissions to access the scopes and logs in all projects.
3434

35+
Similar to [Prometheus data sources on Google Cloud](https://cloud.google.com/stackdriver/docs/managed-prometheus/query#use-serverless), you can also configure a scheduled job to use an OAuth2 access token to view the logs. Please follow the steps in the [data source syncer README](./datasource-syncer/README.md) to configure it.
36+
3537
### Service account impersonation
3638
You can also configure the plugin to use [service account impersonation](https://cloud.google.com/iam/docs/service-account-impersonation).
3739
You need to ensure the service account used by this plugin has the `iam.serviceAccounts.getAccessToken` permission. This permission is in roles like the [Service Account Token Creator role](https://cloud.google.com/iam/docs/understanding-roles#iam.serviceAccountTokenCreator) (roles/iam.serviceAccountTokenCreator). Also, the service account impersonated
@@ -78,4 +80,4 @@ Below is an example of defining a variable for log views.
7880
7981
Cloud Logging Logo (`src/img/logo.svg`) is from Google Cloud's [Official icons and sample diagrams](https://cloud.google.com/icons)
8082

81-
As commented, `JWTForm` and `JWTConfigEditor` are largely based on Apache-2.0 licensed [grafana-google-sdk-react](https://github.com/grafana/grafana-google-sdk-react/)
83+
As commented in the code, `JWTForm` and `JWTConfigEditor` are largely based on the Apache-2.0 licensed [grafana-google-sdk-react](https://github.com/grafana/grafana-google-sdk-react/).

datasource-syncer/Dockerfile

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Copyright 2024 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# Stage 1: Build the application
16+
FROM golang:1.24.2-alpine AS buildbase
17+
18+
WORKDIR /app
19+
20+
# Set CGO_ENABLED=0 to build a static binary, essential for distroless images.
21+
ENV CGO_ENABLED=0
22+
23+
ENV GOEXPERIMENT=noboringcrypto
24+
ENV GOFIPS140=latest
25+
26+
COPY go.mod go.sum ./
27+
RUN go mod download
28+
COPY . .
29+
30+
# Build the Go application.
31+
# -ldflags="-s -w" strips debugging information and symbols, reducing binary size.
32+
# The '.' at the end signifies building the package in the current directory.
33+
RUN go build -ldflags="-s -w" -o datasource-syncer .
34+
35+
# Stage 2: Create the final minimal image
36+
# Using a distroless static image for a minimal runtime environment.
37+
# 'nonroot' variant runs the application as a non-root user for better security.
38+
FROM gcr.io/distroless/static-debian12:nonroot
39+
COPY --from=buildbase /app/datasource-syncer /bin/datasource-syncer
40+
ENTRYPOINT ["/bin/datasource-syncer"]

datasource-syncer/README.md

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
# Data Source Syncer
2+
3+
This CLI tool acts as a scheduled job that remotely syncs data to a given Grafana Cloud Logging data source. This ensures that the Grafana data source has the following set correctly:
4+
5+
* Authentication by refreshing an OAuth2 access token periodically
6+
* The Google Cloud project ID
7+
8+
By regularly refreshing the OAuth2 access token, you can configure Grafana to directly query Google Cloud Logging.
9+
10+
[Google access tokens have a lifetime of 1 hour.](https://cloud.google.com/docs/authentication/token-types#at-lifetime) This script should be scheduled to run every 10 minutes to ensure you have an uninterrupted connection between Grafana and Google Cloud Logging.
11+
12+
## Flags
13+
14+
```bash mdox-exec="bash hack/format_help.sh datasource-syncer"
15+
Usage of datasource-syncer:
16+
-datasource-uids string
17+
datasource-uids is a comma separated list of data source UIDs to update.
18+
-grafana-api-endpoint string
19+
grafana-api-endpoint is the endpoint of the Grafana instance that contains the data sources to update.
20+
-grafana-api-token string
21+
grafana-api-token used to access Grafana. Can be created using: https://grafana.com/docs/grafana/latest/administration/service-accounts/#create-a-service-account-in-grafana
22+
-insecure-skip-verify
23+
Skip TLS certificate verification
24+
-project-id string
25+
Project ID of the Google Cloud Monitoring scoping project to query. Queries sent to this project will union results from all projects within the scope.
26+
-query.credentials-file string
27+
JSON-encoded credentials (service account or refresh token). Can be left empty if default credentials have sufficient permission.
28+
-tls-ca-cert string
29+
Path to the server certificate authority
30+
-tls-cert string
31+
Path to the server TLS certificate.
32+
-tls-key string
33+
Path to the server TLS key.
34+
```
35+
## Scheduled the job using Cloud Run
36+
37+
To deploy and run a serverless data source syncer by using [Cloud Run](https://cloud.google.com/run)
38+
and [Cloud Scheduler](https://cloud.google.com/scheduler), do the following:
39+
40+
1. Choose a project to deploy the data source syncer in. We recommend choosing
41+
the [scoping project of a multi-project log scope](https://cloud.google.com/logging/docs/log-scope/create-and-manage).
42+
The data source syncer uses the configured Google Cloud project as the
43+
scoping project.
44+
45+
Next, configure and authorize a service account for the data source syncer.
46+
The following command sequence creates a service account and grants it
47+
several IAM roles. The first three roles let the service account
48+
read from the Cloud Logging API and generate service account tokens. The
49+
last two roles allow the service account to read the Grafana service account
50+
token from Secret Manager and to invoke Cloud Run:
51+
52+
```bash
53+
# Replace the values if needed
54+
export PROJECT_ID=YOUR_LOGGING_PROJECT_ID
55+
export SA_DS_SYNCER=grafana-ds-syncer-sa
56+
export REGION=us-central1 # The Google Cloud region where you want to run your Cloud Run job, such as us-central1.
57+
58+
59+
gcloud config set project ${PROJECT_ID} \
60+
&&
61+
gcloud iam service-accounts create ${SA_DS_SYNCER} \
62+
&&
63+
gcloud projects add-iam-policy-binding ${PROJECT_ID} \
64+
--member=serviceAccount:${SA_DS_SYNCER}@${PROJECT_ID}.iam.gserviceaccount.com \
65+
--role=roles/logging.viewer \
66+
--condition=None \
67+
&& \
68+
gcloud projects add-iam-policy-binding ${PROJECT_ID} \
69+
--member=serviceAccount:${SA_DS_SYNCER}@${PROJECT_ID}.iam.gserviceaccount.com \
70+
--role=roles/logging.viewAccessor \
71+
--condition=None \
72+
&& \
73+
gcloud projects add-iam-policy-binding ${PROJECT_ID} \
74+
--member=serviceAccount:${SA_DS_SYNCER}@${PROJECT_ID}.iam.gserviceaccount.com \
75+
--role=roles/iam.serviceAccountTokenCreator \
76+
--condition=None \
77+
&& \
78+
gcloud projects add-iam-policy-binding ${PROJECT_ID} \
79+
--member=serviceAccount:${SA_DS_SYNCER}@${PROJECT_ID}.iam.gserviceaccount.com \
80+
--role=roles/secretmanager.secretAccessor \
81+
--condition=None \
82+
&& \
83+
gcloud projects add-iam-policy-binding ${PROJECT_ID} \
84+
--member=serviceAccount:${SA_DS_SYNCER}@${PROJECT_ID}.iam.gserviceaccount.com \
85+
--role=roles/run.invoker \
86+
--condition=None
87+
```
88+
89+
1. Build the container image:
90+
```bash
91+
# Create the artifact registry to store the image
92+
gcloud artifacts repositories create datasource-syncer-repo \
93+
--repository-format=docker \
94+
--location=${REGION} --description="Docker repository for Grafana data source syncer"
95+
96+
gcloud builds submit --region=${REGION} --tag ${REGION}-docker.pkg.dev/${PROJECT_ID}/datasource-syncer-repo/datasource-syncer
97+
```
98+
99+
1. Determine the URL of your Grafana instance, for example
100+
`https://yourcompanyname.grafana.net` for a Grafana Cloud deployment. Your
101+
Grafana instance needs to be accessible from Cloud Run, meaning it
102+
needs to be accessible from the wider internet.
103+
104+
If your Grafana instance is not accessible from the wider internet, we
105+
recommend deploying the data source syncer on Kubernetes instead.
106+
107+
1. Choose the Grafana Cloud Logging data source to
108+
use for Cloud Logging, which can be either a new or a
109+
pre-existing data source, and then find and write down the data
110+
source UID. The data source UID can be found in the last
111+
part of the URL when exploring or configuring a data source, for example
112+
`https://yourcompanyname.grafana.net/connections/datasources/edit/`**_grafana_datasource_uid_**.
113+
114+
**Do not copy the entire datasource URL**. Copy only the unique identifier in
115+
the URL.
116+
117+
![Locate a data source UID in Grafana.](./grafana-datasource-uid.png)
118+
119+
1. [Set up a Grafana service account](https://grafana.com/docs/grafana/latest/administration/service-accounts/#create-a-service-account-in-grafana) by creating the
120+
service account and generating a token for the account to use:
121+
122+
1. In the Grafana navigation sidebar, click
123+
**Administration > Users and Access > Service Accounts**.
124+
125+
1. Create the service account in Grafana by clicking **Add service
126+
account**, giving it a name, and granting it the "Data Sources >
127+
Writer" role. Make sure you hit the **Apply** button to assign the role.
128+
In older versions of Grafana, you can use the "Admin" role
129+
instead.
130+
131+
1. Click **Add service account token**.
132+
133+
1. Set the token expiration to "No expiration" and click **Generate
134+
token**, then copy the generated token to the clipboard for use as
135+
GRAFANA_SERVICE_ACCOUNT_TOKEN in the next step:
136+
137+
![Generate and save a service account token in Grafana.](https://cloud.google.com/static/stackdriver/images/grafana-generate-save-service-account-token.png)
138+
139+
1. Set the following
140+
variables using the results of the previous steps:
141+
142+
```bash
143+
# These values are required.
144+
export GRAFANA_INSTANCE_URL=YOUR_GRAFANA_INSTANCE_URL # The Grafana instance URL from step 2. This is a URL. Include "http://" or "https://".
145+
export GRAFANA_DATASOURCE_UID=YOUR_GRAFANA_DATASOURCE_UID # The Grafana data source UID from step 3. This is not a URL.
146+
export GRAFANA_SERVICE_ACCOUNT_TOKEN=YOUR_GRAFANA_SERVICE_ACCOUNT_TOKEN # The Grafana service account token from step 4.
147+
```
148+
149+
1. Create a secret in Secret Manager:
150+
151+
```bash
152+
gcloud secrets create datasource-syncer --replication-policy="automatic" && \
153+
echo -n ${GRAFANA_SERVICE_ACCOUNT_TOKEN} | gcloud secrets versions add datasource-syncer --data-file=-
154+
```
155+
156+
1. Run the following command to create a YAML file and name it
157+
`cloud-run-datasource-syncer.yaml`:
158+
159+
```bash
160+
cat > cloud-run-datasource-syncer.yaml <<EOF
161+
apiVersion: run.googleapis.com/v1
162+
kind: Job
163+
metadata:
164+
name: logging-datasource-syncer-job
165+
spec:
166+
template:
167+
spec:
168+
taskCount: 1
169+
template:
170+
spec:
171+
containers:
172+
- name: datasource-syncer
173+
image: ${REGION}-docker.pkg.dev/${PROJECT_ID}/datasource-syncer-repo/datasource-syncer
174+
args:
175+
- "--datasource-uids=${GRAFANA_DATASOURCE_UID}"
176+
- "--grafana-api-endpoint=${GRAFANA_INSTANCE_URL}"
177+
- "--project-id=${PROJECT_ID}"
178+
env:
179+
- name: GRAFANA_SERVICE_ACCOUNT_TOKEN
180+
valueFrom:
181+
secretKeyRef:
182+
key: latest
183+
name: datasource-syncer
184+
serviceAccountName: ${SA_DS_SYNCER}@${PROJECT_ID}.iam.gserviceaccount.com
185+
EOF
186+
```
187+
188+
Then run the following command to create a Cloud Run job using the
189+
YAML file:
190+
191+
```bash
192+
gcloud run jobs replace cloud-run-datasource-syncer.yaml --region ${REGION}
193+
```
194+
195+
1. Create a schedule in Cloud Scheduler to run the Cloud Run job
196+
every 10 minutes:
197+
198+
```bash
199+
gcloud scheduler jobs create http datasource-syncer \
200+
--location ${REGION} \
201+
--schedule="*/10 * * * *" \
202+
--uri="https://${REGION}-run.googleapis.com/apis/run.googleapis.com/v1/namespaces/${PROJECT_ID}/jobs/logging-datasource-syncer-job:run" \
203+
--http-method POST \
204+
--oauth-service-account-email=${SA_DS_SYNCER}@${PROJECT_ID}.iam.gserviceaccount.com
205+
```
206+
207+
Then force run the scheduler you just created:
208+
209+
```bash
210+
gcloud scheduler jobs run datasource-syncer --location ${REGION}
211+
```
212+
213+
It can take up to 15 seconds for the data source to be updated.
214+
215+
1. Go to your newly configured Grafana data source and verify the **Access Token
216+
** field has the value `configured` and the **Project ID** field has your cloud project id. You might have to refresh the page. Once verified, go to the bottom
217+
of the page, select **Save & test**, and ensure you see a green checkmark saying that
218+
the datasource is properly configured. You need to select **Save & test** at
219+
least once to ensure that label autocompletion in Grafana works.

datasource-syncer/go.mod

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
module datasource-syncer
2+
3+
go 1.24.2
4+
5+
require (
6+
github.com/go-kit/log v0.2.1
7+
github.com/google/go-cmp v0.7.0
8+
github.com/grafana/grafana-api-golang-client v0.27.0
9+
github.com/hashicorp/go-cleanhttp v0.5.2
10+
golang.org/x/oauth2 v0.30.0
11+
)
12+
13+
require (
14+
cloud.google.com/go/compute/metadata v0.3.0 // indirect
15+
github.com/go-logfmt/logfmt v0.5.1 // indirect
16+
)

datasource-syncer/go.sum

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
2+
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
3+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
4+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
5+
github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU=
6+
github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
7+
github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA=
8+
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
9+
github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b h1:/vQ+oYKu+JoyaMPDsv5FzwuL2wwWBgBbtj/YLCi4LuA=
10+
github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b/go.mod h1:Xo4aNUOrJnVruqWQJBtW6+bTBDTniY8yZum5rF3b5jw=
11+
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
12+
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
13+
github.com/grafana/grafana-api-golang-client v0.27.0 h1:zIwMXcbCB4n588i3O2N6HfNcQogCNTd/vPkEXTr7zX8=
14+
github.com/grafana/grafana-api-golang-client v0.27.0/go.mod h1:uNLZEmgKtTjHBtCQMwNn3qsx2mpMb8zU+7T4Xv3NR9Y=
15+
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
16+
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
17+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
18+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
19+
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
20+
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
21+
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
22+
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
23+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
24+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
147 KB
Loading

0 commit comments

Comments
 (0)