policies/local.go (103 lines of code) (raw):
// 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 policies
import (
"context"
"encoding/json"
"cloud.google.com/go/compute/metadata"
"github.com/GoogleCloudPlatform/osconfig/clog"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
"cloud.google.com/go/osconfig/agentendpoint/apiv1beta/agentendpointpb"
)
// localConfig represents the structure of the config to the JSON parser.
//
// The types of members of the struct are wrappers for protobufs and delegate
// the parsing to protojson lib via their UnmarshalJSON implementations.
type localConfig struct {
Packages []*pkg
PackageRepositories []*packageRepository
SoftwareRecipes []*softwareRecipe
}
type pkg struct {
agentendpointpb.Package
}
func (r *pkg) UnmarshalJSON(b []byte) error {
un := &protojson.UnmarshalOptions{AllowPartial: true, DiscardUnknown: true}
return un.Unmarshal(b, &r.Package)
}
type packageRepository struct {
agentendpointpb.PackageRepository
}
func (r *packageRepository) UnmarshalJSON(b []byte) error {
un := &protojson.UnmarshalOptions{AllowPartial: true, DiscardUnknown: true}
return un.Unmarshal(b, &r.PackageRepository)
}
type softwareRecipe struct {
agentendpointpb.SoftwareRecipe
}
func (r *softwareRecipe) UnmarshalJSON(b []byte) error {
un := &protojson.UnmarshalOptions{AllowPartial: true, DiscardUnknown: true}
return un.Unmarshal(b, &r.SoftwareRecipe)
}
func readLocalConfig(ctx context.Context) (*localConfig, error) {
s, err := metadata.Get("/instance/attributes/gce-software-declaration")
if err != nil {
clog.Debugf(ctx, "No local config: %v", err)
return nil, nil
}
var lc localConfig
return &lc, json.Unmarshal([]byte(s), &lc)
}
// GetId returns a repository Id that is used to group repositories for
// override by higher priotiry policy(-ies).
// For repositories that have no such Id, GetId returns "", in which
// case the repository is never overridden.
func getID(repo *agentendpointpb.PackageRepository) string {
switch repo.Repository.(type) {
case *agentendpointpb.PackageRepository_Yum:
return "yum-" + repo.GetYum().GetId()
case *agentendpointpb.PackageRepository_Zypper:
return "zypper-" + repo.GetZypper().GetId()
default:
return ""
}
}
// mergeConfigs merges the local config with the lookup response, giving priority to the lookup
// result. If both arguments are nil, returns an empty policy.
func mergeConfigs(local *localConfig, egp *agentendpointpb.EffectiveGuestPolicy) *agentendpointpb.EffectiveGuestPolicy {
if egp == nil {
egp = &agentendpointpb.EffectiveGuestPolicy{}
}
if local == nil {
return egp
}
// Ids that are in the maps below
repos := make(map[string]bool)
pkgs := make(map[string]bool)
recipes := make(map[string]bool)
for _, v := range egp.GetPackages() {
pkgs[v.Package.Name] = true
}
for _, v := range egp.GetPackageRepositories() {
if id := getID(v.GetPackageRepository()); id != "" {
repos[id] = true
}
}
for _, v := range egp.GetSoftwareRecipes() {
recipes[v.SoftwareRecipe.Name] = true
}
for _, v := range local.Packages {
if _, ok := pkgs[v.Name]; !ok {
sp := new(agentendpointpb.EffectiveGuestPolicy_SourcedPackage)
sp.Package = &v.Package
egp.Packages = append(egp.Packages, sp)
}
}
for _, v := range local.PackageRepositories {
id := getID(&v.PackageRepository)
if id != "" {
if _, ok := repos[id]; ok {
continue
}
}
sr := new(agentendpointpb.EffectiveGuestPolicy_SourcedPackageRepository)
sr.PackageRepository = &v.PackageRepository
egp.PackageRepositories = append(egp.PackageRepositories, sr)
}
for _, v := range local.SoftwareRecipes {
if _, ok := recipes[v.Name]; !ok {
sp := new(agentendpointpb.EffectiveGuestPolicy_SourcedSoftwareRecipe)
sp.SoftwareRecipe = proto.Clone(&v.SoftwareRecipe).(*agentendpointpb.SoftwareRecipe)
egp.SoftwareRecipes = append(egp.SoftwareRecipes, sp)
}
}
return egp
}