packages/packages.go (131 lines of code) (raw):

// 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 packages provides package management functions for Windows and Linux // systems. package packages import ( "context" "fmt" "os/exec" "sort" "strings" "time" "github.com/GoogleCloudPlatform/osconfig/clog" "github.com/GoogleCloudPlatform/osconfig/osinfo" "github.com/GoogleCloudPlatform/osconfig/util" ) var ( // AptExists indicates whether apt is installed. AptExists bool // DpkgExists indicates whether dpkg is installed. DpkgExists bool // DpkgQueryExists indicates whether dpkg-query is installed. DpkgQueryExists bool // YumExists indicates whether yum is installed. YumExists bool // ZypperExists indicates whether zypper is installed. ZypperExists bool // RPMExists indicates whether rpm is installed. RPMExists bool // RPMQueryExists indicates whether rpmquery is installed. RPMQueryExists bool // COSPkgInfoExists indicates whether COS package information is available. COSPkgInfoExists bool // GemExists indicates whether gem is installed. GemExists bool // PipExists indicates whether pip is installed. PipExists bool // GooGetExists indicates whether googet is installed. GooGetExists bool // MSIExists indicates whether MSIs can be installed. MSIExists bool noarch = osinfo.NormalizeArchitecture("noarch") runner = util.CommandRunner(&util.DefaultRunner{}) ptyrunner = util.CommandRunner(&ptyRunner{}) ) // Packages is a selection of packages based on their manager. type Packages struct { Yum []*PkgInfo `json:"yum,omitempty"` Rpm []*PkgInfo `json:"rpm,omitempty"` Apt []*PkgInfo `json:"apt,omitempty"` Deb []*PkgInfo `json:"deb,omitempty"` Zypper []*PkgInfo `json:"zypper,omitempty"` ZypperPatches []*ZypperPatch `json:"zypperPatches,omitempty"` COS []*PkgInfo `json:"cos,omitempty"` Gem []*PkgInfo `json:"gem,omitempty"` Pip []*PkgInfo `json:"pip,omitempty"` GooGet []*PkgInfo `json:"googet,omitempty"` WUA []*WUAPackage `json:"wua,omitempty"` QFE []*QFEPackage `json:"qfe,omitempty"` WindowsApplication []*WindowsApplication `json:"-"` } // PkgInfo describes a package. type PkgInfo struct { Name, Arch, RawArch, Version string Source Source } // Source represents source package from which binary package was built. type Source struct { Name, Version string } func (i *PkgInfo) String() string { return fmt.Sprintf("%s %s %s", i.Name, i.Arch, i.Version) } // ZypperPatch describes a Zypper patch. type ZypperPatch struct { Name, Category, Severity, Summary string } // WUAPackage describes a Windows Update Agent package. type WUAPackage struct { LastDeploymentChangeTime time.Time Title string Description string SupportURL string UpdateID string Categories []string KBArticleIDs []string MoreInfoURLs []string CategoryIDs []string RevisionNumber int32 } // QFEPackage describes a Windows Quick Fix Engineering package. type QFEPackage struct { Caption, Description, HotFixID, InstalledOn string } // WindowsApplication describes a Windows Application. type WindowsApplication struct { DisplayName string DisplayVersion string InstallDate time.Time Publisher string HelpLink string } func run(ctx context.Context, cmd string, args []string) ([]byte, error) { stdout, stderr, err := runner.Run(ctx, exec.CommandContext(ctx, cmd, args...)) if err != nil { return nil, fmt.Errorf("error running %s with args %q: %v, stdout: %q, stderr: %q", cmd, args, err, stdout, stderr) } return stdout, nil } func runWithDeadline(ctx context.Context, timeout time.Duration, cmd string, args []string) ([]byte, error) { ctxWithTimeout, cancel := context.WithTimeout(ctx, timeout) defer cancel() return run(ctxWithTimeout, cmd, args) } func formatFieldsMappingToFormattingString(fieldsMapping map[string]string) string { fieldsDescriptors := make([]string, 0, len(fieldsMapping)) for name, selector := range fieldsMapping { // Format field name and its selector to one single entry separated by ":" and each of them wrapped in quotes // Examples: // name:source_name, selector:${source:Package -> ""source_name":"${source:Package}"". // name:source_name, selector:%{NAME} -> ""source_name":"%{NAME}"". fieldsDescriptors = append(fieldsDescriptors, fmt.Sprintf("\"%s\":\"%s\"", name, selector)) } // Sort descriptors to get predictable result. sort.Strings(fieldsDescriptors) // Returns string to format all information in json // Example: {"package":"${Package}","architecture":"${Architecture}","version":"${Version}","status":"${db:Status-Status}"...}\n // See dpkgInfoFieldsMapping for full set of fields. return "\\{" + strings.Join(fieldsDescriptors, ",") + "\\}\n" } type packageMetadata struct { Package string `json:"package"` Architecture string `json:"architecture"` Version string `json:"version"` Status string `json:"status"` SourceName string `json:"source_name"` SourceVersion string `json:"source_version"` } func pkgInfoFromPackageMetadata(pm packageMetadata) *PkgInfo { return &PkgInfo{ Name: pm.Package, Arch: osinfo.NormalizeArchitecture(pm.Architecture), Version: pm.Version, Source: Source{ Name: pm.SourceName, Version: pm.SourceVersion, }, } } type ptyRunner struct{} func (p *ptyRunner) Run(ctx context.Context, cmd *exec.Cmd) ([]byte, []byte, error) { clog.Debugf(ctx, "Running %q with args %q\n", cmd.Path, cmd.Args[1:]) stdout, stderr, err := runWithPty(cmd) clog.Debugf(ctx, "%s %q output:\n%s", cmd.Path, cmd.Args[1:], strings.ReplaceAll(string(stdout), "\n", "\n ")) return stdout, stderr, err } // SetCommandRunner allows external clients to set a custom commandRunner. func SetCommandRunner(commandRunner util.CommandRunner) { runner = commandRunner } // SetPtyCommandRunner allows external clients to set a custom // custom commandRunner. func SetPtyCommandRunner(commandRunner util.CommandRunner) { ptyrunner = commandRunner }