//  Copyright 2018 Google Inc. All Rights Reserved.
//
//  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 agentconfig stores and retrieves configuration settings for the OS Config agent.
package agentconfig

import (
	"context"
	"crypto/sha256"
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"time"

	"cloud.google.com/go/compute/metadata"
	"github.com/GoogleCloudPlatform/osconfig/clog"
	"golang.org/x/oauth2/jws"
)

const (
	// metadataIP is the documented metadata server IP address.
	metadataIP = "169.254.169.254"
	// metadataHostEnv is the environment variable specifying the
	// GCE metadata hostname.
	metadataHostEnv = "GCE_METADATA_HOST"
	// InstanceMetadata is the compute metadata URL.
	InstanceMetadata = "http://metadata.google.internal/computeMetadata/v1/instance"
	// IdentityTokenPath is the instance identity token path.
	IdentityTokenPath = "instance/service-accounts/default/identity?audience=osconfig.googleapis.com&format=full"
	// ReportURL is the guest attributes endpoint.
	ReportURL = InstanceMetadata + "/guest-attributes"

	googetRepoDir      = "C:/ProgramData/GooGet/repos"
	googetRepoFilePath = googetRepoDir + "/google_osconfig_managed.repo"
	zypperRepoDir      = "/etc/zypp/repos.d"
	zypperRepoFilePath = zypperRepoDir + "/google_osconfig_managed.repo"
	yumRepoDir         = "/etc/yum.repos.d"
	yumRepoFilePath    = yumRepoDir + "/google_osconfig_managed.repo"
	aptRepoDir         = "/etc/apt/sources.list.d"
	aptRepoFilePath    = aptRepoDir + "/google_osconfig_managed.list"

	prodEndpoint = "{zone}-osconfig.googleapis.com.:443"

	osInventoryEnabledDefault      = false
	guestPoliciesEnabledDefault    = false
	taskNotificationEnabledDefault = false
	debugEnabledDefault            = false

	oldConfigDirLinux = "/etc/osconfig"
	cacheDirLinux     = "/var/lib/google_osconfig_agent"
	windowsCacheDir   = `Google\OSConfig`

	taskStateFileLinux    = cacheDirLinux + "/osconfig_task.state"
	oldTaskStateFileLinux = oldConfigDirLinux + "/osconfig_task.state"

	oldCacheDirWindows      = `C:\Program Files\Google\OSConfig`
	oldTaskStateFileWindows = oldCacheDirWindows + "\\osconfig_task.state"

	restartFileLinux    = cacheDirLinux + "/osconfig_agent_restart_required"
	oldRestartFileLinux = oldConfigDirLinux + "/osconfig_agent_restart_required"

	osConfigPollIntervalDefault = 10
	osConfigMetadataPollTimeout = 60

	// Default Google API domain
	universeDomainDefault = "googleapis.com"
)

var (
	endpoint            = flag.String("endpoint", prodEndpoint, "osconfig endpoint override")
	debug               = flag.Bool("debug", false, "set debug log verbosity")
	stdout              = flag.Bool("stdout", false, "log to stdout")
	disableLocalLogging = flag.Bool("disable_local_logging", false, "disable logging using event log or syslog")

	agentConfig   = &config{}
	agentConfigMx sync.RWMutex
	version       string
	lEtag         = &lastEtag{Etag: "0"}

	// Current supported capabilites for this agent.
	// These are matched server side to what tasks this agent can
	// perform.
	capabilities = []string{"PATCH_GA", "GUEST_POLICY_BETA", "CONFIG_V1"}

	osConfigWatchConfigTimeout = 10 * time.Minute

	defaultClient = &http.Client{
		Transport: &http.Transport{
			Dial: (&net.Dialer{
				Timeout:   2 * time.Second,
				KeepAlive: 30 * time.Second,
			}).Dial,
		},
	}

	freeOSMemory          = strings.ToLower(os.Getenv("OSCONFIG_FREE_OS_MEMORY"))
	disableInventoryWrite = strings.ToLower(os.Getenv("OSCONFIG_DISABLE_INVENTORY_WRITE"))
)

type config struct {
	aptRepoFilePath         string
	instanceName            string
	instanceZone            string
	projectID               string
	svcEndpoint             string
	googetRepoFilePath      string
	zypperRepoFilePath      string
	yumRepoFilePath         string
	instanceID              string
	universeDomain          string
	numericProjectID        int64
	osConfigPollInterval    int
	debugEnabled            bool
	taskNotificationEnabled bool
	guestPoliciesEnabled    bool
	osInventoryEnabled      bool
	scalibrLinuxEnabled     bool
	guestAttributesEnabled  bool
	traceGetInventory       bool
}

func (c *config) parseFeatures(features string, enabled bool) {
	for _, f := range strings.Split(features, ",") {
		f = strings.ToLower(strings.TrimSpace(f))
		switch f {
		case "tasks", "ospatch": // ospatch is the legacy flag
			c.taskNotificationEnabled = enabled
		case "guestpolicies", "ospackage": // ospackage is the legacy flag
			c.guestPoliciesEnabled = enabled
		case "osinventory":
			c.osInventoryEnabled = enabled
		}
	}
}

func (c *config) asSha256() string {
	h := sha256.New()
	h.Write([]byte(fmt.Sprintf("%v", c)))

	return fmt.Sprintf("%x", h.Sum(nil))
}

func getAgentConfig() config {
	agentConfigMx.RLock()
	defer agentConfigMx.RUnlock()
	return *agentConfig
}

type lastEtag struct {
	Etag string
	mu   sync.RWMutex
}

func (e *lastEtag) set(etag string) {
	e.mu.Lock()
	defer e.mu.Unlock()
	e.Etag = etag
}

func (e *lastEtag) get() string {
	e.mu.RLock()
	defer e.mu.RUnlock()
	return e.Etag
}

func parseBool(s string) bool {
	enabled, err := strconv.ParseBool(s)
	if err != nil {
		// Bad entry returns as not enabled.
		return false
	}
	return enabled
}

type metadataJSON struct {
	Instance instanceJSON
	Project  projectJSON
	Universe universeJSON
}

type instanceJSON struct {
	Attributes attributesJSON
	ID         *json.Number
	Zone       string
	Name       string
}

type projectJSON struct {
	Attributes       attributesJSON
	ProjectID        string
	NumericProjectID int64
}

type universeJSON struct {
	UniverseDomain string `json:"universeDomain"`
}

type attributesJSON struct {
	PollIntervalOld       *json.Number `json:"os-config-poll-interval"`
	PollInterval          *json.Number `json:"osconfig-poll-interval"`
	InventoryEnabledOld   string       `json:"os-inventory-enabled"`
	InventoryEnabled      string       `json:"enable-os-inventory"`
	PreReleaseFeaturesOld string       `json:"os-config-enabled-prerelease-features"`
	PreReleaseFeatures    string       `json:"osconfig-enabled-prerelease-features"`
	DebugEnabledOld       string       `json:"enable-os-config-debug"`
	LogLevel              string       `json:"osconfig-log-level"`
	OSConfigEndpointOld   string       `json:"os-config-endpoint"`
	OSConfigEndpoint      string       `json:"osconfig-endpoint"`
	OSConfigEnabled       string       `json:"enable-osconfig"`
	DisabledFeatures      string       `json:"osconfig-disabled-features"`
	EnableGuestAttributes string       `json:"enable-guest-attributes"`
	TraceGetInventory     string       `json:"trace-get-inventory"`
	ScalibrLinuxEnabled   string       `json:"enable-scalibr-linux"`
}

func createConfigFromMetadata(md metadataJSON) *config {
	old := getAgentConfig()
	c := &config{
		osInventoryEnabled:      osInventoryEnabledDefault,
		guestPoliciesEnabled:    guestPoliciesEnabledDefault,
		taskNotificationEnabled: taskNotificationEnabledDefault,
		debugEnabled:            debugEnabledDefault,
		svcEndpoint:             prodEndpoint,
		osConfigPollInterval:    osConfigPollIntervalDefault,

		googetRepoFilePath: googetRepoFilePath,
		zypperRepoFilePath: zypperRepoFilePath,
		yumRepoFilePath:    yumRepoFilePath,
		aptRepoFilePath:    aptRepoFilePath,

		projectID:        old.projectID,
		numericProjectID: old.numericProjectID,
		instanceZone:     old.instanceZone,
		instanceName:     old.instanceName,
		instanceID:       old.instanceID,

		universeDomain: universeDomainDefault,
	}

	if md.Project.ProjectID != "" {
		c.projectID = md.Project.ProjectID
	}
	if md.Project.NumericProjectID != 0 {
		c.numericProjectID = md.Project.NumericProjectID
	}
	if md.Instance.Zone != "" {
		c.instanceZone = md.Instance.Zone
	}
	if md.Instance.Name != "" {
		c.instanceName = md.Instance.Name
	}
	if md.Instance.ID != nil {
		c.instanceID = md.Instance.ID.String()
	}

	if md.Universe.UniverseDomain != "" {
		c.universeDomain = md.Universe.UniverseDomain
	}

	// Check project first then instance as instance metadata overrides project.
	switch {
	case md.Project.Attributes.InventoryEnabled != "":
		c.osInventoryEnabled = parseBool(md.Project.Attributes.InventoryEnabled)
	case md.Project.Attributes.InventoryEnabledOld != "":
		c.osInventoryEnabled = parseBool(md.Project.Attributes.InventoryEnabledOld)
	}

	c.parseFeatures(md.Project.Attributes.PreReleaseFeaturesOld, true)
	c.parseFeatures(md.Project.Attributes.PreReleaseFeatures, true)
	if md.Project.Attributes.OSConfigEnabled != "" {
		e := parseBool(md.Project.Attributes.OSConfigEnabled)
		c.taskNotificationEnabled = e
		c.guestPoliciesEnabled = e
		c.osInventoryEnabled = e
	}
	c.parseFeatures(md.Project.Attributes.DisabledFeatures, false)

	switch {
	case md.Instance.Attributes.InventoryEnabled != "":
		c.osInventoryEnabled = parseBool(md.Instance.Attributes.InventoryEnabled)
	case md.Instance.Attributes.InventoryEnabledOld != "":
		c.osInventoryEnabled = parseBool(md.Instance.Attributes.InventoryEnabledOld)
	}

	c.parseFeatures(md.Instance.Attributes.PreReleaseFeaturesOld, true)
	c.parseFeatures(md.Instance.Attributes.PreReleaseFeatures, true)
	if md.Instance.Attributes.OSConfigEnabled != "" {
		e := parseBool(md.Instance.Attributes.OSConfigEnabled)
		c.taskNotificationEnabled = e
		c.guestPoliciesEnabled = e
		c.osInventoryEnabled = e
	}
	c.parseFeatures(md.Instance.Attributes.DisabledFeatures, false)

	switch {
	case md.Project.Attributes.PollInterval != nil:
		if val, err := md.Project.Attributes.PollInterval.Int64(); err == nil {
			c.osConfigPollInterval = int(val)
		}
	case md.Project.Attributes.PollIntervalOld != nil:
		if val, err := md.Project.Attributes.PollIntervalOld.Int64(); err == nil {
			c.osConfigPollInterval = int(val)
		}
	}

	switch {
	case md.Instance.Attributes.PollInterval != nil:
		if val, err := md.Instance.Attributes.PollInterval.Int64(); err == nil {
			c.osConfigPollInterval = int(val)
		}
	case md.Instance.Attributes.PollIntervalOld != nil:
		if val, err := md.Instance.Attributes.PollInterval.Int64(); err == nil {
			c.osConfigPollInterval = int(val)
		}
	}

	switch {
	case md.Project.Attributes.DebugEnabledOld != "":
		c.debugEnabled = parseBool(md.Project.Attributes.DebugEnabledOld)
	case md.Instance.Attributes.DebugEnabledOld != "":
		c.debugEnabled = parseBool(md.Instance.Attributes.DebugEnabledOld)
	}

	switch strings.ToLower(md.Project.Attributes.LogLevel) {
	case "debug":
		c.debugEnabled = true
	case "info":
		c.debugEnabled = false
	}

	switch strings.ToLower(md.Instance.Attributes.LogLevel) {
	case "debug":
		c.debugEnabled = true
	case "info":
		c.debugEnabled = false
	}

	if md.Project.Attributes.EnableGuestAttributes != "" {
		c.guestAttributesEnabled = parseBool(md.Project.Attributes.EnableGuestAttributes)
	}
	if md.Instance.Attributes.EnableGuestAttributes != "" {
		c.guestAttributesEnabled = parseBool(md.Instance.Attributes.EnableGuestAttributes)
	}

	// Flags take precedence over metadata.
	if *debug {
		c.debugEnabled = true
	}

	setScalibrEnablement(md, c)
	setSVCEndpoint(md, c)
	setTraceGetInventory(md, c)

	return c
}

func setScalibrEnablement(md metadataJSON, c *config) {
	projectSetting := md.Project.Attributes.ScalibrLinuxEnabled
	instanceSetting := md.Instance.Attributes.ScalibrLinuxEnabled
	if projectSetting != "" {
		c.scalibrLinuxEnabled = parseBool(projectSetting)
	}
	if instanceSetting != "" {
		c.scalibrLinuxEnabled = parseBool(instanceSetting)
	}
}

func setSVCEndpoint(md metadataJSON, c *config) {
	switch {
	case *endpoint != prodEndpoint:
		c.svcEndpoint = *endpoint
	case md.Instance.Attributes.OSConfigEndpoint != "":
		c.svcEndpoint = md.Instance.Attributes.OSConfigEndpoint
	case md.Instance.Attributes.OSConfigEndpointOld != "":
		c.svcEndpoint = md.Instance.Attributes.OSConfigEndpointOld
	case md.Project.Attributes.OSConfigEndpoint != "":
		c.svcEndpoint = md.Project.Attributes.OSConfigEndpoint
	case md.Project.Attributes.OSConfigEndpointOld != "":
		c.svcEndpoint = md.Project.Attributes.OSConfigEndpointOld
	}

	// Example instanceZone: projects/123456/zones/us-west1-b
	parts := strings.Split(c.instanceZone, "/")
	zone := parts[len(parts)-1]
	c.svcEndpoint = strings.ReplaceAll(c.svcEndpoint, "{zone}", zone)

	// Change hostname according to the universe domain
	if c.universeDomain != universeDomainDefault {
		c.svcEndpoint = strings.ReplaceAll(c.svcEndpoint, universeDomainDefault, c.universeDomain)
	}
}

func setTraceGetInventory(md metadataJSON, c *config) {
	projectSetting := md.Project.Attributes.TraceGetInventory
	instanceSetting := md.Instance.Attributes.TraceGetInventory
	if projectSetting != "" {
		c.traceGetInventory = parseBool(projectSetting)
	}
	if instanceSetting != "" {
		c.traceGetInventory = parseBool(instanceSetting)
	}
}

func formatMetadataError(err error) error {
	if urlErr, ok := err.(*url.Error); ok {
		if _, ok := urlErr.Err.(*net.DNSError); ok {
			return fmt.Errorf("DNS error when requesting metadata, check DNS settings and ensure metadata.google.internal is setup in your hosts file: %w", err)
		}
		if _, ok := urlErr.Err.(*net.OpError); ok {
			return fmt.Errorf("network error when requesting metadata, make sure your instance has an active network and can reach the metadata server: %w", err)
		}
	}
	return err
}

func getMetadata(suffix string) ([]byte, string, error) {
	host := os.Getenv(metadataHostEnv)
	if host == "" {
		// Using 169.254.169.254 instead of "metadata" here because Go
		// binaries built with the "netgo" tag and without cgo won't
		// know the search suffix for "metadata" is
		// ".google.internal", and this IP address is documented as
		// being stable anyway.
		host = metadataIP
	}
	computeMetadataURL := "http://" + host + "/computeMetadata/v1/" + suffix
	req, err := http.NewRequest("GET", computeMetadataURL, nil)
	if err != nil {
		return nil, "", err
	}
	req.Header.Add("Metadata-Flavor", "Google")
	resp, err := defaultClient.Do(req)
	if err != nil {
		return nil, "", err
	}
	defer resp.Body.Close()

	if resp.StatusCode == http.StatusNotFound {
		return nil, "", err
	}
	if resp.StatusCode != http.StatusOK {
		return nil, "", err
	}
	all, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, "", err
	}

	return all, resp.Header.Get("Etag"), nil
}

// GetCacheDirWindows returns the folder for the temp files location on Windows.
func GetCacheDirWindows() string {
	cacheDir, dirErr := os.UserCacheDir()
	if dirErr != nil {
		cacheDir = os.TempDir()
	}
	return filepath.Join(cacheDir, windowsCacheDir)
}

// WatchConfig looks for changes in metadata keys. Upon receiving successful response,
// it create a new agent config.
func WatchConfig(ctx context.Context) error {
	var md []byte
	var webError error
	// Max watch time, after this WatchConfig will return.
	timeout := time.After(osConfigWatchConfigTimeout)
	// Min watch loop time.
	loopTicker := time.NewTicker(5 * time.Second)
	defer loopTicker.Stop()
	eTag := lEtag.get()
	webErrorCount := 0
	unmarshalErrorCount := 0
	for {
		md, eTag, webError = getMetadata(fmt.Sprintf("?recursive=true&alt=json&wait_for_change=true&last_etag=%s&timeout_sec=%d", lEtag.get(), osConfigMetadataPollTimeout))
		if webError == nil && eTag != lEtag.get() {
			var metadataConfig metadataJSON
			if err := json.Unmarshal(md, &metadataConfig); err != nil {
				// Try up to three times (with 5s sleep) to get and unmarshal metadata.
				// Most unmarshal errors are transient read issues with the metadata server
				// so we should retry without logging the error.
				if unmarshalErrorCount >= 3 {
					return err
				}
				unmarshalErrorCount++
				select {
				case <-timeout:
					return err
				case <-ctx.Done():
					return nil
				case <-loopTicker.C:
					continue
				}
			}
			unmarshalErrorCount = 0
			lEtag.set(eTag)

			newAgentConfig := createConfigFromMetadata(metadataConfig)

			agentConfigMx.Lock()
			if agentConfig.asSha256() != newAgentConfig.asSha256() {
				agentConfig = newAgentConfig
				agentConfigMx.Unlock()
				break
			}
			agentConfigMx.Unlock()
		}

		// Try up to 12 times (60s) to wait for slow network initialization, after
		// that resort to using defaults and returning the error.
		if webError != nil {
			if webErrorCount == 12 {
				return formatMetadataError(webError)
			}
			webErrorCount++
		}

		select {
		case <-timeout:
			return webError
		case <-ctx.Done():
			return nil
		case <-loopTicker.C:
			continue
		}
	}

	return webError
}

// LogFeatures logs the osconfig feature status.
func LogFeatures(ctx context.Context) {
	clog.Infof(ctx, "OSConfig enabled features status:{GuestPolicies: %t, OSInventory: %t, PatchManagement: %t}.", GuestPoliciesEnabled(), OSInventoryEnabled(), TaskNotificationEnabled())
}

// SvcPollInterval returns the frequency to poll the service.
func SvcPollInterval() time.Duration {
	return time.Duration(getAgentConfig().osConfigPollInterval) * time.Minute
}

// SerialLogPort is the serial port to log to.
func SerialLogPort() string {
	if runtime.GOOS == "windows" {
		return "COM1"
	}
	// Don't write directly to the serial port on Linux as syslog already writes there.
	return ""
}

// Debug sets the debug log verbosity.
func Debug() bool {
	return *debug || getAgentConfig().debugEnabled
}

// Stdout flag.
func Stdout() bool {
	return *stdout
}

// DisableLocalLogging flag.
func DisableLocalLogging() bool {
	return *disableLocalLogging
}

// ScalibrLinuxEnabled answers whether scalibr or legacy inventory extractors should be used.
func ScalibrLinuxEnabled() bool {
	return getAgentConfig().scalibrLinuxEnabled
}

// SvcEndpoint is the OS Config service endpoint.
func SvcEndpoint() string {
	return getAgentConfig().svcEndpoint
}

// TraceGetInventory turns on memory tracing while gathering inventory.
func TraceGetInventory() bool {
	return getAgentConfig().traceGetInventory
}

// ZypperRepoDir is the location of the zypper repo files.
func ZypperRepoDir() string {
	return zypperRepoDir
}

// ZypperRepoFormat is the format of the zypper repo files.
func ZypperRepoFormat() string {
	return filepath.Join(zypperRepoDir, "osconfig_managed_%s.repo")
}

// ZypperRepoFilePath is the location where the zypper repo file will be created.
func ZypperRepoFilePath() string {
	return getAgentConfig().zypperRepoFilePath
}

// YumRepoDir is the location of the yum repo files.
func YumRepoDir() string {
	return yumRepoDir
}

// YumRepoFormat is the format of the yum repo files.
func YumRepoFormat() string {
	return filepath.Join(yumRepoDir, "osconfig_managed_%s.repo")
}

// YumRepoFilePath is the location where the yum repo file will be created.
func YumRepoFilePath() string {
	return getAgentConfig().yumRepoFilePath
}

// AptRepoDir is the location of the apt repo files.
func AptRepoDir() string {
	return aptRepoDir
}

// AptRepoFormat is the format of the apt repo files.
func AptRepoFormat() string {
	return filepath.Join(aptRepoDir, "osconfig_managed_%s.list")
}

// AptRepoFilePath is the location where the apt repo file will be created.
func AptRepoFilePath() string {
	return getAgentConfig().aptRepoFilePath
}

// GooGetRepoDir is the location of the googet repo files.
func GooGetRepoDir() string {
	return googetRepoDir
}

// GooGetRepoFormat is the format of the googet repo files.
func GooGetRepoFormat() string {
	return filepath.Join(googetRepoDir, "osconfig_managed_%s.repo")
}

// GooGetRepoFilePath is the location where the googet repo file will be created.
func GooGetRepoFilePath() string {
	return getAgentConfig().googetRepoFilePath
}

// OSInventoryEnabled indicates whether OSInventory should be enabled.
func OSInventoryEnabled() bool {
	return getAgentConfig().osInventoryEnabled
}

// GuestPoliciesEnabled indicates whether GuestPolicies should be enabled.
func GuestPoliciesEnabled() bool {
	return getAgentConfig().guestPoliciesEnabled
}

// TaskNotificationEnabled indicates whether TaskNotification should be enabled.
func TaskNotificationEnabled() bool {
	return getAgentConfig().taskNotificationEnabled
}

// Instance is the URI of the instance the agent is running on.
func Instance() string {
	// Zone contains 'projects/project-id/zones' as a prefix.
	return fmt.Sprintf("%s/instances/%s", Zone(), Name())
}

// NumericProjectID is the numeric project ID of the instance.
func NumericProjectID() int64 {
	return getAgentConfig().numericProjectID
}

// ProjectID is the project ID of the instance.
func ProjectID() string {
	return getAgentConfig().projectID
}

// Zone is the zone the instance is running in.
func Zone() string {
	return getAgentConfig().instanceZone
}

// Name is the instance name.
func Name() string {
	return getAgentConfig().instanceName
}

// ID is the instance id.
func ID() string {
	return getAgentConfig().instanceID
}

// GuestAttributesEnabled is a boolean flag that signal that guest attributes feature is enabled.
func GuestAttributesEnabled() bool {
	return getAgentConfig().guestAttributesEnabled
}

type idToken struct {
	exp *time.Time
	raw string
	sync.Mutex
}

func (t *idToken) get() error {
	data, err := metadata.Get(IdentityTokenPath)
	if err != nil {
		return fmt.Errorf("error getting token from metadata: %w", err)
	}

	cs, err := jws.Decode(data)
	if err != nil {
		return err
	}

	t.raw = data
	exp := time.Unix(cs.Exp, 0)
	t.exp = &exp

	return nil
}

var identity idToken

// IDToken is the instance id token.
func IDToken() (string, error) {
	identity.Lock()
	defer identity.Unlock()

	// Rerequest token if expiry is within 10 minutes.
	if identity.exp == nil || time.Now().After(identity.exp.Add(-10*time.Minute)) {
		if err := identity.get(); err != nil {
			return "", err
		}
	}

	return identity.raw, nil
}

// Version is the agent version.
func Version() string {
	return version
}

// SetVersion sets the agent version.
func SetVersion(v string) {
	version = v
}

// Capabilities returns the agents capabilities.
func Capabilities() []string {
	return capabilities
}

// TaskStateFile is the location of the task state file.
func TaskStateFile() string {
	if runtime.GOOS == "windows" {
		return filepath.Join(GetCacheDirWindows(), "osconfig_task.state")
	}

	return taskStateFileLinux
}

// OldTaskStateFile is the location of the task state file.
func OldTaskStateFile() string {
	if runtime.GOOS == "windows" {
		return oldTaskStateFileWindows
	}
	return oldTaskStateFileLinux
}

// RestartFile is the location of the restart required file.
func RestartFile() string {
	if runtime.GOOS == "windows" {
		return filepath.Join(
			GetCacheDirWindows(), "osconfig_agent_restart_required")
	}

	return restartFileLinux
}

// OldRestartFile is the location of the restart required file.
func OldRestartFile() string {
	return oldRestartFileLinux
}

// CacheDir is the location of the cache directory.
func CacheDir() string {
	if runtime.GOOS == "windows" {
		return GetCacheDirWindows()
	}

	return cacheDirLinux
}

// UserAgent for creating http/grpc clients.
func UserAgent() string {
	return "google-osconfig-agent/" + Version()
}

// DisableInventoryWrite returns true if the DisableInventoryWrite setting is set.
func DisableInventoryWrite() bool {
	return strings.EqualFold(disableInventoryWrite, "true") || disableInventoryWrite == "1"
}

// FreeOSMemory returns true if the FreeOSMemory setting is set.
func FreeOSMemory() bool {
	return strings.EqualFold(freeOSMemory, "true") || freeOSMemory == "1"
}

// DisableCloudLogging returns true if universe domain is not equal to GDU domain.
func DisableCloudLogging() bool {
	return !strings.EqualFold(getAgentConfig().universeDomain, universeDomainDefault)
}

// UniverseDomain is the cloud universe domain
func UniverseDomain() string {
	return getAgentConfig().universeDomain
}
