google_guest_agent/diagnostics.go (97 lines of code) (raw):
// Copyright 2018 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// https://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"context"
"encoding/json"
"reflect"
"runtime"
"slices"
"sync/atomic"
"github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/cfg"
"github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/run"
"github.com/GoogleCloudPlatform/guest-agent/utils"
"github.com/GoogleCloudPlatform/guest-logging-go/logger"
)
const diagnosticsCmd = `C:\Program Files\Google\Compute Engine\diagnostics\diagnostics.exe`
var (
diagnosticsRegKey = "Diagnostics"
diagnosticsDisabled = false
// Indicate whether an existing job is runing to collect logs
// 0 -> not running, 1 -> running
isDiagnosticsRunning int32 = 0
)
type diagnosticsEntry struct {
SignedURL string
ExpireOn string
Trace bool
}
type diagnosticsMgr struct {
// fakeWindows forces Disabled to run as if it was running in a windows system.
// mostly target for unit tests.
fakeWindows bool
}
func (d *diagnosticsMgr) Diff(ctx context.Context) (bool, error) {
return !reflect.DeepEqual(newMetadata.Instance.Attributes.Diagnostics, oldMetadata.Instance.Attributes.Diagnostics), nil
}
func (d *diagnosticsMgr) Timeout(ctx context.Context) (bool, error) {
return false, nil
}
func (d *diagnosticsMgr) Disabled(ctx context.Context) (bool, error) {
var disabled bool
config := cfg.Get()
if !d.fakeWindows && runtime.GOOS != "windows" {
return true, nil
}
defer func() {
if disabled != diagnosticsDisabled {
diagnosticsDisabled = disabled
logStatus("diagnostics", disabled)
}
}()
// Diagnostics are opt-in and enabled by default.
if config.Diagnostics != nil {
return !config.Diagnostics.Enable, nil
}
if newMetadata.Instance.Attributes.EnableDiagnostics != nil {
return !*newMetadata.Instance.Attributes.EnableDiagnostics, nil
}
if newMetadata.Project.Attributes.EnableDiagnostics != nil {
return !*newMetadata.Project.Attributes.EnableDiagnostics, nil
}
return diagnosticsDisabled, nil
}
func (d *diagnosticsMgr) Set(ctx context.Context) error {
logger.Infof("Diagnostics: logs export requested.")
diagnosticsEntries, err := readRegMultiString(regKeyBase, diagnosticsRegKey)
if err != nil && err != errRegNotExist {
return err
}
strEntry := newMetadata.Instance.Attributes.Diagnostics
if slices.Contains(diagnosticsEntries, strEntry) {
return nil
}
diagnosticsEntries = append(diagnosticsEntries, strEntry)
var entry diagnosticsEntry
if err := json.Unmarshal([]byte(strEntry), &entry); err != nil {
return err
}
expired, _ := utils.CheckExpired(entry.ExpireOn)
if entry.SignedURL == "" || expired {
return nil
}
args := []string{
"-signedUrl",
entry.SignedURL,
}
if entry.Trace {
args = append(args, "-trace")
}
// If no existing running job, set it to 1 and block other requests
if !atomic.CompareAndSwapInt32(&isDiagnosticsRunning, 0, 1) {
logger.Infof("Diagnostics: reject the request, as an existing process is collecting logs from the system")
return nil
}
go func() {
logger.Infof("Diagnostics: collecting logs from the system.")
res := run.WithCombinedOutput(ctx, diagnosticsCmd, args...)
logger.Infof(res.Combined)
if res.ExitCode != 0 {
logger.Warningf("Error collecting logs: %v", res.Error())
}
// Job is done, unblock the following requests
atomic.SwapInt32(&isDiagnosticsRunning, 0)
}()
return writeRegMultiString(regKeyBase, diagnosticsRegKey, diagnosticsEntries)
}