Skip to content

Commit

Permalink
astro-cli should prompt the user for namespace name input when pre-de…
Browse files Browse the repository at this point in the history
…ployment validation is configured (#487)

* based on pre-deployment validation config, user should be prompted for k8 namespace name to be enter instead of select from available k8 namespaces

* added test cases for NamespaceFreeformEntry (Pre-Deployment validation webhook)

* fixed broken test cases for namespaceFreeformEntry

* fixed broken test cases

* updated ErrKubernetesNamespaceNotSpecified message changes
  • Loading branch information
ajayy004 committed Jan 27, 2022
1 parent 01f98c5 commit e65e362
Show file tree
Hide file tree
Showing 3 changed files with 278 additions and 10 deletions.
28 changes: 27 additions & 1 deletion deployment/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ var (
ErrKubernetesNamespaceNotAvailable = errors.New("no kubernetes namespaces are available")
ErrNumberOutOfRange = errors.New("number is out of available range")
ErrMajorAirflowVersionUpgrade = fmt.Errorf("Airflow 2.0 has breaking changes. To upgrade to Airflow 2.0, upgrade to %s first and make sure your DAGs and configs are 2.0 compatible", minAirflowVersion) //nolint:golint,stylecheck
ErrKubernetesNamespaceNotSpecified = errors.New("no kubernetes namespaces specified")
errInvalidSSHKeyPath = errors.New("wrong path specified, no file exists for ssh key")
errInvalidKnownHostsPath = errors.New("wrong path specified, no file exists for known hosts")
errHostNotPresent = errors.New("git repository host not present in known hosts file")
Expand Down Expand Up @@ -136,6 +137,14 @@ func CheckPreCreateNamespaceDeployment(client *houston.Client) bool {
return appConfig.Flags.ManualNamespaceNames
}

func CheckNamespaceFreeformEntryDeployment(client *houston.Client) bool {
appConfig, err := AppConfig(client)
if err != nil {
return false
}
return appConfig.Flags.NamespaceFreeformEntry
}

func CheckTriggererEnabled(client *houston.Client) bool {
logrus.Debug("Checking for triggerer flag")
appConfig, err := AppConfig(client)
Expand All @@ -149,14 +158,22 @@ func CheckTriggererEnabled(client *houston.Client) bool {
func Create(label, ws, releaseName, cloudRole, executor, airflowVersion, dagDeploymentType, nfsLocation, gitRepoURL, gitRevision, gitBranchName, gitDAGDir, sshKey, knownHosts string, gitSyncInterval, triggererReplicas int, client *houston.Client, out io.Writer) error {
vars := map[string]interface{}{"label": label, "workspaceId": ws, "executor": executor, "cloudRole": cloudRole}

if CheckPreCreateNamespaceDeployment(client) {
if CheckPreCreateNamespaceDeployment(client) && !CheckNamespaceFreeformEntryDeployment(client) {
namespace, err := getDeploymentSelectionNamespaces(client, out)
if err != nil {
return err
}
vars["namespace"] = namespace
}

if CheckNamespaceFreeformEntryDeployment(client) && CheckPreCreateNamespaceDeployment(client) {
namespace, err := getDeploymentNamespaceName()
if err != nil {
return err
}
vars["namespace"] = namespace
}

if releaseName != "" && checkManualReleaseNames(client) {
vars["releaseName"] = releaseName
}
Expand Down Expand Up @@ -283,6 +300,15 @@ func getDeploymentSelectionNamespaces(client *houston.Client, out io.Writer) (st
return names[i-1].Name, nil
}

func getDeploymentNamespaceName() (string, error) {
namespaceName := input.Text("\nKubernetes Namespace Name: ")
noSpaceString := strings.ReplaceAll(namespaceName, " ", "")
if noSpaceString == "" {
return "", ErrKubernetesNamespaceNotSpecified
}
return namespaceName, nil
}

// List all airflow deployments
func List(ws string, all bool, client *houston.Client, out io.Writer) error {
var deployments []houston.Deployment
Expand Down
249 changes: 245 additions & 4 deletions deployment/deployment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ func TestAppConfig(t *testing.T) {
"baseDomain": "local.astronomer.io",
"smtpConfigured": true,
"manualReleaseNames": false,
"hardDeleteDeployment": false
"hardDeleteDeployment": false,
"featureFlags": {
"namespaceFreeformEntry": false
}
}
}
}`
Expand All @@ -66,6 +69,7 @@ func TestAppConfig(t *testing.T) {
assert.Equal(t, false, config.ManualReleaseNames)
assert.Equal(t, true, config.SMTPConfigured)
assert.Equal(t, "local.astronomer.io", config.BaseDomain)
assert.Equal(t, false, config.Flags.NamespaceFreeformEntry)
}

func TestAppConfigError(t *testing.T) {
Expand Down Expand Up @@ -735,7 +739,7 @@ func TestUpdate(t *testing.T) {
"appConfig": {
"version": "0.15.1",
"baseDomain": "local.astronomer.io",
"smtpConfigured": true,
"smtpConfigured": true,
"manualReleaseNames": false,
"hardDeleteDeployment": true,
"manualNamespaceNames": true,
Expand Down Expand Up @@ -815,7 +819,7 @@ func TestUpdateTriggerer(t *testing.T) {
"appConfig": {
"version": "0.15.1",
"baseDomain": "local.astronomer.io",
"smtpConfigured": true,
"smtpConfigured": true,
"manualReleaseNames": false,
"hardDeleteDeployment": true,
"manualNamespaceNames": true,
Expand Down Expand Up @@ -1387,7 +1391,10 @@ func TestGetDeploymentSelectionNamespaces(t *testing.T) {
"baseDomain": "local.astronomer.io",
"smtpConfigured": true,
"manualReleaseNames": false,
"hardDeleteDeployment": true
"hardDeleteDeployment": true,
"featureFlags": {
"namespaceFreeformEntry": false
}
},
"availableNamespaces": [ { "name": "test1" }, { "name": "test2" } ]
}
Expand Down Expand Up @@ -1557,3 +1564,237 @@ func TestCheckPreCreateNamespacesDeployment(t *testing.T) {
usesPreCreateNamespace := CheckPreCreateNamespaceDeployment(api)
assert.Equal(t, true, usesPreCreateNamespace)
}

func TestGetDeploymentNamespaceName(t *testing.T) {
// mock os.Stdin
input := []byte("Test1")
r, w, err := os.Pipe()
if err != nil {
t.Fatal(err)
}
_, err = w.Write(input)
if err != nil {
t.Error(err)
}
w.Close()
stdin := os.Stdin
// Restore stdin right after the test.
defer func() { os.Stdin = stdin }()
os.Stdin = r

name, _ := getDeploymentNamespaceName()
assert.Equal(t, "Test1", name)
}

func TestGetDeploymentNamespaceNameError(t *testing.T) {
// mock os.Stdin
input := []byte(" ")
r, w, err := os.Pipe()
if err != nil {
t.Fatal(err)
}
_, err = w.Write(input)
if err != nil {
t.Error(err)
}
w.Close()
stdin := os.Stdin
// Restore stdin right after the test.
defer func() { os.Stdin = stdin }()
os.Stdin = r

name, err := getDeploymentNamespaceName()
assert.Equal(t, "", name)
assert.EqualError(t, err, "no kubernetes namespaces specified")
}

func TestCreateWithFreeFormNamespaceDeployment(t *testing.T) {
testUtil.InitTestConfig()
okResponse := `{
"data": {
"appConfig": {
"version": "0.15.1",
"baseDomain": "local.astronomer.io",
"smtpConfigured": true,
"manualReleaseNames": false,
"hardDeleteDeployment": true,
"manualNamespaceNames": true,
"featureFlags": {
"manualNamespaceNames": true,
"namespaceFreeformEntry": true
}
},
"availableNamespaces": [
{
"name": "test1"
},
{
"name": "test2"
}
],
"createDeployment": {
"id": "ckbv818oa00r107606ywhoqtw",
"executor": "CeleryExecutor",
"urls": [
{
"type": "airflow",
"url": "https://deployments.local.astronomer.io/boreal-penumbra-1102/airflow"
},
{
"type": "flower",
"url": "https://deployments.local.astronomer.io/boreal-penumbra-1102/flower"
}
],
"properties": {
"component_version": "0.0.0",
"alert_emails": []
},
"description": "",
"label": "test2",
"releaseName": "boreal-penumbra-1102",
"status": null,
"type": "airflow",
"version": "0.0.0",
"workspace": {
"id": "ckbv7zvb100pe0760xp98qnh9",
"label": "w1"
},
"createdAt": "2020-06-25T20:10:33.898Z",
"updatedAt": "2020-06-25T20:10:33.898Z"
}
}
}`
client := testUtil.NewTestClient(func(req *http.Request) *http.Response {
return &http.Response{
StatusCode: 200,
Body: ioutil.NopCloser(bytes.NewBufferString(okResponse)),
Header: make(http.Header),
}
})
api := houston.NewHoustonClient(client)
label := "label"
ws := "ck1qg6whg001r08691y117hub"
releaseName := ""
role := "test-role"
executor := "CeleryExecutor"
airflowVersion := "1.10.5"
dagDeploymentType := "volume"
nfsLocation := "test:/test"
triggerReplicas := 0
buf := new(bytes.Buffer)

// mock os.Stdin
input := []byte("Test1")
r, w, err := os.Pipe()
if err != nil {
t.Fatal(err)
}
_, err = w.Write(input)
if err != nil {
t.Error(err)
}
w.Close()
stdin := os.Stdin
// Restore stdin right after the test.
defer func() { os.Stdin = stdin }()
os.Stdin = r

err = Create(label, ws, releaseName, role, executor, airflowVersion, dagDeploymentType, nfsLocation, "", "", "", "", "", "", 1, triggerReplicas, api, buf)
assert.NoError(t, err)
assert.Contains(t, buf.String(), "Successfully created deployment with Celery executor. Deployment can be accessed at the following URLs")
}

func TestCreateWithFreeFormNamespaceDeploymentError(t *testing.T) {
testUtil.InitTestConfig()
okResponse := `{
"data": {
"appConfig": {
"version": "0.15.1",
"baseDomain": "local.astronomer.io",
"smtpConfigured": true,
"manualReleaseNames": false,
"hardDeleteDeployment": true,
"manualNamespaceNames": true,
"featureFlags": {
"manualNamespaceNames": true,
"namespaceFreeformEntry": true
}
},
"availableNamespaces": [
{
"name": "test1"
},
{
"name": "test2"
}
],
"createDeployment": {
"id": "ckbv818oa00r107606ywhoqtw",
"executor": "CeleryExecutor",
"urls": [
{
"type": "airflow",
"url": "https://deployments.local.astronomer.io/boreal-penumbra-1102/airflow"
},
{
"type": "flower",
"url": "https://deployments.local.astronomer.io/boreal-penumbra-1102/flower"
}
],
"properties": {
"component_version": "0.0.0",
"alert_emails": []
},
"description": "",
"label": "test2",
"releaseName": "boreal-penumbra-1102",
"status": null,
"type": "airflow",
"version": "0.0.0",
"workspace": {
"id": "ckbv7zvb100pe0760xp98qnh9",
"label": "w1"
},
"createdAt": "2020-06-25T20:10:33.898Z",
"updatedAt": "2020-06-25T20:10:33.898Z"
}
}
}`
client := testUtil.NewTestClient(func(req *http.Request) *http.Response {
return &http.Response{
StatusCode: 200,
Body: ioutil.NopCloser(bytes.NewBufferString(okResponse)),
Header: make(http.Header),
}
})
api := houston.NewHoustonClient(client)
label := "label"
ws := "ck1qg6whg001r08691y117hub"
releaseName := ""
role := "test-role"
executor := "CeleryExecutor"
airflowVersion := "1.10.5"
dagDeploymentType := "volume"
nfsLocation := "test:/test"
triggerReplicas := 0
buf := new(bytes.Buffer)

// mock os.Stdin
input := []byte(" ")
r, w, err := os.Pipe()
if err != nil {
t.Fatal(err)
}
_, err = w.Write(input)
if err != nil {
t.Error(err)
}
w.Close()
stdin := os.Stdin
// Restore stdin right after the test.
defer func() { os.Stdin = stdin }()
os.Stdin = r

err = Create(label, ws, releaseName, role, executor, airflowVersion, dagDeploymentType, nfsLocation, "", "", "", "", "", "", 1, triggerReplicas, api, buf)
assert.EqualError(t, err, "no kubernetes namespaces specified")
}
11 changes: 6 additions & 5 deletions houston/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,11 +290,12 @@ type AppConfig struct {
}

type FeatureFlags struct {
NfsMountDagDeployment bool `json:"nfsMountDagDeployment"`
HardDeleteDeployment bool `json:"hardDeleteDeployment"`
ManualNamespaceNames bool `json:"manualNamespaceNames"`
TriggererEnabled bool `json:"triggererEnabled"`
GitSyncEnabled bool `json:"gitSyncDagDeployment"`
NfsMountDagDeployment bool `json:"nfsMountDagDeployment"`
HardDeleteDeployment bool `json:"hardDeleteDeployment"`
ManualNamespaceNames bool `json:"manualNamespaceNames"`
TriggererEnabled bool `json:"triggererEnabled"`
GitSyncEnabled bool `json:"gitSyncDagDeployment"`
NamespaceFreeformEntry bool `json:"namespaceFreeformEntry"`
}

// coerce a string into SemVer if possible
Expand Down

0 comments on commit e65e362

Please sign in to comment.