| // 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 |
| } |