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) }