drone/types/resolved_feature.go

148 lines
4.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// Copyright 2023 Harness, Inc.
//
// 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 types
import (
"fmt"
"sort"
"strconv"
"strings"
"github.com/Masterminds/semver/v3"
)
type ResolvedFeature struct {
ResolvedOptions map[string]string `json:"options,omitempty"`
OverriddenOptions map[string]any `json:"overridden_options,omitempty"`
Digest string `json:"string,omitempty"`
DownloadedFeature *DownloadedFeature `json:"downloaded_feature,omitempty"`
}
type DownloadedFeature struct {
FeatureFolderName string `json:"feature_folder_name,omitempty"`
Source string `json:"source,omitempty"`
SourceWithoutTag string `json:"source_without_tag,omitempty"`
Tag string `json:"tag,omitempty"`
CanonicalName string `json:"canonical_name,omitempty"`
DevcontainerFeatureConfig *DevcontainerFeatureConfig `json:"devcontainer_feature_config,omitempty"`
}
func (r *ResolvedFeature) Print() string {
options := make([]string, 0, len(r.ResolvedOptions))
for key, value := range r.ResolvedOptions {
options = append(options, fmt.Sprintf("%s=%s", key, value))
}
return fmt.Sprintf("%s %+v", r.DownloadedFeature.Source, options)
}
// CompareResolvedFeature implements the following comparison rules.
// 1. Compare and sort each Feature lexicographically by their fully qualified resource name
// (For OCI-published Features, that means the ID without version or digest.). If the comparison is equal:
// 2. Compare and sort each Feature from oldest to newest tag (latest being the “most new”). If the comparison is equal:
// 3. Compare and sort each Feature by their options by:
// 3.1 Greatest number of user-defined options (note omitting an option will default that value to the Features
// default value and is not considered a user-defined option). If the comparison is equal:
// 3.2 Sort the provided option keys lexicographically. If the comparison is equal:
// 3.3 Sort the provided option values lexicographically. If the comparison is equal:
// 4. Sort Features by their canonical name (For OCI-published Features, the Feature ID resolved to the digest hash).
// 5. If there is no difference based on these comparator rules, the Features are considered equal.
// Reference: https://containers.dev/implementors/features/#definition-feature-equality (Round Stable Sort).
func CompareResolvedFeature(a, b *ResolvedFeature) int {
var comparison int
comparison = strings.Compare(a.DownloadedFeature.SourceWithoutTag, b.DownloadedFeature.SourceWithoutTag)
if comparison != 0 {
return comparison
}
comparison, _ = compareTags(a.DownloadedFeature.Tag, b.DownloadedFeature.Tag)
if comparison != 0 {
return comparison
}
comparison = compareOverriddenOptions(a.OverriddenOptions, b.OverriddenOptions)
if comparison != 0 {
return comparison
}
return strings.Compare(a.DownloadedFeature.CanonicalName, b.DownloadedFeature.CanonicalName)
}
func compareTags(a, b string) (int, error) {
if a == FeatureDefaultTag && b == FeatureDefaultTag {
return 0, nil
}
if a == FeatureDefaultTag {
return 1, nil
}
if b == FeatureDefaultTag {
return -1, nil
}
versionA, err := semver.NewVersion(a)
if err != nil {
return 0, err
}
versionB, err := semver.NewVersion(b)
if err != nil {
return 0, err
}
return versionA.Compare(versionB), nil
}
func compareOverriddenOptions(a, b map[string]any) int {
if len(a) != len(b) {
return len(a) - len(b)
}
keysA, valuesA := getSortedOptions(a)
keysB, valuesB := getSortedOptions(b)
for i := 0; i < len(keysA); i++ {
if keysA[i] == keysB[i] {
continue
}
return strings.Compare(keysA[i], keysB[i])
}
for i := 0; i < len(valuesA); i++ {
if valuesA[i] == valuesB[i] {
continue
}
return strings.Compare(valuesA[i], valuesB[i])
}
return 0
}
// getSortedOptions returns the keys and values of a map sorted lexicographically.
func getSortedOptions(m map[string]any) ([]string, []string) {
keys := make([]string, 0, len(m))
for key := range m {
keys = append(keys, key)
}
sort.Strings(keys)
values := make([]string, 0, len(m))
for _, key := range keys {
value := m[key]
switch v := value.(type) {
case string:
values = append(values, v)
case bool:
values = append(values, strconv.FormatBool(v))
}
}
return keys, values
}