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

Interactive Systems Check #92

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ See also https://docs.resim.ai/changelog/ for all ReSim changes

Changes in this section will be included in the next release.

#### Added

- The ReSim CLI now validates that the batch being created is **compatible** when not in github mode.
That is, it checks that experiences and metrics are registered as compatible with the system that the build
you are using belongs to. This can be overridden via interactive prompt.

### v0.3.0 - April 10 2024

#### Added
Expand Down
617 changes: 532 additions & 85 deletions api/client.gen.go

Large diffs are not rendered by default.

162 changes: 162 additions & 0 deletions cmd/resim/commands/batch.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import (
"context"
"errors"
"fmt"
"log"
"net/http"
Expand All @@ -10,6 +11,7 @@
"time"

"github.com/google/uuid"
"github.com/manifoldco/promptui"
"github.com/resim-ai/api-client/api"
. "github.com/resim-ai/api-client/ptr"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -143,6 +145,9 @@
}

func createBatch(ccmd *cobra.Command, args []string) {
metricsBuildCompatible := true
incompatibleExperienceNames := []string{}

projectID := getProjectID(Client, viper.GetString(batchProjectKey))
batchGithub := viper.GetBool(batchGithubKey)
if !batchGithub {
Expand All @@ -155,6 +160,14 @@
log.Fatal("failed to parse build ID: ", err)
}

// Obtain the system:
build := actualGetBuild(projectID, buildID)
if build.SystemID == nil {
log.Fatal("empty system ID")
}
system := actualGetSystem(projectID, *build.SystemID)
systemID := *system.SystemID

Check failure on line 169 in cmd/resim/commands/batch.go

View workflow job for this annotation

GitHub Actions / Lint and Build

invalid operation: cannot indirect system.SystemID (variable of type uuid.UUID)

Check failure on line 169 in cmd/resim/commands/batch.go

View workflow job for this annotation

GitHub Actions / Lint and Build

invalid operation: cannot indirect system.SystemID (variable of type uuid.UUID)

Check failure on line 169 in cmd/resim/commands/batch.go

View workflow job for this annotation

GitHub Actions / Run end-to-end test

invalid operation: cannot indirect system.SystemID (variable of type uuid.UUID)

var allExperienceIDs []uuid.UUID
var allExperienceNames []string

Expand All @@ -177,6 +190,8 @@
if err != nil {
log.Fatal("failed to parse metrics-build ID: ", err)
}
fmt.Println("Checking the compatiblity")
metricsBuildCompatible = checkSystemMetricsBuildCompatibility(projectID, systemID, metricsBuildID)
}

if viper.IsSet(batchExperienceTagIDsKey) && viper.IsSet(batchExperienceTagNamesKey) {
Expand Down Expand Up @@ -221,6 +236,35 @@
}
}

compatibleExperiences := getCompatibleExperiences(projectID, systemID)
// Validate the experience ID list
for _, experienceID := range allExperienceIDs {
found := false
for _, compatibleExperience := range compatibleExperiences {
if *compatibleExperience.ExperienceID == experienceID {

Check failure on line 244 in cmd/resim/commands/batch.go

View workflow job for this annotation

GitHub Actions / Lint and Build

invalid operation: cannot indirect compatibleExperience.ExperienceID (variable of type uuid.UUID)

Check failure on line 244 in cmd/resim/commands/batch.go

View workflow job for this annotation

GitHub Actions / Lint and Build

invalid operation: cannot indirect compatibleExperience.ExperienceID (variable of type uuid.UUID)

Check failure on line 244 in cmd/resim/commands/batch.go

View workflow job for this annotation

GitHub Actions / Run end-to-end test

invalid operation: cannot indirect compatibleExperience.ExperienceID (variable of type uuid.UUID)
found = true
break
}
}
if !found {
missingExperience := actualGetExperience(projectID, experienceID)
incompatibleExperienceNames = append(incompatibleExperienceNames, *missingExperience.Name)

Check failure on line 251 in cmd/resim/commands/batch.go

View workflow job for this annotation

GitHub Actions / Lint and Build

invalid operation: cannot indirect missingExperience.Name (variable of type string)

Check failure on line 251 in cmd/resim/commands/batch.go

View workflow job for this annotation

GitHub Actions / Lint and Build

invalid operation: cannot indirect missingExperience.Name (variable of type string)

Check failure on line 251 in cmd/resim/commands/batch.go

View workflow job for this annotation

GitHub Actions / Run end-to-end test

invalid operation: cannot indirect missingExperience.Name (variable of type string)
}
}
// Validate the experience name list:
for _, experienceName := range allExperienceNames {
found := false
for _, compatibleExperience := range compatibleExperiences {
if *compatibleExperience.Name == experienceName {

Check failure on line 258 in cmd/resim/commands/batch.go

View workflow job for this annotation

GitHub Actions / Lint and Build

invalid operation: cannot indirect compatibleExperience.Name (variable of type string)

Check failure on line 258 in cmd/resim/commands/batch.go

View workflow job for this annotation

GitHub Actions / Lint and Build

invalid operation: cannot indirect compatibleExperience.Name (variable of type string)

Check failure on line 258 in cmd/resim/commands/batch.go

View workflow job for this annotation

GitHub Actions / Run end-to-end test

invalid operation: cannot indirect compatibleExperience.Name (variable of type string)
found = true
break
}
}
if !found {
incompatibleExperienceNames = append(incompatibleExperienceNames, experienceName)
}
}

// Build the request body
body := api.BatchInput{
BuildID: &buildID,
Expand All @@ -247,6 +291,29 @@
body.MetricsBuildID = &metricsBuildID
}

if !batchGithub {
// If the metrics build is incompatible or there are incompatible experiences, prompt the user:
if !metricsBuildCompatible {
wordPromptContent := promptContent{
"Please choose either Y/n.",
"The metrics build you have chosen is not registered as compatible with the build. Are you sure you want to continue? [Y/n]",
}
word := promptGetInput(wordPromptContent)
if word == "n" {
log.Fatal("Batch not created, due to incompatible metrics build")
}
}
if len(incompatibleExperienceNames) > 0 {
wordPromptContent := promptContent{
"Please choose either Y/n.",
fmt.Sprintf("The following experience(s) are not compatible with the system:\n %v. Are you sure you want to continue? [Y/n]", incompatibleExperienceNames),
}
word := promptGetInput(wordPromptContent)
if word == "n" {
log.Fatal("Batch not created, due to incompatible experiences")
}
}
}
// Make the request
response, err := Client.CreateBatchWithResponse(context.Background(), projectID, body)
if err != nil {
Expand Down Expand Up @@ -486,3 +553,98 @@
ValidateResponse(http.StatusOK, "failed to cancel batch", response.HTTPResponse, response.Body)
fmt.Println("Batch cancelled successfully!")
}

// Helpers
func checkSystemMetricsBuildCompatibility(projectID uuid.UUID, systemID uuid.UUID, metricsBuildID uuid.UUID) bool {
found := false
var pageToken *string = nil
pageLoop:
for {
// Check if the metrics build is compatible with the system
response, err := Client.GetSystemsForMetricsBuildWithResponse(context.Background(), projectID, metricsBuildID, &api.GetSystemsForMetricsBuildParams{
PageSize: Ptr(100),
PageToken: pageToken,
})
if err != nil {
log.Fatal("failed to list systems for metrics build:", err)
}
ValidateResponse(http.StatusOK, "failed to list systems for metrics build", response.HTTPResponse, response.Body)
if response.JSON200 == nil {
log.Fatal("empty response when listing systems for metrics build")
}
pageToken = response.JSON200.NextPageToken
systems := *response.JSON200.Systems
for _, s := range systems {
if *s.SystemID == systemID {

Check failure on line 578 in cmd/resim/commands/batch.go

View workflow job for this annotation

GitHub Actions / Lint and Build

invalid operation: cannot indirect s.SystemID (variable of type uuid.UUID)

Check failure on line 578 in cmd/resim/commands/batch.go

View workflow job for this annotation

GitHub Actions / Lint and Build

invalid operation: cannot indirect s.SystemID (variable of type uuid.UUID)

Check failure on line 578 in cmd/resim/commands/batch.go

View workflow job for this annotation

GitHub Actions / Run end-to-end test

invalid operation: cannot indirect s.SystemID (variable of type uuid.UUID)
found = true
break pageLoop
}
}
if pageToken == nil || *pageToken == "" {
break
}
}
return found
}

func getCompatibleExperiences(projectID uuid.UUID, systemID uuid.UUID) []api.Experience {
var pageToken *string = nil
var compatibleExperiences []api.Experience
for {
// Page through the applicable experiecnes
response, err := Client.ListExperiencesForSystemWithResponse(context.Background(), projectID, systemID, &api.ListExperiencesForSystemParams{
PageSize: Ptr(100),
PageToken: pageToken,
})
if err != nil {
log.Fatal("failed to list experiences for system:", err)
}
ValidateResponse(http.StatusOK, "failed to list experiences for system", response.HTTPResponse, response.Body)
if response.JSON200 == nil {
log.Fatal("empty response when listing experiences for system")
}
pageToken = response.JSON200.NextPageToken
compatibleExperiences = append(compatibleExperiences, *response.JSON200.Experiences...)
if pageToken == nil || *pageToken == "" {
break
}
}
return compatibleExperiences
}

type promptContent struct {
errorMsg string
label string
}

func promptGetInput(pc promptContent) string {
validate := func(input string) error {
if input != "Y" && input != "n" {
return errors.New(pc.errorMsg)
}
return nil
}

templates := &promptui.PromptTemplates{
Prompt: "{{ . }} ",
Valid: "{{ . | green }} ",
Invalid: "{{ . | red }} ",
Success: "{{ . | bold }} ",
}

prompt := promptui.Prompt{
Label: pc.label,
Templates: templates,
Validate: validate,
}

result, err := prompt.Run()
if err != nil {
fmt.Printf("Prompt failed %v\n", err)
os.Exit(1)
}

fmt.Printf("Input: %s\n", result)

return result
}
11 changes: 11 additions & 0 deletions cmd/resim/commands/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -274,3 +274,14 @@ func createBuild(ccmd *cobra.Command, args []string) {
fmt.Printf("Build ID: %s\n", build.BuildID.String())
}
}

func actualGetBuild(projectID uuid.UUID, buildID uuid.UUID) *api.Build {
var build *api.Build
response, err := Client.GetBuildWithResponse(context.Background(), projectID, buildID)
if err != nil {
log.Fatal("unable to retrieve build:", err)
}
ValidateResponse(http.StatusOK, "unable to retrieve build", response.HTTPResponse, response.Body)
build = response.JSON200
return build
}
13 changes: 13 additions & 0 deletions cmd/resim/commands/experience.go
Original file line number Diff line number Diff line change
Expand Up @@ -419,3 +419,16 @@ func getExperienceID(client api.ClientWithResponsesInterface, projectID uuid.UUI
}
return experienceID
}

func actualGetExperience(projectID uuid.UUID, experienceID uuid.UUID) api.Experience {
response, err := Client.GetExperienceWithResponse(context.Background(), projectID, experienceID)
if err != nil {
log.Fatal("unable to retrieve experience:", err)
}
if response.HTTPResponse.StatusCode == http.StatusNotFound {
log.Fatal("failed to find experience with requested id: ", experienceID.String())
} else {
ValidateResponse(http.StatusOK, "unable to retrieve experience", response.HTTPResponse, response.Body)
}
return *response.JSON200
}
18 changes: 11 additions & 7 deletions cmd/resim/commands/system.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,23 +139,27 @@ func init() {
}

func getSystem(ccmd *cobra.Command, args []string) {
var system *api.System
var system api.System
projectID := getProjectID(Client, viper.GetString(systemProjectKey))
systemID := getSystemID(Client, projectID, viper.GetString(systemKey), true)
system = actualGetSystem(projectID, systemID)
enc := json.NewEncoder(os.Stdout)
enc.SetEscapeHTML(false)
enc.SetIndent("", " ")
enc.Encode(system)
}

func actualGetSystem(projectID uuid.UUID, systemID uuid.UUID) api.System {
response, err := Client.GetSystemWithResponse(context.Background(), projectID, systemID)
if err != nil {
log.Fatal("unable to retrieve system:", err)
}
if response.HTTPResponse.StatusCode == http.StatusNotFound {
log.Fatal("failed to find system with requested id: ", projectID.String())
log.Fatal("failed to find system with requested id: ", systemID.String())
} else {
ValidateResponse(http.StatusOK, "unable to retrieve system", response.HTTPResponse, response.Body)
}
system = response.JSON200
enc := json.NewEncoder(os.Stdout)
enc.SetEscapeHTML(false)
enc.SetIndent("", " ")
enc.Encode(system)
return *response.JSON200
}

func listSystems(cmd *cobra.Command, args []string) {
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ require (
)

require (
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/manifoldco/promptui v0.9.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
)
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpV
github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo=
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cli/browser v1.3.0 h1:LejqCrpWr+1pRqmEPDGnTZOjsMe7sehifLynZJuqJpo=
Expand Down Expand Up @@ -221,6 +222,8 @@ github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
Expand Down Expand Up @@ -410,6 +413,7 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand Down
Loading