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

Implement kyma provision #1964

Merged
merged 1 commit into from
Feb 29, 2024
Merged
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
87 changes: 87 additions & 0 deletions internal/btp/provision.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package btp

import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)

const provisionEndpoint = "provisioning/v1/environments"

type ProvisionEnvironment struct {
// Description string `json:"description,omitempty"`
EnvironmentType string `json:"environmentType"`
// LandscapeLabel string `json:"landscapeLabel,omitempty"`
Name string `json:"name"`
// Origin string `json:"origin,omitempty"`
Parameters KymaParameters `json:"parameters"`
PlanName string `json:"planName"`
// ServiceName string `json:"serviceName,omitempty"`
// TechnicalKey string `json:"technicalKey,omitempty"`
User string `json:"user"`
}

type KymaParameters struct {
Name string `json:"name"`
Region string `json:"region"`
}

type ProvisionResponse struct {
ID string `json:"id"`
Name string `json:"name"`
BrokerID string `json:"brokerId"`
GlobalAccountGUID string `json:"globalAccountGUID"`
SubaccountGUID string `json:"subaccountGUID"`
TenantID string `json:"tenantId"`
ServiceID string `json:"serviceId"`
PlanID string `json:"planId"`
DashboardURL string `json:"dashboardUrl"`
Operation string `json:"operation"`
Parameters string `json:"parameters"`
Labels string `json:"labels"`
// CustomLabels struct {} `json:"customLabels"`
Type string `json:"type"`
Status string `json:"status"`
EnvironmentType string `json:"environmentType"`
PlatformID string `json:"platformId"`
CreatedDate int64 `json:"createdDate"`
ModifiedDate int64 `json:"modifiedDate"`
State string `json:"state"`
StateMessage string `json:"stateMessage"`
ServiceName string `json:"serviceName"`
PlanName string `json:"planName"`
}

func (c *LocalClient) Provision(pe *ProvisionEnvironment) (*ProvisionResponse, error) {
reqData, err := json.Marshal(pe)
if err != nil {
return nil, err
}

provisionURL := fmt.Sprintf("%s/%s", c.credentials.Endpoints.ProvisioningServiceURL, provisionEndpoint)
options := requestOptions{
Body: bytes.NewBuffer(reqData),
Headers: map[string]string{
"Content-Type": "application/json",
},
}

response, err := c.cis.post(provisionURL, options)
if err != nil {
return nil, fmt.Errorf("failed to provision: %s", err.Error())
}
defer response.Body.Close()

return decodeProvisionSuccessResponse(response)
}

func decodeProvisionSuccessResponse(response *http.Response) (*ProvisionResponse, error) {
provisionResponse := ProvisionResponse{}
err := json.NewDecoder(response.Body).Decode(&provisionResponse)
if err != nil {
return nil, fmt.Errorf("failed to decode response: %s", err.Error())
}

return &provisionResponse, nil
}
142 changes: 142 additions & 0 deletions internal/btp/provision_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package btp

import (
"encoding/json"
"errors"
"fmt"
"net/http"
"net/http/httptest"
"testing"

"github.com/stretchr/testify/require"
)

func fixProvisionHandler(t *testing.T) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != fmt.Sprintf("/%s", provisionEndpoint) {
w.WriteHeader(404)
return
}
request := ProvisionEnvironment{}
err := json.NewDecoder(r.Body).Decode(&request)
require.NoError(t, err)

resp := ProvisionResponse{
Name: request.Name,
}

data, err := json.Marshal(resp)
require.NoError(t, err)

w.WriteHeader(202)
_, err = w.Write(data)
require.NoError(t, err)
}
}

func fixProvisionErrorHandler(t *testing.T) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, _ *http.Request) {
data := cisErrorResponse{
Error: cisError{
Message: "error",
},
}
response, err := json.Marshal(data)
require.NoError(t, err)

w.WriteHeader(401)
_, err = w.Write(response)
require.NoError(t, err)
}
}

func TestCISClient_Provision(t *testing.T) {
t.Parallel()

svrGood := httptest.NewServer(http.HandlerFunc(fixProvisionHandler(t)))
defer svrGood.Close()
svrBad := httptest.NewServer(http.HandlerFunc(fixProvisionErrorHandler(t)))
defer svrBad.Close()

tests := []struct {
name string
credentials *CISCredentials
token *XSUAAToken
pe *ProvisionEnvironment
wantedResponse *ProvisionResponse
expectedErr error
}{
{
name: "Correct data",
credentials: &CISCredentials{
Endpoints: Endpoints{
ProvisioningServiceURL: svrGood.URL,
},
},
token: &XSUAAToken{},
pe: &ProvisionEnvironment{
Name: "name",
},
wantedResponse: &ProvisionResponse{
Name: "name",
},
expectedErr: nil,
},
{
name: "Incorrect URL",
credentials: &CISCredentials{
Endpoints: Endpoints{
ProvisioningServiceURL: "?\n?",
},
},
token: &XSUAAToken{},
pe: &ProvisionEnvironment{
Name: "name",
},
wantedResponse: nil,
expectedErr: errors.New("failed to provision: failed to build request: parse \"?\\n?/provisioning/v1/environments\": net/url: invalid control character in URL"),
},
{
name: "Wrong URL",
credentials: &CISCredentials{
Endpoints: Endpoints{
ProvisioningServiceURL: "http://doesnotexist",
},
},
token: &XSUAAToken{},
pe: &ProvisionEnvironment{
Name: "name",
},
wantedResponse: nil,
expectedErr: errors.New("failed to provision: failed to get data from server: Post \"http://doesnotexist/provisioning/v1/environments\": dial tcp: lookup doesnotexist: no such host"),
},
{
name: "Error response",
credentials: &CISCredentials{
Endpoints: Endpoints{
ProvisioningServiceURL: svrBad.URL,
},
},
token: &XSUAAToken{},
pe: &ProvisionEnvironment{
Name: "name",
},
wantedResponse: nil,
expectedErr: errors.New("failed to provision: error"),
},
}
for _, tt := range tests {
pe := tt.pe
credentials := tt.credentials
token := tt.token
wantedResponse := tt.wantedResponse
expectedErr := tt.expectedErr
t.Run(tt.name, func(t *testing.T) {
c := NewLocalClient(credentials, token)

response, err := c.Provision(pe)
require.Equal(t, expectedErr, err)
require.Equal(t, wantedResponse, response)
})
}
}
35 changes: 29 additions & 6 deletions internal/cmd/provision/provision.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package provision

import (
"encoding/json"
"fmt"

"github.com/kyma-project/cli.v3/internal/btp"
Expand All @@ -10,20 +9,33 @@ import (

type provisionConfig struct {
credentialsPath string
plan string
environmentName string
clusterName string
region string
}

func NewProvisionCMD() *cobra.Command {
config := provisionConfig{}

cmd := &cobra.Command{
Use: "provision",
Use: "provision",
Short: "Provisions a Kyma cluster on the BTP.",
Long: `Use this command to provision a Kyma environment on the SAP BTP platform.
`,
RunE: func(_ *cobra.Command, _ []string) error {
return runProvision(&config)
},
}

cmd.PersistentFlags()
cmd.Flags().StringVar(&config.credentialsPath, "credentials-path", "", "Path to the CIS credentials file.")

cmd.Flags().StringVar(&config.plan, "plan", "trial", "Name of the Kyma environment plan, e.g trial, azure, aws, gcp.")
cmd.Flags().StringVar(&config.environmentName, "environment-name", "kyma", "Name of the environment in the BTP.")
cmd.Flags().StringVar(&config.clusterName, "cluster-name", "kyma", "Name of the Kyma cluster.")
cmd.Flags().StringVar(&config.region, "region", "", "Name of the region of the Kyma cluster.")

_ = cmd.MarkFlagRequired("credentials-path")

return cmd
Expand All @@ -40,13 +52,24 @@ func runProvision(config *provisionConfig) error {
return fmt.Errorf("failed to get access token: %s", err.Error())
}

// TODO: remove in next interation
data, err := json.Marshal(token)
localCISClient := btp.NewLocalClient(credentials, token)

ProvisionEnvironment := &btp.ProvisionEnvironment{
EnvironmentType: "kyma",
PlanName: config.plan,
Name: config.environmentName,
User: "kyma-cli",
Parameters: btp.KymaParameters{
Name: config.clusterName,
Region: config.region,
},
}
response, err := localCISClient.Provision(ProvisionEnvironment)
if err != nil {
return err
return fmt.Errorf("failed to provision kyma runtime: %s", err.Error())
}

fmt.Printf("%s\n", data)
fmt.Printf("Kyma environment provisioning, environment name: '%s', id: '%s'\n", response.Name, response.ID)

return nil
}
Loading