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

Add command marketplace #817

Merged
merged 13 commits into from
Mar 24, 2019
Merged
Show file tree
Hide file tree
Changes from 6 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: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,3 @@ node_modules
# do not store vendor dir
vendor
tmp-systemservices
node_modules
15 changes: 14 additions & 1 deletion commands/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ type ServiceExecutor interface {
ServiceDeleteAll(deleteData bool) error
ServiceDelete(deleteData bool, ids ...string) error
ServiceDeploy(path string, env map[string]string, statuses chan provider.DeployStatus) (sid string, hash string, validationError, err error)
ServicePublishDefinitionFile(path string) (string, error)
ServiceListenEvents(id, eventFilter string) (chan *coreapi.EventData, chan error, error)
ServiceListenResults(id, taskFilter, outputFilter string, tagFilters []string) (chan *coreapi.ResultData, chan error, error)
ServiceLogs(id string, dependencies ...string) (logs []*provider.Log, closer func(), errC chan error, err error)
Expand All @@ -39,6 +38,19 @@ type ServiceExecutor interface {
ServiceInitDownloadTemplate(t *servicetemplate.Template, dst string) error
}

// MarketplaceExecutor is an interface that handles marketplace commands.
type MarketplaceExecutor interface {
CreateManifest(path string, hash string) (provider.MarketplaceManifestData, error)
UploadServiceFiles(path string, manifest provider.MarketplaceManifestData) (protocol string, source string, err error)
PublishServiceVersion(sid, manifest, manifestProtocol, from string) (provider.Transaction, error)
CreateServiceOffer(sid string, price string, duration string, from string) (provider.Transaction, error)
DisableServiceOffer(sid, offerIndex, from string) (provider.Transaction, error)
Purchase(sid, offerIndex, from string) ([]provider.Transaction, error)
TransferServiceOwnership(sid, newOwner, from string) (provider.Transaction, error)
SendSignedTransaction(signedTransaction string) (provider.TransactionReceipt, error)
GetService(sid string) (provider.MarketplaceService, error)
}

// WalletExecutor is an interface that handles wallet commands.
type WalletExecutor interface {
List() ([]string, error)
Expand All @@ -54,6 +66,7 @@ type WalletExecutor interface {
type Executor interface {
RootExecutor
ServiceExecutor
MarketplaceExecutor
WalletExecutor
}

Expand Down
105 changes: 105 additions & 0 deletions commands/marketplace_create_offer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package commands

import (
"fmt"
"strings"

"github.com/mesg-foundation/core/commands/provider"
"github.com/mesg-foundation/core/utils/pretty"
"github.com/spf13/cobra"
survey "gopkg.in/AlecAivazis/survey.v1"
)

type marketplaceCreateOfferCmd struct {
baseMarketplaceCmd

service provider.MarketplaceService
price string // in MESG token
duration string

e Executor
}

func newMarketplaceCreateOfferCmd(e Executor) *marketplaceCreateOfferCmd {
c := &marketplaceCreateOfferCmd{e: e}
c.cmd = newCommand(&cobra.Command{
Use: "create-offer",
Short: "Create a new offer on a service on the MESG Marketplace",
Example: `mesg-core marketplace create-offer SID`,
PreRunE: c.preRunE,
RunE: c.runE,
Args: cobra.MinimumNArgs(1),
})
c.setupFlags()
c.cmd.Flags().StringVarP(&c.price, "price", "", c.price, "Price (in MESG token) of the offer to create")
c.cmd.Flags().StringVarP(&c.duration, "duration", "", c.duration, "Duration (in second) of the offer to create")
return c
}

func (c *marketplaceCreateOfferCmd) preRunE(cmd *cobra.Command, args []string) error {
var (
err error
)

if err := c.askAccountAndPassphrase(); err != nil {
return err
}

if c.price == "" {
if err := askInput("Enter the price (in MESG Token) of the offer", &c.price); err != nil {
return err
}
}
// if c.price < 0 {
// return fmt.Errorf("Price cannot be negative")
// }
if c.duration == "" {
if err := askInput("Enter the duration (in second) of the offer", &c.duration); err != nil {
return err
}
}

pretty.Progress("Getting service data...", func() {
c.service, err = c.e.GetService(args[0])
})
if err != nil {
return err
}

if !strings.EqualFold(c.service.Owner, c.account) {
Copy link
Member

Choose a reason for hiding this comment

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

Fine but I think this check should be done by the system service marketplace, the CLI should just call the createOffer task without having to do any check on the data

return fmt.Errorf("the service's owner %q is different than the specified account", c.service.Owner)
}

var confirmed bool
if err := survey.AskOne(&survey.Confirm{
Message: fmt.Sprintf("Are you sure to create offer on service %q with price %q and duration %q?", args[0], c.price, c.duration),
}, &confirmed, nil); err != nil {
return err
}
if !confirmed {
return fmt.Errorf("cancelled")
}

return nil
}

func (c *marketplaceCreateOfferCmd) runE(cmd *cobra.Command, args []string) error {
var (
tx provider.Transaction
err error
)
pretty.Progress("Creating offer on the marketplace...", func() {
tx, err = c.e.CreateServiceOffer(args[0], c.price, c.duration, c.account)
if err != nil {
return
}
_, err = c.signAndSendTransaction(c.e, tx)
})
if err != nil {
return err
}
fmt.Printf("%s Offer created with success\n", pretty.SuccessSign)
Copy link
Member

Choose a reason for hiding this comment

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

If we have these commands in the CLI we need to display all relevant information. Here for example the index of the offer. Here I cannot from the CLI only do anything after that

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes you are right. The complicated thing is the marketplace service is returning a generic transaction receipt. It is not properly decoded because it doesn't know which method / event has been called or triggered.
I will work on a solution, but this is not a blocking thing for this PR.

Copy link
Member

Choose a reason for hiding this comment

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

Not blocking for this PR for sure but one solution is to listen at the event from the marketplace.
That's the implementation i did for the js cli. Like that we have the confirmation with the receipt (but we don't care much) and we have the event with the relevant data from the event from the smart contract.

Copy link
Member

Choose a reason for hiding this comment

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

here is an example for this implementation based on event
feature/command-marketplace...feature/command-marketplace-with-listen

fmt.Printf("%s See it on the marketplace: https://marketplace.mesg.com/services/%s\n", pretty.SuccessSign, c.service.Sid)

return nil
}
118 changes: 118 additions & 0 deletions commands/marketplace_publish.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package commands

import (
"fmt"
"strings"

"github.com/mesg-foundation/core/commands/provider"
"github.com/mesg-foundation/core/utils/pretty"
"github.com/spf13/cobra"
survey "gopkg.in/AlecAivazis/survey.v1"
)

type marketplacePublishCmd struct {
baseMarketplaceCmd

path string
manifest provider.MarketplaceManifestData
service provider.MarketplaceService

e Executor
}

func newMarketplacePublishCmd(e Executor) *marketplacePublishCmd {
c := &marketplacePublishCmd{e: e}
c.cmd = newCommand(&cobra.Command{
Use: "publish",
Short: "Publish a service on the MESG Marketplace",
Example: `mesg-core marketplace publish PATH_TO_SERVICE`,
PreRunE: c.preRunE,
RunE: c.runE,
Args: cobra.MinimumNArgs(1),
})
c.setupFlags()
return c
}

func (c *marketplacePublishCmd) preRunE(cmd *cobra.Command, args []string) error {
var (
err error
question = "a new service"
)

if err := c.askAccountAndPassphrase(); err != nil {
return err
}

c.path = getFirstOrCurrentPath(args)

sid, hash, err := deployService(c.e, c.path, map[string]string{})
if err != nil {
return err
}
fmt.Printf("%s Service deployed with sid %s and hash %s\n", pretty.SuccessSign, pretty.Success(sid), pretty.Success(hash))

c.manifest, err = c.e.CreateManifest(c.path, hash)
if err != nil {
return err
}

pretty.Progress("Getting service data...", func() {
c.service, err = c.e.GetService(c.manifest.Service.Definition.Sid)
})
outputError, isMarketplaceError := err.(provider.MarketplaceErrorOutput)
if err != nil && !isMarketplaceError {
return err
}
if outputError.Code != "notFound" {
question = "a new version of service"
if !strings.EqualFold(c.service.Owner, c.account) {
return fmt.Errorf("the service's owner %q is different than the specified account", c.service.Owner)
}
}

var confirmed bool
if err := survey.AskOne(&survey.Confirm{
Message: fmt.Sprintf("Are you sure to publish %s %q from path %q using account %q?", question, c.manifest.Service.Definition.Sid, c.path, c.account),
}, &confirmed, nil); err != nil {
return err
}
if !confirmed {
return fmt.Errorf("cancelled")
}

return nil
}

func (c *marketplacePublishCmd) runE(cmd *cobra.Command, args []string) error {
var (
tx provider.Transaction
err error
manifestProtocol string
manifestSource string
)

pretty.Progress("Uploading service source code on the marketplace...", func() {
// TODO: add a progress for the upload
manifestProtocol, manifestSource, err = c.e.UploadServiceFiles(c.path, c.manifest)
})
if err != nil {
return err
}
pretty.Progress("Publishing service on the marketplace...", func() {
tx, err = c.e.PublishServiceVersion(c.manifest.Service.Definition.Sid, manifestSource, manifestProtocol, c.account)
if err != nil {
return
}
_, err = c.signAndSendTransaction(c.e, tx)
})
if err != nil {
return err
}
fmt.Printf("%s Service published with success\n", pretty.SuccessSign)
fmt.Printf("%s See it on the marketplace: https://marketplace.mesg.com/services/%s\n", pretty.SuccessSign, c.manifest.Service.Definition.Sid)

fmt.Printf("%s To create a service offer, execute the command:\n\tmesg-core marketplace create-offer %s\n", pretty.SuccessSign, c.manifest.Service.Definition.Sid)
antho1404 marked this conversation as resolved.
Show resolved Hide resolved

return nil
}
108 changes: 108 additions & 0 deletions commands/marketplace_purchase.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package commands

import (
"fmt"
"strconv"

"github.com/mesg-foundation/core/commands/provider"
"github.com/mesg-foundation/core/utils/pretty"
"github.com/spf13/cobra"
survey "gopkg.in/AlecAivazis/survey.v1"
)

type marketplacePurchaseCmd struct {
baseMarketplaceCmd

offerIndex string

e Executor
}

func newMarketplacePurchaseCmd(e Executor) *marketplacePurchaseCmd {
c := &marketplacePurchaseCmd{e: e}
c.cmd = newCommand(&cobra.Command{
Use: "purchase",
Short: "Purchase a service on the MESG Marketplace",
Example: `mesg-core marketplace purchase SID`,
PreRunE: c.preRunE,
RunE: c.runE,
Args: cobra.ExactArgs(1),
})
c.setupFlags()
c.cmd.Flags().StringVarP(&c.offerIndex, "offerIndex", "i", c.offerIndex, "Offer index to purchase")
NicolasMahe marked this conversation as resolved.
Show resolved Hide resolved
return c
}

func (c *marketplacePurchaseCmd) preRunE(cmd *cobra.Command, args []string) error {
var (
service provider.MarketplaceService
err error
)

if err := c.askAccountAndPassphrase(); err != nil {
return err
}

if c.offerIndex == "" {
// TODO: should display the list of offers and ask to select one
if err := askInput("Enter the offer index to purchase", &c.offerIndex); err != nil {
return err
}
}

pretty.Progress("Getting service data...", func() {
service, err = c.e.GetService(args[0])
})
if err != nil {
return err
}

offerIndex, err := strconv.Atoi(c.offerIndex)
if err != nil {
return err
}
if offerIndex >= len(service.Offers) {
return fmt.Errorf("offer index %d doesn't exist", offerIndex)
}
offer := service.Offers[offerIndex]
if !offer.Active {
return fmt.Errorf("cannot purchase this offer because it is not active")
}

var confirmed bool
if err := survey.AskOne(&survey.Confirm{
Message: fmt.Sprintf("Are you sure to purchase service %q for price %q and duration %q?", args[0], offer.Price, offer.Duration),
}, &confirmed, nil); err != nil {
return err
}
if !confirmed {
return fmt.Errorf("cancelled")
}

return nil
}

func (c *marketplacePurchaseCmd) runE(cmd *cobra.Command, args []string) error {
var (
transactions []provider.Transaction
err error
)
pretty.Progress("Purchasing offer on the marketplace...", func() {
transactions, err = c.e.Purchase(args[0], c.offerIndex, c.account)
if err != nil {
return
}
for _, tx := range transactions {
_, err = c.signAndSendTransaction(c.e, tx)
if err != nil {
return
}
}
})
if err != nil {
return err
}
fmt.Printf("%s Offer purchased with success\n", pretty.SuccessSign)

return nil
}
Loading