Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: changes to support motd #385

Open
wants to merge 5 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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())
Copy link
Collaborator

Choose a reason for hiding this comment

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

There needs to a nil check here since this can be null.

}

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