diff --git a/conf/cm-grasshopper.yaml b/conf/cm-grasshopper.yaml index 695d337..2ae79e5 100644 --- a/conf/cm-grasshopper.yaml +++ b/conf/cm-grasshopper.yaml @@ -1,3 +1,6 @@ cm-grasshopper: listen: port: 8084 + honeybee: + server_address: 127.0.0.1 + server_port: 8081 diff --git a/go.mod b/go.mod index c8dabfa..e048f90 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,14 @@ module github.com/cloud-barista/cm-grasshopper go 1.22.3 require ( + github.com/cloud-barista/cm-honeybee/server v0.0.0-20240522171320-cb61f875f480 github.com/glebarez/sqlite v1.11.0 github.com/jollaman999/utils v1.0.10 github.com/labstack/echo/v4 v4.12.0 + github.com/melbahja/goph v1.4.0 github.com/swaggo/echo-swagger v1.4.1 github.com/swaggo/swag v1.16.3 + golang.org/x/crypto v0.23.0 gopkg.in/yaml.v3 v3.0.1 gorm.io/gorm v1.25.10 ) @@ -26,17 +29,19 @@ require ( github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/josharian/intern v1.0.0 // indirect + github.com/kr/fs v0.1.0 // indirect github.com/labstack/gommon v0.4.2 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect github.com/opencontainers/selinux v1.11.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pkg/sftp v1.13.5 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/swaggo/files/v2 v2.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect - golang.org/x/crypto v0.23.0 // indirect golang.org/x/net v0.25.0 // indirect golang.org/x/sys v0.20.0 // indirect golang.org/x/text v0.15.0 // indirect diff --git a/go.sum b/go.sum index cc62280..541cafd 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,8 @@ github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/cloud-barista/cm-honeybee/server v0.0.0-20240522171320-cb61f875f480 h1:+nHbaiKaqrq1QvqKNnQ0tL7SVLDWLvnEZna1xAVCJdE= +github.com/cloud-barista/cm-honeybee/server v0.0.0-20240522171320-cb61f875f480/go.mod h1:T51G/+V1S3MYUJINegDg3jXznuV+XFTHppG85RX4fgw= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= @@ -32,6 +35,8 @@ github.com/jollaman999/utils v1.0.10 h1:LBXsM9fKH1tXACTgsRnSKHXkHE3TaCymwiMp2kFX github.com/jollaman999/utils v1.0.10/go.mod h1:fb4x+o0k105MFIBBaNRN+tat7F3RbkolnURccQd3UEA= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -47,16 +52,24 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/melbahja/goph v1.4.0 h1:z0PgDbBFe66lRYl3v5dGb9aFgPy0kotuQ37QOwSQFqs= +github.com/melbahja/goph v1.4.0/go.mod h1:uG+VfK2Dlhk+O32zFrRlc3kYKTlV6+BtvPWd/kK7U68= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaLpt7tQ7oU= github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.13.5 h1:a3RLUqkyjYRtBTZJZ1VRrKbN3zhuPLlUc3sphVz81go= +github.com/pkg/sftp v1.13.5/go.mod h1:wHDZ0IZX6JcBYRK1TH9bcVq8G7TLpVHYIGJRFnmPfxg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/swaggo/echo-swagger v1.4.1 h1:Yf0uPaJWp1uRtDloZALyLnvdBeoEL5Kc7DtnjzO/TUk= @@ -69,29 +82,65 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw= golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s= diff --git a/lib/config/cm-grasshopper.go b/lib/config/cm-grasshopper.go index 2cf6da2..dfe5896 100644 --- a/lib/config/cm-grasshopper.go +++ b/lib/config/cm-grasshopper.go @@ -17,6 +17,10 @@ type cmGrasshopperConfig struct { Listen struct { Port string `yaml:"port"` } `yaml:"listen"` + Honeybee struct { + ServerAddress string `yaml:"server_address"` + ServerPort string `yaml:"server_port"` + } `yaml:"honeybee"` } `yaml:"cm-grasshopper"` } @@ -32,6 +36,14 @@ func checkCMGrasshopperConfigFile() error { return errors.New("config error: cm-grasshopper.listen.port has invalid value") } + if CMGrasshopperConfig.CMGrasshopper.Honeybee.ServerPort == "" { + return errors.New("config error: cm-grasshopper.honeybee.ServerPort is empty") + } + port, err = strconv.Atoi(CMGrasshopperConfig.CMGrasshopper.Honeybee.ServerPort) + if err != nil || port < 1 || port > 65535 { + return errors.New("config error: cm-grasshopper.honeybee.ServerPort has invalid value") + } + return nil } diff --git a/lib/ssh/client.go b/lib/ssh/client.go new file mode 100644 index 0000000..646489e --- /dev/null +++ b/lib/ssh/client.go @@ -0,0 +1,77 @@ +package ssh + +import ( + "encoding/json" + "errors" + "github.com/cloud-barista/cm-grasshopper/lib/config" + "github.com/cloud-barista/cm-grasshopper/pkg/api/rest/common" + honeybee "github.com/cloud-barista/cm-honeybee/server/pkg/api/rest/model" + "github.com/melbahja/goph" + "golang.org/x/crypto/ssh" + "net" +) + +type Client struct { + *goph.Client + ConnectionInfo honeybee.ConnectionInfo +} + +func AddKnownHost(host string, remote net.Addr, key ssh.PublicKey) error { + hostFound, err := goph.CheckKnownHost(host, remote, key, "") + + // Host in known hosts but key mismatch! + // Maybe because of MAN IN THE MIDDLE ATTACK! + if hostFound && err != nil { + return err + } + + if hostFound { + return nil + } + + return goph.AddKnownHost(host, remote, key, "") +} + +func NewSSHClient(connectionInfoUUID string) (*Client, error) { + data, err := common.GetHTTPRequest("http://" + config.CMGrasshopperConfig.CMGrasshopper.Honeybee.ServerAddress + + ":" + config.CMGrasshopperConfig.CMGrasshopper.Honeybee.ServerPort + + "/connection_info/" + connectionInfoUUID) + if err != nil { + return nil, err + } + + var connectionInfo honeybee.ConnectionInfo + err = json.Unmarshal(data, &connectionInfo) + if err != nil { + return nil, err + } + + var auth goph.Auth + if connectionInfo.PrivateKey != "" { + auth, err = goph.RawKey(connectionInfo.PrivateKey, "") + if err != nil { + return nil, err + } + } else if connectionInfo.Password != "" { + auth = goph.Password(connectionInfo.Password) + } else { + return nil, errors.New("failed to determine auth method") + } + + client, err := goph.NewConn(&goph.Config{ + User: connectionInfo.User, + Addr: connectionInfo.IPAddress, + Port: uint(connectionInfo.SSHPort), + Auth: auth, + Timeout: goph.DefaultTimeout, + Callback: AddKnownHost, + }) + if err != nil { + return nil, err + } + + return &Client{ + client, + connectionInfo, + }, nil +} diff --git a/lib/ssh/runCmd.go b/lib/ssh/runCmd.go new file mode 100644 index 0000000..de26435 --- /dev/null +++ b/lib/ssh/runCmd.go @@ -0,0 +1,10 @@ +package ssh + +func (c Client) RunBash(cmd string) (string, error) { + out, err := c.Run("bash -c '" + cmd + "'") + if err != nil { + return "", err + } + + return string(out), nil +} diff --git a/pkg/api/rest/controller/software.go b/pkg/api/rest/controller/software.go index 9029e1f..84ea23a 100644 --- a/pkg/api/rest/controller/software.go +++ b/pkg/api/rest/controller/software.go @@ -1,26 +1,65 @@ package controller import ( + "github.com/cloud-barista/cm-grasshopper/lib/ssh" "github.com/cloud-barista/cm-grasshopper/pkg/api/rest/common" + "github.com/cloud-barista/cm-grasshopper/pkg/api/rest/model" "github.com/labstack/echo/v4" + "net/http" + "strings" ) -func SoftwareGetList(c echo.Context) error { - uuid := c.QueryParam("uuid") - if uuid == "" { - return common.ReturnErrorMsg(c, "uuid is empty") +// InstallSoftware godoc +// +// @Summary Install Software +// @Description Install pieces of software to target. +// @Tags [Software] +// @Accept json +// @Produce json +// @Param softwareInstallReq body model.SoftwareInstallReq true "Software install request." +// @Success 200 {object} model.SoftwareInstallRes "Successfully sent SSH command." +// @Failure 400 {object} common.ErrorResponse "Sent bad request." +// @Failure 500 {object} common.ErrorResponse "Failed to sent SSH command." +// @Router /software/install [post] +func InstallSoftware(c echo.Context) error { + var err error + + softwareInstallReq := new(model.SoftwareInstallReq) + err = c.Bind(softwareInstallReq) + if err != nil { + return err + } + + client, err := ssh.NewSSHClient(softwareInstallReq.ConnectionUUID) + if err != nil { + return common.ReturnErrorMsg(c, err.Error()) + } + + var softwareInstallRes model.SoftwareInstallRes + + var packageNames string + var out string + + for _, name := range softwareInstallReq.PackageNames { + packageNames += " " + name } - //data, err := common.GetHTTPRequest("http://XXX/software") - //if err != nil { - // return common.ReturnInternalError(c, err, "Error occurred while getting software list.") - //} - //err = json.Unmarshal(data, &XXX) - //if err != nil { - // return common.ReturnInternalError(c, err, "Error occurred while parsing software list.") - //} - // - //return c.JSONPretty(http.StatusOK, softwareList, " ") - - return nil + packageType := strings.ToLower(softwareInstallReq.PackageType) + if packageType == "apt" { + out, err = client.RunBash("echo " + client.ConnectionInfo.Password + " | sudo -S -k apt-get install" + packageNames) + if err != nil { + return common.ReturnErrorMsg(c, err.Error()) + } + } else if packageType == "yum" { + out, err = client.RunBash("echo " + client.ConnectionInfo.Password + " | sudo -S -k yum install" + packageNames) + if err != nil { + return common.ReturnErrorMsg(c, err.Error()) + } + } else { + return common.ReturnErrorMsg(c, "Invalid package type: "+softwareInstallReq.PackageType) + } + + softwareInstallRes.Output = out + + return c.JSONPretty(http.StatusOK, softwareInstallRes, " ") } diff --git a/pkg/api/rest/docs/docs.go b/pkg/api/rest/docs/docs.go index 8a951fd..f527004 100644 --- a/pkg/api/rest/docs/docs.go +++ b/pkg/api/rest/docs/docs.go @@ -43,6 +43,52 @@ const docTemplate = `{ } } } + }, + "/software/install": { + "post": { + "description": "Install pieces of software to target.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "[Software]" + ], + "summary": "Install Software", + "parameters": [ + { + "description": "Software install request.", + "name": "softwareInstallReq", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/github_com_cloud-barista_cm-grasshopper_pkg_api_rest_model.SoftwareInstallReq" + } + } + ], + "responses": { + "200": { + "description": "Successfully sent SSH command.", + "schema": { + "$ref": "#/definitions/github_com_cloud-barista_cm-grasshopper_pkg_api_rest_model.SoftwareInstallRes" + } + }, + "400": { + "description": "Sent bad request.", + "schema": { + "$ref": "#/definitions/github_com_cloud-barista_cm-grasshopper_pkg_api_rest_common.ErrorResponse" + } + }, + "500": { + "description": "Failed to sent SSH command.", + "schema": { + "$ref": "#/definitions/github_com_cloud-barista_cm-grasshopper_pkg_api_rest_common.ErrorResponse" + } + } + } + } } }, "definitions": { @@ -54,6 +100,36 @@ const docTemplate = `{ } } }, + "github_com_cloud-barista_cm-grasshopper_pkg_api_rest_model.SoftwareInstallReq": { + "type": "object", + "required": [ + "connection_uuid", + "package_names", + "package_type" + ], + "properties": { + "connection_uuid": { + "type": "string" + }, + "package_names": { + "type": "array", + "items": { + "type": "string" + } + }, + "package_type": { + "type": "string" + } + } + }, + "github_com_cloud-barista_cm-grasshopper_pkg_api_rest_model.SoftwareInstallRes": { + "type": "object", + "properties": { + "output": { + "type": "string" + } + } + }, "pkg_api_rest_controller.SimpleMsg": { "type": "object", "properties": { diff --git a/pkg/api/rest/docs/swagger.json b/pkg/api/rest/docs/swagger.json index 5afc589..17c18e8 100644 --- a/pkg/api/rest/docs/swagger.json +++ b/pkg/api/rest/docs/swagger.json @@ -32,6 +32,52 @@ } } } + }, + "/software/install": { + "post": { + "description": "Install pieces of software to target.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "[Software]" + ], + "summary": "Install Software", + "parameters": [ + { + "description": "Software install request.", + "name": "softwareInstallReq", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/github_com_cloud-barista_cm-grasshopper_pkg_api_rest_model.SoftwareInstallReq" + } + } + ], + "responses": { + "200": { + "description": "Successfully sent SSH command.", + "schema": { + "$ref": "#/definitions/github_com_cloud-barista_cm-grasshopper_pkg_api_rest_model.SoftwareInstallRes" + } + }, + "400": { + "description": "Sent bad request.", + "schema": { + "$ref": "#/definitions/github_com_cloud-barista_cm-grasshopper_pkg_api_rest_common.ErrorResponse" + } + }, + "500": { + "description": "Failed to sent SSH command.", + "schema": { + "$ref": "#/definitions/github_com_cloud-barista_cm-grasshopper_pkg_api_rest_common.ErrorResponse" + } + } + } + } } }, "definitions": { @@ -43,6 +89,36 @@ } } }, + "github_com_cloud-barista_cm-grasshopper_pkg_api_rest_model.SoftwareInstallReq": { + "type": "object", + "required": [ + "connection_uuid", + "package_names", + "package_type" + ], + "properties": { + "connection_uuid": { + "type": "string" + }, + "package_names": { + "type": "array", + "items": { + "type": "string" + } + }, + "package_type": { + "type": "string" + } + } + }, + "github_com_cloud-barista_cm-grasshopper_pkg_api_rest_model.SoftwareInstallRes": { + "type": "object", + "properties": { + "output": { + "type": "string" + } + } + }, "pkg_api_rest_controller.SimpleMsg": { "type": "object", "properties": { diff --git a/pkg/api/rest/docs/swagger.yaml b/pkg/api/rest/docs/swagger.yaml index ae9df33..a29e1f5 100644 --- a/pkg/api/rest/docs/swagger.yaml +++ b/pkg/api/rest/docs/swagger.yaml @@ -4,6 +4,26 @@ definitions: error: type: string type: object + github_com_cloud-barista_cm-grasshopper_pkg_api_rest_model.SoftwareInstallReq: + properties: + connection_uuid: + type: string + package_names: + items: + type: string + type: array + package_type: + type: string + required: + - connection_uuid + - package_names + - package_type + type: object + github_com_cloud-barista_cm-grasshopper_pkg_api_rest_model.SoftwareInstallRes: + properties: + output: + type: string + type: object pkg_api_rest_controller.SimpleMsg: properties: message: @@ -31,4 +51,34 @@ paths: summary: Check Grasshopper is alive tags: - '[Admin] System management' + /software/install: + post: + consumes: + - application/json + description: Install pieces of software to target. + parameters: + - description: Software install request. + in: body + name: softwareInstallReq + required: true + schema: + $ref: '#/definitions/github_com_cloud-barista_cm-grasshopper_pkg_api_rest_model.SoftwareInstallReq' + produces: + - application/json + responses: + "200": + description: Successfully sent SSH command. + schema: + $ref: '#/definitions/github_com_cloud-barista_cm-grasshopper_pkg_api_rest_model.SoftwareInstallRes' + "400": + description: Sent bad request. + schema: + $ref: '#/definitions/github_com_cloud-barista_cm-grasshopper_pkg_api_rest_common.ErrorResponse' + "500": + description: Failed to sent SSH command. + schema: + $ref: '#/definitions/github_com_cloud-barista_cm-grasshopper_pkg_api_rest_common.ErrorResponse' + summary: Install Software + tags: + - '[Software]' swagger: "2.0" diff --git a/pkg/api/rest/model/software.go b/pkg/api/rest/model/software.go new file mode 100644 index 0000000..a6a88d9 --- /dev/null +++ b/pkg/api/rest/model/software.go @@ -0,0 +1,11 @@ +package model + +type SoftwareInstallReq struct { + ConnectionUUID string `json:"connection_uuid" yaml:"connection_uuid" validate:"required"` + PackageType string `json:"package_type" yaml:"package_type" validate:"required"` + PackageNames []string `json:"package_names" yaml:"package_names" validate:"required"` +} + +type SoftwareInstallRes struct { + Output string `json:"output" yaml:"output"` +} diff --git a/pkg/api/rest/route/software.go b/pkg/api/rest/route/software.go index 6a09631..859500b 100644 --- a/pkg/api/rest/route/software.go +++ b/pkg/api/rest/route/software.go @@ -6,5 +6,5 @@ import ( ) func Software(e *echo.Echo) { - e.GET("/software/list", controller.SoftwareGetList) + e.POST("/software/install", controller.InstallSoftware) }