Skip to content

Commit 1fb0bd6

Browse files
authored
Merge pull request #171 from Clever/DEAD-13
Dead 13
2 parents 1722316 + 6b2dff7 commit 1fb0bd6

File tree

4 files changed

+170
-9
lines changed

4 files changed

+170
-9
lines changed

VERSION

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
v1.1.0
2-
goci can now publish utilities to the catalog
1+
v1.2.0
2+
goci now supports validation of node version
3+
34

45
Previously:
6+
- goci can now publish utilities to the catalog
57
- updated goci to catch errors for no go.mod file
68
- bugfix: dont lazy load the oidc token
79
- Updating goci warning message, including recent version release date

circleci/catapult-publish

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ REPO_NAME=$CIRCLE_PROJECT_REPONAME
4343
SHORT_SHA=${CIRCLE_SHA1:0:7}
4444
FULL_SHA=${CIRCLE_SHA1}
4545

46-
# If file go.mod exists
47-
if [ -f go.mod ]; then
46+
# If file go.mod or package.json exists
47+
if [ -f go.mod ] || [ -f package.json ]; then
4848
. $DIR/install-goci
4949

5050
set +e

cmd/goci/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ goci accepts very limited arguments which merely change the mode it runs in. The
1010

1111
1. `goci detect` detects any changed applications according to their launch configuration. This can be used to pass a name of apps to another script.
1212
2. `goci artifact-build-publish-deploy` builds, publishes and deploys any application artifacts.
13-
3. `goci validate` validates an applications go version, while also checking for compatible branch naming conventions for catapult.
13+
3. `goci validate` validates an applications go version and node version, while also checking for compatible branch naming conventions for catapult.
1414
4. `goci publish-utility` publishes catalog-info.yaml to the service catalog.
1515

1616

cmd/goci/main.go

Lines changed: 163 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package main
22

33
import (
44
"context"
5+
"encoding/json"
56
"errors"
67
"fmt"
78
"io"
@@ -10,6 +11,7 @@ import (
1011
"regexp"
1112
"strconv"
1213
"strings"
14+
"time"
1315

1416
"golang.org/x/mod/modfile"
1517

@@ -160,12 +162,144 @@ func run(mode string) error {
160162
return nil
161163
}
162164

163-
// validateRun checks the env.branch and go version to ensure the build is valid.
164-
func validateRun() error {
165-
if strings.Contains(environment.Branch(), "/") {
166-
return &ValidationError{Message: fmt.Sprintf("branch name %s contains a `/` character, which is not supported by catapult", environment.Branch())}
165+
// find a Long Term Support (LTS) Node.js version which is the newest major version, but the oldest minor and patch version
166+
// which is LTS within this major version.
167+
// pull releases from https://nodejs.org/dist/index.json
168+
func fetchLastestLTSNodeVersion() (string, string, error) {
169+
resp, err := http.Get("https://nodejs.org/dist/index.json")
170+
if err != nil {
171+
return "", "", fmt.Errorf("failed to fetch Node.js versions: %v", err)
167172
}
173+
defer resp.Body.Close()
168174

175+
if resp.StatusCode != http.StatusOK {
176+
return "", "", fmt.Errorf("failed to fetch Node.js versions: status code %d", resp.StatusCode)
177+
}
178+
179+
// Read the response body to json array
180+
bodyBytes, err := io.ReadAll(resp.Body)
181+
if err != nil {
182+
return "", "", fmt.Errorf("failed to read response body: %v", err)
183+
}
184+
185+
// Parse the JSON response
186+
var nodeReleases []map[string]interface{}
187+
if err := json.Unmarshal(bodyBytes, &nodeReleases); err != nil {
188+
return "", "", fmt.Errorf("failed to parse JSON response: %v", err)
189+
}
190+
191+
// Find the latest LTS version
192+
var latestLTSMajorVersion string
193+
var selectedRelease map[string]interface{}
194+
for _, release := range nodeReleases {
195+
if lts, ok := release["lts"]; ok && lts != nil {
196+
ltsBool, boolOk := lts.(bool)
197+
ltsString, stringOk := lts.(string)
198+
if (boolOk && ltsBool) || (stringOk && ltsString != "") {
199+
releaseVersion, ok := release["version"].(string)
200+
if !ok {
201+
return "", "", fmt.Errorf("failed to parse version in Node.js release")
202+
}
203+
if latestLTSMajorVersion == "" {
204+
// we found our most recent LTS version
205+
latestLTSMajorVersion = strings.Split(releaseVersion, ".")[0]
206+
}
207+
if strings.HasPrefix(releaseVersion, latestLTSMajorVersion) {
208+
// this is an earlier LTS release for the selected major version
209+
selectedRelease = release
210+
} else {
211+
break
212+
}
213+
} else if selectedRelease != nil {
214+
break // we have passed the earliest LTS release for this major version
215+
}
216+
}
217+
}
218+
219+
if selectedRelease == nil {
220+
return "", "", fmt.Errorf("no LTS version found in Node.js releases")
221+
}
222+
223+
selectedLTSVersion, ok := selectedRelease["version"].(string)
224+
if !ok {
225+
return "", "", fmt.Errorf("failed to parse version in Node.js release")
226+
}
227+
selectedLTSReleaseDate, ok := selectedRelease["date"].(string)
228+
if !ok {
229+
return "", "", fmt.Errorf("failed to parse release date in Node.js release")
230+
}
231+
return selectedLTSVersion, selectedLTSReleaseDate, nil
232+
}
233+
234+
func parseCurrentNodeMajorVersion() (string, error) {
235+
dockerfilePath := "./Dockerfile"
236+
fileBytes, err := os.ReadFile(dockerfilePath)
237+
if err != nil {
238+
return "", fmt.Errorf("failed to read Dockerfile: %v", err)
239+
}
240+
241+
// Use a regex to find the Node.js version in the Dockerfile
242+
re := regexp.MustCompile(`FROM node:([0-9]+)`)
243+
matches := re.FindStringSubmatch(string(fileBytes))
244+
if len(matches) == 2 {
245+
return matches[1], nil
246+
}
247+
// look for an explicit download of a Node.js version in the Dockerfile
248+
// This is a fallback in case the Dockerfile does not use the standard Node.js image
249+
re = regexp.MustCompile(`deb\.nodesource\.com\/setup_([0-9]+)\.`)
250+
matches = re.FindStringSubmatch(string(fileBytes))
251+
if len(matches) == 2 {
252+
return matches[1], nil
253+
}
254+
return "", fmt.Errorf("failed to find Node.js version in Dockerfile")
255+
}
256+
257+
func validateNodeVersion() error {
258+
minimumEnforcementVersion := 24
259+
ltsVersion, ltsReleaseDate, err := fetchLastestLTSNodeVersion()
260+
if err != nil {
261+
return err
262+
}
263+
re := regexp.MustCompile(`v([0-9]+)\.`)
264+
var ltsMajorVersion string
265+
if matches := re.FindStringSubmatch(ltsVersion); len(matches) == 2 {
266+
ltsMajorVersion = matches[1]
267+
} else {
268+
return fmt.Errorf("failed to parse LTS major version from %s", ltsVersion)
269+
}
270+
currentMajorVersion, err := parseCurrentNodeMajorVersion()
271+
if err != nil {
272+
return err
273+
}
274+
// compare the major version of the current Node.js version with the latest LTS version
275+
currentMajorVersionInt, err := strconv.Atoi(currentMajorVersion)
276+
if err != nil {
277+
return fmt.Errorf("failed to parse current major version: %v", err)
278+
}
279+
ltsMajorVersionInt, err := strconv.Atoi(ltsMajorVersion)
280+
if err != nil {
281+
return fmt.Errorf("failed to parse LTS major version: %v", err)
282+
}
283+
// Check if the current major version is less than the LTS major version
284+
// and that we have already performed the initial migration to get to at least the minimum enforcement version
285+
if currentMajorVersionInt < ltsMajorVersionInt && currentMajorVersionInt >= minimumEnforcementVersion {
286+
// parse the release date of the LTS version
287+
releaseDate, err := time.Parse("2006-01-02", ltsReleaseDate)
288+
if err != nil {
289+
return fmt.Errorf("failed to parse LTS release date: %v", err)
290+
}
291+
if time.Since(releaseDate) > 6*30*24*time.Hour { // 6 months in hours
292+
return &ValidationError{
293+
Message: fmt.Sprintf("Your current Node.js version %s is no longer supported. Please upgrade to the latest Long Term Support version %s or later", currentMajorVersion, ltsVersion),
294+
}
295+
} else {
296+
fmt.Printf("A new Node.js Long Term Support version is out, released on (%s). After 6 months of release, Your current Node.js version v%d will fail CI workflows if it is not upgraded to v%d.\n", ltsReleaseDate, currentMajorVersionInt, ltsMajorVersionInt)
297+
}
298+
}
299+
return nil
300+
}
301+
302+
func validateGoVersion() error {
169303
latestGoVersion, releaseDate, err := fetchLatestGoVersion()
170304
if err != nil {
171305
return fmt.Errorf("failed to fetch latest Go version: %v", err)
@@ -216,6 +350,31 @@ func validateRun() error {
216350
// We'll give a PR comment to the Author to warn them about the need to upgrade
217351
fmt.Printf("A new Go version is out, released on (%v). After 6 months of release, Your current Go version (%v) will fail CI workflows if it is not upgraded.\n", releaseDate, f.Go.Version)
218352
}
353+
354+
return nil
355+
}
356+
357+
// validateRun checks the env.branch and go version to ensure the build is valid.
358+
func validateRun() error {
359+
if strings.Contains(environment.Branch(), "/") {
360+
return &ValidationError{Message: fmt.Sprintf("branch name %s contains a `/` character, which is not supported by catapult", environment.Branch())}
361+
}
362+
363+
// if package.json exists, we will validate the Node.js version
364+
if _, err := os.Stat("./package.json"); err == nil {
365+
err = validateNodeVersion()
366+
if err != nil {
367+
return fmt.Errorf("failed to validate Node.js version: %v", err)
368+
}
369+
}
370+
371+
// if go.mod exists, we will validate the Go version
372+
if _, err := os.Stat("./go.mod"); err == nil {
373+
err = validateGoVersion()
374+
if err != nil {
375+
return fmt.Errorf("failed to validate Go version: %v", err)
376+
}
377+
}
219378
return nil
220379
}
221380

0 commit comments

Comments
 (0)