1/*
2   Copyright The containerd Authors.
3
4   Licensed under the Apache License, Version 2.0 (the "License");
5   you may not use this file except in compliance with the License.
6   You may obtain a copy of the License at
7
8       http://www.apache.org/licenses/LICENSE-2.0
9
10   Unless required by applicable law or agreed to in writing, software
11   distributed under the License is distributed on an "AS IS" BASIS,
12   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   See the License for the specific language governing permissions and
14   limitations under the License.
15*/
16
17package platforms
18
19import specs "github.com/opencontainers/image-spec/specs-go/v1"
20
21// MatchComparer is able to match and compare platforms to
22// filter and sort platforms.
23type MatchComparer interface {
24	Matcher
25
26	Less(specs.Platform, specs.Platform) bool
27}
28
29// Only returns a match comparer for a single platform
30// using default resolution logic for the platform.
31//
32// For ARMv8, will also match ARMv7, ARMv6 and ARMv5 (for 32bit runtimes)
33// For ARMv7, will also match ARMv6 and ARMv5
34// For ARMv6, will also match ARMv5
35func Only(platform specs.Platform) MatchComparer {
36	platform = Normalize(platform)
37	if platform.Architecture == "arm" {
38		if platform.Variant == "v8" {
39			return orderedPlatformComparer{
40				matchers: []Matcher{
41					&matcher{
42						Platform: platform,
43					},
44					&matcher{
45						Platform: specs.Platform{
46							Architecture: platform.Architecture,
47							OS:           platform.OS,
48							OSVersion:    platform.OSVersion,
49							OSFeatures:   platform.OSFeatures,
50							Variant:      "v7",
51						},
52					},
53					&matcher{
54						Platform: specs.Platform{
55							Architecture: platform.Architecture,
56							OS:           platform.OS,
57							OSVersion:    platform.OSVersion,
58							OSFeatures:   platform.OSFeatures,
59							Variant:      "v6",
60						},
61					},
62					&matcher{
63						Platform: specs.Platform{
64							Architecture: platform.Architecture,
65							OS:           platform.OS,
66							OSVersion:    platform.OSVersion,
67							OSFeatures:   platform.OSFeatures,
68							Variant:      "v5",
69						},
70					},
71				},
72			}
73		}
74		if platform.Variant == "v7" {
75			return orderedPlatformComparer{
76				matchers: []Matcher{
77					&matcher{
78						Platform: platform,
79					},
80					&matcher{
81						Platform: specs.Platform{
82							Architecture: platform.Architecture,
83							OS:           platform.OS,
84							OSVersion:    platform.OSVersion,
85							OSFeatures:   platform.OSFeatures,
86							Variant:      "v6",
87						},
88					},
89					&matcher{
90						Platform: specs.Platform{
91							Architecture: platform.Architecture,
92							OS:           platform.OS,
93							OSVersion:    platform.OSVersion,
94							OSFeatures:   platform.OSFeatures,
95							Variant:      "v5",
96						},
97					},
98				},
99			}
100		}
101		if platform.Variant == "v6" {
102			return orderedPlatformComparer{
103				matchers: []Matcher{
104					&matcher{
105						Platform: platform,
106					},
107					&matcher{
108						Platform: specs.Platform{
109							Architecture: platform.Architecture,
110							OS:           platform.OS,
111							OSVersion:    platform.OSVersion,
112							OSFeatures:   platform.OSFeatures,
113							Variant:      "v5",
114						},
115					},
116				},
117			}
118		}
119	}
120
121	return singlePlatformComparer{
122		Matcher: &matcher{
123			Platform: platform,
124		},
125	}
126}
127
128// Ordered returns a platform MatchComparer which matches any of the platforms
129// but orders them in order they are provided.
130func Ordered(platforms ...specs.Platform) MatchComparer {
131	matchers := make([]Matcher, len(platforms))
132	for i := range platforms {
133		matchers[i] = NewMatcher(platforms[i])
134	}
135	return orderedPlatformComparer{
136		matchers: matchers,
137	}
138}
139
140// Any returns a platform MatchComparer which matches any of the platforms
141// with no preference for ordering.
142func Any(platforms ...specs.Platform) MatchComparer {
143	matchers := make([]Matcher, len(platforms))
144	for i := range platforms {
145		matchers[i] = NewMatcher(platforms[i])
146	}
147	return anyPlatformComparer{
148		matchers: matchers,
149	}
150}
151
152// All is a platform MatchComparer which matches all platforms
153// with preference for ordering.
154var All MatchComparer = allPlatformComparer{}
155
156type singlePlatformComparer struct {
157	Matcher
158}
159
160func (c singlePlatformComparer) Less(p1, p2 specs.Platform) bool {
161	return c.Match(p1) && !c.Match(p2)
162}
163
164type orderedPlatformComparer struct {
165	matchers []Matcher
166}
167
168func (c orderedPlatformComparer) Match(platform specs.Platform) bool {
169	for _, m := range c.matchers {
170		if m.Match(platform) {
171			return true
172		}
173	}
174	return false
175}
176
177func (c orderedPlatformComparer) Less(p1 specs.Platform, p2 specs.Platform) bool {
178	for _, m := range c.matchers {
179		p1m := m.Match(p1)
180		p2m := m.Match(p2)
181		if p1m && !p2m {
182			return true
183		}
184		if p1m || p2m {
185			return false
186		}
187	}
188	return false
189}
190
191type anyPlatformComparer struct {
192	matchers []Matcher
193}
194
195func (c anyPlatformComparer) Match(platform specs.Platform) bool {
196	for _, m := range c.matchers {
197		if m.Match(platform) {
198			return true
199		}
200	}
201	return false
202}
203
204func (c anyPlatformComparer) Less(p1, p2 specs.Platform) bool {
205	var p1m, p2m bool
206	for _, m := range c.matchers {
207		if !p1m && m.Match(p1) {
208			p1m = true
209		}
210		if !p2m && m.Match(p2) {
211			p2m = true
212		}
213		if p1m && p2m {
214			return false
215		}
216	}
217	// If one matches, and the other does, sort match first
218	return p1m && !p2m
219}
220
221type allPlatformComparer struct{}
222
223func (allPlatformComparer) Match(specs.Platform) bool {
224	return true
225}
226
227func (allPlatformComparer) Less(specs.Platform, specs.Platform) bool {
228	return false
229}
230