istio

Форк
0
231 строка · 6.0 Кб
1
// Copyright Istio Authors
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//     http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14

15
package progress
16

17
import (
18
	"fmt"
19
	"io"
20
	"sort"
21
	"strings"
22
	"sync"
23

24
	"github.com/cheggaaa/pb/v3"
25

26
	"istio.io/istio/operator/pkg/name"
27
)
28

29
type InstallState int
30

31
const (
32
	StateInstalling InstallState = iota
33
	StatePruning
34
	StateComplete
35
	StateUninstallComplete
36
)
37

38
// Log records the progress of an installation
39
// This aims to provide information about the install of multiple components in parallel, while working
40
// around the limitations of the pb library, which will only support single lines. To do this, we aggregate
41
// the current components into a single line, and as components complete there final state is persisted to a new line.
42
type Log struct {
43
	components map[string]*ManifestLog
44
	bar        *pb.ProgressBar
45
	template   string
46
	mu         sync.Mutex
47
	state      InstallState
48
}
49

50
func NewLog() *Log {
51
	return &Log{
52
		components: map[string]*ManifestLog{},
53
		bar:        createBar(),
54
	}
55
}
56

57
const inProgress = `{{ yellow (cycle . "-" "-" "-" " ") }} `
58

59
// createStatus will return a string to report the current status.
60
// ex: - Processing resources for components. Waiting for foo, bar
61
func (p *Log) createStatus(maxWidth int) string {
62
	comps := make([]string, 0, len(p.components))
63
	wait := make([]string, 0, len(p.components))
64
	for c, l := range p.components {
65
		comps = append(comps, name.UserFacingComponentName(name.ComponentName(c)))
66
		wait = append(wait, l.waitingResources()...)
67
	}
68
	sort.Strings(comps)
69
	sort.Strings(wait)
70
	msg := fmt.Sprintf(`Processing resources for %s.`, strings.Join(comps, ", "))
71
	if len(wait) > 0 {
72
		msg += fmt.Sprintf(` Waiting for %s`, strings.Join(wait, ", "))
73
	}
74
	prefix := inProgress
75
	if !p.bar.GetBool(pb.Terminal) {
76
		// If we aren't a terminal, no need to spam extra lines
77
		prefix = `{{ yellow "-" }} `
78
	}
79
	// reduce by 2 to allow for the "- " that will be added below
80
	maxWidth -= 2
81
	if maxWidth > 0 && len(msg) > maxWidth {
82
		return prefix + msg[:maxWidth-3] + "..."
83
	}
84
	// cycle will alternate between "-" and " ". "-" is given multiple times to avoid quick flashing back and forth
85
	return prefix + msg
86
}
87

88
// For testing only
89
var testWriter *io.Writer
90

91
func createBar() *pb.ProgressBar {
92
	// Don't set a total and use Static so we can explicitly control when you write. This is needed
93
	// for handling the multiline issues.
94
	bar := pb.New(0)
95
	bar.Set(pb.Static, true)
96
	if testWriter != nil {
97
		bar.SetWriter(*testWriter)
98
	}
99
	bar.Start()
100
	// if we aren't a terminal, we will return a new line for each new message
101
	if !bar.GetBool(pb.Terminal) {
102
		bar.Set(pb.ReturnSymbol, "\n")
103
	}
104
	return bar
105
}
106

107
// reportProgress will report an update for a given component
108
// Because the bar library does not support multiple lines/bars at once, we need to aggregate current
109
// progress into a single line. For example "Waiting for x, y, z". Once a component completes, we want
110
// a new line created so the information is not lost. To do this, we spin up a new bar with the remaining components
111
// on a new line, and create a new bar. For example, this becomes "x succeeded", "waiting for y, z".
112
func (p *Log) reportProgress(component string) func() {
113
	return func() {
114
		cliName := name.UserFacingComponentName(name.ComponentName(component))
115
		p.mu.Lock()
116
		defer p.mu.Unlock()
117
		cmp := p.components[component]
118
		// The component has completed
119
		cmp.mu.Lock()
120
		finished := cmp.finished
121
		cmpErr := cmp.err
122
		cmp.mu.Unlock()
123
		if finished || cmpErr != "" {
124
			if finished {
125
				p.SetMessage(fmt.Sprintf(`{{ green "✔" }} %s installed`, cliName), true)
126
			} else {
127
				p.SetMessage(fmt.Sprintf(`{{ red "✘" }} %s encountered an error: %s`, cliName, cmpErr), true)
128
			}
129
			// Close the bar out, outputting a new line
130
			delete(p.components, component)
131

132
			// Now we create a new bar, which will have the remaining components
133
			p.bar = createBar()
134
			return
135
		}
136
		p.SetMessage(p.createStatus(p.bar.Width()), false)
137
	}
138
}
139

140
func (p *Log) SetState(state InstallState) {
141
	p.mu.Lock()
142
	defer p.mu.Unlock()
143
	p.state = state
144
	switch p.state {
145
	case StatePruning:
146
		p.bar.SetTemplateString(inProgress + `Pruning removed resources`)
147
		p.bar.Write()
148
	case StateComplete:
149
		p.bar.SetTemplateString(`{{ green "✔" }} Installation complete`)
150
		p.bar.Write()
151
	case StateUninstallComplete:
152
		p.bar.SetTemplateString(`{{ green "✔" }} Uninstall complete`)
153
		p.bar.Write()
154
	}
155
}
156

157
func (p *Log) NewComponent(component string) *ManifestLog {
158
	ml := &ManifestLog{
159
		report: p.reportProgress(component),
160
	}
161
	p.mu.Lock()
162
	defer p.mu.Unlock()
163
	p.components[component] = ml
164
	return ml
165
}
166

167
func (p *Log) SetMessage(status string, finish bool) {
168
	// if we are not a terminal and there is no change, do not write
169
	// This avoids redundant lines
170
	if !p.bar.GetBool(pb.Terminal) && status == p.template {
171
		return
172
	}
173
	p.template = status
174
	p.bar.SetTemplateString(p.template)
175
	if finish {
176
		p.bar.Finish()
177
	}
178
	p.bar.Write()
179
}
180

181
// ManifestLog records progress for a single component
182
type ManifestLog struct {
183
	report   func()
184
	err      string
185
	finished bool
186
	waiting  []string
187
	mu       sync.Mutex
188
}
189

190
func (p *ManifestLog) ReportProgress() {
191
	if p == nil {
192
		return
193
	}
194
	p.report()
195
}
196

197
func (p *ManifestLog) ReportError(err string) {
198
	if p == nil {
199
		return
200
	}
201
	p.mu.Lock()
202
	p.err = err
203
	p.mu.Unlock()
204
	p.report()
205
}
206

207
func (p *ManifestLog) ReportFinished() {
208
	if p == nil {
209
		return
210
	}
211
	p.mu.Lock()
212
	p.finished = true
213
	p.mu.Unlock()
214
	p.report()
215
}
216

217
func (p *ManifestLog) ReportWaiting(resources []string) {
218
	if p == nil {
219
		return
220
	}
221
	p.mu.Lock()
222
	p.waiting = resources
223
	p.mu.Unlock()
224
	p.report()
225
}
226

227
func (p *ManifestLog) waitingResources() []string {
228
	p.mu.Lock()
229
	defer p.mu.Unlock()
230
	return p.waiting
231
}
232

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.