This repository has been archived by the owner on Dec 7, 2023. It is now read-only.
/
rpm.go
163 lines (139 loc) · 4.46 KB
/
rpm.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
package resource
import (
"context"
"fmt"
"strings"
"github.com/cavaliercoder/go-rpm/version"
"github.com/weaveworks/cluster-api-provider-existinginfra/pkg/plan"
)
// RPM represents an RPM package.
//
// It isn't legal to provide a Release if no Version is specified.
// TODO: What about epoch?
type RPM struct {
Name string `structs:"name"`
// Version is optional
Version string `structs:"version,omitempty"`
Release string `structs:"release,omitempty"`
IgnoreOtherVersions bool `structs:"ignoreOtherVersions,omitempty"`
DisableExcludes string `structs:"disableExcludes,omitempty"`
}
type rpmState plan.State
// Name implements version.Interface
func (s rpmState) Name() string {
if name, ok := s["name"]; ok {
return name.(string)
}
return ""
}
// Epoch implements version.Interface
func (s rpmState) Epoch() int {
return 0
}
// Version implements version.Interface
func (s rpmState) Version() string {
if version, ok := s["version"]; ok {
return version.(string)
}
return ""
}
// Release implements version.Interface
func (s rpmState) Release() string {
if release, ok := s["release"]; ok {
return release.(string)
}
return ""
}
var _ plan.Resource = plan.RegisterResource(&RPM{})
// State implements plan.Resource.
func (p *RPM) State() plan.State {
return ToState(p)
}
func lowerRevisionThan(state1, state2 plan.State) bool {
return version.Compare(rpmState(state1), rpmState(state2)) < 0
}
func label(name, version, release string) string {
if release != "" {
return fmt.Sprintf("%s-%s-%s", name, version, release)
}
if version != "" {
return fmt.Sprintf("%s-%s", name, version)
}
return name
}
func (p *RPM) label() string {
return label(p.Name, p.Version, p.Release)
}
// QueryState implements plan.Resource.
func (p *RPM) QueryState(ctx context.Context, r plan.Runner) (plan.State, error) {
output, err := r.RunCommand(ctx, fmt.Sprintf("rpm -q --queryformat '%%{NAME} %%{VERSION} %%{RELEASE}\\n' %s", p.label()), nil)
if err != nil && strings.Contains(output, "is not installed") {
// Package isn't installed.
return plan.EmptyState, nil
}
if err != nil {
// An error happened running rpm.
return plan.EmptyState, fmt.Errorf("query rpm %s failed: %v -- %s", p.label(), err, output)
}
// XXX: in theory rpm queries can return multiple versions of the same package
// if all of them are installed a the same. This shouldn't be a thing for the
// packages we query.
l := line(output)
parts := strings.Split(l, " ")
queriedPackage := &RPM{
Name: parts[0],
Version: parts[1],
Release: parts[2],
}
return queriedPackage.State(), nil
}
func (p *RPM) stateDifferent(current plan.State) bool {
if current.IsEmpty() {
return true
}
desired := p.label()
installed := label(current.String("name"), current.String("version"), current.String("release"))
return !strings.HasPrefix(installed, desired)
}
// WouldChangeState returns false if a call to Apply() is guaranteed not to change the installed version of the package, and true otherwise.
func (p *RPM) WouldChangeState(ctx context.Context, r plan.Runner) (bool, error) {
current, err := p.QueryState(ctx, r)
if err != nil {
return false, err
}
return p.stateDifferent(current), nil
}
// Apply implements plan.Resource.
func (p *RPM) Apply(ctx context.Context, r plan.Runner, diff plan.Diff) (bool, error) {
if !p.stateDifferent(diff.CurrentState) {
return false, nil
}
// First assume the package doesn't exist at all
var cmd string
switch {
case diff.CurrentState.IsEmpty():
cmd = fmt.Sprintf("yum -y install %s", p.label())
case lowerRevisionThan(diff.CurrentState, p.State()):
cmd = fmt.Sprintf("yum -y upgrade-to %s", p.label())
case lowerRevisionThan(p.State(), diff.CurrentState):
cmd = fmt.Sprintf("yum -y remove %s && yum -y install %s", p.Name, p.label())
}
if p.DisableExcludes != "" {
cmd = fmt.Sprintf("%s --disableexcludes %s", cmd, p.DisableExcludes)
}
_, err := r.RunCommand(ctx, cmd, nil)
return err == nil, err
}
// Separate the action out so that it can be mocked
var undoAction = func(ctx context.Context, p *RPM, r plan.Runner, current plan.State, pkgDescription string) error {
_, err := r.RunCommand(ctx, fmt.Sprintf("yum -y remove %s || true", pkgDescription), nil)
return err
}
// Undo implements plan.Resource
func (p *RPM) Undo(ctx context.Context, r plan.Runner, current plan.State) error {
pkgDescription := p.Name
if p.IgnoreOtherVersions {
pkgDescription = p.label()
}
return undoAction(ctx, p, r, current, pkgDescription)
}