-
Notifications
You must be signed in to change notification settings - Fork 118
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added the new Hana Map command that fulfills the new CLI requirements (…
- Loading branch information
Showing
9 changed files
with
364 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package hana | ||
|
||
import ( | ||
"encoding/json" | ||
"github.com/kyma-project/cli.v3/internal/clierror" | ||
"os" | ||
) | ||
|
||
func ReadCredentialsFromFile(path string) (*HanaAdminCredentials, clierror.Error) { | ||
bytes, err := os.ReadFile(path) | ||
if err != nil { | ||
return nil, clierror.Wrap(err, clierror.New("failed to read credentials file")) | ||
} | ||
credentials := &HanaAdminCredentials{} | ||
|
||
err = json.Unmarshal(bytes, credentials) | ||
if err != nil { | ||
return nil, clierror.Wrap(err, clierror.New("failed to parse credentials file")) | ||
} | ||
return credentials, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package hana | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"github.com/kyma-project/cli.v3/internal/clierror" | ||
"io" | ||
"net/http" | ||
) | ||
|
||
func GetID(baseURL, token string) (string, clierror.Error) { | ||
return getID(fmt.Sprintf("https://%s", baseURL), token) | ||
} | ||
|
||
func getID(baseURL, token string) (string, clierror.Error) { | ||
client := &http.Client{} | ||
requestGet, err := http.NewRequest("GET", fmt.Sprintf("%s/inventory/v2/serviceInstances", baseURL), nil) | ||
if err != nil { | ||
return "", clierror.Wrap(err, clierror.New("failed to create a get Hana instances request")) | ||
} | ||
requestGet.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token)) | ||
|
||
response, err := client.Do(requestGet) | ||
if err != nil { | ||
return "", clierror.Wrap(err, clierror.New("failed to get Hana instances")) | ||
} | ||
|
||
if response.StatusCode != http.StatusOK { | ||
return "", clierror.New(fmt.Sprintf("unexpected status code: %d", response.StatusCode)) | ||
} | ||
hanaInstanceBytes, err := io.ReadAll(response.Body) | ||
if err != nil { | ||
return "", clierror.Wrap(err, clierror.New("failed to read Hana instances from the response")) | ||
} | ||
hanaInstance := HanaInstance{} | ||
err = json.Unmarshal(hanaInstanceBytes, &hanaInstance) | ||
if err != nil { | ||
return "", clierror.Wrap(err, clierror.New("failed to parse Hana instances from the response")) | ||
} | ||
if len(hanaInstance.Data) == 0 { | ||
return "", clierror.New("no Hana instances found in the response") | ||
} | ||
return hanaInstance.Data[0].ID, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
package hana | ||
|
||
import ( | ||
"fmt" | ||
"github.com/stretchr/testify/require" | ||
"net/http" | ||
"net/http/httptest" | ||
"testing" | ||
) | ||
|
||
func TestGetID(t *testing.T) { | ||
authToken := "test-token" | ||
t.Run("get hana instance ID from server", func(t *testing.T) { | ||
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
require.Equal(t, "/inventory/v2/serviceInstances", r.URL.Path) | ||
require.Equal(t, fmt.Sprintf("Bearer %s", authToken), r.Header.Get("Authorization")) | ||
require.Equal(t, http.MethodGet, r.Method) | ||
|
||
_, err := w.Write([]byte(`{"data":[{"id":"test-id"}]}`)) | ||
require.NoError(t, err) | ||
w.WriteHeader(http.StatusOK) | ||
})) | ||
defer testServer.Close() | ||
|
||
id, err := getID(testServer.URL, authToken) | ||
require.Nil(t, err) | ||
require.Equal(t, "test-id", id) | ||
}) | ||
t.Run("response body empty", func(t *testing.T) { | ||
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
require.Equal(t, "/inventory/v2/serviceInstances", r.URL.Path) | ||
require.Equal(t, fmt.Sprintf("Bearer %s", authToken), r.Header.Get("Authorization")) | ||
require.Equal(t, http.MethodGet, r.Method) | ||
_, err := w.Write([]byte(`{"data":[]}`)) | ||
require.NoError(t, err) | ||
w.WriteHeader(http.StatusOK) | ||
})) | ||
defer testServer.Close() | ||
|
||
id, err := getID(testServer.URL, authToken) | ||
require.Contains(t, err.String(), "no Hana instances found in the response") | ||
require.Empty(t, id) | ||
}) | ||
t.Run("can't create new GET request", func(t *testing.T) { | ||
id, err := getID(": ", authToken) | ||
require.Contains(t, err.String(), "failed to create a get Hana instances request") | ||
require.Empty(t, id) | ||
}) | ||
t.Run("can't send GET request", func(t *testing.T) { | ||
id, err := getID("https://localhost", authToken) | ||
require.Contains(t, err.String(), "failed to get Hana instances") | ||
require.Empty(t, id) | ||
}) | ||
t.Run("response status not OK", func(t *testing.T) { | ||
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
w.WriteHeader(http.StatusInternalServerError) | ||
})) | ||
defer testServer.Close() | ||
|
||
id, err := getID(testServer.URL, authToken) | ||
require.Contains(t, err.String(), "unexpected status code: 500") | ||
require.Empty(t, id) | ||
}) | ||
t.Run("failed to unmarshal response", func(t *testing.T) { | ||
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
_, err := w.Write([]byte(`wrong format {`)) | ||
require.NoError(t, err) | ||
w.WriteHeader(http.StatusOK) | ||
})) | ||
defer testServer.Close() | ||
|
||
id, err := getID(testServer.URL, authToken) | ||
require.Contains(t, err.String(), "failed to parse Hana instances from the response") | ||
require.Empty(t, id) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package hana | ||
|
||
import ( | ||
"bytes" | ||
"encoding/json" | ||
"fmt" | ||
"github.com/kyma-project/cli.v3/internal/clierror" | ||
"net/http" | ||
) | ||
|
||
func MapInstance(baseURL, clusterID, hanaID, token string) clierror.Error { | ||
return mapInstance(fmt.Sprintf("https://%s", baseURL), clusterID, hanaID, token) | ||
} | ||
|
||
func mapInstance(baseURL, clusterID, hanaID, token string) clierror.Error { | ||
client := &http.Client{} | ||
|
||
requestData := HanaMapping{ | ||
Platform: "kubernetes", | ||
PrimaryID: clusterID, | ||
} | ||
|
||
requestString, err := json.Marshal(requestData) | ||
if err != nil { | ||
return clierror.Wrap(err, clierror.New("failed to marshal mapping request")) | ||
} | ||
|
||
request, err := http.NewRequest("POST", fmt.Sprintf("%s/inventory/v2/serviceInstances/%s/instanceMappings", baseURL, hanaID), bytes.NewBuffer(requestString)) | ||
if err != nil { | ||
return clierror.Wrap(err, clierror.New("failed to create mapping request")) | ||
} | ||
request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token)) | ||
|
||
resp, err := client.Do(request) | ||
if err != nil { | ||
return clierror.Wrap(err, clierror.New("failed to post mapping request")) | ||
} | ||
|
||
// server sends status Created when mapping is created, and 200 if it already exists | ||
if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK { | ||
return clierror.Wrap(fmt.Errorf("status code: %d", resp.StatusCode), clierror.New("unexpected status code")) | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package hana | ||
|
||
import ( | ||
"fmt" | ||
"github.com/stretchr/testify/require" | ||
"net/http" | ||
"net/http/httptest" | ||
"testing" | ||
) | ||
|
||
func TestMapInstance(t *testing.T) { | ||
t.Run("map hana instance", func(t *testing.T) { | ||
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
require.Equal(t, "/inventory/v2/serviceInstances/test-id/instanceMappings", r.URL.Path) | ||
require.Equal(t, fmt.Sprintf("Bearer %s", "test-token"), r.Header.Get("Authorization")) | ||
require.Equal(t, http.MethodPost, r.Method) | ||
|
||
w.WriteHeader(http.StatusCreated) | ||
w.WriteHeader(http.StatusOK) | ||
})) | ||
defer testServer.Close() | ||
|
||
err := mapInstance(testServer.URL, "test-cluster-id", "test-id", "test-token") | ||
require.Nil(t, err) | ||
}) | ||
t.Run("failed to create mapping request", func(t *testing.T) { | ||
err := mapInstance(": ", "test-cluster-id", "test-id", "test-token") | ||
require.Contains(t, err.String(), "failed to create mapping request") | ||
}) | ||
t.Run("failed to post mapping request", func(t *testing.T) { | ||
|
||
err := mapInstance("https://localhost", "test-cluster-id", "test-id", "test-token") | ||
require.Contains(t, err.String(), "failed to post mapping request") | ||
}) | ||
t.Run("unexpected status code", func(t *testing.T) { | ||
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
w.WriteHeader(http.StatusInternalServerError) | ||
})) | ||
defer testServer.Close() | ||
|
||
err := mapInstance(testServer.URL, "test-cluster-id", "test-id", "test-token") | ||
require.Contains(t, err.String(), "unexpected status code") | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package hana | ||
|
||
import "github.com/kyma-project/cli.v3/internal/btp/auth" | ||
|
||
type HanaInstance struct { | ||
Data []HanaInstanceData `json:"data"` | ||
} | ||
|
||
type HanaInstanceData struct { | ||
ID string `json:"id"` | ||
Name string `json:"name"` | ||
ServicePlanName string `json:"servicePlanName"` | ||
} | ||
|
||
type HanaAdminCredentials struct { | ||
BaseURL string `json:"baseurl"` | ||
UAA auth.UAA `json:"uaa"` | ||
} | ||
|
||
type HanaMapping struct { | ||
Platform string `json:"platform"` | ||
PrimaryID string `json:"primaryID"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package hana | ||
|
||
import ( | ||
"github.com/kyma-project/cli.v3/internal/cmdcommon" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
func NewHanaCMD(kymaConfig *cmdcommon.KymaConfig) *cobra.Command { | ||
cmd := &cobra.Command{ | ||
Use: "hana", | ||
Short: "Manage a Hana instance on the Kyma platform.", | ||
Long: `Use this command to manage a Hana instance on the SAP Kyma platform.`, | ||
DisableFlagsInUseLine: true, | ||
} | ||
|
||
cmd.AddCommand(NewMapHanaCMD(kymaConfig)) | ||
|
||
return cmd | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
package hana | ||
|
||
import ( | ||
"context" | ||
"github.com/kyma-project/cli.v3/internal/btp/auth" | ||
"github.com/kyma-project/cli.v3/internal/btp/hana" | ||
"github.com/kyma-project/cli.v3/internal/clierror" | ||
"github.com/kyma-project/cli.v3/internal/cmdcommon" | ||
"github.com/spf13/cobra" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/client-go/kubernetes" | ||
) | ||
|
||
type hanaMapConfig struct { | ||
*cmdcommon.KymaConfig | ||
|
||
hanaID string | ||
credentialsPath string | ||
} | ||
|
||
func NewMapHanaCMD(kymaConfig *cmdcommon.KymaConfig) *cobra.Command { | ||
config := hanaMapConfig{ | ||
KymaConfig: kymaConfig, | ||
} | ||
|
||
cmd := &cobra.Command{ | ||
Use: "map", | ||
Short: "Map the Hana instance to the Kyma cluster.", | ||
Long: "Use this command to map the Hana instance to the Kyma cluster.", | ||
Run: func(_ *cobra.Command, _ []string) { | ||
clierror.Check(runHanaMap(&config)) | ||
}, | ||
} | ||
|
||
cmd.Flags().StringVar(&config.credentialsPath, "credentials-path", "", "Path to the credentials json file.") | ||
cmd.Flags().StringVar(&config.hanaID, "hana-id", "", "Hana instance ID.") | ||
_ = cmd.MarkFlagRequired("credentials-path") | ||
|
||
return cmd | ||
} | ||
|
||
func runHanaMap(config *hanaMapConfig) clierror.Error { | ||
client, err := config.GetKubeClientWithClierr() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
clusterID, err := getClusterID(config.Ctx, client.Static()) | ||
if err != nil { | ||
return clierror.WrapE(err, clierror.New("while getting cluster ID")) | ||
} | ||
|
||
credentials, err := hana.ReadCredentialsFromFile(config.credentialsPath) | ||
if err != nil { | ||
return clierror.WrapE(err, clierror.New("while reading Hana credentials from file")) | ||
} | ||
|
||
token, err := auth.GetOAuthToken("client_credentials", credentials.UAA.URL, credentials.UAA.ClientID, credentials.UAA.ClientSecret) | ||
if err != nil { | ||
return clierror.WrapE(err, clierror.New("while getting OAuth token")) | ||
} | ||
|
||
// get Hana ID if not provided by the user | ||
hanaID := config.hanaID | ||
if hanaID == "" { | ||
hanaID, err = hana.GetID(credentials.BaseURL, token.AccessToken) | ||
if err != nil { | ||
return clierror.WrapE(err, clierror.New("while getting hana ID")) | ||
|
||
} | ||
} | ||
err = hana.MapInstance(credentials.BaseURL, clusterID, hanaID, token.AccessToken) | ||
if err != nil { | ||
return clierror.WrapE(err, clierror.New("while mapping Hana instance")) | ||
} | ||
return nil | ||
} | ||
|
||
func getClusterID(ctx context.Context, client kubernetes.Interface) (string, clierror.Error) { | ||
secret, geterr := client.CoreV1().Secrets("kyma-system").Get(ctx, "sap-btp-manager", metav1.GetOptions{}) | ||
if geterr != nil { | ||
return "", clierror.Wrap(geterr, clierror.New("failed to get secret kyma-system/sap-btp-manager")) | ||
} | ||
|
||
if secret.Data["cluster_id"] == nil { | ||
return "", clierror.New("cluster_id not found in the secret kyma-system/sap-btp-manager") | ||
} | ||
|
||
return string(secret.Data["cluster_id"]), nil | ||
} |