Skip to content

Commit

Permalink
add support for starting custom playgrounds and contributed challenges
Browse files Browse the repository at this point in the history
  • Loading branch information
iximiuz committed Dec 14, 2024
1 parent 2ea025d commit 578a44f
Show file tree
Hide file tree
Showing 12 changed files with 224 additions and 7 deletions.
50 changes: 49 additions & 1 deletion cmd/challenge/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/iximiuz/labctl/cmd/sshproxy"
"github.com/iximiuz/labctl/internal/api"
"github.com/iximiuz/labctl/internal/labcli"
"github.com/iximiuz/labctl/internal/safety"
issh "github.com/iximiuz/labctl/internal/ssh"
)

Expand All @@ -30,6 +31,8 @@ type startOptions struct {
keepAlive bool

ide bool

safetyDisclaimerConsent bool
}

func newStartCommand(cli labcli.CLI) *cobra.Command {
Expand Down Expand Up @@ -97,6 +100,12 @@ func newStartCommand(cli labcli.CLI) *cobra.Command {
false,
`Open the challenge playground in the IDE (only VSCode is supported at the moment)`,
)
flags.BoolVar(
&opts.safetyDisclaimerConsent,
"safety-disclaimer-consent",
false,
`Acknowledge the safety disclaimer`,
)

return cmd
}
Expand All @@ -113,7 +122,15 @@ const (
)

func runStartChallenge(ctx context.Context, cli labcli.CLI, opts *startOptions) error {
chal, err := cli.Client().StartChallenge(ctx, opts.challenge)
var err error
opts.safetyDisclaimerConsent, err = showSafetyDisclaimerIfNeeded(ctx, opts.challenge, cli, opts.safetyDisclaimerConsent)
if err != nil {
return err
}

chal, err := cli.Client().StartChallenge(ctx, opts.challenge, api.StartChallengeOptions{
SafetyDisclaimerConsent: opts.safetyDisclaimerConsent,
})
if err != nil {
return fmt.Errorf("couldn't start solving the challenge: %w", err)
}
Expand Down Expand Up @@ -272,3 +289,34 @@ func runStartChallenge(ctx context.Context, cli labcli.CLI, opts *startOptions)
}
}
}

func showSafetyDisclaimerIfNeeded(
ctx context.Context,
chalName string,
cli labcli.CLI,
consent bool,
) (bool, error) {
if consent {
return true, nil
}

ch, err := cli.Client().GetChallenge(ctx, chalName)
if err != nil {
return false, fmt.Errorf("couldn't get challenge %q: %w", chalName, err)
}

if ch.IsOfficial() {
return true, nil
}

me, err := cli.Client().GetMe(ctx)
if err != nil {
return false, fmt.Errorf("couldn't get the current user info: %w", err)
}

if ch.IsAuthoredBy(me.ID) {
return true, nil
}

return safety.ShowSafetyDisclaimer(cli)
}
2 changes: 1 addition & 1 deletion cmd/content/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ func createTutorial(ctx context.Context, cli labcli.CLI, opts *createOptions) (c
func hasAuthorProfile(ctx context.Context, cli labcli.CLI) (bool, error) {
me, err := cli.Client().GetMe(ctx)
if err != nil {
return false, fmt.Errorf("couldn't get the current user: %w", err)
return false, fmt.Errorf("couldn't get the current user info: %w", err)
}

authors, err := cli.Client().ListAuthors(ctx, api.ListAuthorsFilter{
Expand Down
44 changes: 43 additions & 1 deletion cmd/playground/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/iximiuz/labctl/cmd/sshproxy"
"github.com/iximiuz/labctl/internal/api"
"github.com/iximiuz/labctl/internal/labcli"
"github.com/iximiuz/labctl/internal/safety"
)

const startPlaygroundTimeout = 10 * time.Minute
Expand All @@ -30,6 +31,8 @@ type startOptions struct {

ide bool

safetyDisclaimerConsent bool

skipWaitInit bool

quiet bool
Expand Down Expand Up @@ -95,6 +98,12 @@ func newStartCommand(cli labcli.CLI) *cobra.Command {
false,
`Open the playground in the IDE (only VSCode is supported at the moment)`,
)
flags.BoolVar(
&opts.safetyDisclaimerConsent,
"safety-disclaimer-consent",
false,
`Acknowledge the safety disclaimer`,
)
flags.BoolVar(
&opts.skipWaitInit,
"skip-wait-init",
Expand All @@ -113,8 +122,15 @@ func newStartCommand(cli labcli.CLI) *cobra.Command {
}

func runStartPlayground(ctx context.Context, cli labcli.CLI, opts *startOptions) error {
var err error
opts.safetyDisclaimerConsent, err = showSafetyDisclaimerIfNeeded(ctx, opts.playground, cli, opts.safetyDisclaimerConsent)
if err != nil {
return err
}

play, err := cli.Client().CreatePlay(ctx, api.CreatePlayRequest{
Playground: opts.playground,
Playground: opts.playground,
SafetyDisclaimerConsent: opts.safetyDisclaimerConsent,
})
if err != nil {
return fmt.Errorf("couldn't create a new playground: %w", err)
Expand Down Expand Up @@ -205,3 +221,29 @@ func listKnownPlaygrounds(ctx context.Context, cli labcli.CLI) string {

return strings.Join(res, "\n")
}

func showSafetyDisclaimerIfNeeded(ctx context.Context, playgroundName string, cli labcli.CLI, consent bool) (bool, error) {
if consent {
return true, nil
}

playground, err := cli.Client().GetPlayground(ctx, playgroundName)
if err != nil {
return false, fmt.Errorf("couldn't get the playground: %w", err)
}

if playground.Owner == "" { // official playgrounds don't need consent
return true, nil
}

me, err := cli.Client().GetMe(ctx)
if err != nil {
return false, fmt.Errorf("couldn't get the current user info: %w", err)
}

if me.ID == playground.Owner {
return true, nil
}

return safety.ShowSafetyDisclaimer(cli)
}
4 changes: 4 additions & 0 deletions internal/api/authors.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@ import (
)

type Author struct {
UserID string `json:"userId"`

DisplayName string `json:"displayName"`
ExternalProfileURL string `json:"externalProfileUrl"`

Official bool `json:"official,omitempty"`
}

type CreateAuthorRequest struct {
Expand Down
37 changes: 35 additions & 2 deletions internal/api/challenges.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ type Challenge struct {
Categories []string `json:"categories" yaml:"categories"`
Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"`

Authors []Author `json:"authors" yaml:"authors"`

PageURL string `json:"pageUrl" yaml:"pageUrl"`

AttemptCount int `json:"attemptCount" yaml:"attemptCount"`
Expand All @@ -41,6 +43,24 @@ func (ch *Challenge) GetPageURL() string {
return ch.PageURL
}

func (ch *Challenge) IsOfficial() bool {
for _, author := range ch.Authors {
if !author.Official {
return false
}
}
return len(ch.Authors) > 0
}

func (ch *Challenge) IsAuthoredBy(userID string) bool {
for _, a := range ch.Authors {
if a.UserID == userID {
return true
}
}
return false
}

type CreateChallengeRequest struct {
Name string `json:"name"`
Sample bool `json:"sample"`
Expand Down Expand Up @@ -86,8 +106,21 @@ func (c *Client) ListAuthoredChallenges(ctx context.Context) ([]Challenge, error
return challenges, c.GetInto(ctx, "/challenges/authored", nil, nil, &challenges)
}

func (c *Client) StartChallenge(ctx context.Context, name string) (*Challenge, error) {
body, err := toJSONBody(map[string]any{"started": true})
type StartChallengeOptions struct {
SafetyDisclaimerConsent bool
}

func (c *Client) StartChallenge(ctx context.Context, name string, opts StartChallengeOptions) (*Challenge, error) {
type startChallengeRequest struct {
Started bool `json:"started"`
SafetyDisclaimerConsent bool `json:"safetyDisclaimerConsent,omitempty"`
}
req := startChallengeRequest{
Started: true,
SafetyDisclaimerConsent: opts.SafetyDisclaimerConsent,
}

body, err := toJSONBody(req)
if err != nil {
return nil, err
}
Expand Down
20 changes: 20 additions & 0 deletions internal/api/courses.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ type Course struct {
Title string `json:"title" yaml:"title"`

PageURL string `json:"pageUrl" yaml:"pageUrl"`

Authors []Author `json:"authors" yaml:"authors"`
}

var _ content.Content = (*Course)(nil)
Expand All @@ -30,6 +32,24 @@ func (c *Course) GetPageURL() string {
return c.PageURL
}

func (c *Course) IsOfficial() bool {
for _, author := range c.Authors {
if !author.Official {
return false
}
}
return len(c.Authors) > 0
}

func (c *Course) IsAuthoredBy(userID string) bool {
for _, a := range c.Authors {
if a.UserID == userID {
return true
}
}
return false
}

type CourseVariant string

const (
Expand Down
1 change: 1 addition & 0 deletions internal/api/playgrounds.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
)

type Playground struct {
Owner string `json:"owner"`
Name string `json:"name"`
Title string `json:"title"`
Description string `json:"description"`
Expand Down
3 changes: 2 additions & 1 deletion internal/api/plays.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,8 @@ func (m *Machine) HasUser(name string) bool {
}

type CreatePlayRequest struct {
Playground string `json:"playground"`
Playground string `json:"playground"`
SafetyDisclaimerConsent bool `json:"safetyDisclaimerConsent"`
}

func (c *Client) CreatePlay(ctx context.Context, req CreatePlayRequest) (*Play, error) {
Expand Down
20 changes: 20 additions & 0 deletions internal/api/skillpath.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ type SkillPath struct {
Title string `json:"title" yaml:"title"`

PageURL string `json:"pageUrl" yaml:"pageUrl"`

Authors []Author `json:"authors" yaml:"authors"`
}

var _ content.Content = (*SkillPath)(nil)
Expand All @@ -30,6 +32,24 @@ func (t *SkillPath) GetPageURL() string {
return t.PageURL
}

func (t *SkillPath) IsOfficial() bool {
for _, author := range t.Authors {
if !author.Official {
return false
}
}
return len(t.Authors) > 0
}

func (t *SkillPath) IsAuthoredBy(userID string) bool {
for _, a := range t.Authors {
if a.UserID == userID {
return true
}
}
return false
}

type CreateSkillPathRequest struct {
Name string `json:"name"`
Sample bool `json:"sample"`
Expand Down
20 changes: 20 additions & 0 deletions internal/api/tutorials.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ type Tutorial struct {
Title string `json:"title" yaml:"title"`

PageURL string `json:"pageUrl" yaml:"pageUrl"`

Authors []Author `json:"authors" yaml:"authors"`
}

var _ content.Content = (*Tutorial)(nil)
Expand All @@ -30,6 +32,24 @@ func (t *Tutorial) GetPageURL() string {
return t.PageURL
}

func (t *Tutorial) IsOfficial() bool {
for _, author := range t.Authors {
if !author.Official {
return false
}
}
return len(t.Authors) > 0
}

func (t *Tutorial) IsAuthoredBy(userID string) bool {
for _, a := range t.Authors {
if a.UserID == userID {
return true
}
}
return false
}

type CreateTutorialRequest struct {
Name string `json:"name"`
Sample bool `json:"sample"`
Expand Down
5 changes: 4 additions & 1 deletion internal/labcli/cliutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,10 @@ type StatusError struct {
var _ error = StatusError{}

func NewStatusError(code int, format string, a ...any) StatusError {
status := strings.TrimSuffix(fmt.Sprintf(format, a...), ".") + "."
status := fmt.Sprintf(format, a...)
if !strings.HasSuffix(status, ".") && !strings.HasSuffix(status, "!") {
status += "."
}
return StatusError{
code: code,
status: strings.ToUpper(status[:1]) + status[1:],
Expand Down
25 changes: 25 additions & 0 deletions internal/safety/disclaimer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package safety

import (
"github.com/iximiuz/labctl/internal/labcli"
)

func ShowSafetyDisclaimer(cli labcli.CLI) (bool, error) {
cli.PrintAux("\n!!! THIRD-PARTY PLAYGROUND SAFETY WARNING !!!\n\n")
cli.PrintAux("You are about to start a playground created by another user.\n")
cli.PrintAux("Please follow these guidelines to ensure your security:\n\n")
cli.PrintAux(" • Do not process or store any confidential or sensitive data\n")
cli.PrintAux(" • Never enter passwords or API keys of sensitive accounts\n")
cli.PrintAux("\n")
cli.PrintAux("The platform is not responsible for any damage resulting from\n")
cli.PrintAux("the use of third-party playgrounds.\n\n")

if cli.Confirm(
"Do you acknowledge the risks and agree to continue?",
"Yes", "No",
) {
return true, nil
}

return false, labcli.NewStatusError(0, "See you later!")
}

0 comments on commit 578a44f

Please sign in to comment.