Connect marketplace API to microservice (#2130)

This commit is contained in:
Javier Adriel
2022-07-06 23:11:23 -05:00
committed by GitHub
parent 63e2793272
commit e48958f5a0
9 changed files with 183 additions and 46 deletions

View File

@@ -44,7 +44,7 @@ builds:
- --tags=kqueue,operator
ldflags:
- -s -w -X github.com/minio/console/pkg.ReleaseTag={{.Tag}} -X github.com/minio/console/pkg.CommitID={{.FullCommit}} -X github.com/minio/console/pkg.Version={{.Version}} -X github.com/minio/console/pkg.ShortCommitID={{.ShortCommit}} -X github.com/minio/console/pkg.ReleaseTime={{.Date}}
- -s -w -X github.com/minio/console/pkg.ReleaseTag={{.Tag}} -X github.com/minio/console/pkg.CommitID={{.FullCommit}} -X github.com/minio/console/pkg.Version={{.Version}} -X github.com/minio/console/pkg.ShortCommitID={{.ShortCommit}} -X github.com/minio/console/pkg.ReleaseTime={{.Date}} -X github.com/minio/console/pkg.MPSecret={{.Env.MPSECRET}}
archives:
-

View File

@@ -36,6 +36,9 @@ type MpIntegration struct {
// email
Email string `json:"email,omitempty"`
// is in e u
IsInEU bool `json:"isInEU,omitempty"`
}
// Validate validates this mp integration

View File

@@ -321,7 +321,12 @@ func init() {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/mpIntegration"
"type": "object",
"properties": {
"isEmailSet": {
"type": "boolean"
}
}
}
},
"default": {
@@ -3358,6 +3363,9 @@ func init() {
"properties": {
"email": {
"type": "string"
},
"isInEU": {
"type": "boolean"
}
}
},
@@ -4995,7 +5003,12 @@ func init() {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/mpIntegration"
"type": "object",
"properties": {
"isEmailSet": {
"type": "boolean"
}
}
}
},
"default": {
@@ -8863,6 +8876,9 @@ func init() {
"properties": {
"email": {
"type": "string"
},
"isInEU": {
"type": "boolean"
}
}
},

View File

@@ -1,5 +1,5 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
@@ -19,14 +19,21 @@ package operatorapi
import (
"context"
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
"github.com/go-openapi/runtime/middleware"
"github.com/golang-jwt/jwt/v4"
"github.com/minio/console/cluster"
"github.com/minio/console/models"
"github.com/minio/console/operatorapi/operations"
"github.com/minio/console/operatorapi/operations/operator_api"
"github.com/minio/console/pkg"
errors "github.com/minio/console/restapi"
"github.com/minio/pkg/env"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@@ -34,7 +41,11 @@ import (
var (
mpConfigMapDefault = "mp-config"
mpConfigMapKey = "MP_CONFIG_KEY"
mpEmail = "email"
mpHostEnvVar = "MP_HOST"
defaultMPHost = "https://marketplace.apps.min.dev"
mpEUHostEnvVar = "MP_EU_HOST"
defaultEUMPHost = "https://marketplace-eu.apps.min.dev"
isMPEmailSet = "isEmailSet"
emailNotSetMsg = "Email was not sent in request"
)
@@ -56,62 +67,112 @@ func registerMarketplaceHandlers(api *operations.OperatorAPI) {
})
}
func getMPIntegrationResponse(session *models.Principal, params operator_api.GetMPIntegrationParams) (*models.MpIntegration, *models.Error) {
if true { // This block will be removed once service to register emails is deployed
return nil, &models.Error{Code: 501}
}
func getMPIntegrationResponse(session *models.Principal, params operator_api.GetMPIntegrationParams) (*operator_api.GetMPIntegrationOKBody, *models.Error) {
clientSet, err := cluster.K8sClient(session.STSSessionToken)
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
if err != nil {
return nil, errors.ErrorWithContext(ctx, err)
}
mpEmail, err := getMPEmail(ctx, &k8sClient{client: clientSet})
isMPEmailSet, err := getMPEmail(ctx, &k8sClient{client: clientSet})
if err != nil {
return nil, errors.ErrorWithContext(ctx, errors.ErrNotFound)
}
return &models.MpIntegration{
Email: mpEmail,
return &operator_api.GetMPIntegrationOKBody{
IsEmailSet: isMPEmailSet,
}, nil
}
func getMPEmail(ctx context.Context, clientSet K8sClientI) (string, error) {
func getMPEmail(ctx context.Context, clientSet K8sClientI) (bool, error) {
cm, err := clientSet.getConfigMap(ctx, "default", getMPConfigMapKey(mpConfigMapKey), metav1.GetOptions{})
if err != nil {
return "", err
return false, err
}
return cm.Data[mpEmail], nil
return cm.Data[isMPEmailSet] == "true", nil
}
func postMPIntegrationResponse(session *models.Principal, params operator_api.PostMPIntegrationParams) *models.Error {
if true { // This block will be removed once service to register emails is deployed
return &models.Error{Code: 501}
}
clientSet, err := cluster.K8sClient(session.STSSessionToken)
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
if err != nil {
return errors.ErrorWithContext(ctx, err)
}
return setMPIntegration(ctx, params.Body.Email, &k8sClient{client: clientSet})
return setMPIntegration(ctx, params.Body.Email, params.Body.IsInEU, &k8sClient{client: clientSet})
}
func setMPIntegration(ctx context.Context, email string, clientSet K8sClientI) *models.Error {
func setMPIntegration(ctx context.Context, email string, isInEU bool, clientSet K8sClientI) *models.Error {
if email == "" {
return errors.ErrorWithContext(ctx, errors.ErrBadRequest, fmt.Errorf(emailNotSetMsg))
}
if _, err := setMPEmail(ctx, email, clientSet); err != nil {
if _, err := setMPEmail(ctx, email, isInEU, clientSet); err != nil {
return errors.ErrorWithContext(ctx, err)
}
return nil
}
func setMPEmail(ctx context.Context, email string, clientSet K8sClientI) (*corev1.ConfigMap, error) {
cm := createCM(email)
func setMPEmail(ctx context.Context, email string, isInEU bool, clientSet K8sClientI) (*corev1.ConfigMap, error) {
if err := postEmailToMP(email, isInEU); err != nil {
return nil, err
}
cm := createCM()
return clientSet.createConfigMap(ctx, "default", cm, metav1.CreateOptions{})
}
func createCM(email string) *corev1.ConfigMap {
func postEmailToMP(email string, isInEU bool) error {
mpURL, err := getMPURL(isInEU)
if err != nil {
return err
}
return makePostRequestToMP(mpURL, email)
}
func getMPURL(isInEU bool) (string, error) {
mpHost := getMPHost(isInEU)
if mpHost == "" {
return "", fmt.Errorf("mp host not set")
}
return fmt.Sprintf("%s/mp-email", mpHost), nil
}
func getMPHost(isInEU bool) string {
if isInEU {
return env.Get(mpEUHostEnvVar, defaultEUMPHost)
}
return env.Get(mpHostEnvVar, defaultMPHost)
}
func makePostRequestToMP(url, email string) error {
request, err := createMPRequest(url, email)
if err != nil {
return err
}
client := &http.Client{Timeout: 3 * time.Second}
if res, err := client.Do(request); err != nil {
return err
} else if res.StatusCode >= http.StatusBadRequest {
b, _ := io.ReadAll(res.Body)
return fmt.Errorf("request to %s failed with status code %d and error %s", url, res.StatusCode, string(b))
}
return nil
}
func createMPRequest(url, email string) (*http.Request, error) {
request, err := http.NewRequest("POST", url, strings.NewReader(fmt.Sprintf("{\"email\":\"%s\"}", email)))
if err != nil {
return nil, err
}
jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{})
jwtTokenString, err := jwtToken.SignedString([]byte(pkg.MPSecret))
if err != nil {
return nil, err
}
request.Header.Add("Cookie", fmt.Sprintf("jwtToken=%s", jwtTokenString))
request.Header.Add("Content-Type", "application/json")
return request, nil
}
func createCM() *corev1.ConfigMap {
return &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
Kind: "ConfigMap",
@@ -121,7 +182,7 @@ func createCM(email string) *corev1.ConfigMap {
Name: getMPConfigMapKey(mpConfigMapKey),
Namespace: "default",
},
Data: map[string]string{mpEmail: email},
Data: map[string]string{isMPEmailSet: "true"},
}
}

View File

@@ -82,7 +82,7 @@ func (suite *MarketplaceTestSuite) getConfigMapMock(ctx context.Context, namespa
if testWithError {
return nil, errMock
}
return &corev1.ConfigMap{Data: map[string]string{mpEmail: "mock@mock.com"}}, nil
return &corev1.ConfigMap{Data: map[string]string{isMPEmailSet: "true"}}, nil
}
func (suite *MarketplaceTestSuite) createConfigMapMock(ctx context.Context, namespace string, cm *corev1.ConfigMap, opts metav1.CreateOptions) (*corev1.ConfigMap, error) {
@@ -115,11 +115,6 @@ func (suite *MarketplaceTestSuite) TestRegisterMarketplaceHandlers() {
suite.assert.NotNil(api.OperatorAPIPostMPIntegrationHandler)
}
// TODO
// WIP - Complete successful tests to RUD handlers
// WIP - Add tests to POST handler
// WIP - Check how to mock k8s objects for tests with no error
func (suite *MarketplaceTestSuite) TestGetMPIntegrationHandlerWithError() {
api := &operations.OperatorAPI{}
registerMarketplaceHandlers(api)
@@ -130,6 +125,19 @@ func (suite *MarketplaceTestSuite) TestGetMPIntegrationHandlerWithError() {
suite.assert.True(ok)
}
func (suite *MarketplaceTestSuite) TestPostMPIntegrationHandlerWithError() {
api := &operations.OperatorAPI{}
registerMarketplaceHandlers(api)
params := operator_api.NewPostMPIntegrationParams()
params.Body = &models.MpIntegration{Email: ""}
params.HTTPRequest = &http.Request{}
params.HTTPRequest.Header = map[string][]string{}
params.HTTPRequest.AddCookie(&http.Cookie{Value: "token", Name: "token"})
response := api.OperatorAPIPostMPIntegrationHandler.Handle(params, &models.Principal{})
_, ok := response.(*operator_api.PostMPIntegrationDefault)
suite.assert.True(ok)
}
func (suite *MarketplaceTestSuite) TestGetMPEmailWithError() {
testWithError = true
ctx, cancel := context.WithCancel(context.Background())
@@ -143,15 +151,15 @@ func (suite *MarketplaceTestSuite) TestGetMPEmailNoError() {
testWithError = false
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
email, err := getMPEmail(ctx, &suite.kClient)
isSet, err := getMPEmail(ctx, &suite.kClient)
suite.assert.Nil(err)
suite.assert.NotEmpty(email)
suite.assert.True(isSet)
}
func (suite *MarketplaceTestSuite) TestSetMPIntegrationNoEmail() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
err := setMPIntegration(ctx, "", &suite.kClient)
err := setMPIntegration(ctx, "", false, &suite.kClient)
suite.assert.NotNil(err)
}
@@ -159,17 +167,20 @@ func (suite *MarketplaceTestSuite) TestSetMPIntegrationWithError() {
testWithError = true
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
err := setMPIntegration(ctx, "mock@mock.com", &suite.kClient)
os.Setenv(mpHostEnvVar, " ")
err := setMPIntegration(ctx, "mock@mock.com", false, &suite.kClient)
suite.assert.NotNil(err)
os.Unsetenv(mpHostEnvVar)
}
func (suite *MarketplaceTestSuite) TestSetMPIntegrationNoError() {
testWithError = false
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
err := setMPIntegration(ctx, "mock@mock.com", &suite.kClient)
suite.assert.Nil(err)
}
// TODO: Add mock server for testing microservice
// func (suite *MarketplaceTestSuite) TestSetMPIntegrationNoError() {
// testWithError = false
// ctx, cancel := context.WithCancel(context.Background())
// defer cancel()
// err := setMPIntegration(ctx, "mock@mock.com", "token", &suite.kClient)
// suite.assert.Nil(err)
// }
func TestMarketplace(t *testing.T) {
suite.Run(t, new(MarketplaceTestSuite))

View File

@@ -23,9 +23,12 @@ package operator_api
// Editing this file might prove futile when you re-run the generate command
import (
"context"
"net/http"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/minio/console/models"
)
@@ -86,3 +89,40 @@ func (o *GetMPIntegration) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
o.Context.Respond(rw, r, route.Produces, route, res)
}
// GetMPIntegrationOKBody get m p integration o k body
//
// swagger:model GetMPIntegrationOKBody
type GetMPIntegrationOKBody struct {
// is email set
IsEmailSet bool `json:"isEmailSet,omitempty"`
}
// Validate validates this get m p integration o k body
func (o *GetMPIntegrationOKBody) Validate(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this get m p integration o k body based on context it is used
func (o *GetMPIntegrationOKBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (o *GetMPIntegrationOKBody) MarshalBinary() ([]byte, error) {
if o == nil {
return nil, nil
}
return swag.WriteJSON(o)
}
// UnmarshalBinary interface implementation
func (o *GetMPIntegrationOKBody) UnmarshalBinary(b []byte) error {
var res GetMPIntegrationOKBody
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*o = res
return nil
}

View File

@@ -42,7 +42,7 @@ type GetMPIntegrationOK struct {
/*
In: Body
*/
Payload *models.MpIntegration `json:"body,omitempty"`
Payload *GetMPIntegrationOKBody `json:"body,omitempty"`
}
// NewGetMPIntegrationOK creates GetMPIntegrationOK with default headers values
@@ -52,13 +52,13 @@ func NewGetMPIntegrationOK() *GetMPIntegrationOK {
}
// WithPayload adds the payload to the get m p integration o k response
func (o *GetMPIntegrationOK) WithPayload(payload *models.MpIntegration) *GetMPIntegrationOK {
func (o *GetMPIntegrationOK) WithPayload(payload *GetMPIntegrationOKBody) *GetMPIntegrationOK {
o.Payload = payload
return o
}
// SetPayload sets the payload to the get m p integration o k response
func (o *GetMPIntegrationOK) SetPayload(payload *models.MpIntegration) {
func (o *GetMPIntegrationOK) SetPayload(payload *GetMPIntegrationOKBody) {
o.Payload = payload
}

View File

@@ -27,4 +27,5 @@ var (
CommitID = "(dev)"
// ShortCommitID - first 12 characters from CommitID.
ShortCommitID = "(dev)"
MPSecret = "(dev)"
)

View File

@@ -1240,7 +1240,10 @@ paths:
200:
description: A successful response.
schema:
$ref: "#/definitions/mpIntegration"
type: object
properties:
isEmailSet:
type: boolean
default:
description: Generic error response.
schema:
@@ -3394,4 +3397,6 @@ definitions:
properties:
email:
type: string
isInEU:
type: boolean