ospatch/zypper_patch.go (152 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 ospatch
import (
"context"
"fmt"
"github.com/GoogleCloudPlatform/osconfig/clog"
"github.com/GoogleCloudPlatform/osconfig/packages"
)
const zypper = "/usr/bin/zypper"
var (
zypperPatchArgs = []string{"patch", "-y"}
)
type zypperPatchOpts struct {
categories []string
severities []string
excludes []*Exclude
exclusivePatches []string
withOptional bool
withUpdate bool
dryrun bool
}
// ZypperPatchOption is an option for zypper patch.
type ZypperPatchOption func(*zypperPatchOpts)
// ZypperPatchCategories returns a ZypperUpdateOption that specifies what
// categories to add to the --categories flag.
func ZypperPatchCategories(categories []string) ZypperPatchOption {
return func(args *zypperPatchOpts) {
args.categories = categories
}
}
// ZypperPatchSeverities returns a ZypperUpdateOption that specifies what
// categories to add to the --categories flag.
func ZypperPatchSeverities(severities []string) ZypperPatchOption {
return func(args *zypperPatchOpts) {
args.severities = severities
}
}
// ZypperUpdateWithOptional returns a ZypperUpdateOption that specifies the
// --with-optional flag should be used.
func ZypperUpdateWithOptional(withOptional bool) ZypperPatchOption {
return func(args *zypperPatchOpts) {
args.withOptional = withOptional
}
}
// ZypperUpdateWithUpdate returns a ZypperUpdateOption that specifies the
// --with-update flag should be used.
func ZypperUpdateWithUpdate(withUpdate bool) ZypperPatchOption {
return func(args *zypperPatchOpts) {
args.withUpdate = withUpdate
}
}
// ZypperUpdateWithExcludes returns a ZypperUpdateOption that specifies
// list of packages to be excluded from update
func ZypperUpdateWithExcludes(excludes []*Exclude) ZypperPatchOption {
return func(args *zypperPatchOpts) {
args.excludes = excludes
}
}
// ZypperUpdateWithExclusivePatches returns a ZypperUpdateOption that specifies
// list of exclusive packages to be updated
func ZypperUpdateWithExclusivePatches(exclusivePatches []string) ZypperPatchOption {
return func(args *zypperPatchOpts) {
args.exclusivePatches = exclusivePatches
}
}
// ZypperUpdateDryrun returns a ZypperUpdateOption that specifies the runner.
func ZypperUpdateDryrun(dryrun bool) ZypperPatchOption {
return func(args *zypperPatchOpts) {
args.dryrun = dryrun
}
}
// RunZypperPatch runs zypper patch.
func RunZypperPatch(ctx context.Context, opts ...ZypperPatchOption) error {
zOpts := &zypperPatchOpts{
excludes: nil,
exclusivePatches: nil,
categories: nil,
severities: nil,
withOptional: false,
withUpdate: false,
}
for _, opt := range opts {
opt(zOpts)
}
zListOpts := []packages.ZypperListOption{
packages.ZypperListPatchCategories(zOpts.categories),
packages.ZypperListPatchSeverities(zOpts.severities),
packages.ZypperListPatchWithOptional(zOpts.withOptional),
// if there is no filter on category and severity,
// zypper fetches all available patch updates
}
patches, err := packages.ZypperPatches(ctx, zListOpts...)
if err != nil {
return err
}
// if user specifies, --with-update get the necessary patch/package
// information and then runfilter on them
var pkgToPatchesMap map[string][]string
var pkgUpdates []*packages.PkgInfo
if zOpts.withUpdate {
pkgUpdates, err = packages.ZypperUpdates(ctx)
if err != nil {
return err
}
pkgToPatchesMap, err = packages.ZypperPackagesInPatch(ctx, patches)
if err != nil {
return err
}
}
fPatches, fpkgs, err := runFilter(patches, zOpts.exclusivePatches, zOpts.excludes, pkgUpdates, pkgToPatchesMap, zOpts.withUpdate)
if len(fPatches) == 0 && len(fpkgs) == 0 {
clog.Infof(ctx, "No updates required.")
return nil
}
var ops opsToReport
if len(fPatches) == 0 {
clog.Infof(ctx, "No patches to install.")
} else {
msg := fmt.Sprintf("%d patches: %s", len(fPatches), formatPatches(fPatches))
if zOpts.dryrun {
clog.Infof(ctx, "Running in dryrun mode, not installing %s", msg)
} else {
ops.patches = fPatches
}
}
if len(fpkgs) == 0 {
clog.Infof(ctx, "No non-patch packages to update.")
} else {
msg := fmt.Sprintf("%d patches: %q", len(fpkgs), fpkgs)
if zOpts.dryrun {
clog.Infof(ctx, "Running in dryrun mode, not Updating %s", msg)
} else {
ops.packages = fpkgs
}
}
logOps(ctx, ops)
if zOpts.dryrun {
return nil
}
err = packages.ZypperInstall(ctx, fPatches, fpkgs)
if err == nil {
logSuccess(ctx, ops)
} else {
logFailure(ctx, ops, err)
}
return err
}
func runFilter(patches []*packages.ZypperPatch, exclusivePatches []string, excludes []*Exclude, pkgUpdates []*packages.PkgInfo, pkgToPatchesMap map[string][]string, withUpdate bool) ([]*packages.ZypperPatch, []*packages.PkgInfo, error) {
// exclusive patches
var fPatches []*packages.ZypperPatch
var fPkgs []*packages.PkgInfo
if len(exclusivePatches) > 0 {
for _, patch := range patches {
if containsString(exclusivePatches, patch.Name) {
fPatches = append(fPatches, patch)
}
}
return fPatches, fPkgs, nil
}
// if --with-update is specified, filter out the packages
// that will be updated as a part of a patch update
if withUpdate {
for _, pkg := range pkgUpdates {
if _, ok := pkgToPatchesMap[pkg.Name]; !ok {
fPkgs = append(fPkgs, pkg)
}
}
}
// we have the list of patches which is already filtered
// as per the configurations provided by user;
// we remove the excluded patches from the list
for _, patch := range patches {
// in zypper we're filtering patches instead of packages, but the method is still the same
if !shouldPackageBeExcluded(excludes, &patch.Name) {
fPatches = append(fPatches, patch)
}
}
return fPatches, fPkgs, nil
}