Skip to content

Commit 9b90da6

Browse files
authored
Config Management via UAP (#1919)
1 parent 1fd2474 commit 9b90da6

File tree

11 files changed

+344
-21
lines changed

11 files changed

+344
-21
lines changed

Dockerfile

-10
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,6 @@ RUN ./pkg/rpm/build.sh
176176
COPY cmd/ops_agent_uap_plugin cmd/ops_agent_uap_plugin
177177
COPY ./builds/ops_agent_plugin.sh .
178178
RUN ./ops_agent_plugin.sh /work/cache/
179-
RUN source VERSION && echo $PKG_VERSION > /work/cache/OPS_AGENT_VERSION
180179
RUN ./pkg/plugin/build.sh /work/cache centos8
181180

182181

@@ -290,7 +289,6 @@ RUN ./pkg/rpm/build.sh
290289
COPY cmd/ops_agent_uap_plugin cmd/ops_agent_uap_plugin
291290
COPY ./builds/ops_agent_plugin.sh .
292291
RUN ./ops_agent_plugin.sh /work/cache/
293-
RUN source VERSION && echo $PKG_VERSION > /work/cache/OPS_AGENT_VERSION
294292
RUN ./pkg/plugin/build.sh /work/cache rockylinux9
295293

296294

@@ -399,7 +397,6 @@ RUN ./pkg/deb/build.sh
399397
COPY cmd/ops_agent_uap_plugin cmd/ops_agent_uap_plugin
400398
COPY ./builds/ops_agent_plugin.sh .
401399
RUN ./ops_agent_plugin.sh /work/cache/
402-
RUN source VERSION && echo $PKG_VERSION > /work/cache/OPS_AGENT_VERSION
403400
RUN ./pkg/plugin/build.sh /work/cache bookworm
404401

405402

@@ -508,7 +505,6 @@ RUN ./pkg/deb/build.sh
508505
COPY cmd/ops_agent_uap_plugin cmd/ops_agent_uap_plugin
509506
COPY ./builds/ops_agent_plugin.sh .
510507
RUN ./ops_agent_plugin.sh /work/cache/
511-
RUN source VERSION && echo $PKG_VERSION > /work/cache/OPS_AGENT_VERSION
512508
RUN ./pkg/plugin/build.sh /work/cache bullseye
513509

514510

@@ -636,7 +632,6 @@ RUN ./pkg/rpm/build.sh
636632
COPY cmd/ops_agent_uap_plugin cmd/ops_agent_uap_plugin
637633
COPY ./builds/ops_agent_plugin.sh .
638634
RUN ./ops_agent_plugin.sh /work/cache/
639-
RUN source VERSION && echo $PKG_VERSION > /work/cache/OPS_AGENT_VERSION
640635
RUN ./pkg/plugin/build.sh /work/cache sles12
641636

642637

@@ -750,7 +745,6 @@ RUN ./pkg/rpm/build.sh
750745
COPY cmd/ops_agent_uap_plugin cmd/ops_agent_uap_plugin
751746
COPY ./builds/ops_agent_plugin.sh .
752747
RUN ./ops_agent_plugin.sh /work/cache/
753-
RUN source VERSION && echo $PKG_VERSION > /work/cache/OPS_AGENT_VERSION
754748
RUN ./pkg/plugin/build.sh /work/cache sles15
755749

756750

@@ -859,7 +853,6 @@ RUN ./pkg/deb/build.sh
859853
COPY cmd/ops_agent_uap_plugin cmd/ops_agent_uap_plugin
860854
COPY ./builds/ops_agent_plugin.sh .
861855
RUN ./ops_agent_plugin.sh /work/cache/
862-
RUN source VERSION && echo $PKG_VERSION > /work/cache/OPS_AGENT_VERSION
863856
RUN ./pkg/plugin/build.sh /work/cache focal
864857

865858

@@ -968,7 +961,6 @@ RUN ./pkg/deb/build.sh
968961
COPY cmd/ops_agent_uap_plugin cmd/ops_agent_uap_plugin
969962
COPY ./builds/ops_agent_plugin.sh .
970963
RUN ./ops_agent_plugin.sh /work/cache/
971-
RUN source VERSION && echo $PKG_VERSION > /work/cache/OPS_AGENT_VERSION
972964
RUN ./pkg/plugin/build.sh /work/cache jammy
973965

974966

@@ -1077,7 +1069,6 @@ RUN ./pkg/deb/build.sh
10771069
COPY cmd/ops_agent_uap_plugin cmd/ops_agent_uap_plugin
10781070
COPY ./builds/ops_agent_plugin.sh .
10791071
RUN ./ops_agent_plugin.sh /work/cache/
1080-
RUN source VERSION && echo $PKG_VERSION > /work/cache/OPS_AGENT_VERSION
10811072
RUN ./pkg/plugin/build.sh /work/cache noble
10821073

10831074

@@ -1186,7 +1177,6 @@ RUN ./pkg/deb/build.sh
11861177
COPY cmd/ops_agent_uap_plugin cmd/ops_agent_uap_plugin
11871178
COPY ./builds/ops_agent_plugin.sh .
11881179
RUN ./ops_agent_plugin.sh /work/cache/
1189-
RUN source VERSION && echo $PKG_VERSION > /work/cache/OPS_AGENT_VERSION
11901180
RUN ./pkg/plugin/build.sh /work/cache oracular
11911181

11921182

builds/ops_agent_plugin.sh

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
set -x -e
1717
DESTDIR=$1
18+
source VERSION && echo $PKG_VERSION > "$DESTDIR/OPS_AGENT_VERSION"
1819
mkdir -p "$DESTDIR/opt/google-cloud-ops-agent"
1920
go build -buildvcs=false -ldflags "-s -w" -o "$DESTDIR/opt/google-cloud-ops-agent/plugin" \
2021
github.com/GoogleCloudPlatform/ops-agent/cmd/ops_agent_uap_plugin

cmd/ops_agent_uap_plugin/plugin.go

+37
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@ import (
2121
"net"
2222
"os"
2323
"os/exec"
24+
"path/filepath"
2425
"sync"
2526

27+
"buf.build/go/protoyaml"
2628
"google.golang.org/grpc"
2729
"google.golang.org/grpc/reflection"
2830

@@ -96,3 +98,38 @@ func main() {
9698
os.Exit(1)
9799
}
98100
}
101+
102+
func writeCustomConfigToFile(req *pb.StartRequest, configPath string) error {
103+
customConfig := []byte{}
104+
switch req.GetServiceConfig().(type) {
105+
case *pb.StartRequest_StringConfig:
106+
customConfig = []byte(req.GetStringConfig())
107+
case *pb.StartRequest_StructConfig:
108+
structConfig := req.GetStructConfig()
109+
yamlBytes, err := protoyaml.Marshal(structConfig)
110+
if err != nil {
111+
return fmt.Errorf("failed to parse the custom Ops Agent config: %v", err)
112+
}
113+
customConfig = yamlBytes
114+
}
115+
116+
if len(customConfig) > 0 {
117+
parentDir := filepath.Dir(configPath)
118+
if _, err := os.Stat(parentDir); os.IsNotExist(err) {
119+
err := os.MkdirAll(parentDir, 0755)
120+
if err != nil {
121+
return fmt.Errorf("failed to create parent directory %s: %v", parentDir, err)
122+
}
123+
}
124+
125+
file, err := os.OpenFile(configPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
126+
if err != nil {
127+
return fmt.Errorf("failed to open the config.yaml file at location: %s, error: %v", configPath, err)
128+
}
129+
defer file.Close()
130+
if _, err := file.Write(customConfig); err != nil {
131+
return fmt.Errorf("failed to write to the config.yaml file at location: %s, error: %v", configPath, err)
132+
}
133+
}
134+
return nil
135+
}
+152
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
// Copyright 2025 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+
package main
15+
16+
import (
17+
"context"
18+
"fmt"
19+
"os"
20+
"path/filepath"
21+
"testing"
22+
23+
"buf.build/go/protoyaml" // Import the protoyaml-go package
24+
"github.com/GoogleCloudPlatform/ops-agent/apps"
25+
"github.com/GoogleCloudPlatform/ops-agent/confgenerator"
26+
"github.com/GoogleCloudPlatform/ops-agent/internal/platform"
27+
28+
pb "github.com/GoogleCloudPlatform/ops-agent/cmd/ops_agent_uap_plugin/google_guest_agent/plugin"
29+
spb "google.golang.org/protobuf/types/known/structpb"
30+
)
31+
32+
func customLogPathByOsType(ctx context.Context) string {
33+
osType := platform.FromContext(ctx).Name()
34+
if osType == "linux" {
35+
return "/var/log"
36+
}
37+
return `C:\mylog`
38+
}
39+
func TestWriteCustomConfigToFile(t *testing.T) {
40+
yamlConfig := fmt.Sprintf(`logging:
41+
receivers:
42+
mylog_source:
43+
type: files
44+
include_paths:
45+
- %s
46+
exporters:
47+
google:
48+
type: google_cloud_logging
49+
processors:
50+
my_exclude:
51+
type: exclude_logs
52+
match_any:
53+
- jsonPayload.missing_field = "value"
54+
- jsonPayload.message =~ "test pattern"
55+
service:
56+
pipelines:
57+
my_pipeline:
58+
receivers: [mylog_source]
59+
processors: [my_exclude]
60+
exporters: [google]`, customLogPathByOsType(context.Background()))
61+
structConfig := &spb.Struct{}
62+
err := protoyaml.Unmarshal([]byte(yamlConfig), structConfig)
63+
if err != nil {
64+
t.Fatalf("Failed to unmarshal YAML into structpb.Struct: %v", err)
65+
}
66+
67+
tests := []struct {
68+
name string
69+
req *pb.StartRequest
70+
}{
71+
{
72+
name: "Received a valid StringConfig from UAP, the output should be a valid Ops agent yaml",
73+
req: &pb.StartRequest{
74+
ServiceConfig: &pb.StartRequest_StringConfig{
75+
StringConfig: yamlConfig,
76+
},
77+
},
78+
},
79+
{
80+
name: "Received a valid StructConfig from UAP, the output should be a valid Ops agent yaml",
81+
req: &pb.StartRequest{
82+
ServiceConfig: &pb.StartRequest_StructConfig{
83+
StructConfig: structConfig,
84+
},
85+
},
86+
},
87+
}
88+
89+
for _, tc := range tests {
90+
t.Run(tc.name, func(t *testing.T) {
91+
// Create a temporary directory for the test file
92+
tmpDir := t.TempDir()
93+
configPath := filepath.Join(tmpDir, "ops-agent-config", fmt.Sprintf("%sconfig.yaml", tc.name))
94+
95+
err := writeCustomConfigToFile(tc.req, configPath)
96+
97+
if err != nil {
98+
t.Errorf("%v: writeCustomConfigToFile got error: %v, want nil error", tc.name, err)
99+
}
100+
101+
_, err = confgenerator.MergeConfFiles(context.Background(), configPath, apps.BuiltInConfStructs)
102+
if err != nil {
103+
t.Errorf("%v: conf generator fails to validate the output Ops agent yaml: %v", tc.name, err)
104+
}
105+
})
106+
}
107+
}
108+
109+
func TestWriteCustomConfigToFile_receivedEmptyCustomConfig(t *testing.T) {
110+
tests := []struct {
111+
name string
112+
req *pb.StartRequest
113+
}{
114+
{
115+
name: "The ops agent config.yaml file should not be modified if UAP does not send any StringConfig",
116+
req: &pb.StartRequest{},
117+
},
118+
{
119+
name: "The ops agent config.yaml file should not be modified if UAP does not send any StructConfig",
120+
req: &pb.StartRequest{},
121+
},
122+
}
123+
124+
for _, tc := range tests {
125+
t.Run(tc.name, func(t *testing.T) {
126+
configFile, err := os.CreateTemp("", "config.yaml")
127+
if err != nil {
128+
t.Fatalf("%v: failed to create the config.yaml file at location: %s, error: %v", tc.name, configFile.Name(), err)
129+
}
130+
configPath := configFile.Name()
131+
wantFileContent := "1234"
132+
if _, err := configFile.WriteString(wantFileContent); err != nil {
133+
t.Fatalf("%v: failed to write to the config.yaml file at location: %s, error: %v", tc.name, configPath, err)
134+
}
135+
136+
err = writeCustomConfigToFile(tc.req, configPath)
137+
if err != nil {
138+
t.Errorf("%v: writeCustomConfigToFile got error: %v, want nil error", tc.name, err)
139+
}
140+
141+
gotContent, err := os.ReadFile(configPath)
142+
if err != nil {
143+
t.Fatalf("%s: failed to read the config.yaml file content: %v", tc.name, err)
144+
}
145+
if string(gotContent) != wantFileContent {
146+
t.Errorf("%s: got config.yaml content: %v, want: %v", tc.name, string(gotContent), wantFileContent)
147+
}
148+
configFile.Close()
149+
os.Remove(configPath)
150+
})
151+
}
152+
}

cmd/ops_agent_uap_plugin/service_linux.go

+12-6
Original file line numberDiff line numberDiff line change
@@ -88,13 +88,13 @@ func (ps *OpsAgentPluginServer) Start(ctx context.Context, msg *pb.StartRequest)
8888
if err != nil {
8989
ps.Stop(ctx, &pb.StopRequest{Cleanup: false})
9090
log.Printf("Start() failed, because it cannot determine the plugin install location: %s", err)
91-
return nil, status.Error(1, err.Error())
91+
return nil, status.Error(13, err.Error()) // Internal
9292
}
9393
pluginInstallPath, err = filepath.EvalSymlinks(pluginInstallPath)
9494
if err != nil {
9595
ps.Stop(ctx, &pb.StopRequest{Cleanup: false})
9696
log.Printf("Start() failed, because it cannot determine the plugin install location: %s", err)
97-
return nil, status.Error(1, err.Error())
97+
return nil, status.Error(13, err.Error()) // Internal
9898
}
9999
pluginInstallDir := filepath.Dir(pluginInstallPath)
100100

@@ -108,21 +108,27 @@ func (ps *OpsAgentPluginServer) Start(ctx context.Context, msg *pb.StartRequest)
108108
if foundConflictingInstallations || err != nil {
109109
ps.Stop(ctx, &pb.StopRequest{Cleanup: false})
110110
log.Printf("Start() failed: %s", err)
111-
return nil, status.Error(1, err.Error())
111+
return nil, status.Error(9, err.Error()) // FailedPrecondition
112+
}
113+
114+
// Receive config from the Start request and write it to the Ops Agent config file.
115+
if err := writeCustomConfigToFile(msg, OpsAgentConfigLocationLinux); err != nil {
116+
log.Printf("Start() failed: %s", err)
117+
ps.Stop(ctx, &pb.StopRequest{Cleanup: false})
118+
return nil, status.Errorf(13, "failed to write the custom Ops Agent config to file: %s", err) // Internal
112119
}
113120

114121
// Ops Agent config validation
115122
if err := validateOpsAgentConfig(pContext, pluginInstallDir, pluginStateDir, ps.runCommand); err != nil {
116123
log.Printf("Start() failed: %s", err)
117124
ps.Stop(ctx, &pb.StopRequest{Cleanup: false})
118-
return nil, status.Errorf(1, "failed to validate Ops Agent config: %s", err)
125+
return nil, status.Errorf(9, "failed to validate Ops Agent config: %s", err) // FailedPrecondition
119126
}
120-
121127
// Subagent config generation
122128
if err := generateSubagentConfigs(pContext, ps.runCommand, pluginInstallDir, pluginStateDir); err != nil {
123129
log.Printf("Start() failed: %s", err)
124130
ps.Stop(ctx, &pb.StopRequest{Cleanup: false})
125-
return nil, status.Errorf(1, "failed to generate subagent configs: %s", err)
131+
return nil, status.Errorf(9, "failed to generate subagent configs: %s", err) // FailedPrecondition
126132
}
127133

128134
// the diagnostics service and subagent startups

cmd/ops_agent_uap_plugin/service_windows.go

+8
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,14 @@ func (ps *OpsAgentPluginServer) Start(ctx context.Context, msg *pb.StartRequest)
134134
return nil, status.Error(13, err.Error()) // Internal
135135
}
136136

137+
// Receive config from the Start request and write it to the Ops Agent config file.
138+
if err := writeCustomConfigToFile(msg, OpsAgentConfigLocationWindows); err != nil {
139+
ps.Stop(ctx, &pb.StopRequest{Cleanup: false})
140+
windowsEventLogger.Close()
141+
log.Printf("Start() failed, because it failed to write the custom Ops Agent config to file: %s", err)
142+
return nil, status.Errorf(13, "failed to write the custom Ops Agent config to file: %s", err) // Internal
143+
}
144+
137145
// Subagents config validation and generation.
138146
if err := generateSubAgentConfigs(ctx, OpsAgentConfigLocationWindows, pluginStateDir, windowsEventLogger); err != nil {
139147
ps.Stop(ctx, &pb.StopRequest{Cleanup: false})

dockerfiles/template

-1
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,6 @@ COPY --from={target_name}-build-wrapper /work/cache /work/cache
9494
COPY cmd/ops_agent_uap_plugin cmd/ops_agent_uap_plugin
9595
COPY ./builds/ops_agent_plugin.sh .
9696
RUN ./ops_agent_plugin.sh /work/cache/
97-
RUN source VERSION && echo $PKG_VERSION > /work/cache/OPS_AGENT_VERSION
9897
RUN ./pkg/plugin/build.sh /work/cache {target_name}
9998

10099

0 commit comments

Comments
 (0)