ci-tools/main.go (331 lines of code) (raw):
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you 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 main
import (
"encoding/json"
"errors"
"flag"
"fmt"
"go/format"
"io"
"net/http"
"os"
"path/filepath"
"slices"
"strconv"
"strings"
"golang.org/x/mod/modfile"
"golang.org/x/mod/semver"
)
type args struct {
version string
unregisteredVersionsCount int
}
func main() {
currentVersion, err := lookupVersion()
if err != nil {
fmt.Println(err)
}
commandsArgs := &args{}
getVersionCmd := flag.NewFlagSet("get-version", flag.ExitOnError)
validateRegisteredVersionsCmd := flag.NewFlagSet("validate-registered-versions", flag.ExitOnError)
getUnregisteredVersionsCmd := flag.NewFlagSet("get-unregistered-versions", flag.ExitOnError)
getUnregisteredVersionsCmd.IntVar(&commandsArgs.unregisteredVersionsCount, "count", lookupUnregisteredVersionsCount(),
"Number of unregistered versions to list")
registerWasmCmd := flag.NewFlagSet("register-wasm", flag.ExitOnError)
addVersionFlag(&commandsArgs.version, currentVersion, registerWasmCmd)
generateConstantsCmd := flag.NewFlagSet("generate-constants", flag.ExitOnError)
addVersionFlag(&commandsArgs.version, currentVersion, generateConstantsCmd)
generateProcessorsUpdateCmd := flag.NewFlagSet("generate-processors-update", flag.ExitOnError)
addVersionFlag(&commandsArgs.version, currentVersion, generateProcessorsUpdateCmd)
switch os.Args[1] {
case getVersionCmd.Name():
version, err := extractProcessorsVersionFromGoModule()
if err != nil {
fmt.Println(err)
} else {
fmt.Println(version)
}
case generateConstantsCmd.Name():
_ = generateConstantsCmd.Parse(os.Args[2:])
err = generateVersionsDotGoFile(commandsArgs.version)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(commandsArgs.version)
}
case registerWasmCmd.Name():
_ = registerWasmCmd.Parse(os.Args[2:])
err = registerWebAssemblyVersion(commandsArgs.version)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(commandsArgs.version)
}
case generateProcessorsUpdateCmd.Name():
_ = generateProcessorsUpdateCmd.Parse(os.Args[2:])
argument, err := generateProcessorsGoGetArgument(commandsArgs.version)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(argument)
}
case getUnregisteredVersionsCmd.Name():
_ = getUnregisteredVersionsCmd.Parse(os.Args[2:])
releases, err := getUnregisteredVersions(commandsArgs.unregisteredVersionsCount)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(releases)
}
case validateRegisteredVersionsCmd.Name():
err = validateWebAssemblyVersions()
if err != nil {
fmt.Println(err.Error())
os.Exit(1)
}
default:
flag.PrintDefaults()
os.Exit(1)
}
}
func addVersionFlag(target *string, defaultVal string, cmd *flag.FlagSet) {
cmd.StringVar(target, "version", defaultVal, "opentelemetry-collector-contrib version")
}
func lookupVersion() (string, error) {
version := os.Getenv("PROCESSORS_VERSION")
if version == "" {
return extractProcessorsVersionFromGoModule()
}
return version, nil
}
func lookupWasmVersionsJSONPath() string {
path, ok := os.LookupEnv("WASM_VERSIONS_FILE")
if ok {
return path
}
return filepath.Join("web", "public", "wasm", "versions.json")
}
func lookupUnregisteredVersionsCount() int {
defaultVersionsCount := 10
value, ok := os.LookupEnv("MAX_WASM_PROCESSORS_VERSIONS")
if ok {
intValue, err := strconv.Atoi(value)
if err == nil {
return intValue
}
}
return defaultVersionsCount
}
func extractProcessorsVersionFromGoModule() (string, error) {
goModFile, err := os.Open("go.mod")
if err != nil {
return "", err
}
goModFileBytes, err := io.ReadAll(goModFile)
_ = goModFile.Close()
if err != nil {
return "", err
}
goModInfo, err := modfile.Parse("go.mod", goModFileBytes, nil)
if err != nil {
return "", err
}
var version string
for _, dep := range goModInfo.Require {
if dep.Indirect {
continue
}
if strings.HasPrefix(dep.Mod.Path, "github.com/open-telemetry/opentelemetry-collector-contrib/processor/") {
if version != "" && version != dep.Mod.Version {
return "", fmt.Errorf("multiple opentelemetry-collector-contrib versions found: %q and %q", version, dep.Mod.Version)
}
version = dep.Mod.Version
}
}
return version, nil
}
func registerWebAssemblyVersion(version string) error {
wasmVersionsFilePath := lookupWasmVersionsJSONPath()
wasmVersionsFile, err := os.OpenFile(wasmVersionsFilePath, os.O_RDWR|os.O_CREATE, 0666)
if err != nil {
return err
}
wasmVersionsFileBytes, err := io.ReadAll(wasmVersionsFile)
if err != nil {
return err
}
_ = wasmVersionsFile.Close()
var wasmVersions map[string]any
if len(wasmVersionsFileBytes) == 0 {
wasmVersions = map[string]any{}
} else {
err = json.Unmarshal(wasmVersionsFileBytes, &wasmVersions)
if err != nil {
return err
}
}
if len(wasmVersions) == 0 {
wasmVersions["versions"] = []any{}
}
for _, v := range wasmVersions["versions"].([]any) {
if v.(map[string]any)["version"].(string) == version {
return nil
}
}
wasmName := fmt.Sprintf("wasm/ottlplayground-%s.wasm", version)
wasmVersions["versions"] = append(wasmVersions["versions"].([]any), map[string]any{
"artifact": wasmName,
"version": version,
})
slices.SortFunc(wasmVersions["versions"].([]any), func(a, b any) int {
return semver.Compare(b.(map[string]any)["version"].(string), a.(map[string]any)["version"].(string))
})
modifiedWasmVersions, err := json.MarshalIndent(&wasmVersions, "", " ")
if err != nil {
return err
}
err = os.WriteFile(wasmVersionsFilePath, modifiedWasmVersions, 0600)
if err != nil {
return err
}
return nil
}
func validateWebAssemblyVersions() error {
wasmVersionsFilePath := lookupWasmVersionsJSONPath()
wasmVersionsFileBytes, err := os.ReadFile(wasmVersionsFilePath)
if err != nil {
return fmt.Errorf("file %s not found", wasmVersionsFilePath)
}
var wasmVersions map[string]any
if len(wasmVersionsFileBytes) == 0 {
return nil
} else {
err = json.Unmarshal(wasmVersionsFileBytes, &wasmVersions)
if err != nil {
return err
}
}
if len(wasmVersions) == 0 {
return nil
}
var errorsList strings.Builder
for _, version := range wasmVersions["versions"].([]any) {
artifact := version.(map[string]any)["artifact"]
_, err = os.Stat(fmt.Sprintf("web/public/%s", artifact.(string)))
if err != nil {
errorsList.WriteString(fmt.Sprintf("version %s: artifact not found: %s \n", version.(map[string]any)["version"].(string), artifact))
}
}
if errorsList.Len() > 0 {
return errors.New(errorsList.String())
}
return nil
}
func generateProcessorsGoGetArgument(version string) (string, error) {
goModFile, err := os.Open("go.mod")
if err != nil {
return "", err
}
defer func(goModFile *os.File) {
_ = goModFile.Close()
}(goModFile)
goModFileBytes, err := io.ReadAll(goModFile)
if err != nil {
return "", err
}
goModInfo, err := modfile.Parse("go.mod", goModFileBytes, nil)
if err != nil {
return "", err
}
var argument strings.Builder
seem := map[string]bool{}
for _, dep := range goModInfo.Require {
if dep.Indirect {
continue
}
if !seem[dep.Mod.Path] && strings.HasPrefix(dep.Mod.Path, "github.com/open-telemetry/opentelemetry-collector-contrib/processor/") {
seem[dep.Mod.Path] = true
argument.WriteString(fmt.Sprintf("%s@%s", dep.Mod.Path, version))
argument.WriteString(" ")
}
}
return argument.String(), nil
}
func generateVersionsDotGoFile(version string) error {
versionsGoFile, err := os.Create(filepath.Join("internal", "versions.go"))
if err != nil {
return err
}
defer func(versionsGoFile *os.File) {
_ = versionsGoFile.Close()
}(versionsGoFile)
content := strings.Builder{}
content.WriteString("/*\n * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one\n * or more contributor license agreements. See the NOTICE file distributed with\n * this work for additional information regarding copyright\n * ownership. Elasticsearch B.V. licenses this file to you under\n * the Apache License, Version 2.0 (the \"License\"); you may\n * not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *\thttp://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\n\n")
content.WriteString("// Code generated. DO NOT EDIT.\n\n")
content.WriteString("package internal\n\n")
content.WriteString(fmt.Sprintf("const CollectorContribProcessorsVersion = \"%s\" \n", version))
formattedSource, err := format.Source([]byte(content.String()))
if err != nil {
return err
}
_, err = fmt.Fprint(versionsGoFile, string(formattedSource))
if err != nil {
return err
}
return nil
}
func getUnregisteredVersions(maxNumOfVersions int) (string, error) {
wasmVersionsFilePath := lookupWasmVersionsJSONPath()
registeredVersions := map[string]bool{}
_, err := os.Stat(wasmVersionsFilePath)
if err == nil {
var wasmVersionsFileBytes []byte
wasmVersionsFileBytes, err = os.ReadFile(wasmVersionsFilePath)
if err != nil {
return "", err
}
if len(wasmVersionsFileBytes) > 0 {
var fileContent map[string]any
err = json.Unmarshal(wasmVersionsFileBytes, &fileContent)
if err != nil {
return "", err
}
for _, v := range fileContent["versions"].([]any) {
registeredVersions[v.(map[string]any)["version"].(string)] = true
}
}
}
tagsRes, err := http.Get("https://api.github.com/repos/open-telemetry/opentelemetry-collector-contrib/releases")
if err != nil {
return "", err
}
defer func(Body io.ReadCloser) {
_ = Body.Close()
}(tagsRes.Body)
if tagsRes.StatusCode != http.StatusOK {
return "", fmt.Errorf("failed to get release list. status: %s", tagsRes.Status)
}
var data []map[string]any
err = json.NewDecoder(tagsRes.Body).Decode(&data)
if err != nil {
return "", err
}
ignoredVersions := map[string]struct{}{}
for _, ignoredVersion := range strings.Split(os.Getenv("IGNORED_WASM_PROCESSORS_VERSIONS"), " ") {
ignoredVersions[ignoredVersion] = struct{}{}
}
var newVersions []string
for _, release := range data {
version := release["name"].(string)
if _, ok := ignoredVersions[version]; ok {
continue
}
// versions <= v0.110.0 fails to compile due to some breaking changes
if !registeredVersions[version] && semver.Compare(version, "v0.110.0") > 0 {
newVersions = append(newVersions, version)
}
}
slices.SortFunc(newVersions, semver.Compare)
if len(newVersions) > maxNumOfVersions {
newVersions = newVersions[len(newVersions)-maxNumOfVersions:]
}
return strings.Join(newVersions, " "), nil
}