Skip to content
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
23 changes: 10 additions & 13 deletions docs/command/atlas-kubernetes-operator-install.txt
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ Options
-
- false
- Flag that indicates whether to import existing Atlas resources into the cluster for the operator to manage.
* - --ipAccessList
- string
- false
- A comma-separated list of IP or CIDR block to allowlist for Operator to communicate with Atlas APIs. Read more: https://www.mongodb.com/docs/atlas/configure-api-access-project/
* - --kubeContext
- string
- false
Expand Down Expand Up @@ -123,46 +127,39 @@ Examples
:copyable: false

# Install the latest version of the operator targeting Atlas for Government instead of regular commercial Atlas:
atlas kubernetes operator install --atlasGov
atlas kubernetes operator install --atlasGov --ipAccessList=<IP_ADDRESS_OR_CIDR>


.. code-block::
:copyable: false

# Install a specific version of the operator:
atlas kubernetes operator install --operatorVersion=2.12.0
atlas kubernetes operator install --ipAccessList=<IP_ADDRESS_OR_CIDR> --operatorVersion=2.12.0


.. code-block::
:copyable: false

# Install a specific version of the operator to a namespace and watch only this namespace and a second one:
atlas kubernetes operator install --operatorVersion=2.12.0 --targetNamespace=<namespace> --watchNamespace=<namespace>,<secondNamespace>
atlas kubernetes operator install --ipAccessList=<IP_ADDRESS_OR_CIDR> --operatorVersion=2.12.0 --targetNamespace=<namespace> --watchNamespace=<namespace>,<secondNamespace>


.. code-block::
:copyable: false

# Install and import all objects from an organization:
atlas kubernetes operator install --targetNamespace=<namespace> --orgID <orgID> --import
atlas kubernetes operator install --ipAccessList=<IP_ADDRESS_OR_CIDR> --targetNamespace=<namespace> --orgID <orgID> --import


.. code-block::
:copyable: false

# Install and import objects from a specific project:
atlas kubernetes operator install --targetNamespace=<namespace> --orgID <orgID> --projectName <project> --import
atlas kubernetes operator install --ipAccessList=<IP_ADDRESS_OR_CIDR> --targetNamespace=<namespace> --orgID <orgID> --projectName <project> --import


.. code-block::
:copyable: false

# Install the operator and disable deletion protection:
atlas kubernetes operator install --resourceDeletionProtection=false


.. code-block::
:copyable: false

# Install the operator and disable deletion protection for sub-resources (Atlas project integrations, private endpoints, etc.):
atlas kubernetes operator install --subresourceDeletionProtection=false
atlas kubernetes operator install --ipAccessList=<IP_ADDRESS_OR_CIDR> --resourceDeletionProtection=false
40 changes: 30 additions & 10 deletions internal/cli/kubernetes/operator/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ package operator

import (
"context"
"errors"
"fmt"
"net"
"strings"

"github.com/google/go-github/v61/github"
"github.com/mongodb/atlas-cli-core/config"
Expand Down Expand Up @@ -53,6 +56,7 @@ type InstallOpts struct {
featureDeletionProtection bool
featureSubDeletionProtection bool
configOnly bool
ipAccessList string
}

func (opts *InstallOpts) defaults() error {
Expand Down Expand Up @@ -103,6 +107,23 @@ func (opts *InstallOpts) ValidateWatchNamespace() error {
return nil
}

func (opts *InstallOpts) ValidateIpAccessList() error {
if opts.ipAccessList == "" {
return errors.New("IP access list cannot be empty")
}

list := strings.Split(opts.ipAccessList, ",")
for _, entry := range list {
if _, _, err := net.ParseCIDR(entry); err != nil {
if net.ParseIP(entry) == nil {
return fmt.Errorf("IP access list \"%s\" must be a valid IP address or CIDR", entry)
}
}
}

return nil
}

func (opts *InstallOpts) Run(ctx context.Context) error {
kubeCtl, err := kubernetes.NewKubeCtl(opts.KubeConfig, opts.KubeContext)
if err != nil {
Expand All @@ -129,7 +150,7 @@ func (opts *InstallOpts) Run(ctx context.Context) error {
return err
}

err = operator.NewInstall(installer, atlasStore, credStore, featureValidator, kubeCtl, opts.operatorVersion).
err = operator.NewInstall(installer, atlasStore, credStore, featureValidator, kubeCtl, opts.operatorVersion, opts.ipAccessList).
WithNamespace(opts.targetNamespace).
WithWatchNamespaces(opts.watchNamespace).
WithWatchProjectName(opts.projectName).
Expand Down Expand Up @@ -164,25 +185,22 @@ The key is scoped to the project when you specify the --projectName option and t
atlas kubernetes operator install

# Install the latest version of the operator targeting Atlas for Government instead of regular commercial Atlas:
atlas kubernetes operator install --atlasGov
atlas kubernetes operator install --atlasGov --ipAccessList=<IP_ADDRESS_OR_CIDR>

# Install a specific version of the operator:
atlas kubernetes operator install --operatorVersion=2.12.0
atlas kubernetes operator install --ipAccessList=<IP_ADDRESS_OR_CIDR> --operatorVersion=2.12.0

# Install a specific version of the operator to a namespace and watch only this namespace and a second one:
atlas kubernetes operator install --operatorVersion=2.12.0 --targetNamespace=<namespace> --watchNamespace=<namespace>,<secondNamespace>
atlas kubernetes operator install --ipAccessList=<IP_ADDRESS_OR_CIDR> --operatorVersion=2.12.0 --targetNamespace=<namespace> --watchNamespace=<namespace>,<secondNamespace>

# Install and import all objects from an organization:
atlas kubernetes operator install --targetNamespace=<namespace> --orgID <orgID> --import
atlas kubernetes operator install --ipAccessList=<IP_ADDRESS_OR_CIDR> --targetNamespace=<namespace> --orgID <orgID> --import

# Install and import objects from a specific project:
atlas kubernetes operator install --targetNamespace=<namespace> --orgID <orgID> --projectName <project> --import
atlas kubernetes operator install --ipAccessList=<IP_ADDRESS_OR_CIDR> --targetNamespace=<namespace> --orgID <orgID> --projectName <project> --import

# Install the operator and disable deletion protection:
atlas kubernetes operator install --resourceDeletionProtection=false

# Install the operator and disable deletion protection for sub-resources (Atlas project integrations, private endpoints, etc.):
atlas kubernetes operator install --subresourceDeletionProtection=false`,
atlas kubernetes operator install --ipAccessList=<IP_ADDRESS_OR_CIDR> --resourceDeletionProtection=false`,
PreRunE: func(_ *cobra.Command, _ []string) error {
opts.versionProvider = version.NewOperatorVersion(github.NewClient(nil))

Expand All @@ -192,6 +210,7 @@ The key is scoped to the project when you specify the --projectName option and t
opts.ValidateOperatorVersion,
opts.ValidateTargetNamespace,
opts.ValidateWatchNamespace,
opts.ValidateIpAccessList,
)
},
RunE: func(cmd *cobra.Command, _ []string) error {
Expand All @@ -213,6 +232,7 @@ The key is scoped to the project when you specify the --projectName option and t
flags.BoolVar(&opts.featureDeletionProtection, flag.OperatorResourceDeletionProtection, true, usage.OperatorResourceDeletionProtection)
flags.BoolVar(&opts.featureSubDeletionProtection, flag.OperatorSubResourceDeletionProtection, true, usage.OperatorSubResourceDeletionProtection)
flags.BoolVar(&opts.configOnly, flag.OperatorConfigOnly, false, usage.OperatorConfigOnly)
flags.StringVar(&opts.ipAccessList, flag.IPAccessList, "", usage.IPAccessList)

return cmd
}
45 changes: 45 additions & 0 deletions internal/cli/kubernetes/operator/install_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,48 @@
//go:build unit

package operator

import (
"errors"
"testing"

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

func TestInstallOptsValidateIpAccessList(t *testing.T) {
tests := map[string]struct {
ipAccessList string
err error
}{
"valid single IP": {
ipAccessList: "104.30.164.5",
},
"valid CIDR block": {
ipAccessList: "192.168.100.177/24",
},
"valid list of entries": {
ipAccessList: "104.30.164.5,192.168.100.177/24",
},
"empty string": {
ipAccessList: "",
err: errors.New("IP access list cannot be empty"),
},
"invalid IP": {
ipAccessList: "256.256.256.256",
err: errors.New("IP access list \"256.256.256.256\" must be a valid IP address or CIDR"),
},
"invalid CIDR block": {
ipAccessList: "192.168.100.177/33",
err: errors.New("IP access list \"192.168.100.177/33\" must be a valid IP address or CIDR"),
},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
opts := &InstallOpts{
ipAccessList: tt.ipAccessList,
}
err := opts.ValidateIpAccessList()
assert.Equal(t, tt.err, err)
})
}
}
1 change: 1 addition & 0 deletions internal/flag/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,5 @@ const (
KubernetesClusterContext = "kubeContext" // KubeContext flag
DataFederationName = "dataFederationName" // DataFederationName flag
IndependentResources = "independentResources" // IndependentResources flag
IPAccessList = "ipAccessList" // IPAccessList flag
)
37 changes: 37 additions & 0 deletions internal/kubernetes/operator/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"context"
"errors"
"fmt"
"strings"

"github.com/mongodb/atlas-cli-plugin-kubernetes/internal/kubernetes"
"github.com/mongodb/atlas-cli-plugin-kubernetes/internal/kubernetes/operator/features"
Expand Down Expand Up @@ -53,6 +54,7 @@ type Install struct {
importResources bool
atlasGov bool
configOnly bool
ipAccessList string
}

func (i *Install) WithConfigOnly(configOnly bool) *Install {
Expand Down Expand Up @@ -109,6 +111,11 @@ func (i *Install) Run(ctx context.Context, orgID string) error {
return err
}

err = i.addAPIKeyIPAccessList(orgID, keys.GetId())
if err != nil {
return err
}

if err = i.installResources.InstallCRDs(ctx, i.version, len(i.watch) > 0); err != nil {
return err
}
Expand Down Expand Up @@ -204,6 +211,34 @@ func (i *Install) generateKeys(orgID string) (*admin.ApiKeyUserDetails, error) {
return keys, nil
}

func (i *Install) addAPIKeyIPAccessList(orgID, apiKeyID string) error {
list := strings.Split(i.ipAccessList, ",")
entries := make([]admin.UserAccessListRequest, 0, len(list))

for _, entry := range list {
if strings.Contains(entry, "/") {
entries = append(entries, admin.UserAccessListRequest{
CidrBlock: &entry,
})
} else {
entries = append(entries, admin.UserAccessListRequest{
IpAddress: &entry,
})
}
}

err := i.atlasStore.AddIPAccessList(
orgID,
apiKeyID,
&entries,
)
if err != nil {
return fmt.Errorf("failed to add IP access list to API key: %w", err)
}

return nil
}

func (i *Install) importAtlasResources(orgID, apiKeyID string) error {
projectsIDs := make([]string, 0)

Expand Down Expand Up @@ -326,6 +361,7 @@ func NewInstall(
featureValidator features.FeatureValidator,
kubectl *kubernetes.KubeCtl,
version string,
ipAccessList string,
) *Install {
return &Install{
installResources: installer,
Expand All @@ -334,5 +370,6 @@ func NewInstall(
featureValidator: featureValidator,
kubectl: kubectl,
version: version,
ipAccessList: ipAccessList,
}
}
73 changes: 73 additions & 0 deletions internal/kubernetes/operator/install_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright 2024 MongoDB Inc
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build unit

package operator

import (
"errors"
"fmt"
"testing"

"github.com/golang/mock/gomock"
"github.com/mongodb/atlas-cli-plugin-kubernetes/internal/mocks"
"github.com/stretchr/testify/assert"
"go.mongodb.org/atlas-sdk/v20250312006/admin"
)

func TestInstall_addAPIKeyIPAccessList(t *testing.T) {
tests := map[string]struct {
ipAccessList string
expectedErr error
}{
"An IP is provided": {
ipAccessList: "104.30.164.5",
expectedErr: nil,
},
"An CIDR is provided": {
ipAccessList: "192.168.100.177/24",
expectedErr: nil,
},
"Multiple entries are provided": {
ipAccessList: "104.30.164.5,192.168.100.177/24",
expectedErr: nil,
},
"API failed to add ip access list": {
ipAccessList: "104.30.164.5,192.168.100.177/24",
expectedErr: fmt.Errorf("failed to add IP access list to API key: %w", errors.New("failed to add IP access list")),
},
}
for name, tt := range tests {
storeMock := mocks.NewMockOperatorGenericStore(gomock.NewController(t))
storeMock.EXPECT().AddIPAccessList("orgID", "apiKeyID", gomock.Any()).
DoAndReturn(func(string, string, *[]admin.UserAccessListRequest) error {
if tt.expectedErr != nil {
return errors.New("failed to add IP access list")
}

return nil
}).
Times(1)

t.Run(name, func(t *testing.T) {
i := &Install{
ipAccessList: tt.ipAccessList,
atlasStore: storeMock,
}
err := i.addAPIKeyIPAccessList("orgID", "apiKeyID")
assert.Equal(t, tt.expectedErr, err)
})
}
}
14 changes: 14 additions & 0 deletions internal/mocks/mock_api_keys.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading