policies/policies.go (200 lines of code) (raw):
// 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 policies configures OS Guest Policies based on osconfig API response.
package policies
import (
"bytes"
"context"
"crypto/sha256"
"hash"
"io"
"os"
"time"
"github.com/GoogleCloudPlatform/osconfig/agentconfig"
"github.com/GoogleCloudPlatform/osconfig/agentendpoint"
"github.com/GoogleCloudPlatform/osconfig/clog"
"github.com/GoogleCloudPlatform/osconfig/packages"
"github.com/GoogleCloudPlatform/osconfig/policies/recipes"
"github.com/GoogleCloudPlatform/osconfig/retryutil"
"github.com/GoogleCloudPlatform/osconfig/tasker"
"github.com/GoogleCloudPlatform/osconfig/util"
"cloud.google.com/go/osconfig/agentendpoint/apiv1beta/agentendpointpb"
)
func run(ctx context.Context) {
var resp *agentendpointpb.EffectiveGuestPolicy
client, err := agentendpoint.NewBetaClient(ctx)
if err != nil {
clog.Errorf(ctx, "agentendpoint.NewBetaClient Error: %v", err)
} else {
defer client.Close()
resp, err = client.LookupEffectiveGuestPolicies(ctx)
if err != nil {
clog.Errorf(ctx, "Error running LookupEffectiveGuestPolicies: %v", err)
}
}
local, err := readLocalConfig(ctx)
if err != nil {
clog.Errorf(ctx, "Error reading local software config: %v", err)
}
effective := mergeConfigs(local, resp)
// We don't check the error from setConfig or installRecipes as all errors are already logged.
setConfig(ctx, effective)
installRecipes(ctx, effective)
}
// Run looks up osconfigs and applies them using tasker.Enqueue.
func Run(ctx context.Context) {
tasker.Enqueue(ctx, "Run GuestPolicies", func() { run(ctx) })
}
func installRecipes(ctx context.Context, egp *agentendpointpb.EffectiveGuestPolicy) error {
for _, recipe := range egp.GetSoftwareRecipes() {
if r := recipe.GetSoftwareRecipe(); r != nil {
if err := recipes.InstallRecipe(ctx, r); err != nil {
clog.Errorf(ctx, "Error installing recipe: %v", err)
}
}
}
return nil
}
func setConfig(ctx context.Context, egp *agentendpointpb.EffectiveGuestPolicy) {
var aptRepos []*agentendpointpb.AptRepository
var yumRepos []*agentendpointpb.YumRepository
var zypperRepos []*agentendpointpb.ZypperRepository
var gooRepos []*agentendpointpb.GooRepository
for _, repo := range egp.GetPackageRepositories() {
if r := repo.GetPackageRepository().GetGoo(); r != nil {
gooRepos = append(gooRepos, r)
continue
}
if r := repo.GetPackageRepository().GetApt(); r != nil {
aptRepos = append(aptRepos, r)
continue
}
if r := repo.GetPackageRepository().GetYum(); r != nil {
yumRepos = append(yumRepos, r)
continue
}
if r := repo.GetPackageRepository().GetZypper(); r != nil {
zypperRepos = append(zypperRepos, r)
continue
}
}
var gooInstallPkgs, gooRemovePkgs, gooUpdatePkgs []*agentendpointpb.Package
var aptInstallPkgs, aptRemovePkgs, aptUpdatePkgs []*agentendpointpb.Package
var yumInstallPkgs, yumRemovePkgs, yumUpdatePkgs []*agentendpointpb.Package
var zypperInstallPkgs, zypperRemovePkgs, zypperUpdatePkgs []*agentendpointpb.Package
for _, pkg := range egp.GetPackages() {
switch pkg.GetPackage().GetManager() {
case agentendpointpb.Package_ANY, agentendpointpb.Package_MANAGER_UNSPECIFIED:
switch pkg.GetPackage().GetDesiredState() {
case agentendpointpb.DesiredState_INSTALLED, agentendpointpb.DesiredState_DESIRED_STATE_UNSPECIFIED:
gooInstallPkgs = append(gooInstallPkgs, pkg.GetPackage())
aptInstallPkgs = append(aptInstallPkgs, pkg.GetPackage())
yumInstallPkgs = append(yumInstallPkgs, pkg.GetPackage())
zypperInstallPkgs = append(zypperInstallPkgs, pkg.GetPackage())
case agentendpointpb.DesiredState_REMOVED:
gooRemovePkgs = append(gooRemovePkgs, pkg.GetPackage())
aptRemovePkgs = append(aptRemovePkgs, pkg.GetPackage())
yumRemovePkgs = append(yumRemovePkgs, pkg.GetPackage())
zypperRemovePkgs = append(zypperRemovePkgs, pkg.GetPackage())
case agentendpointpb.DesiredState_UPDATED:
gooUpdatePkgs = append(gooUpdatePkgs, pkg.GetPackage())
aptUpdatePkgs = append(aptUpdatePkgs, pkg.GetPackage())
yumUpdatePkgs = append(yumUpdatePkgs, pkg.GetPackage())
zypperUpdatePkgs = append(zypperUpdatePkgs, pkg.GetPackage())
}
case agentendpointpb.Package_GOO:
switch pkg.GetPackage().GetDesiredState() {
case agentendpointpb.DesiredState_INSTALLED, agentendpointpb.DesiredState_DESIRED_STATE_UNSPECIFIED:
gooInstallPkgs = append(gooInstallPkgs, pkg.GetPackage())
case agentendpointpb.DesiredState_REMOVED:
gooRemovePkgs = append(gooRemovePkgs, pkg.GetPackage())
case agentendpointpb.DesiredState_UPDATED:
gooUpdatePkgs = append(gooUpdatePkgs, pkg.GetPackage())
}
case agentendpointpb.Package_APT:
switch pkg.GetPackage().GetDesiredState() {
case agentendpointpb.DesiredState_INSTALLED, agentendpointpb.DesiredState_DESIRED_STATE_UNSPECIFIED:
aptInstallPkgs = append(aptInstallPkgs, pkg.GetPackage())
case agentendpointpb.DesiredState_REMOVED:
aptRemovePkgs = append(aptRemovePkgs, pkg.GetPackage())
case agentendpointpb.DesiredState_UPDATED:
aptUpdatePkgs = append(aptUpdatePkgs, pkg.GetPackage())
}
case agentendpointpb.Package_YUM:
switch pkg.GetPackage().GetDesiredState() {
case agentendpointpb.DesiredState_INSTALLED, agentendpointpb.DesiredState_DESIRED_STATE_UNSPECIFIED:
yumInstallPkgs = append(yumInstallPkgs, pkg.GetPackage())
case agentendpointpb.DesiredState_REMOVED:
yumRemovePkgs = append(yumRemovePkgs, pkg.GetPackage())
case agentendpointpb.DesiredState_UPDATED:
yumUpdatePkgs = append(yumUpdatePkgs, pkg.GetPackage())
}
case agentendpointpb.Package_ZYPPER:
switch pkg.GetPackage().GetDesiredState() {
case agentendpointpb.DesiredState_INSTALLED, agentendpointpb.DesiredState_DESIRED_STATE_UNSPECIFIED:
zypperInstallPkgs = append(zypperInstallPkgs, pkg.GetPackage())
case agentendpointpb.DesiredState_REMOVED:
zypperRemovePkgs = append(zypperRemovePkgs, pkg.GetPackage())
case agentendpointpb.DesiredState_UPDATED:
zypperUpdatePkgs = append(zypperUpdatePkgs, pkg.GetPackage())
}
}
}
if packages.GooGetExists {
if err := googetRepositories(ctx, gooRepos, agentconfig.GooGetRepoFilePath()); err != nil {
clog.Errorf(ctx, "Error writing googet repo file: %v", err)
}
if err := retryutil.RetryFunc(ctx, 1*time.Minute, "Applying googet changes", func() error {
return googetChanges(ctx, gooInstallPkgs, gooRemovePkgs, gooUpdatePkgs)
}); err != nil {
clog.Errorf(ctx, "Error performing googet changes: %v", err)
}
}
if packages.AptExists {
if err := aptRepositories(ctx, aptRepos, agentconfig.AptRepoFilePath()); err != nil {
clog.Errorf(ctx, "Error writing apt repo file: %v", err)
}
if err := retryutil.RetryFunc(ctx, 1*time.Minute, "Applying apt changes", func() error {
return aptChanges(ctx, aptInstallPkgs, aptRemovePkgs, aptUpdatePkgs)
}); err != nil {
clog.Errorf(ctx, "Error performing apt changes: %v", err)
}
}
if packages.YumExists {
if err := yumRepositories(ctx, yumRepos, agentconfig.YumRepoFilePath()); err != nil {
clog.Errorf(ctx, "Error writing yum repo file: %v", err)
}
if err := retryutil.RetryFunc(ctx, 1*time.Minute, "Applying yum changes", func() error {
return yumChanges(ctx, yumInstallPkgs, yumRemovePkgs, yumUpdatePkgs)
}); err != nil {
clog.Errorf(ctx, "Error performing yum changes: %v", err)
}
}
if packages.ZypperExists {
if err := zypperRepositories(ctx, zypperRepos, agentconfig.ZypperRepoFilePath()); err != nil {
clog.Errorf(ctx, "Error writing zypper repo file: %v", err)
}
if err := retryutil.RetryFunc(ctx, 1*time.Minute, "Applying zypper changes.", func() error {
return zypperChanges(ctx, zypperInstallPkgs, zypperRemovePkgs, zypperUpdatePkgs)
}); err != nil {
clog.Errorf(ctx, "Error performing zypper changes: %v", err)
}
}
}
func checksum(r io.Reader) hash.Hash {
hash := sha256.New()
io.Copy(hash, r)
return hash
}
func writeIfChanged(ctx context.Context, content []byte, path string) error {
file, err := os.Open(path)
if err != nil && !os.IsNotExist(err) {
return err
}
if !os.IsNotExist(err) {
reader := bytes.NewReader(content)
h1 := checksum(reader)
h2 := checksum(file)
file.Close()
if bytes.Equal(h1.Sum(nil), h2.Sum(nil)) {
return nil
}
}
clog.Infof(ctx, "Writing repo file %s with updated contents", path)
return util.AtomicWrite(path, content, 0644)
}