Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .nancy-ignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
CVE-2022-32149 # No upgrade path for github.com/Xuanwo/go-locale, github.com/onsi/gomega and github.com/onsi/ginkgo/v2 at this time
CVE-2023-39325 # The CLI SDK does not host a server (and the network clients made available here will not behave maliciously as described in the issue), so this vulnerability is not a concern
97 changes: 97 additions & 0 deletions bluemix/api/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package api

import (
"fmt"
"regexp"
"strings"

"github.com/IBM-Cloud/ibm-cloud-cli-sdk/i18n"
"github.com/Masterminds/semver"
)

const (
ConstraintAllVersions = "*"
)

var coercableSemver = regexp.MustCompile(`^\d+(\.\d+)?$`)

type SemverConstraintInvalidError struct {
Constraint string
Err error
}

func (e SemverConstraintInvalidError) Error() string {
return i18n.T("Version constraint {{.Constraint}} is invalid:\n",
map[string]interface{}{"Constraint": e.Constraint}) + e.Err.Error()
}

type SemverConstraint interface {
Satisfied(string) bool
IsRange() bool

fmt.Stringer
}

func NewSemverConstraint(versionOrRange string) (SemverConstraint, error) {
versionOrRange = strings.TrimPrefix(versionOrRange, "v")
versionOrRange = coerce(versionOrRange)

if _, err := semver.NewVersion(versionOrRange); err == nil {
return semverVersion(versionOrRange), nil
}

constraints, err := semver.NewConstraint(versionOrRange)
if err != nil {
return nil, SemverConstraintInvalidError{Constraint: versionOrRange, Err: err}
}

return semverRange{repr: versionOrRange, constraints: constraints}, nil
}

type semverVersion string

func (v semverVersion) Satisfied(version string) bool {
return strings.EqualFold(string(v), version)
}

func (v semverVersion) IsRange() bool {
return false
}

func (v semverVersion) String() string {
return string(v)
}

type semverRange struct {
repr string // user-provided string representation
constraints *semver.Constraints
}

func (r semverRange) Satisfied(version string) bool {
sv, err := semver.NewVersion(version)
if err != nil {
return false
}

return r.constraints.Check(sv)
}

func (r semverRange) IsRange() bool {
return true
}

func (r semverRange) String() string {
return r.repr
}

// coerce takes an incomplete semver range (e.g. '1' or '1.2') and turns them into a valid constraint. github.com/mastermind/semver's
// default behavior will fill any a missing minor/patch with 0's, so we bypass that to create ranges; e.g.
//
// '1' -> '1.x'
// '1.2' -> '1.2.x'
func coerce(semverRange string) string {
if !coercableSemver.MatchString(semverRange) {
return semverRange
}
return semverRange + ".x"
}
2 changes: 2 additions & 0 deletions bluemix/configuration/core_config/bx_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"github.com/IBM-Cloud/ibm-cloud-cli-sdk/bluemix"
"github.com/IBM-Cloud/ibm-cloud-cli-sdk/bluemix/configuration"
"github.com/IBM-Cloud/ibm-cloud-cli-sdk/bluemix/models"

// "github.com/IBM-Cloud/ibm-cloud-cli-sdk/plugin"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Remove comment

"github.com/fatih/structs"
)

Expand Down
Empty file.
63 changes: 63 additions & 0 deletions bluemix/configuration/core_config/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{
"APIEndpoint": "",
"IsPrivate": false,
"IsAccessFromVPC": false,
"ConsoleEndpoint": "",
"ConsolePrivateEndpoint": "",
"ConsolePrivateVPCEndpoint": "",
"CloudType": "",
"CloudName": "",
"CRIType": "",
"Region": "",
"RegionID": "",
"IAMEndpoint": "",
"IAMPrivateEndpoint": "",
"IAMPrivateVPCEndpoint": "",
"IAMToken": "",
"IAMRefreshToken": "",
"IsLoggedInAsCRI": false,
"Account": {
"GUID": "",
"Name": "",
"Owner": ""
},
"Profile": {
"ID": "",
"Name": "",
"ComputeResource": {
"Name": "",
"ID": ""
},
"User": {
"Name": "",
"ID": ""
}
},
"ResourceGroup": {
"GUID": "",
"Name": "",
"State": "",
"Default": false,
"QuotaID": ""
},
"LoginAt": "0001-01-01T00:00:00Z",
"CFEETargeted": false,
"CFEEEnvID": "",
"PluginRepos": null,
"SSLDisabled": false,
"Locale": "",
"MessageOfTheDayTime": 0,
"LastSessionUpdateTime": 1697560381,
"Trace": "",
"ColorEnabled": "",
"HTTPTimeout": 0,
"CLIInfoEndpoint": "",
"CheckCLIVersionDisabled": false,
"UsageStatsDisabled": false,
"UsageStatsEnabled": false,
"UsageStatsEnabledLastUpdate": "0001-01-01T00:00:00Z",
"SDKVersion": "1.1.1",
"UpdateCheckInterval": 0,
"UpdateRetryCheckInterval": 0,
"UpdateNotificationInterval": 0
}
37 changes: 37 additions & 0 deletions bluemix/configuration/core_config/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package core_config

import "runtime"

func DeterminePlatform() string {
arch := runtime.GOARCH

switch runtime.GOOS {
case "windows":
if arch == "386" {
return "win32"
} else {
return "win64"
}
case "linux":
switch arch {
case "386":
return "linux32"
case "amd64":
return "linux64"
case "ppc64le":
return "ppc64le"
case "s390x":
return "s390x"
case "arm64":
return "linux-arm64"
}
case "darwin":
switch arch {
case "arm64":
return "osx-arm64"
default:
return "osx"
}
}
return "unknown"
}
105 changes: 105 additions & 0 deletions bluemix/motd/motd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package motd

import (
"strconv"
"strings"
"time"

"github.com/IBM-Cloud/ibm-cloud-cli-sdk/bluemix"
"github.com/IBM-Cloud/ibm-cloud-cli-sdk/bluemix/api"
"github.com/IBM-Cloud/ibm-cloud-cli-sdk/bluemix/configuration/core_config"
"github.com/IBM-Cloud/ibm-cloud-cli-sdk/bluemix/terminal"
"github.com/IBM-Cloud/ibm-cloud-cli-sdk/common/rest"
"github.com/IBM-Cloud/ibm-cloud-cli-sdk/plugin"
)

type MODMessages struct {
VersionRange string `json:"versionRange"`
Region string `json:"region"`
OS string `json:"os"`
Message string `json:"message"`
}

type MODResponse struct {
Messages []MODMessages `json:"messages"`
}

func CheckMessageOftheDayForPlugin(pluginConfig plugin.PluginConfig) bool {
currentMessagOfTheDay := pluginConfig.Get("MessageOfTheDay")
if currentMessagOfTheDay == nil {
return false
}
if currentMessagOfTheDayTimestamp, ok := currentMessagOfTheDay.(string); ok {
lastCheckTime, parseErr := strconv.ParseInt(currentMessagOfTheDayTimestamp, 10, 64)
if parseErr != nil {
return false
}
return time.Since(time.Unix(lastCheckTime, 0)).Hours() < 24
}
return false
}

func CheckMessageOfTheDay(client *rest.Client, config core_config.ReadWriter, pluginConfig plugin.PluginConfig, modURL string, ui terminal.UI, version string) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The version parameter is never used in this method. Lets remove it.

// the pluginConfig variable will be cast-able to a pointer to a plugin config type if the display is for a plugin

if config != nil {
if !config.CheckMessageOfTheDay() {
return
}
defer config.SetMessageOfTheDayTime()
} else {
if !CheckMessageOftheDayForPlugin(pluginConfig) {
return
}
defer pluginConfig.Set("MessageOfTheDayTime", time.Now().Unix())
}

var mod MODResponse

_, err := client.Do(rest.GetRequest(modURL), &mod, nil)
if err != nil {
return
}

for _, mes := range mod.Messages {

if mes.VersionRange != "" {
constraint, err := api.NewSemverConstraint(mes.VersionRange)
if err != nil || !constraint.Satisfied(bluemix.Version.String()) {
continue
}
}

// Only print message if targeted region matches or if message region is empty(print for all regions)
if mes.Region != "" {
skip := true
for _, region := range strings.Split(mes.Region, ",") {
region = strings.TrimSpace(region)
if strings.EqualFold(region, config.CurrentRegion().Name) {
skip = false
break
}
}
if skip {
continue
}
}

// Only print message if matching OS or if message's OS is empty(print for all platforms)
if mes.OS != "" {
skip := true
for _, platform := range strings.Split(mes.OS, ",") {
platform = strings.TrimSpace(platform)
if strings.EqualFold(platform, core_config.DeterminePlatform()) {
skip = false
break
}
}
if skip {
continue
}
}

ui.Warn(mes.Message)
}
}
Loading