<|start_filename|>packages/pip.go<|end_filename|>
//  Copyright 2019 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 packages

import (
	"context"
	"encoding/json"
	"runtime"

	"github.com/GoogleCloudPlatform/osconfig/util"
)

var (
	pip string

	pipListArgs     = []string{"list", "--format=json"}
	pipOutdatedArgs = append(pipListArgs, "--outdated")
)

func init() {
	if runtime.GOOS != "windows" {
		pip = "/usr/bin/pip"
	}
	PipExists = util.Exists(pip)
}

type pipUpdatesPkg struct {
	Name          string `json:"name"`
	LatestVersion string `json:"latest_version"`
}

type pipInstalledPkg struct {
	Name    string `json:"name"`
	Version string `json:"version"`
}

// PipUpdates queries for all available pip updates.
func PipUpdates(ctx context.Context) ([]*PkgInfo, error) {
	out, err := run(ctx, pip, pipOutdatedArgs)
	if err != nil {
		return nil, err
	}

	var pipUpdates []pipUpdatesPkg
	if err := json.Unmarshal(out, &pipUpdates); err != nil {
		return nil, err
	}

	var pkgs []*PkgInfo
	for _, pkg := range pipUpdates {
		pkgs = append(pkgs, &PkgInfo{Name: pkg.Name, Arch: noarch, Version: pkg.LatestVersion})
	}

	return pkgs, nil
}

// InstalledPipPackages queries for all installed pip packages.
func InstalledPipPackages(ctx context.Context) ([]*PkgInfo, error) {
	out, err := run(ctx, pip, pipListArgs)
	if err != nil {
		return nil, err
	}

	var pipUpdates []pipInstalledPkg
	if err := json.Unmarshal(out, &pipUpdates); err != nil {
		return nil, err
	}

	var pkgs []*PkgInfo
	for _, pkg := range pipUpdates {
		pkgs = append(pkgs, &PkgInfo{Name: pkg.Name, Arch: noarch, Version: pkg.Version})
	}

	return pkgs, nil
}


<|start_filename|>inventory/inventory.go<|end_filename|>
//  Copyright 2017 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 inventory scans the current inventory (patches and package installed and available)
// and writes them to Guest Attributes.
package inventory

import (
	"context"
	"time"

	"github.com/GoogleCloudPlatform/osconfig/agentconfig"
	"github.com/GoogleCloudPlatform/osconfig/clog"
	"github.com/GoogleCloudPlatform/osconfig/osinfo"
	"github.com/GoogleCloudPlatform/osconfig/packages"
)

// InstanceInventory is an instances inventory data.
type InstanceInventory struct {
	Hostname             string
	LongName             string
	ShortName            string
	Version              string
	Architecture         string
	KernelVersion        string
	KernelRelease        string
	OSConfigAgentVersion string
	InstalledPackages    *packages.Packages
	PackageUpdates       *packages.Packages
	LastUpdated          string
}

// Get generates inventory data.
func Get(ctx context.Context) *InstanceInventory {
	clog.Debugf(ctx, "Gathering instance inventory.")

	installedPackages, err := packages.GetInstalledPackages(ctx)
	if err != nil {
		clog.Errorf(ctx, "packages.GetInstalledPackages() error: %v", err)
	}

	packageUpdates, err := packages.GetPackageUpdates(ctx)
	if err != nil {
		clog.Errorf(ctx, "packages.GetPackageUpdates() error: %v", err)
	}

	oi, err := osinfo.Get()
	if err != nil {
		clog.Errorf(ctx, "osinfo.Get() error: %v", err)
	}

	return &InstanceInventory{
		Hostname:             oi.Hostname,
		LongName:             oi.LongName,
		ShortName:            oi.ShortName,
		Version:              oi.Version,
		KernelVersion:        oi.KernelVersion,
		KernelRelease:        oi.KernelRelease,
		Architecture:         oi.Architecture,
		OSConfigAgentVersion: agentconfig.Version(),
		InstalledPackages:    installedPackages,
		PackageUpdates:       packageUpdates,
		LastUpdated:          time.Now().UTC().Format(time.RFC3339),
	}
}


<|start_filename|>packages/gem.go<|end_filename|>
//  Copyright 2019 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 packages

import (
	"context"
	"os/exec"
	"runtime"
	"strings"

	"github.com/GoogleCloudPlatform/osconfig/clog"
	"github.com/GoogleCloudPlatform/osconfig/util"
)

var (
	gem string

	gemListArgs     = []string{"list", "--local"}
	gemOutdatedArgs = []string{"outdated", "--local"}
)

func init() {
	if runtime.GOOS != "windows" {
		gem = "/usr/bin/gem"
	}
	GemExists = util.Exists(gem)
}

// GemUpdates queries for all available gem updates.
func GemUpdates(ctx context.Context) ([]*PkgInfo, error) {
	stdout, _, err := runner.Run(ctx, exec.CommandContext(ctx, gem, gemOutdatedArgs...))
	if err != nil {
		return nil, err
	}
	/*
	   foo (1.2.8 < 1.3.2)
	   bar (1.0.0 < 1.1.2)
	   ...
	*/

	lines := strings.Split(strings.TrimSpace(string(stdout)), "\n")
	if len(lines) == 0 {
		return nil, nil
	}

	var pkgs []*PkgInfo
	for _, ln := range lines {
		pkg := strings.Fields(ln)
		if len(pkg) != 4 {
			clog.Debugf(ctx, "%q does not represent a gem update\n", ln)
			continue
		}
		ver := strings.Trim(pkg[3], ")")
		pkgs = append(pkgs, &PkgInfo{Name: pkg[0], Arch: noarch, Version: ver})
	}
	return pkgs, nil
}

// InstalledGemPackages queries for all installed gem packages.
func InstalledGemPackages(ctx context.Context) ([]*PkgInfo, error) {
	stdout, _, err := runner.Run(ctx, exec.CommandContext(ctx, gem, gemListArgs...))
	if err != nil {
		return nil, err
	}

	/*

	   *** LOCAL GEMS ***

	   foo (1.2.3, 1.2.4)
	   bar (1.2.3)
	   ...
	*/
	lines := strings.Split(strings.TrimSpace(string(stdout)), "\n")

	if len(lines) == 0 {
		clog.Debugf(ctx, "No gems installed.")
		return nil, nil
	}

	var pkgs []*PkgInfo
	for _, ln := range lines[2:] {
		pkg := strings.Fields(ln)
		if len(pkg) < 2 {
			clog.Debugf(ctx, "'%s' does not represent a gem", ln)
			continue
		}
		for _, ver := range strings.Split(strings.Trim(pkg[1], "()"), ", ") {
			pkgs = append(pkgs, &PkgInfo{Name: pkg[0], Arch: noarch, Version: ver})
		}
	}
	return pkgs, nil
}


<|start_filename|>ospatch/updates_test.go<|end_filename|>
//  Copyright 2019 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 ospatch

import (
	"io/ioutil"
	"os"
	"testing"
)

func TestGetBtime(t *testing.T) {
	tests := []struct {
		name    string
		in      string
		want    int64
		wantErr bool
	}{
		{"NormalCase", "procs_running 2\nprocs_blocked 0\nctxt 22762852599\nbtime 1561478350\nprocesses 15504510", 1561478350, false},
		{"NoBtime", "procs_running 2\nprocs_blocked 0\nctxt 22762852599\nprocesses 15504510", 0, true},
		{"CantParseInt", "procs_running 2\nprocs_blocked 0\nctxt 22762852599\nbtime notanint\nprocesses 15504510", 0, true},
		{"CantParseLine", "procs_running 2\nprocs_blocked 0\nctxt 22762852599\nbtime1561478350\nprocesses 15504510", 0, true},
	}
	for _, tt := range tests {
		td, err := ioutil.TempDir(os.TempDir(), "")
		if err != nil {
			t.Fatalf("error creating temp dir: %v", err)
		}
		defer os.RemoveAll(td)
		t.Run(tt.name, func(t *testing.T) {
			f, err := ioutil.TempFile(td, "")
			if err != nil {
				t.Fatalf("error creating temp file: %v", err)
			}
			if _, err := f.Write([]byte(tt.in)); err != nil {
				t.Fatalf("error writing temp file: %v", err)
			}
			if err := f.Close(); err != nil {
				t.Fatalf("error writing temp file: %v", err)
			}

			got, err := getBtime(f.Name())
			if (err != nil) != tt.wantErr {
				t.Errorf("getBtime() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if got != tt.want {
				t.Errorf("getBtime() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestRpmRebootRequired(t *testing.T) {
	type args struct {
		pkgs  []byte
		btime int64
	}
	tests := []struct {
		name string
		args args
		want bool
	}{
		{"RebootRequired", args{[]byte("1\n3\n2\n6"), 5}, true},
		{"NoRebootRequired", args{[]byte("1\n3\n2\n5"), 5}, false},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := rpmRebootRequired(tt.args.pkgs, tt.args.btime)
			if got != tt.want {
				t.Errorf("rpmRebootRequired() = %v, want %v", got, tt.want)
			}
		})
	}
}


<|start_filename|>config/exec_resource_test.go<|end_filename|>
//  Copyright 2021 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 config

import (
	"context"
	"io/ioutil"
	"math/rand"
	"os"
	"path"
	"path/filepath"
	"reflect"
	"testing"

	agentendpointpb "google.golang.org/genproto/googleapis/cloud/osconfig/agentendpoint/v1"
)

func TestExecResourceDownload(t *testing.T) {
	ctx := context.Background()

	var tests = []struct {
		name                 string
		erpb                 *agentendpointpb.OSPolicy_Resource_ExecResource
		wantValidatePath     string
		wantValidateContents string
		wantEnforcePath      string
		wantEnforceContents  string
		goos                 string
	}{
		{
			"Script NONE Linux",
			&agentendpointpb.OSPolicy_Resource_ExecResource{
				Validate: &agentendpointpb.OSPolicy_Resource_ExecResource_Exec{
					Source:      &agentendpointpb.OSPolicy_Resource_ExecResource_Exec_Script{Script: "validate"},
					Interpreter: agentendpointpb.OSPolicy_Resource_ExecResource_Exec_NONE,
				},
				Enforce: &agentendpointpb.OSPolicy_Resource_ExecResource_Exec{
					Source:      &agentendpointpb.OSPolicy_Resource_ExecResource_Exec_Script{Script: "enforce"},
					Interpreter: agentendpointpb.OSPolicy_Resource_ExecResource_Exec_NONE,
				},
			},
			"script",
			"validate",
			"script",
			"enforce",
			"linux",
		},
		{
			"Script NONE Windows",
			&agentendpointpb.OSPolicy_Resource_ExecResource{
				Validate: &agentendpointpb.OSPolicy_Resource_ExecResource_Exec{
					Source:      &agentendpointpb.OSPolicy_Resource_ExecResource_Exec_Script{Script: "validate"},
					Interpreter: agentendpointpb.OSPolicy_Resource_ExecResource_Exec_NONE,
				},
				Enforce: &agentendpointpb.OSPolicy_Resource_ExecResource_Exec{
					Source:      &agentendpointpb.OSPolicy_Resource_ExecResource_Exec_Script{Script: "enforce"},
					Interpreter: agentendpointpb.OSPolicy_Resource_ExecResource_Exec_NONE,
				},
			},
			"script.cmd",
			"validate",
			"script.cmd",
			"enforce",
			"windows",
		},
		{
			"Script SHELL Linux",
			&agentendpointpb.OSPolicy_Resource_ExecResource{
				Validate: &agentendpointpb.OSPolicy_Resource_ExecResource_Exec{
					Source:      &agentendpointpb.OSPolicy_Resource_ExecResource_Exec_Script{Script: "validate"},
					Interpreter: agentendpointpb.OSPolicy_Resource_ExecResource_Exec_SHELL,
				},
				Enforce: &agentendpointpb.OSPolicy_Resource_ExecResource_Exec{
					Source:      &agentendpointpb.OSPolicy_Resource_ExecResource_Exec_Script{Script: "enforce"},
					Interpreter: agentendpointpb.OSPolicy_Resource_ExecResource_Exec_SHELL,
				},
			},
			"script.sh",
			"validate",
			"script.sh",
			"enforce",
			"linux",
		},
		{
			"Script SHELL Windows",
			&agentendpointpb.OSPolicy_Resource_ExecResource{
				Validate: &agentendpointpb.OSPolicy_Resource_ExecResource_Exec{
					Source:      &agentendpointpb.OSPolicy_Resource_ExecResource_Exec_Script{Script: "validate"},
					Interpreter: agentendpointpb.OSPolicy_Resource_ExecResource_Exec_SHELL,
				},
				Enforce: &agentendpointpb.OSPolicy_Resource_ExecResource_Exec{
					Source:      &agentendpointpb.OSPolicy_Resource_ExecResource_Exec_Script{Script: "enforce"},
					Interpreter: agentendpointpb.OSPolicy_Resource_ExecResource_Exec_SHELL,
				},
			},
			"script.cmd",
			"validate",
			"script.cmd",
			"enforce",
			"windows",
		},
		{
			"Script POWERSHELL Windows",
			&agentendpointpb.OSPolicy_Resource_ExecResource{
				Validate: &agentendpointpb.OSPolicy_Resource_ExecResource_Exec{
					Source:      &agentendpointpb.OSPolicy_Resource_ExecResource_Exec_Script{Script: "validate"},
					Interpreter: agentendpointpb.OSPolicy_Resource_ExecResource_Exec_POWERSHELL,
				},
				Enforce: &agentendpointpb.OSPolicy_Resource_ExecResource_Exec{
					Source:      &agentendpointpb.OSPolicy_Resource_ExecResource_Exec_Script{Script: "enforce"},
					Interpreter: agentendpointpb.OSPolicy_Resource_ExecResource_Exec_POWERSHELL,
				},
			},
			"script.ps1",
			"validate",
			"script.ps1",
			"enforce",
			"windows",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			goos = tt.goos
			pr := &OSPolicyResource{
				OSPolicy_Resource: &agentendpointpb.OSPolicy_Resource{
					ResourceType: &agentendpointpb.OSPolicy_Resource_Exec{
						Exec: tt.erpb,
					},
				},
			}
			defer pr.Cleanup(ctx)

			if err := pr.Validate(ctx); err != nil {
				t.Fatalf("Unexpected error: %v", err)
			}
			resource := pr.resource.(*execResource)

			if tt.wantValidatePath != path.Base(resource.validatePath) {
				t.Errorf("unexpected validate path: %q", resource.validatePath)
			}
			data, err := ioutil.ReadFile(resource.validatePath)
			if err != nil {
				t.Fatal(err)
			}
			if tt.wantValidateContents != string(data) {
				t.Errorf("unexpected validate contents: %q", data)
			}

			if tt.wantEnforcePath != path.Base(resource.enforcePath) {
				t.Errorf("unexpected enforce path: %q", resource.enforcePath)
			}
			data, err = ioutil.ReadFile(resource.enforcePath)
			if err != nil {
				t.Fatal(err)
			}
			if tt.wantEnforceContents != string(data) {
				t.Errorf("unexpected enforce contents: %q", data)
			}
		})
	}
}

func TestExecOutput(t *testing.T) {
	ctx := context.Background()
	tmpDir, err := ioutil.TempDir("", "")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(tmpDir)

	fileA := filepath.Join(tmpDir, "fileA")
	contentsA := []byte("here is some text\nand some more\n")
	if err := ioutil.WriteFile(fileA, contentsA, 0600); err != nil {
		t.Fatal(err)
	}

	fileB := filepath.Join(tmpDir, "fileB")
	contentsB := make([]byte, maxExecOutputSize*2)
	if _, err := rand.Read(contentsB); err != nil {
		t.Fatal(err)
	}
	if err := ioutil.WriteFile(fileB, contentsB, 0600); err != nil {
		t.Fatal(err)
	}

	var tests = []struct {
		name     string
		filePath string
		want     []byte
		wantErr  bool
	}{
		{
			"empty path",
			"",
			nil,
			false,
		},
		{
			"path DNE",
			"DNE",
			nil,
			true,
		},
		{
			"normal case",
			fileA,
			contentsA,
			false,
		},
		{
			"file to large case",
			fileB,
			contentsB[:maxExecOutputSize],
			true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := execOutput(ctx, tt.filePath)
			if err != nil && !tt.wantErr {
				t.Errorf("Unexpected error from execOutput: %v", err)
			}
			if err == nil && tt.wantErr {
				t.Error("Did not get expected error from execOutput")
			}

			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("got != want, string(got) = %q string(want) = %q", got, tt.want)
			}
		})
	}
}


<|start_filename|>config/config.go<|end_filename|>
//  Copyright 2020 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 config

import (
	"context"
	"errors"
	"fmt"
	"runtime"

	agentendpointpb "google.golang.org/genproto/googleapis/cloud/osconfig/agentendpoint/v1"
)

var goos = runtime.GOOS

// OSPolicyResource is a single OSPolicy resource.
type OSPolicyResource struct {
	resource
	*agentendpointpb.OSPolicy_Resource

	managedResources *ManagedResources
	inDesiredState   bool
}

// InDesiredState reports whether this resource is in the desired state.
// CheckState or EnforceState should be run prior to calling InDesiredState.
func (r *OSPolicyResource) InDesiredState() bool {
	return r.inDesiredState
}

// ManagedResources returns the resources that this OSPolicyResource manages.
func (r *OSPolicyResource) ManagedResources() *ManagedResources {
	return r.managedResources
}

type resource interface {
	validate(context.Context) (*ManagedResources, error)
	checkState(context.Context) (bool, error)
	enforceState(context.Context) (bool, error)
	populateOutput(*agentendpointpb.OSPolicyResourceCompliance)
	cleanup(context.Context) error
}

// ManagedResources are the resources that an OSPolicyResource manages.
type ManagedResources struct {
	Packages     []ManagedPackage
	Repositories []ManagedRepository
	Files        []ManagedFile
}

// Validate validates this resource.
// Validate must be called before other methods.
func (r *OSPolicyResource) Validate(ctx context.Context) error {
	switch x := r.GetResourceType().(type) {
	case *agentendpointpb.OSPolicy_Resource_Pkg:
		r.resource = resource(&packageResouce{OSPolicy_Resource_PackageResource: x.Pkg})
	case *agentendpointpb.OSPolicy_Resource_Repository:
		r.resource = resource(&repositoryResource{OSPolicy_Resource_RepositoryResource: x.Repository})
	case *agentendpointpb.OSPolicy_Resource_File_:
		r.resource = resource(&fileResource{OSPolicy_Resource_FileResource: x.File})
	case *agentendpointpb.OSPolicy_Resource_Exec:
		r.resource = resource(&execResource{OSPolicy_Resource_ExecResource: x.Exec})

	case nil:
		return errors.New("ResourceType field not set")
	default:
		return fmt.Errorf("ResourceType has unexpected type: %T", x)
	}

	var err error
	r.managedResources, err = r.validate(ctx)
	return err
}

// CheckState checks this resources state.
// Validate must be called prior to running CheckState.
func (r *OSPolicyResource) CheckState(ctx context.Context) error {
	if r.resource == nil {
		return errors.New("CheckState run before Validate")
	}

	inDesiredState, err := r.checkState(ctx)
	r.inDesiredState = inDesiredState
	return err
}

// EnforceState enforces this resources state.
// Validate must be called prior to running EnforceState.
func (r *OSPolicyResource) EnforceState(ctx context.Context) error {
	if r.resource == nil {
		return errors.New("EnforceState run before Validate")
	}

	inDesiredState, err := r.enforceState(ctx)
	r.inDesiredState = inDesiredState
	return err
}

// PopulateOutput populates the output field of the provided
// OSPolicyResourceCompliance for this resource.
func (r *OSPolicyResource) PopulateOutput(rCompliance *agentendpointpb.OSPolicyResourceCompliance) error {
	if r.resource == nil {
		return errors.New("PopulateOutput run before Validate")
	}
	r.populateOutput(rCompliance)
	return nil
}

// Cleanup cleans up any temporary files that this resource may have created.
func (r *OSPolicyResource) Cleanup(ctx context.Context) error {
	if r.resource == nil {
		return errors.New("Cleanup run before Validate")
	}
	return r.cleanup(ctx)
}
