cmd/ops_agent_windows/install_windows.go (143 lines of code) (raw):

// Copyright 2022 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 // // http://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 ( "fmt" "syscall" "time" "github.com/hashicorp/go-multierror" "golang.org/x/sys/windows/svc" "golang.org/x/sys/windows/svc/eventlog" "golang.org/x/sys/windows/svc/mgr" ) func escapeExe(exepath string, args []string) string { // from https://github.com/golang/sys/blob/22da62e12c0c/windows/svc/mgr/mgr.go#L123 s := syscall.EscapeArg(exepath) for _, v := range args { s += " " + syscall.EscapeArg(v) } return s } func uninstallDiagnosticService(m *mgr.Mgr) error { diagnosticsServiceHandle, err := m.OpenService("google-cloud-ops-agent-diagnostics") // err == nil means the service exists. // If err != nil, the service does not exist, so nothing to delete. if err == nil { defer diagnosticsServiceHandle.Close() if err := stopService(diagnosticsServiceHandle, 30*time.Second); err != nil { return fmt.Errorf("failed to stop the diagnostics Windows service: %w", err) } if err := diagnosticsServiceHandle.Delete(); err != nil { return fmt.Errorf("fails to delete the diagnostics Windows service: %w", err) } } return nil } func install() error { m, err := mgr.Connect() if err != nil { return err } defer m.Disconnect() diagnosticError := uninstallDiagnosticService(m) if diagnosticError != nil { return diagnosticError } handles := make([]*mgr.Service, len(services)) for i, s := range services { // Registering with the event log is required to suppress the "The description for Event ID 1 from source Google Cloud Ops Agent cannot be found" message in the logs. if err := eventlog.InstallAsEventCreate(s.name, eventlog.Error|eventlog.Warning|eventlog.Info); err != nil { // Ignore error since it likely means the event log already exists. } deps := []string{"rpcss"} if i > 0 { // All services depend on the config generation service. deps = append(deps, services[0].name) } serviceHandle, err := m.OpenService(s.name) if err == nil { // Service already exists; just update its configuration. defer serviceHandle.Close() config, err := serviceHandle.Config() if err != nil { return err } config.DisplayName = s.displayName config.BinaryPathName = escapeExe(s.exepath, s.args) config.Dependencies = deps config.DelayedAutoStart = true if err := serviceHandle.UpdateConfig(config); err != nil { return err } } else { serviceHandle, err = m.CreateService( s.name, s.exepath, mgr.Config{ DisplayName: s.displayName, StartType: mgr.StartAutomatic, Dependencies: deps, DelayedAutoStart: true, }, s.args..., ) if err != nil { return err } defer serviceHandle.Close() } // restart after 1s then 2s, reset error counter after 60s serviceHandle.SetRecoveryActions([]mgr.RecoveryAction{ {Type: mgr.ServiceRestart, Delay: time.Second}, {Type: mgr.ServiceRestart, Delay: 2 * time.Second}, }, 60) handles[i] = serviceHandle } // Automatically (re)start the Ops Agent service. for i := len(services) - 1; i >= 0; i-- { if err := stopService(handles[i], 30*time.Second); err != nil { return fmt.Errorf("failed to stop service: %v, error: %v", services[i].name, err) } } return handles[0].Start() } func uninstall() error { m, err := mgr.Connect() if err != nil { return err } defer m.Disconnect() var errs error // Have to remove the services in reverse order. for i := len(services) - 1; i >= 0; i-- { s := services[i] serviceHandle, err := m.OpenService(s.name) if err != nil { // Service does not exist, so nothing to delete. continue } defer serviceHandle.Close() if err := stopService(serviceHandle, 30*time.Second); err != nil { // Don't return until all services have been processed. errs = multierror.Append(errs, err) } if err := serviceHandle.Delete(); err != nil { // Don't return until all services have been processed. errs = multierror.Append(errs, err) } } diagnosticError := uninstallDiagnosticService(m) if diagnosticError != nil { errs = multierror.Append(errs, diagnosticError) } return errs } func stopService(serviceHandle *mgr.Service, timeout time.Duration) error { var status svc.Status var err error if status, err = serviceHandle.Query(); err != nil { return err } if status.State == svc.Stopped { return nil } if status, err = serviceHandle.Control(svc.Stop); err != nil { return err } startTime := time.Now() for status.State != svc.Stopped { if time.Since(startTime) > timeout { return fmt.Errorf("Timed out (>%v) waiting for service to stop", timeout) } time.Sleep(1 * time.Second) if status, err = serviceHandle.Query(); err != nil { return err } } return nil }